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

This commit is contained in:
shilixia 2021-09-02 14:09:52 +08:00
commit bcd9fcb604
7 changed files with 276 additions and 25 deletions

View File

@ -0,0 +1,60 @@
# Generated by Django 3.2.6 on 2021-09-01 08:20
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),
('mtm', '0007_auto_20210831_1441'),
]
operations = [
migrations.CreateModel(
name='RecordForm',
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='表格名称')),
('type', models.IntegerField(choices=[(1, '生产记录')], default=1, verbose_name='表格类型')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recordform_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='recordform_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '记录表格',
'verbose_name_plural': '记录表格',
},
),
migrations.CreateModel(
name='RecordFormField',
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的选项')),
('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='recordformfield_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('form', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='关联表格')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='recordformfield_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '记录表格字段',
'verbose_name_plural': '记录表格字段',
},
),
migrations.DeleteModel(
name='StepOperationItem',
),
]

View File

@ -70,15 +70,27 @@ class Step(CommonAModel):
def __str__(self):
return self.name
# class StepTable(CommonAModel):
# """
# 过程记录表格
# """
class StepOperationItem(CommonAModel):
class RecordForm(CommonAModel):
"""
操作记录条目
记录表格
"""
type_choices=(
(1, '生产记录'),
)
name = models.CharField('表格名称', max_length=100, unique=True)
type = models.IntegerField('表格类型', choices=type_choices, default=1)
step = models.ForeignKey(Step, verbose_name='关联子工序', on_delete=models.CASCADE)
class Meta:
verbose_name = '记录表格'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class RecordFormField(CommonAModel):
"""
记录字段表
"""
field_type_choices = (
('string', '字符串'),
@ -93,7 +105,7 @@ class StepOperationItem(CommonAModel):
('selects', '多选下拉'),
('textarea', '文本域'),
)
step = models.ForeignKey(Step, on_delete=models.CASCADE, verbose_name='关联步骤')
form = models.ForeignKey(RecordForm, on_delete=models.CASCADE, verbose_name='关联表格')
field_type = models.CharField('类型', max_length=50, choices=field_type_choices)
field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突')
field_name = models.CharField('字段名称', max_length=50)
@ -103,7 +115,7 @@ class StepOperationItem(CommonAModel):
help_text='radio,checkbox,select,multiselect类型可供选择的选项格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号')
sort = models.IntegerField('排序号', default=1)
class Meta:
verbose_name = '操作记录条目'
verbose_name = '记录表格字段'
verbose_name_plural = verbose_name
def __str__(self):

View File

