Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop
This commit is contained in:
commit
03ff317841
|
|
@ -2,5 +2,5 @@
|
|||
ENV = 'production'
|
||||
|
||||
# base api
|
||||
VUE_APP_BASE_API = ''
|
||||
VUE_APP_BASE_API = 'http://47.95.0.242:2222/api'
|
||||
|
||||
|
|
|
|||
|
|
@ -17,46 +17,45 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@riophae/vue-treeselect": "^0.4.0",
|
||||
"axios": "0.18.1",
|
||||
"axios": "^0.21.1",
|
||||
"element-ui": "^2.15.5",
|
||||
"file-saver": "^2.0.2",
|
||||
"js-cookie": "2.2.0",
|
||||
"normalize.css": "7.0.0",
|
||||
"js-cookie": "^3.0.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"vue": "2.6.10",
|
||||
"vue-json-editor": "^1.4.3",
|
||||
"vue-router": "3.0.6",
|
||||
"vuex": "3.1.0",
|
||||
"xlsx": "^0.15.5"
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.2",
|
||||
"vuex": "^3.6.2",
|
||||
"xlsx": "^0.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.0.0",
|
||||
"@babel/register": "7.0.0",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/register": "^7.15.3",
|
||||
"@vue/cli-plugin-babel": "3.6.0",
|
||||
"@vue/cli-plugin-eslint": "^3.9.1",
|
||||
"@vue/cli-plugin-unit-jest": "3.6.3",
|
||||
"@vue/cli-service": "3.6.0",
|
||||
"@vue/test-utils": "1.0.0-beta.29",
|
||||
"@vue/test-utils": "^1.2.2",
|
||||
"autoprefixer": "^9.5.1",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "23.6.0",
|
||||
"chalk": "2.4.2",
|
||||
"connect": "3.6.6",
|
||||
"connect": "^3.7.0",
|
||||
"eslint": "5.15.3",
|
||||
"eslint-plugin-vue": "5.2.2",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
"mockjs": "^1.1.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"runjs": "^4.3.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"script-ext-html-webpack-plugin": "2.1.3",
|
||||
"script-ext-html-webpack-plugin": "^2.1.5",
|
||||
"script-loader": "0.7.2",
|
||||
"serve-static": "^1.13.2",
|
||||
"svg-sprite-loader": "4.1.3",
|
||||
"svgo": "1.2.2",
|
||||
"vue-template-compiler": "2.6.10"
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-31 06:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('em', '0002_alter_equipment_model'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='type',
|
||||
field=models.IntegerField(choices=[(1, '生产设备'), (2, '检验工具')], default=1, verbose_name='类型'),
|
||||
),
|
||||
]
|
||||
|
|
@ -18,6 +18,11 @@ class Equipment(CommonBModel):
|
|||
(0, '停用'),
|
||||
(2, '报废')
|
||||
)
|
||||
type_choices = (
|
||||
(1, '生产设备'),
|
||||
(2, '检验工具')
|
||||
)
|
||||
type = models.IntegerField('类型', choices=type_choices, default=1)
|
||||
name = models.CharField('设备名称', max_length=50)
|
||||
number = models.CharField('设备编号', max_length=50, unique=True)
|
||||
model = models.CharField('规格型号', max_length=60, null=True, blank=True)
|
||||
|
|
|
|||
|
|
@ -16,3 +16,8 @@ class EquipmentSerializer(ModelSerializer):
|
|||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.select_related('belong_dept','keeper')
|
||||
return queryset
|
||||
|
||||
class EquipmentSimpleSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Equipment
|
||||
fields = ['id', 'number', 'name']
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-27 02:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='stepoperationitem',
|
||||
name='sort',
|
||||
field=models.IntegerField(default=1, verbose_name='排序号'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-27 08:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0002_stepoperationitem_sort'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='material',
|
||||
name='process',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='material',
|
||||
name='processes',
|
||||
field=models.JSONField(blank=True, default=list, verbose_name='工艺流程'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-30 03:29
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0003_auto_20210827_1604'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='inputmaterial',
|
||||
name='process',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='关联工序'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inputmaterial',
|
||||
name='product',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='inputmaterial_product', to='mtm.material', verbose_name='关联产品'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='inputmaterial',
|
||||
name='sort',
|
||||
field=models.IntegerField(default=1, verbose_name='排序号'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='outputmaterial',
|
||||
name='process',
|
||||
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='关联工序'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='outputmaterial',
|
||||
name='product',
|
||||
field=models.ForeignKey(default=3, on_delete=django.db.models.deletion.CASCADE, related_name='outputmaterial_product', to='mtm.material', verbose_name='关联产品'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='outputmaterial',
|
||||
name='sort',
|
||||
field=models.IntegerField(default=1, verbose_name='排序号'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inputmaterial',
|
||||
name='material',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inputmaterial', to='mtm.material', verbose_name='输入物料'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inputmaterial',
|
||||
name='number',
|
||||
field=models.FloatField(default=1, verbose_name='消耗量'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='outputmaterial',
|
||||
name='material',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outputmaterial', to='mtm.material', verbose_name='输出物料'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='outputmaterial',
|
||||
name='number',
|
||||
field=models.FloatField(default=1, verbose_name='产出量'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-30 05:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0004_auto_20210830_1129'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='inputmaterial',
|
||||
name='unit',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='outputmaterial',
|
||||
name='unit',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='material',
|
||||
name='unit',
|
||||
field=models.CharField(choices=[('块', '块'), ('套', '套')], default='块', max_length=10, verbose_name='基准计量单位'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-30 05:31
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mtm', '0005_auto_20210830_1313'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='inputmaterial',
|
||||
old_name='number',
|
||||
new_name='count',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='outputmaterial',
|
||||
old_name='number',
|
||||
new_name='count',
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-31 06:41
|
||||
|
||||
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 = [
|
||||
('system', '0003_auto_20210812_0909'),
|
||||
('em', '0003_equipment_type'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('mtm', '0006_auto_20210830_1331'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='process',
|
||||
name='workshop',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='system.organization', verbose_name='生产车间'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='step',
|
||||
name='equipments',
|
||||
field=models.ManyToManyField(to='em.Equipment', verbose_name='使用设备'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UsedStep',
|
||||
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='删除标记')),
|
||||
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='usedstep_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='关联产品')),
|
||||
('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='usedsteps', to='mtm.step', verbose_name='子工序')),
|
||||
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='usedstep_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '产品生产子工序',
|
||||
'verbose_name_plural': '产品生产子工序',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -5,6 +5,8 @@ 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.system.models import Organization
|
||||
from apps.em.models import Equipment
|
||||
|
||||
class Material(CommonAModel):
|
||||
"""
|
||||
|
|
@ -15,11 +17,16 @@ class Material(CommonAModel):
|
|||
(2, '半成品'),
|
||||
(3, '原材料')
|
||||
)
|
||||
unit_choices =(
|
||||
('块', '块'),
|
||||
('套', '套')
|
||||
)
|
||||
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')
|
||||
processes = models.JSONField('工艺流程', default=list, blank=True)
|
||||
unit = models.CharField('基准计量单位', choices=unit_choices, default='块', max_length=10)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '物料表'
|
||||
|
|
@ -36,6 +43,7 @@ class Process(CommonAModel):
|
|||
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)
|
||||
workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '工序'
|
||||
|
|
@ -53,6 +61,7 @@ class Step(CommonAModel):
|
|||
number = models.CharField('步骤编号', max_length=100, null=True, blank=True)
|
||||
instruction_content = models.TextField('相应操作指导', null=True, blank=True)
|
||||
sort = models.IntegerField('排序号', default=1)
|
||||
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备')
|
||||
|
||||
class Meta:
|
||||
verbose_name = '工序步骤'
|
||||
|
|
@ -61,6 +70,12 @@ class Step(CommonAModel):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# class StepTable(CommonAModel):
|
||||
# """
|
||||
# 过程记录表格
|
||||
# """
|
||||
|
||||
|
||||
class StepOperationItem(CommonAModel):
|
||||
"""
|
||||
操作记录条目
|
||||
|
|
@ -110,23 +125,40 @@ class InputMaterial(CommonAModel):
|
|||
"""
|
||||
输入物料
|
||||
"""
|
||||
material = models.ForeignKey(Material, verbose_name='输入物料', on_delete=models.CASCADE)
|
||||
number = models.FloatField('消耗量', default=0)
|
||||
unit = models.CharField('单位', max_length=20)
|
||||
material = models.ForeignKey(Material, verbose_name='输入物料', on_delete=models.CASCADE, related_name='inputmaterial')
|
||||
count = models.FloatField('消耗量', default=1)
|
||||
product = models.ForeignKey(Material, verbose_name='关联产品', on_delete=models.CASCADE, related_name='inputmaterial_product')
|
||||
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 OutputMaterial(CommonAModel):
|
||||
"""
|
||||
输出物料
|
||||
"""
|
||||
material = models.ForeignKey(Material, verbose_name='输出物料', on_delete=models.CASCADE)
|
||||
number = models.FloatField('产出量', default=0)
|
||||
unit = models.CharField('单位', max_length=20)
|
||||
material = models.ForeignKey(Material, verbose_name='输出物料', on_delete=models.CASCADE, related_name='outputmaterial')
|
||||
count = models.FloatField('产出量', default=1)
|
||||
product = models.ForeignKey(Material, verbose_name='关联产品', on_delete=models.CASCADE, related_name='outputmaterial_product')
|
||||
process = models.ForeignKey(Process, verbose_name='关联工序', on_delete=models.CASCADE)
|
||||
sort = models.IntegerField('排序号', default=1)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '输出物料'
|
||||
verbose_name_plural = verbose_name
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
class UsedStep(CommonAModel):
|
||||
"""
|
||||
产品生产子工序
|
||||
"""
|
||||
step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedsteps')
|
||||
product = models.ForeignKey(Material, verbose_name='关联产品', on_delete=models.CASCADE)
|
||||
process = models.ForeignKey(Process, verbose_name='关联工序', on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '产品生产子工序'
|
||||
verbose_name_plural = verbose_name
|
||||
|
|
|
|||
|
|
@ -1,43 +1,139 @@
|
|||
from rest_framework.serializers import ModelSerializer
|
||||
|
||||
from .models import Material, Process, ProductProcess, Step
|
||||
from apps.system.serializers import FileSimpleSerializer
|
||||
from apps.em.serializers import EquipmentSerializer, EquipmentSimpleSerializer
|
||||
from rest_framework import serializers
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from .models import InputMaterial, Material, OutputMaterial, Process, ProductProcess, Step, UsedStep
|
||||
from apps.system.serializers import FileSimpleSerializer, OrganizationSimpleSerializer
|
||||
|
||||
|
||||
class MaterialSerializer(ModelSerializer):
|
||||
class MaterialSerializer(serializers.ModelSerializer):
|
||||
processes = serializers.ListField(child=serializers.IntegerField(min_value=1))
|
||||
class Meta:
|
||||
model = Material
|
||||
fields = '__all__'
|
||||
|
||||
class MaterialSimpleSerializer(ModelSerializer):
|
||||
class MaterialDetailSerializer(serializers.ModelSerializer):
|
||||
processes_ = serializers.SerializerMethodField()
|
||||
class Meta:
|
||||
model = Material
|
||||
fields = ['id', 'name', 'number']
|
||||
fields = '__all__'
|
||||
|
||||
def get_processes_(self, obj):
|
||||
objs = Process.objects.filter(id__in=obj.processes)
|
||||
return ProcessSimpleSerializer(instance=objs, many=True).data
|
||||
|
||||
class ProcessSerializer(ModelSerializer):
|
||||
|
||||
class MaterialSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Material
|
||||
fields = ['id', 'name', 'number', 'unit']
|
||||
|
||||
class ProcessSerializer(serializers.ModelSerializer):
|
||||
instruction_ = FileSimpleSerializer(source='instruction', read_only=True)
|
||||
workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True)
|
||||
class Meta:
|
||||
model = Process
|
||||
fields = '__all__'
|
||||
|
||||
class ProcessSimpleSerializer(ModelSerializer):
|
||||
class ProcessSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Process
|
||||
fields = ['id', 'name', 'number']
|
||||
|
||||
class StepSerializer(ModelSerializer):
|
||||
class StepSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = '__all__'
|
||||
|
||||
class ProductProcessListSerializer(ModelSerializer):
|
||||
class StepSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = ['id', 'name', 'sort']
|
||||
|
||||
class StepDetailSerializer(serializers.ModelSerializer):
|
||||
equipments_ = EquipmentSimpleSerializer(source='equipments', many=True)
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
queryset = queryset.prefetch_related('equipments')
|
||||
return queryset
|
||||
|
||||
class ProductProcessListSerializer(serializers.ModelSerializer):
|
||||
process_ = ProcessSimpleSerializer(source='process', read_only=True)
|
||||
product_ = MaterialSimpleSerializer(source='product', read_only=True)
|
||||
class Meta:
|
||||
model = ProductProcess
|
||||
fields = '__all__'
|
||||
|
||||
class ProductProcessUpdateSerializer(ModelSerializer):
|
||||
class ProductProcessUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ProductProcess
|
||||
fields = ['sort']
|
||||
fields = ['sort']
|
||||
|
||||
class InputMaterialListSerializer(serializers.ModelSerializer):
|
||||
material_ = MaterialSimpleSerializer(source='material', read_only=True)
|
||||
class Meta:
|
||||
model = InputMaterial
|
||||
fields = '__all__'
|
||||
|
||||
class OutputMaterialListSerializer(serializers.ModelSerializer):
|
||||
material_ = MaterialSimpleSerializer(source='material', read_only=True)
|
||||
class Meta:
|
||||
model = OutputMaterial
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class InputMaterialSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = InputMaterial
|
||||
fields = ['count', 'sort', 'material', 'product', 'process']
|
||||
|
||||
def create(self, validated_data):
|
||||
if InputMaterial.objects.filter(material=validated_data['material'], product=validated_data['product'], process=validated_data['process'], is_deleted=False).exists():
|
||||
raise ValidationError('该物料已存在')
|
||||
return super().create(validated_data)
|
||||
|
||||
class InputMaterialUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = InputMaterial
|
||||
fields = ['count', 'sort']
|
||||
|
||||
class OutputMaterialSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OutputMaterial
|
||||
fields = ['count', 'sort', 'material', 'product', 'process']
|
||||
|
||||
def create(self, validated_data):
|
||||
if OutputMaterial.objects.filter(material=validated_data['material'], product=validated_data['product'], process=validated_data['process'], is_deleted=False).exists():
|
||||
raise ValidationError('该物料已存在')
|
||||
return super().create(validated_data)
|
||||
|
||||
class OutputMaterialUpdateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OutputMaterial
|
||||
fields = ['count', 'sort']
|
||||
|
||||
class UsedStepCreateSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
产品生产子工序创建
|
||||
"""
|
||||
class Meta:
|
||||
model = UsedStep
|
||||
fields = ['step', 'product', 'process']
|
||||
|
||||
class UsedStepListSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
产品生产子工序序列化
|
||||
"""
|
||||
step_ = StepSimpleSerializer(source='step', read_only=True)
|
||||
class Meta:
|
||||
model = UsedStep
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.select_related('step')
|
||||
return queryset
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
from django.db.models import base
|
||||
from rest_framework import urlpatterns
|
||||
from apps.mtm.views import MaterialViewSet, ProcessViewSet, ProductProcessViewSet, StepViewSet
|
||||
from apps.mtm.views import InputMaterialViewSet, MaterialViewSet, OutputMaterialViewSet, ProcessViewSet, StepViewSet, UsedStepViewSet
|
||||
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('productprocess', ProductProcessViewSet, basename='productprocess')
|
||||
# router.register('productprocess', ProductProcessViewSet, basename='productprocess')
|
||||
router.register('step', StepViewSet, basename='step')
|
||||
router.register('inputmaterial', InputMaterialViewSet, basename='inputmaterial')
|
||||
router.register('outputmaterial', OutputMaterialViewSet, basename='outputmaterial')
|
||||
router.register('usedstep', UsedStepViewSet, basename='usedstep')
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ from django.shortcuts import render
|
|||
from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
||||
from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin
|
||||
|
||||
from apps.mtm.models import Material, Process, ProductProcess, Step
|
||||
from apps.mtm.serializers import MaterialSerializer, ProductProcessListSerializer, ProductProcessUpdateSerializer, ProcessSerializer, StepSerializer
|
||||
from apps.mtm.models import InputMaterial, Material, OutputMaterial, Process, ProductProcess, Step, UsedStep
|
||||
from apps.mtm.serializers import InputMaterialListSerializer, InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OutputMaterialListSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, ProductProcessListSerializer, ProductProcessUpdateSerializer, ProcessSerializer, StepDetailSerializer, StepSerializer, UsedStepCreateSerializer, UsedStepListSerializer
|
||||
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from utils.pagination import PageOrNot
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
|
@ -24,14 +25,19 @@ class MaterialViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
|
|||
ordering_fields = ['number', 'sort_str']
|
||||
ordering = ['number']
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'*'}, 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=False), many=True)
|
||||
return Response(serializer.data)
|
||||
def get_serializer_class(self, *args, **kwargs):
|
||||
if self.action == 'retrieve':
|
||||
return MaterialDetailSerializer
|
||||
return MaterialSerializer
|
||||
|
||||
# @action(methods=['get'], detail=True, perms_map={'get':'*'}, pagination_class=None, serializer_class=MaterialSimpleSerializer)
|
||||
# def processes(self, request, pk=None):
|
||||
# """
|
||||
# 产品生产工艺流程
|
||||
# """
|
||||
# material = self.get_object()
|
||||
# serializer = self.serializer_class(instance=Process.objects.filter(id__in=material.processes), many=True)
|
||||
# return Response(serializer.data)
|
||||
|
||||
|
||||
class ProcessViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
|
||||
|
|
@ -40,10 +46,10 @@ class ProcessViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
|
|||
"""
|
||||
perms_map = {'get': '*', 'post': 'process_create',
|
||||
'put': 'process_update', 'delete': 'process_delete'}
|
||||
queryset = Process.objects.select_related('instruction').all()
|
||||
queryset = Process.objects.select_related('instruction', 'workshop').all()
|
||||
serializer_class = ProcessSerializer
|
||||
search_fields = ['name', 'number']
|
||||
filterset_fields = ['number']
|
||||
filterset_fields = ['number', 'workshop']
|
||||
ordering_fields = ['number']
|
||||
ordering = ['number']
|
||||
|
||||
|
|
@ -56,8 +62,9 @@ class ProcessViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
|
|||
serializer = self.serializer_class(instance=Step.objects.filter(process=process, is_deleted=False), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
class StepViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
class StepViewSet(CreateUpdateModelAMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
"""
|
||||
子工序-增删改查
|
||||
"""
|
||||
perms_map = {'*':'process_update'}
|
||||
queryset = Step.objects.all()
|
||||
|
|
@ -66,18 +73,70 @@ class StepViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, Destro
|
|||
filterset_fields = ['process']
|
||||
ordering = ['sort']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'retrieve':
|
||||
return StepDetailSerializer
|
||||
return StepSerializer
|
||||
|
||||
class ProductProcessViewSet(PageOrNot, CreateModelMixin, UpdateModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
# class ProductProcessViewSet(PageOrNot, CreateModelMixin, UpdateModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
# """
|
||||
# 产品生产工艺流程增删改查
|
||||
# """
|
||||
# perms_map={'*':'*'}
|
||||
# queryset = ProductProcess.objects.select_related('process', 'product').all()
|
||||
# filterset_fields = ['process', 'product']
|
||||
# serializer_class = ProductProcessListSerializer
|
||||
# ordering = ['sort']
|
||||
|
||||
# def get_serializer_class(self):
|
||||
# if self.action == 'update':
|
||||
# return ProductProcessUpdateSerializer
|
||||
# return super().get_serializer_class()
|
||||
|
||||
class InputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
||||
"""
|
||||
产品生产工艺流程增删改查
|
||||
输入物料-增删改查
|
||||
"""
|
||||
perms_map={'*':'*'}
|
||||
queryset = ProductProcess.objects.select_related('process', 'product').all()
|
||||
filterset_fields = ['process', 'product']
|
||||
serializer_class = ProductProcessListSerializer
|
||||
ordering = ['sort']
|
||||
perms_map = {'*':'*'}
|
||||
queryset = InputMaterial.objects.select_related('material').all()
|
||||
serializer_class = InputMaterialSerializer
|
||||
filterset_fields = ['process', 'material']
|
||||
ordering = ['sort', '-create_time']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'update':
|
||||
return ProductProcessUpdateSerializer
|
||||
return super().get_serializer_class()
|
||||
if self.action == 'list':
|
||||
return InputMaterialListSerializer
|
||||
elif self.action == 'update':
|
||||
return InputMaterialUpdateSerializer
|
||||
return InputMaterialSerializer
|
||||
|
||||
class OutputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
||||
"""
|
||||
输出物料-增删改查
|
||||
"""
|
||||
perms_map = {'*':'*'}
|
||||
queryset = OutputMaterial.objects.select_related('material').all()
|
||||
serializer_class = OutputMaterialSerializer
|
||||
filterset_fields = ['process', 'material']
|
||||
ordering = ['sort', '-create_time']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return OutputMaterialListSerializer
|
||||
elif self.action == 'update':
|
||||
return OutputMaterialUpdateSerializer
|
||||
return OutputMaterialSerializer
|
||||
|
||||
class UsedStepViewSet(OptimizationMixin, CreateModelMixin, DestroyModelMixin, ListModelMixin, GenericViewSet):
|
||||
"""
|
||||
产品生产子工序表
|
||||
"""
|
||||
perms_map = {'*':'*'}
|
||||
queryset = UsedStep.objects.all()
|
||||
filterset_fields = ['process', 'product', 'step']
|
||||
ordering = ['step__sort', '-step__create_time']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action =='create':
|
||||
return UsedStepCreateSerializer
|
||||
return UsedStepListSerializer
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-27 03:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wf', '0006_auto_20210825_1542'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='ticket',
|
||||
name='add_node_man',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='ticketflow',
|
||||
name='participant',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='state',
|
||||
name='distribute_type',
|
||||
field=models.IntegerField(choices=[(1, '主动接单'), (2, '直接处理'), (3, '随机分配'), (4, '全部处理')], default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)', verbose_name='分配方式'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='state',
|
||||
name='participant',
|
||||
field=models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\\部门id\\角色id\\变量(create_by,create_by_tl)\\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot', verbose_name='参与者'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='act_state',
|
||||
field=models.IntegerField(choices=[(0, '草稿中'), (1, '进行中'), (2, '被退回'), (3, '被撤回'), (4, '已完成'), (5, '已关闭')], default=1, help_text='当前工单的进行状态', verbose_name='进行状态'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='multi_all_person',
|
||||
field=models.JSONField(blank=True, default=dict, help_text='需要当前状态处理人全部处理时实际的处理结果,json格式', verbose_name='全部处理的结果'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='participant',
|
||||
field=models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表', verbose_name='当前处理人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='participant_type',
|
||||
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='workflow',
|
||||
name='sn_prefix',
|
||||
field=models.CharField(default='hb', max_length=50, 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,参与人填create_by', verbose_name='参与者类型'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='state',
|
||||
name='type',
|
||||
field=models.IntegerField(choices=[(0, '普通'), (1, '开始'), (2, '结束')], default=0, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)', verbose_name='状态类型'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflow',
|
||||
name='display_form_str',
|
||||
field=models.JSONField(blank=True, default=list, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='展现表单字段'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflow',
|
||||
name='limit_expression',
|
||||
field=models.JSONField(blank=True, default=dict, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)', verbose_name='限制表达式'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-27 03:04
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('wf', '0007_auto_20210827_1103'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ticket',
|
||||
name='add_node_man',
|
||||
field=models.ForeignKey(blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='加签人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='ticketflow',
|
||||
name='participant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='处理人'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.6 on 2021-08-27 09:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wf', '0008_auto_20210827_1104'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ticketflow',
|
||||
name='participant_type',
|
||||
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='处理人类型'),
|
||||
),
|
||||
]
|
||||
|
|
@ -12,11 +12,11 @@ class Workflow(CommonAModel):
|
|||
工作流
|
||||
"""
|
||||
name = models.CharField('名称', max_length=50)
|
||||
sn_prefix = models.CharField('流水号前缀', max_length=50)
|
||||
sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb')
|
||||
description = models.CharField('描述', max_length=200)
|
||||
view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单')
|
||||
limit_expression = models.JSONField('限制表达式', max_length=1000, default=dict, blank=True, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)')
|
||||
display_form_str = models.JSONField('展现表单字段', max_length=10000, default=list, blank=True, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
limit_expression = models.JSONField('限制表达式', default=dict, blank=True, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)')
|
||||
display_form_str = models.JSONField('展现表单字段', default=list, blank=True, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
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}')
|
||||
|
||||
|
|
@ -60,6 +60,10 @@ class State(CommonAModel):
|
|||
(STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'),
|
||||
(STATE_DISTRIBUTE_TYPE_ALL, '全部处理'),
|
||||
)
|
||||
|
||||
STATE_FIELD_READONLY= 1 # 字段只读
|
||||
STATE_FIELD_REQUIRED = 2 # 字段必填
|
||||
STATE_FIELD_OPTIONAL = 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中不显示此状态(当前处于此状态时除外)')
|
||||
|
|
@ -69,7 +73,7 @@ class State(CommonAModel):
|
|||
participant_type = models.IntegerField('参与者类型', choices=type2_choices, default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by')
|
||||
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:可选. 示例:{"created_at":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, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
|
||||
class Transition(CommonAModel):
|
||||
"""
|
||||
|
|
@ -150,7 +154,7 @@ class Ticket(CommonAModel):
|
|||
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单状态', related_name='ticket_parent_state')
|
||||
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='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
|
||||
add_node_man = models.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL, null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
|
||||
|
||||
participant_type = models.IntegerField('当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.type2_choices)
|
||||
participant = models.JSONField('当前处理人', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表')
|
||||
|
|
@ -164,6 +168,7 @@ 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)
|
||||
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.type2_choices)
|
||||
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, 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格式')
|
||||
|
|
@ -48,6 +48,10 @@ class TicketCreateSerializer(serializers.ModelSerializer):
|
|||
class Meta:
|
||||
model=Ticket
|
||||
fields=['title','workflow','ticket_data', 'transition']
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data.pop('transition')
|
||||
return super().create(validated_data)
|
||||
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Tuple
|
||||
from apps.system.models import User
|
||||
from apps.wf.models import CustomField, State, Ticket, Transition, Workflow
|
||||
from rest_framework.exceptions import APIException
|
||||
|
|
@ -114,41 +115,65 @@ class WfService(object):
|
|||
回到初始状态
|
||||
"""
|
||||
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
destination_participant=ticket.create_by,
|
||||
multi_all_person=dict())
|
||||
destination_participant=ticket.create_by.id,
|
||||
multi_all_person={})
|
||||
elif state.type == State.STATE_TYPE_END:
|
||||
"""
|
||||
到达结束状态
|
||||
"""
|
||||
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
destination_participant='',
|
||||
multi_all_person=dict())
|
||||
destination_participant=0,
|
||||
multi_all_person={})
|
||||
multi_all_person_dict = {}
|
||||
destination_participant_type, destination_participant = State.participant_type, State.participant
|
||||
|
||||
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, '') if destination_participant in ticket_data else Ticket.ticket_data.get(destination_participant, '')
|
||||
destination_participant = ticket_data.get(destination_participant, None) if destination_participant in ticket_data else Ticket.ticket_data.get(destination_participant, None)
|
||||
|
||||
if destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#单部门
|
||||
destination_participant = User.objects.filter(dept=destination_participant).values_list('id')
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#单部门
|
||||
destination_participant = list(User.objects.filter(dept=destination_participant).values_list('id', flat=True))
|
||||
|
||||
if destination_participant_type == State.PARTICIPANT_TYPE_ROLE:#单角色
|
||||
destination_participant = User.objects.filter(roles=destination_participant).values_list('id')
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_ROLE:#单角色
|
||||
destination_participant = list(User.objects.filter(roles=destination_participant).values_list('id', flat=True))
|
||||
|
||||
if type(destination_participant) == list:
|
||||
destination_participant_type = State.PARTICIPANT_TYPE_MULTI
|
||||
destination_participant = list(set(destination_participant))
|
||||
else:
|
||||
destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL
|
||||
|
||||
if destination_participant_type == State.PARTICIPANT_TYPE_MULTI:
|
||||
if state.distribute_type == State.STATE_DISTRIBUTE_TYPE_RANDOM:
|
||||
destination_participant = random.choice(destination_participant)
|
||||
elif state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ALL:
|
||||
for i in destination_participant:
|
||||
multi_all_person_dict[i]={}
|
||||
|
||||
return dict(destination_participant_type=destination_participant_type,
|
||||
destination_participant=destination_participant,
|
||||
multi_all_person=multi_all_person_dict)
|
||||
|
||||
@classmethod
|
||||
def ticket_handle_permission_check(cls, ticket:Ticket, user:User)-> dict:
|
||||
transitions = cls.get_state_transitions(ticket.state)
|
||||
if not transitions:
|
||||
return dict(permission=True, msg="工单当前状态无需操作")
|
||||
current_participant_count = 1
|
||||
participant_type = ticket.participant_type
|
||||
participant = ticket.participant
|
||||
state = ticket.state
|
||||
if participant_type == State.PARTICIPANT_TYPE_PERSONAL:
|
||||
if user.id != participant:
|
||||
return dict(permission=False, msg="非当前处理人")
|
||||
elif participant_type in [State.PARTICIPANT_TYPE_MULTI, State.PARTICIPANT_TYPE_DEPT, State.PARTICIPANT_TYPE_ROLE]:
|
||||
if user.id not in participant:
|
||||
return dict(permission=False, msg="非当前处理人")
|
||||
current_participant_count = len(participant.split(','))
|
||||
if current_participant_count >1 and state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ACTIVE:
|
||||
return dict(permission=False, msg="需要先接单再处理")
|
||||
if ticket.in_add_node:
|
||||
return dict(permission=False, msg="工单当前处于加签中,请加签完成后操作")
|
||||
return dict(permission=True, msg="")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
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, TicketCreateSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from rest_framework.decorators import action, api_view
|
||||
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition
|
||||
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow
|
||||
from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin
|
||||
from apps.wf.services import WfService
|
||||
from rest_framework.exceptions import APIException
|
||||
|
|
@ -104,28 +105,63 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
|
|||
"""
|
||||
新建工单
|
||||
"""
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
rdata = request.data
|
||||
serializer = self.get_serializer(data=rdata)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
start_state = WfService.get_workflow_start_state(serializer.data['workflow'])
|
||||
transition = Transition.objects.get(pk=serializer.data['transition'])
|
||||
ticket_data = serializer.data['ticket_data']
|
||||
start_state = WfService.get_workflow_start_state(rdata['workflow'])
|
||||
transition = Transition.objects.get(pk=rdata['transition'])
|
||||
ticket_data = rdata['ticket_data']
|
||||
if transition.field_require_check:
|
||||
for key, value in start_state.state_fields.items(): #校验必填项
|
||||
if value == 2:
|
||||
if value == State.STATE_FIELD_REQUIRED:
|
||||
if key not in ticket_data or not ticket_data[key]:
|
||||
raise APIException('字段{}必填'.format(key))
|
||||
ticket = serializer.save()
|
||||
ticket = serializer.save(state=start_state) # 先创建出来
|
||||
next_state = WfService.get_next_state_id_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', '')
|
||||
multi_all_person = participant_info.get('multi_all_person', '{}') # 多人需要全部处理情况
|
||||
sn = WfService.get_ticket_sn(ticket.workflow)
|
||||
multi_all_person = participant_info.get('multi_all_person', {}) # 多人需要全部处理情况
|
||||
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
|
||||
if next_state.type == State.STATE_TYPE_END:
|
||||
act_state = Ticket.TICKET_ACT_STATE_FINISH
|
||||
elif next_state.type == State.STATE_TYPE_START:
|
||||
pass
|
||||
|
||||
act_state = Ticket.TICKET_ACT_STATE_DRAFT
|
||||
else:
|
||||
act_state = Ticket.TICKET_ACT_STATE_ONGOING
|
||||
title = rdata.get('title', '')
|
||||
title_template = ticket.workflow.title_template
|
||||
if title_template:
|
||||
all_ticket_data = {**rdata, **rdata['ticket_data']}
|
||||
title = title_template.format(**all_ticket_data)
|
||||
# 更新工单
|
||||
ticket.sn=sn
|
||||
ticket.title=title
|
||||
ticket.state=next_state
|
||||
ticket.participant=destination_participant
|
||||
ticket.participant_type=destination_participant_type
|
||||
ticket.act_state=act_state
|
||||
ticket.multi_all_person=multi_all_person
|
||||
ticket.save()
|
||||
# 新增流转记录
|
||||
TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=ticket_data,
|
||||
suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
participant=ticket.create_by, transition=transition)
|
||||
return Response(TicketSerializer(instance=ticket).data)
|
||||
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'get':'*'})
|
||||
def handle(self, request, pk=None):
|
||||
"""
|
||||
处理工单
|
||||
"""
|
||||
try:
|
||||
ticket = Ticket.objects.get(pk=pk)
|
||||
except:
|
||||
raise APIException('工单不存在')
|
||||
data = request.data
|
||||
result = WfService.ticket_handle_permission_check()
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'*'})
|
||||
def flowsteps(self, request, pk=None):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from django.conf import settings
|
|||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.conf.urls import url
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import routers
|
||||
|
|
@ -47,6 +48,7 @@ urlpatterns = [
|
|||
|
||||
# api文档
|
||||
path('api/docs/', include_docs_urls(title="接口文档", authentication_classes=[], permission_classes=[])),
|
||||
url(r'^api/swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||
path('api/swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,21 @@ class MyPagination(PageNumberPagination):
|
|||
page_size = 10
|
||||
page_size_query_param = 'page_size'
|
||||
|
||||
def paginate_queryset(self, queryset, request, view):
|
||||
if request.query_params.get('pageoff', None) or request.query_params.get('page', None)=='0':
|
||||
if queryset.count()<500:
|
||||
return None
|
||||
elif queryset.count()>=500:
|
||||
raise ParseError('单次请求数据量大,请分页获取')
|
||||
return super().paginate_queryset(queryset, request, view=view)
|
||||
|
||||
class PageOrNot:
|
||||
def paginate_queryset(self, queryset):
|
||||
if (self.paginator is None):
|
||||
return None
|
||||
elif self.request.query_params.get('pageoff', None) and queryset.count()<500:
|
||||
return None
|
||||
elif self.request.query_params.get('pageoff', None) and queryset.count()>=500:
|
||||
raise ParseError('单次请求数据量大,请求中止')
|
||||
elif self.request.query_params.get('pageoff', None) or self.request.query_params.get('page', None) == 0:
|
||||
if queryset.count()<500:
|
||||
return None
|
||||
elif queryset.count()>=500:
|
||||
raise ParseError('单次请求数据量大,请求中止')
|
||||
return self.paginator.paginate_queryset(queryset, self.request, view=self)
|
||||
|
|
|
|||
|
|
@ -50,11 +50,13 @@ class FitJSONRenderer(JSONRenderer):
|
|||
response_body.code = response.status_code
|
||||
if response_body.code >= 400: # 响应异常
|
||||
response_body.data = data # data里是详细异常信息
|
||||
prefix = ""
|
||||
if isinstance(data, dict):
|
||||
data = data[list(data.keys())[0]]
|
||||
elif isinstance(data, list):
|
||||
prefix = list(data.keys())[0]
|
||||
data = data[prefix]
|
||||
if isinstance(data, list):
|
||||
data = data[0]
|
||||
response_body.msg = data # 取一部分放入msg,方便前端alert
|
||||
response_body.msg = prefix + ":" + data # 取一部分放入msg,方便前端alert
|
||||
else:
|
||||
response_body.data = data
|
||||
renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分
|
||||
|
|
|
|||
Loading…
Reference in New Issue