-
-
-
-
+
+
-
+
-
+
+
+
@@ -305,15 +320,9 @@
>
-
-
-
-
+
+
-
+
+
+
@@ -507,11 +522,12 @@
import { getMaterialList,getMaterial,getInputmaterialList,createInputmaterial,updateInputmaterial
,deleteInputmaterial,getOutputmaterialList,createOutputmaterial,updateOutputmaterial,deleteOutputmaterial,
getUsedstepList,createUsedstep,deleteUsedstep,getStepList,gettechdocList,createtechdoc,updatetechdoc,deletetechdoc
-,getsubproducationList,createsubproducation,updatesubproducation,deletesubproducation } from "@/api/mtm";
+,getsubproducationList,createsubproducation,updatesubproducation,deletesubproducation,getProcessList } from "@/api/mtm";
import { quillEditor } from 'vue-quill-editor'
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
+
import { upUrl, upHeaders } from "@/api/file";
import checkPermission from "@/utils/permission";
import { genTree } from "@/utils";
@@ -529,7 +545,7 @@ const defaulttechdoc = {
};
const defaultsubproducation = {
-
+
};
export default {
@@ -541,6 +557,7 @@ export default {
subproducationData:"",
inputtableData:"",
editorOption: {} ,
+ processOptions:[],
techdoc: defaulttechdoc,
subproducation:defaultsubproducation,
inputmaterial: defaultinputmaterial,
@@ -625,7 +642,12 @@ export default {
this.listLoading = false;
});
},
-
+ //工序清单
+ getProcessList() {
+ getProcessList().then((res) => {
+ this.processOptions = genTree(res.data.results);
+ });
+ },
//获取产品,工艺
getMaterial(){
getMaterial(this.product).then((response) => {
@@ -639,11 +661,14 @@ export default {
handleCurrentChange(row){
this.product=row.id;
this.getMaterial();
+ this.getProcessList();
this.getsubproducationList();
+
},
//点击产品分解弹出输入、输出物料,子工序,技术文件
handlespChange(row){
this.subproduction = row.id;
+ this.processes = row.process;
this.getmaterialList();//物料列表
this.getInputmaterialLists();//输入物料
@@ -911,7 +936,7 @@ export default {
},
getstepList() {
-
+ this.listQuerystep.process=this.processes;
getStepList(this.listQuerystep).then((response) => {
if (response.data) {
this.stepoptions = genTree(response.data);
@@ -1071,3 +1096,4 @@ export default {
},
};
+
diff --git a/hb_client/src/views/pm/plan.vue b/hb_client/src/views/pm/plan.vue
index 98a559d..a8f2c6e 100644
--- a/hb_client/src/views/pm/plan.vue
+++ b/hb_client/src/views/pm/plan.vue
@@ -51,7 +51,12 @@
{{ scope.row.order_.delivery_date }}
-
+
+
+ 否
+ 是
+
+
{{ scope.row.create_time }}
@@ -61,13 +66,14 @@
width="100px"
>
-
车间排产
-
+ v-if="scope.row.is_planed"
+ @click="handleselectplan(scope)"
+ >查看子计划
+ 生产子计划
@@ -118,6 +124,8 @@
{{ scope.row.delivery_date }}
+
+
{{ scope.row.create_time }}
@@ -159,7 +167,7 @@
-
+
@@ -190,7 +198,7 @@
\ No newline at end of file
diff --git a/hb_client/src/views/sam/order.vue b/hb_client/src/views/sam/order.vue
index 79e731b..175621e 100644
--- a/hb_client/src/views/sam/order.vue
+++ b/hb_client/src/views/sam/order.vue
@@ -74,7 +74,7 @@
编辑
diff --git a/hb_server/apps/em/serializers.py b/hb_server/apps/em/serializers.py
index 55d0674..677383b 100644
--- a/hb_server/apps/em/serializers.py
+++ b/hb_server/apps/em/serializers.py
@@ -23,6 +23,7 @@ class EquipmentSerializer(ModelSerializer):
def get_step_(self, obj):
return Step.objects.filter(equipments=obj).values('id', 'name', 'number')
+
class EquipmentSimpleSerializer(ModelSerializer):
class Meta:
model = Equipment
diff --git a/hb_server/apps/mtm/migrations/0022_auto_20211014_0944.py b/hb_server/apps/mtm/migrations/0022_auto_20211014_0944.py
new file mode 100644
index 0000000..4a6f086
--- /dev/null
+++ b/hb_server/apps/mtm/migrations/0022_auto_20211014_0944.py
@@ -0,0 +1,26 @@
+# Generated by Django 3.2.6 on 2021-10-14 01:44
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('em', '0009_auto_20210916_1108'),
+ ('mtm', '0021_auto_20211013_0856'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='subproduction',
+ name='process',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='subproduction_process', to='mtm.process', verbose_name='隶属大工序'),
+ preserve_default=False,
+ ),
+ migrations.AlterField(
+ model_name='step',
+ name='equipments',
+ field=models.ManyToManyField(related_name='step_equips', to='em.Equipment', verbose_name='使用设备'),
+ ),
+ ]
diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py
index 82b39c7..7675957 100644
--- a/hb_server/apps/mtm/models.py
+++ b/hb_server/apps/mtm/models.py
@@ -64,7 +64,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='使用设备')
+ equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips')
class Meta:
verbose_name = '工序步骤'
@@ -134,6 +134,7 @@ class SubProduction(CommonAModel):
"""
name = models.CharField('命名', max_length=50, null=True, blank=True)
product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE)
+ process = models.ForeignKey(Process, verbose_name='隶属大工序', on_delete=models.CASCADE, related_name='subproduction_process')
sort = models.IntegerField('排序号', default=1)
class Meta:
@@ -173,7 +174,7 @@ class UsedStep(CommonAModel):
"""
涉及的生产子工序
"""
- step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedsteps')
+ step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedstep')
remark = models.TextField('生产备注', null=True, blank=True)
subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE)
diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py
index 7cd8fe5..5da68a4 100644
--- a/hb_server/apps/mtm/serializers.py
+++ b/hb_server/apps/mtm/serializers.py
@@ -17,8 +17,9 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
fields = '__all__'
def get_processes_(self, obj):
- steps = UsedStep.objects.filter(subproduction__product=obj).values_list('step', flat=True)
- objs = Process.objects.filter(step_process__id__in=steps).distinct().order_by('number')
+ # steps = UsedStep.objects.filter(subproduction__product=obj).values_list('step', flat=True)
+ # objs = Process.objects.filter(step_process__id__in=steps).distinct().order_by('number')
+ objs = Process.objects.filter(subproduction_process__product=obj, subproduction_process__is_deleted=False, is_deleted=False).order_by('number')
return ProcessSimpleSerializer(instance=objs, many=True).data
@@ -61,6 +62,7 @@ class StepDetailSerializer(serializers.ModelSerializer):
return queryset
class SubProductionSerializer(serializers.ModelSerializer):
+ process_ = ProcessSimpleSerializer(source='process', read_only=True)
class Meta:
model = SubProduction
fields = '__all__'
diff --git a/hb_server/apps/mtm/views.py b/hb_server/apps/mtm/views.py
index 0e1c44f..22378ba 100644
--- a/hb_server/apps/mtm/views.py
+++ b/hb_server/apps/mtm/views.py
@@ -74,8 +74,8 @@ class SubProductionViewSet(CreateUpdateModelAMixin, ModelViewSet):
产品生产分解增删改查
"""
perms_map={'*':'*'}
- queryset = SubProduction.objects.all()
- filterset_fields = ['product']
+ queryset = SubProduction.objects.select_related('process').all()
+ filterset_fields = ['product', 'process']
search_fields = ['name']
serializer_class = SubProductionSerializer
ordering = ['sort']
diff --git a/hb_server/apps/pm/migrations/0003_auto_20211014_1503.py b/hb_server/apps/pm/migrations/0003_auto_20211014_1503.py
new file mode 100644
index 0000000..c4b3798
--- /dev/null
+++ b/hb_server/apps/pm/migrations/0003_auto_20211014_1503.py
@@ -0,0 +1,45 @@
+# Generated by Django 3.2.6 on 2021-10-14 07:03
+
+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'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('mtm', '0022_auto_20211014_0944'),
+ ('pm', '0002_alter_productionplan_count'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='productionplan',
+ name='is_planed',
+ field=models.BooleanField(default=False, verbose_name='是否已排产'),
+ ),
+ migrations.CreateModel(
+ name='SubProductionPlan',
+ 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='删除标记')),
+ ('start_date', models.DateField(verbose_name='计划开工日期')),
+ ('end_date', models.DateField(verbose_name='计划完工日期')),
+ ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subproductionplan_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
+ ('process', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='关联大工序')),
+ ('production_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pm.productionplan', verbose_name='关联主生产计划')),
+ ('subproduction', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.subproduction', verbose_name='关联生产分解')),
+ ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subproductionplan_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
+ ('workshop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.organization', verbose_name='生产车间')),
+ ],
+ options={
+ 'verbose_name': '子生产计划',
+ 'verbose_name_plural': '子生产计划',
+ },
+ ),
+ ]
diff --git a/hb_server/apps/pm/migrations/0004_subproductionplan_steps.py b/hb_server/apps/pm/migrations/0004_subproductionplan_steps.py
new file mode 100644
index 0000000..56b78dd
--- /dev/null
+++ b/hb_server/apps/pm/migrations/0004_subproductionplan_steps.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.6 on 2021-10-15 02:14
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('pm', '0003_auto_20211014_1503'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='subproductionplan',
+ name='steps',
+ field=models.JSONField(default=list, verbose_name='工艺步骤'),
+ ),
+ ]
diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py
index 062e0f1..5aab5f3 100644
--- a/hb_server/apps/pm/models.py
+++ b/hb_server/apps/pm/models.py
@@ -1,4 +1,4 @@
-from apps.system.models import CommonAModel
+from apps.system.models import CommonAModel, Organization
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models.base import Model
@@ -6,10 +6,9 @@ import django.utils.timezone as timezone
from django.db.models.query import QuerySet
from utils.model import SoftModel, BaseModel
-from apps.mtm.models import Material
+from apps.mtm.models import Material, Process, SubProduction
from apps.sam.models import Order
-
class ProductionPlan(CommonAModel):
"""
生产计划
@@ -20,7 +19,7 @@ class ProductionPlan(CommonAModel):
count = models.IntegerField('生产数量', default=1)
start_date = models.DateField('计划开工日期')
end_date = models.DateField('计划完工日期')
-
+ is_planed = models.BooleanField('是否已排产', default=False)
class Meta:
verbose_name = '生产计划'
verbose_name_plural = verbose_name
@@ -28,3 +27,24 @@ class ProductionPlan(CommonAModel):
def __str__(self):
return self.number
+class SubProductionPlan(CommonAModel):
+ """
+ 子生产计划
+ """
+ production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE)
+ subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE)
+ start_date = models.DateField('计划开工日期')
+ end_date = models.DateField('计划完工日期')
+ workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE)
+ process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE)
+ steps = models.JSONField('工艺步骤', default=list)
+ class Meta:
+ verbose_name = '子生产计划'
+ verbose_name_plural = verbose_name
+
+# class ProductionProgress(BaseModel):
+# """
+# 子计划生产进度
+# """
+# subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE)
+# material = models.
\ No newline at end of file
diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py
index e3975c1..1950bce 100644
--- a/hb_server/apps/pm/serializers.py
+++ b/hb_server/apps/pm/serializers.py
@@ -1,7 +1,8 @@
-from apps.pm.models import ProductionPlan
+from apps.pm.models import ProductionPlan, SubProductionPlan
from rest_framework import serializers
from apps.sam.serializers import OrderSerializer
-from apps.mtm.serializers import MaterialSimpleSerializer
+from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer
+from apps.system.serializers import OrganizationSimpleSerializer
class ProductionPlanCreateFromOrderSerializer(serializers.ModelSerializer):
@@ -15,3 +16,25 @@ class ProductionPlanSerializer(serializers.ModelSerializer):
class Meta:
model = ProductionPlan
fields ='__all__'
+
+class ResourceCalSerializer(serializers.Serializer):
+ id = serializers.IntegerField(label='产品ID')
+ count = serializers.IntegerField(label='生产数量')
+
+class ResourceCalListSerializer(serializers.ListSerializer):
+ child = ResourceCalSerializer()
+
+class SubProductionPlanListSerializer(serializers.ModelSerializer):
+ workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True)
+ process_ = ProcessSimpleSerializer(source='process', read_only=True)
+ class Meta:
+ model=SubProductionPlan
+ fields = '__all__'
+
+class SubProductionPlanUpdateSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = SubProductionPlan
+ fields = ['start_date', 'end_date']
+
+class GenSubPlanSerializer(serializers.Serializer):
+ pass
diff --git a/hb_server/apps/pm/urls.py b/hb_server/apps/pm/urls.py
index 69e52f0..f6f6279 100644
--- a/hb_server/apps/pm/urls.py
+++ b/hb_server/apps/pm/urls.py
@@ -1,12 +1,13 @@
-from apps.pm.views import ProductionPlanViewSet
+from apps.pm.views import ProductionPlanViewSet, ResourceViewSet, SubProductionPlanViewSet
from django.db.models import base
from rest_framework import urlpatterns
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
-router.register('productionplan', ProductionPlanViewSet, basename='productionplan')
-
+router.register('production_plan', ProductionPlanViewSet, basename='production_plan')
+router.register('subproduction_plan', SubProductionPlanViewSet, basename='subproduction_plan')
+router.register('resource', ResourceViewSet, basename='resource')
urlpatterns = [
path('', include(router.urls)),
]
diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py
index a0d28fa..a9856cd 100644
--- a/hb_server/apps/pm/views.py
+++ b/hb_server/apps/pm/views.py
@@ -1,13 +1,19 @@
+from rest_framework import serializers
from rest_framework.views import APIView
+from apps.em.models import Equipment
+from apps.em.serializers import EquipmentSerializer
+from apps.mtm.models import InputMaterial, Step, SubProduction, UsedStep
from apps.system.mixins import CreateUpdateModelAMixin
-from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer
-from rest_framework.mixins import CreateModelMixin, ListModelMixin
-from apps.pm.models import ProductionPlan
+from apps.pm.serializers import GenSubPlanSerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer
+from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelMixin
+from apps.pm.models import ProductionPlan, SubProductionPlan
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from django.shortcuts import render
from apps.sam.models import Order
from rest_framework.exceptions import APIException
from rest_framework.response import Response
+from rest_framework.decorators import action
+from django.db.models import F
# Create your views here.
def updateOrderPlanedCount(order):
@@ -36,7 +42,9 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
def get_serializer_class(self):
if self.action in ['create']:
return ProductionPlanCreateFromOrderSerializer
- return ProductionPlanSerializer
+ elif self.action == 'list':
+ return ProductionPlanSerializer
+ return super().get_serializer_class()
def create(self, request, *args, **kwargs):
data = request.data
@@ -51,6 +59,90 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
instance = serializer.save(create_by=request.user, product=order.product)
updateOrderPlanedCount(instance.order)
return Response()
+
+ @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=GenSubPlanSerializer)
+ def gen_subplan(self, request, pk=None):
+ """
+ 生成子计划
+ """
+ production_plan=self.get_object()
+ if production_plan.is_planed:
+ raise APIException('已生成子计划')
+ subps = SubProduction.objects.filter(product=production_plan.product).order_by('process__number')
+ for i in subps:
+ steps = Step.objects.filter(usedstep__subproduction=i, usedstep__subproduction__is_deleted=False,
+ usedstep__is_deleted=False, is_deleted=False).values('id', 'number', 'name', 'usedstep__remark')
+ SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i,
+ start_date=production_plan.start_date, end_date=production_plan.end_date,
+ workshop=i.process.workshop, process=i.process, create_by=request.user,
+ steps = list(steps))
+ production_plan.is_planed=True
+ production_plan.save()
+ return Response()
+
+class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
+ """
+ 子生产计划-列表/修改
+ """
+ perms_map = {'*': '*'}
+ queryset = SubProductionPlan.objects.select_related('process', 'workshop')
+ search_fields = []
+ filterset_fields = ['production_plan']
+ ordering_fields = ['process__number']
+ ordering = ['process__number']
+
+ def get_serializer_class(self):
+ if self.action == 'list':
+ return SubProductionPlanListSerializer
+ elif self.action == 'update':
+ return SubProductionPlanUpdateSerializer
+ return SubProductionPlanListSerializer
+
+
+class ResourceViewSet(GenericViewSet):
+
+ perms_map = {'*': '*'}
+ @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=ResourceCalListSerializer)
+ def cal(self, request, pk=None):
+ """
+ 物料消耗计算
+ """
+ rdata = request.data
+ serializer = self.get_serializer(data=rdata)
+ serializer.is_valid(raise_exception=True)
+ res_d_list = []
+ res = []
+ for i in rdata:
+ materials = InputMaterial.objects.filter(subproduction__product__id=i['id'],
+ subproduction__is_deleted=False, is_deleted=False, material__type__in=[3,4]).order_by('material__number')\
+ .values('material__id', 'material__name', 'material__number', 'material__type', 'count', 'material__count')
+ l_m = list(materials)
+ for m in l_m:
+ if m['material__id'] in res_d_list:
+ index = res_d_list.index(m['material__id'])
+ res[index]['count'] = res[index]['count'] + m['count']*i['count']
+ else:
+ res_d_list.append(m['material__id'])
+ res.append({'id':m['material__id'], 'name':m['material__name'],
+ 'type':m['material__type'], 'number':m['material__number'],
+ 'count':m['count']*i['count'], 'inv_count':m['material__count']})
+ return Response(res)
+
+ @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=ResourceCalListSerializer)
+ def cal_equip(self, request, pk=None):
+ """
+ 设备状态查看
+ """
+ rdata = request.data
+ serializer = self.get_serializer(data=rdata)
+ serializer.is_valid(raise_exception=True)
+ rdata_l = []
+ for i in rdata:
+ rdata_l.append(i['id'])
+ subproductions = SubProduction.objects.filter(product__id__in=rdata_l, is_deleted=False)
+ steps = Step.objects.filter(usedstep__is_deleted=False, usedstep__subproduction__in=subproductions)
+ equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False)
+ serializer = EquipmentSerializer(instance=equips, many=True)
+ return Response(serializer.data)
+
-class ResourceCalculate(APIView):
- pass
\ No newline at end of file
diff --git a/hb_server/apps/wf/filters.py b/hb_server/apps/wf/filters.py
index 7e2990c..742af75 100644
--- a/hb_server/apps/wf/filters.py
+++ b/hb_server/apps/wf/filters.py
@@ -16,7 +16,7 @@ class TicketFilterSet(filters.FilterSet):
elif value == 'duty':
queryset = queryset.filter(participant__contains=user.id).exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
elif value == 'worked':
- queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user)
+ queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user).order_by('-update_time').distinct()
elif value == 'all':
pass
else:
diff --git a/hb_server/apps/wf/migrations/0013_alter_ticketflow_transition.py b/hb_server/apps/wf/migrations/0013_alter_ticketflow_transition.py
new file mode 100644
index 0000000..4b3fc80
--- /dev/null
+++ b/hb_server/apps/wf/migrations/0013_alter_ticketflow_transition.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.6 on 2021-10-14 01:44
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('wf', '0012_ticketflow_intervene_type'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ticketflow',
+ name='transition',
+ field=models.ForeignKey(blank=True, help_text='与worklow.Transition关联, 为空时表示认为干预的操作', null=True, on_delete=django.db.models.deletion.CASCADE, to='wf.transition', verbose_name='流转id'),
+ ),
+ ]
diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py
index 4878086..4540eb0 100644
--- a/hb_server/apps/wf/models.py
+++ b/hb_server/apps/wf/models.py
@@ -201,7 +201,7 @@ class TicketFlow(BaseModel):
工单流转日志
"""
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单', related_name='ticketflow_ticket')
- transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联, 为0时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
+ transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联, 为空时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')
diff --git a/hb_server/apps/wf/serializers.py b/hb_server/apps/wf/serializers.py
index 9075341..45369a5 100644
--- a/hb_server/apps/wf/serializers.py
+++ b/hb_server/apps/wf/serializers.py
@@ -129,4 +129,17 @@ class TicketHandleSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="处理意见", required = False)
class TicketRetreatSerializer(serializers.Serializer):
- suggestion = serializers.CharField(label="撤回原因", required = False)
\ No newline at end of file
+ suggestion = serializers.CharField(label="撤回原因", required = False)
+
+class TicketCloseSerializer(serializers.Serializer):
+ suggestion = serializers.CharField(label="关闭原因", required = False)
+
+class TicketAddNodeSerializer(serializers.Serializer):
+ suggestion = serializers.CharField(label="加签说明", required = False)
+ toadd_user = serializers.IntegerField(label='发送给谁去加签')
+
+class TicketAddNodeEndSerializer(serializers.Serializer):
+ suggestion = serializers.CharField(label="加签意见", required = False)
+
+class TicketDestorySerializer(serializers.Serializer):
+ ids = serializers.ListField(child=serializers.IntegerField(), label='工单ID列表')
\ No newline at end of file
diff --git a/hb_server/apps/wf/services.py b/hb_server/apps/wf/services.py
index c86fcb7..a6ec576 100644
--- a/hb_server/apps/wf/services.py
+++ b/hb_server/apps/wf/services.py
@@ -32,7 +32,18 @@ class WfService(object):
return wf_state_obj
except:
raise Exception('工作流状态配置错误')
-
+
+ @staticmethod
+ def get_workflow_end_state(workflow:Workflow):
+ """
+ 获取工作流结束状态
+ """
+ try:
+ wf_state_obj = State.objects.get(workflow=workflow, type=State.STATE_TYPE_END, is_deleted=False)
+ return wf_state_obj
+ except:
+ raise Exception('工作流状态配置错误')
+
@staticmethod
def get_workflow_custom_fields(workflow:Workflow):
"""
@@ -124,7 +135,7 @@ class WfService(object):
"""
到达结束状态
"""
- return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
+ return dict(destination_participant_type=0,
destination_participant=0,
multi_all_person={})
multi_all_person_dict = {}
diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py
index a1aab22..eab077e 100644
--- a/hb_server/apps/wf/views.py
+++ b/hb_server/apps/wf/views.py
@@ -1,9 +1,10 @@
+from apps.system.models import User
from apps.wf.filters import TicketFilterSet
from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
-from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
+from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
from django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view
@@ -11,6 +12,7 @@ from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, Tic
from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin
from apps.wf.services import WfService
from rest_framework.exceptions import APIException, PermissionDenied
+from rest_framework import status
# Create your views here.
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
@@ -225,7 +227,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING
if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
- transition.act_state = Ticket.TICKET_ACT_STATE_BACK
+ ticket.act_state = Ticket.TICKET_ACT_STATE_BACK
# 只更新必填和可选的字段
for key, value in ticket.state.state_fields.items():
@@ -312,16 +314,75 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
participant=request.user, transition=None)
return Response()
- @action(methods=['post'], detail=True, perms_map={'post':'*'})
+ @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeSerializer)
def add_node(self, request, pk=None):
"""
加签
"""
+ ticket = self.get_object()
+ data = request.data
+ add_user = User.objects.get(pk=data['toadd_user'])
+ ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
+ ticket.participant = add_user.id
+ ticket.in_add_node = True
+ ticket.add_node_man = request.user
+ ticket.save()
+ # 更新流转记录
+ suggestion = request.data.get('suggestion', '') # 加签说明
+ TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
+ suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
+ participant=request.user, transition=None)
+ return Response()
+ @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeEndSerializer)
+ def add_node_end(self, request, pk=None):
+ """
+ 加签完成
+ """
+ ticket = self.get_object()
+ ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
+ ticket.in_add_node = False
+ ticket.participant = ticket.add_node_man.id
+ ticket.add_node_man = None
+ ticket.save()
+ # 更新流转记录
+ suggestion = request.data.get('suggestion', '') # 加签意见
+ TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
+ suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
+ participant=request.user, transition=None)
+ return Response()
+
+
+ @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer)
def close(self, request, pk=None):
"""
- 关闭工单(超级管理员或者创建人在初始状态)
+ 关闭工单(创建人在初始状态)
"""
+ ticket = self.get_object()
+ if ticket.state.type == State.STATE_TYPE_START and ticket.create_by==request.user:
+ end_state = WfService.get_workflow_end_state(ticket.workflow)
+ ticket.state = end_state
+ ticket.participant_type = 0
+ ticket.participant = 0
+ ticket.act_state = Ticket.TICKET_ACT_STATE_CLOSED
+ ticket.save()
+ # 更新流转记录
+ suggestion = request.data.get('suggestion', '') # 关闭原因
+ TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
+ suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
+ participant=request.user, transition=None)
+ return Response()
+ else:
+ return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST)
+
+ @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=TicketDestorySerializer)
+ def destory(self, request, pk=None):
+ """
+ 批量物理删除
+ """
+ Ticket.objects.filter(id__in=request.data.get('ids', [])).delete(soft=False)
+ return Response()
+
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
diff --git a/hb_server/utils/response.py b/hb_server/utils/response.py
index 4657b5e..f4379f4 100644
--- a/hb_server/utils/response.py
+++ b/hb_server/utils/response.py
@@ -56,7 +56,7 @@ class FitJSONRenderer(JSONRenderer):
data = data[prefix]
if isinstance(data, list):
data = data[0]
- response_body.msg = prefix + ":" + data # 取一部分放入msg,方便前端alert
+ response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert
else:
response_body.data = data
renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分