@ -1,7 +1,7 @@
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 .models import InputMaterial, Material, OutputMaterial, Process, ProductProcess, RecordForm, RecordFormField, Step, UsedStep
from apps.system.serializers import FileSimpleSerializer, OrganizationSimpleSerializer
@ -137,3 +137,53 @@ class UsedStepListSerializer(serializers.ModelSerializer):
""" Perform necessary eager loading of data. """
queryset = queryset.select_related('step')
return queryset
class RecordFormSerializer(serializers.ModelSerializer):
step_ = StepSimpleSerializer(source='step', read_only=True)
"""
记录表格序列化
"""
class Meta:
model = RecordForm
fields = '__all__'
@staticmethod
def setup_eager_loading(queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.select_related('step')
return queryset
class RecordFormCreateSerializer(serializers.ModelSerializer):
class Meta:
model = RecordForm
fields = ['name', 'type', 'step']
class RecordFormUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = RecordForm
fields = ['name', 'type']
class RecordFormFieldSerializer(serializers.ModelSerializer):
class Meta:
model = RecordFormField
fields = '__all__'
class RecordFormFieldCreateSerializer(serializers.ModelSerializer):
class Meta:
model = RecordFormField
fields = ['form', 'field_type', 'field_key', 'field_name', 'boolean_field_display', 'field_choice', 'sort']
def validate(self, data):
if RecordFormField.objects.filter(field_key=data['field_key'], form=data['form'], is_deleted=False).exists():
raise ValidationError('字段key已存在!')
return data
class RecordFormFieldUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = RecordFormField
fields = ['field_type', 'field_name', 'boolean_field_display', 'field_choice', 'sort']
class RecordFormFieldSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = RecordFormField
fields = '__all__'

View File

@ -1,6 +1,6 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.mtm.views import InputMaterialViewSet, MaterialViewSet, OutputMaterialViewSet, ProcessViewSet, StepViewSet, UsedStepViewSet
from apps.mtm.views import InputMaterialViewSet, MaterialViewSet, OutputMaterialViewSet, ProcessViewSet, RecordFormFieldViewSet, RecordFormViewSet, StepViewSet, UsedStepViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
@ -12,6 +12,8 @@ router.register('step', StepViewSet, basename='step')
router.register('inputmaterial', InputMaterialViewSet, basename='inputmaterial')
router.register('outputmaterial', OutputMaterialViewSet, basename='outputmaterial')
router.register('usedstep', UsedStepViewSet, basename='usedstep')
router.register('recordform', RecordFormViewSet, basename='recordform')
router.register('recordform-field', RecordFormFieldViewSet, basename='recordform-field')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -2,8 +2,8 @@ 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 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.mtm.models import InputMaterial, Material, OutputMaterial, Process, ProductProcess, RecordForm, RecordFormField, Step, UsedStep
from apps.mtm.serializers import InputMaterialListSerializer, InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OutputMaterialListSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, ProductProcessListSerializer, ProductProcessUpdateSerializer, ProcessSerializer, RecordFormCreateSerializer, RecordFormFieldCreateSerializer, RecordFormFieldSerializer, RecordFormFieldUpdateSerializer, RecordFormSerializer, RecordFormUpdateSerializer, StepDetailSerializer, StepSerializer, UsedStepCreateSerializer, UsedStepListSerializer
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from rest_framework.decorators import action
from rest_framework.response import Response
@ -127,7 +127,7 @@ class OutputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet):
return OutputMaterialUpdateSerializer
return OutputMaterialSerializer
class UsedStepViewSet(OptimizationMixin, CreateModelMixin, DestroyModelMixin, ListModelMixin, GenericViewSet):
class UsedStepViewSet(OptimizationMixin, CreateUpdateModelAMixin, CreateModelMixin, DestroyModelMixin, ListModelMixin, GenericViewSet):
"""
产品生产子工序表
"""
@ -140,3 +140,36 @@ class UsedStepViewSet(OptimizationMixin, CreateModelMixin, DestroyModelMixin, Li
if self.action =='create':
return UsedStepCreateSerializer
return UsedStepListSerializer
class RecordFormViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet):
"""
记录表格增删改查
"""
perms_map = {'*':'*'}
queryset = RecordForm.objects.all()
filterset_fields = ['step', 'type']
search_fields = ['name']
def get_serializer_class(self):
if self.action =='create':
return RecordFormCreateSerializer
elif self.action == 'update':
return RecordFormUpdateSerializer
return RecordFormSerializer
class RecordFormFieldViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet):
"""
表格字段表 增删改查
"""
perms_map = {'*':'*'}
queryset = RecordFormField.objects.all()
filterset_fields = ['field_type', 'form']
search_fields = ['field_name', 'field_key']
def get_serializer_class(self):
if self.action =='create':
return RecordFormFieldCreateSerializer
elif self.action == 'update':
return RecordFormFieldUpdateSerializer
return RecordFormFieldSerializer

View File

@ -89,7 +89,7 @@ class WfService(object):
@classmethod
def get_next_state_id_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition)->object:
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition)->object:
"""
获取下个节点状态
"""
@ -173,7 +173,36 @@ class WfService(object):
return dict(permission=False, msg="工单当前处于加签中,请加签完成后操作")
return dict(permission=True, msg="")
@classmethod
def check_dict_has_all_same_value(cls, dict_obj: object)->tuple:
"""
check whether all key are equal in a dict
:param dict_obj:
:return:
"""
value_list = []
for key, value in dict_obj.items():
value_list.append(value)
value_0 = value_list[0]
for value in value_list:
if value_0 != value:
return False
return True
@classmethod
def get_ticket_all_field_value(cls, ticket: Ticket)->dict:
"""
工单所有字段的值
get ticket's all field value
:param ticket:
:return:
"""
# 获取工单基础表中的字段中的字段信息
field_info_dict = ticket.get_dict()
# 获取自定义字段的值
custom_fields_queryset = cls.get_workflow_custom_fields(ticket.workflow)
for i in custom_fields_queryset:
field_info_dict[i.field_key] = ticket.ticket_data.get(i.field_key, None)
return field_info_dict

View File

@ -9,7 +9,7 @@ from rest_framework.decorators import action, api_view
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
from rest_framework.exceptions import APIException, PermissionDenied
# Create your views here.
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
@ -117,10 +117,10 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key))
ticket = serializer.save(state=start_state) # 先创建出来
next_state = WfService.get_next_state_id_by_transition_and_ticket_info(ticket=ticket, transition=transition)
next_state = WfService.get_next_state_by_transition_and_ticket_info(ticket=ticket, transition=transition)
participant_info = WfService.get_ticket_state_participant_info(state=next_state, ticket=ticket, ticket_data=ticket.ticket_data)
destination_participant_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', '')
destination_participant = participant_info.get('destination_participant', None)
multi_all_person = participant_info.get('multi_all_person', {}) # 多人需要全部处理情况
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
if next_state.type == State.STATE_TYPE_END:
@ -144,9 +144,9 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket.multi_all_person=multi_all_person
ticket.save()
# 新增流转记录
TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=ticket_data,
TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=ticket.create_by, transition=transition)
participant=ticket.create_by, transition=transition, create_by=request.user)
return Response(TicketSerializer(instance=ticket).data)
@ -160,7 +160,72 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
except:
raise APIException('工单不存在')
data = request.data
result = WfService.ticket_handle_permission_check()
result = WfService.ticket_handle_permission_check(ticket, request.user)
source_state = ticket.state
source_ticket_data = ticket.ticket_data
if result.get('permission') is False:
raise PermissionDenied(result.get('msg'))
# 校验表单必填项目
transition = Transition.objects.get(pk=data['transition'])
ticket_data = data['ticket_data']
if transition.field_require_check:
for key, value in ticket.state.state_fields.items():
if value == State.STATE_FIELD_REQUIRED:
if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key))
destination_state = WfService.get_next_state_by_transition_and_ticket_info(ticket, transition)
multi_all_person = ticket.multi_all_person
if multi_all_person:
multi_all_person[request.user.id] =dict(transition=transition.id)
# 判断所有人处理结果是否一致
if WfService.check_dict_has_all_same_value(multi_all_person):
participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data'])
destination_participant_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', None)
multi_all_person = {}
else:
# 处理人没有没有全部处理完成或者处理动作不一致
destination_participant_type = ticket.participant_type
destination_state = ticket.state # 保持原状态
destination_participant = []
for key, value in multi_all_person.items():
if not value:
destination_participant.push(key)
else:
# 当前处理人类型非全部处理
participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data'])
destination_participant_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', None)
multi_all_person = participant_info.get('multi_all_person', {})
# 更新工单信息:基础字段及自定义字段, add_relation字段 需要下个处理人是部门、角色等的情况
ticket.state = destination_state
ticket.participant_type = destination_participant_type
ticket.participant = destination_participant
ticket.multi_all_person = multi_all_person
if destination_state.type == State.STATE_TYPE_END:
ticket.act_state = State.TICKET_ACT_STATE_FINISH
elif destination_state.type == State.STATE_TYPE_START:
ticket.act_state = State.TICKET_ACT_STATE_DRAFT
else:
ticket.act_state = State.TICKET_ACT_STATE_ONGOING
if transition.attribute_type == State.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
transition.act_state = State.TICKET_ACT_STATE_BACK
# 只更新必填和可选的字段
for key, value in ticket.state.state_fields.items():
if value in (State.STATE_FIELD_REQUIRED, State.STATE_FIELD_OPTIONAL):
source_ticket_data[key] = ticket_data[key]
ticket.ticket_data = source_ticket_data
ticket.save()
# 更新工单流转记录
TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=data.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=request.user, transition=transition, create_by=request.user)
return Response(TicketSerializer(instance=ticket).data)
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def flowsteps(self, request, pk=None):