第一个案例,是芋道框架中的一个监听器实现方式.所有流程会触发一个公共监听器.而通过流程唯一的KEY,可以通过继承把公共监听器分散n个二级监听器,各自实现和KEY对应的自定义逻辑.
public abstract class BpmProcessInstanceStatusEventListener
implements ApplicationListener<BpmProcessInstanceStatusEvent> {
@Override
public final void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
if (!getProcessDefinitionKey().contains(event.getProcessDefinitionKey())) {
return;
}
onEvent(event);
}
/**
* @return 返回监听的流程定义 Key
*/
protected abstract List<String> getProcessDefinitionKey();
/**
* 处理事件
*
* @param event 事件
*/
protected abstract void onEvent(BpmProcessInstanceStatusEvent event);
}
@Component
public class ViolateReportStatusListener extends BpmProcessInstanceStatusEventListener {
@Resource
private SafetyManageApi safetyManageApi;
@Resource
private BpmTaskService bpmTaskService;
@Override
protected List<String> getProcessDefinitionKey() {
return Collections.singletonList(SafetyConstants.VIOLATE_REPORT_KEY);
}
@Override
protected void onEvent(BpmProcessInstanceStatusEvent statusEvent) {
List<HistoricTaskInstance> taskListByProcessInstanceId = bpmTaskService.getTaskListByProcessInstanceId(statusEvent.getId(), true);
String taskReason = FlowableUtils.getTaskReason(taskListByProcessInstanceId.get(1));
safetyManageApi.violateReportReview(new CertificatesLicensesReviewReqDTO()
.setId(Long.parseLong(statusEvent.getBusinessKey()))
.setRemark(taskReason)
.setApprovalStatus(statusEvent.getStatus()));
}
}
public interface SafetyConstants {
/**
* 三违上报流程标识
*/
String VIOLATE_REPORT_KEY = "oa_violate";
}
第二个案例: 是自己实现一个类流程引擎的极简逻辑结构.
// 第一步: 有一张流程记录表(流程,无非就是把每个节点和节点的状态记录而已)
CREATE TABLE `t_busi_flow` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键',
`business_type` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '1监察计划 2监察任务 3投诉管理',
`business_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '业务id',
`person` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '人员',
`dept` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '部门',
`flow_desc` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '节点描述',
`state` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '状态',
`remark` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '备注',
`file_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '附件id 多个附件用逗号相隔',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='流程表';
// 第二步: 流程表的单表增删改查
public interface FlowService extends IService<Flow> {
/**
* 根据业务类型和业务ids获取监察流程
*/
Map<String, List<FlowVO>> getFlowMap(String businessType, List<String> businessIds);
/**
* 新增
*/
String add(FlowAddDTO dto);
/**
* 添加流程记录
*
* @param businessType 业务类型
* @param businessId 业务ID
* @param actionType 操作类型
* @param actionDesc 操作描述
* @return 是否成功
*/
boolean addFlow(String businessType, String businessId, String actionType, String actionDesc);
}
@Service
@RequiredArgsConstructor
public class FlowServiceImpl extends ServiceImpl<FlowMapper, Flow> implements FlowService {
private final FlowMapper flowMapper;
private final SysUserMapper sysUserMapper;
private final SysOrgMapper sysOrgMapper;
private final SysOssService sysOssService;
/**
* 根据业务类型和业务ids获取监察流程
*/
@Override
public Map<String, List<FlowVO>> getFlowMap(String businessType, List<String> businessIds) {
List<Flow> flows = flowMapper.selectList(Wrappers.lambdaQuery(Flow.class)
.eq(Flow::getBusinessType, businessType)
.in(Flow::getBusinessId, businessIds)
.orderByAsc(Flow::getCreateTime));
if (flows.isEmpty()) {
return Collections.emptyMap();
}
// 单位信息
Map<String, String> orgMap = sysOrgMapper.selectList(Wrappers.lambdaQuery(SysOrg.class)
.in(SysOrg::getId, flows.stream()
.flatMap(flow -> Optional.ofNullable(flow.getDept())
.map(dept -> Arrays.stream(dept.split(",")))
.orElseGet(Stream::empty))
.filter(s -> !s.trim().isEmpty())
.distinct()
.collect(Collectors.toList())
)
.select(SysOrg::getId, SysOrg::getName))
.stream()
.collect(Collectors.toMap(SysOrg::getId, SysOrg::getName));
// 用户信息
List<String> userIds = flows.stream().map(Flow::getPerson).distinct().collect(Collectors.toList());
Map<String, String> userMap = sysUserMapper.selectList(Wrappers.lambdaQuery(SysUser.class)
.select(SysUser::getId, SysUser::getName)
.in(SysUser::getId, userIds)).stream()
.collect(Collectors.toMap(SysUser::getId, SysUser::getName));
// 附件信息
List<Long> fileIds = flows.stream()
.flatMap(flow -> Optional.ofNullable(flow.getFileId())
.map(file -> Arrays.stream(file.split(",")))
.orElseGet(Stream::empty))
.filter(s -> !s.trim().isEmpty())
.map(Long::parseLong)
.distinct()
.collect(Collectors.toList());
Map<String, SysOssVO> fileMap = sysOssService.getFileMap(fileIds);
return flows.stream().map(flow -> {
FlowVO vo = MapstructUtils.convert(flow, FlowVO.class)
.setPerson(userMap.getOrDefault(flow.getPerson(), ""));
// 部门信息
String deptIds = flow.getDept();
if (StringUtils.hasText(deptIds)) {
// 按逗号分割成列表
List<String> deptIdList = Arrays.asList(deptIds.split(","));
// 对应的部门名称
List<String> deptNames = deptIdList.stream()
.map(orgMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 将部门名称列表按逗号拼接成一个字符串
vo.setDept(String.join(",", deptNames));
} else {
vo.setDept("未知单位");
}
// 附件
String fileIdstr = flow.getFileId();
if (StringUtils.hasText(fileIdstr)) {
List<SysOssVO> files = Arrays.stream(fileIdstr.split(","))
.map(fileMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
vo.setFiles(files);
}
return vo;
}).collect(Collectors.groupingBy(FlowVO::getBusinessId));
}
/**
* 新增
*/
@Override
public String add(FlowAddDTO dto) {
if (dto.getCreateTime() == null) {
dto.setCreateTime(new Date());
}
Flow flow = MapstructUtils.convert(dto, Flow.class);
this.save(flow);
return flow.getId();
}
/**
* 添加流程记录
*/
@Override
public boolean addFlow(String businessType, String businessId, String actionType, String actionDesc) {
return true;
}
}
// 那么第三步就非常简单了,在需要流程记录的地方调用添加流程记录就行.
//最后我最感兴趣想要记录的是,通过封装,继承,多态实现的流程流转过程结构.
public interface VendorPersonnelAdmissionState {
/**
* 提交申请
* @param admission 准入申请实体
* @return 是否操作成功
*/
void submit(VendorPersonnelAdmission admission);
/**
* 开始培训
* @param admission 准入申请实体
* @return 是否操作成功
*/
void startTraining(VendorPersonnelAdmission admission);
/**
* 开始考试
* @param admission 准入申请实体
* @return 是否操作成功
*/
void startExam(VendorPersonnelAdmission admission);
/**
* 提交审批
* @param admission 准入申请实体
* @return 是否操作成功
*/
void submitApproval(VendorPersonnelAdmission admission);
/**
* 培训完成节点记录
* @param admission 准入申请实体
* @return 是否操作成功
*/
void TrainedApproval(VendorPersonnelAdmission admission);
/**
* 培训之后有考试但记录节点
* @param admission 准入申请实体
* @return 是否操作成功
*/
void TrainingPlanApproval(VendorPersonnelAdmission admission);
/**
* 审批通过
* @param admission 准入申请实体
* @return 是否操作成功
*/
void approve(VendorPersonnelAdmission admission);
/**
* 审批驳回
* @param admission 准入申请实体
* @return 是否操作成功
*/
void reject(VendorPersonnelAdmission admission);
/**
* 获取当前状态
* @return 状态码
*/
String getStatus();
}
@Slf4j
public class VendorPersonnelStateFactory {
private static final Map<String, VendorPersonnelAdmissionState> stateMap = new HashMap<>();
public VendorPersonnelStateFactory() {
}
static {
// 初始化所有状态
stateMap.put(VendorPersonnelAdmissionStatusEnum.DRAFT.getCode(), SpringUtil.getBean(ApplyDraftState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.COMMIT.getCode(), SpringUtil.getBean(ApplyCommitState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.TRAINING.getCode(), SpringUtil.getBean(ApplyTrainingState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.EXAMINING.getCode(), SpringUtil.getBean(ApplyExaminingState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.PENDING_APPROVAL.getCode(), SpringUtil.getBean(ApplyPendingApprovalState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.REJECTED.getCode(), SpringUtil.getBean(ApplyRejectedState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.APPROVED.getCode(), SpringUtil.getBean(ApplyApprovedState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.START_EXAM.getCode(), SpringUtil.getBean(ApplyTrainingPlanState.class));
stateMap.put(VendorPersonnelAdmissionStatusEnum.TRAINED.getCode(), SpringUtil.getBean(ApplyTrainedState.class));
}
public static VendorPersonnelAdmissionState getState(VendorPersonnelAdmission vendorPersonnelAdmission) {
String status = vendorPersonnelAdmission.getStatus();
VendorPersonnelAdmissionState state = stateMap.get(status);
if (state == null) {
throw new BizException("未找到状态为" + status + "的处理器,将使用默认的未开始状态");
}
return state;
}
}
@Slf4j
public abstract class AbstractVendorPersonnelAdmissionState implements VendorPersonnelAdmissionState {
@Override
public void submit(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持提交操作", getStatusName()));
}
@Override
public void startTraining(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持开始培训操作", getStatusName()));
}
@Override
public void startExam(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持开始考试操作", getStatusName()));
}
@Override
public void submitApproval(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持提交审批操作", getStatusName()));
}
@Override
public void TrainingPlanApproval(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持提交审批操作", getStatusName()));
}
@Override
public void TrainedApproval(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持提交审批操作", getStatusName()));
}
@Override
public void approve(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持审批通过操作", getStatusName()));
}
@Override
public void reject(VendorPersonnelAdmission admission) {
throw new BizException(String.format("当前状态 [{}] 不支持审批驳回操作", getStatusName()));
}
/**
* 获取状态名称
*
* @return 状态名称
*/
protected abstract String getStatusName();
}
@Slf4j
@Component
public class ApplyCommitState extends AbstractVendorPersonnelAdmissionState {
@Resource
private VendorPersonnelAdmissionService vendorPersonnelAdmissionService;
@Resource
private BusiServiceProviderClient busiServiceProviderClient;
@Resource
private TrainingCourseService trainingCourseService;
@Resource
private UserCourseService userCourseService;
@Resource
private ExamUserExamInfoService userExamInfoService;
@Resource
private ExamPaperService examPaperService;
@Resource
private FlowClient flowClient;
@Override
public void startTraining(VendorPersonnelAdmission admission) {
admission.setStatus(VendorPersonnelAdmissionStatusEnum.TRAINING.getCode());
vendorPersonnelAdmissionService.update(Wrappers.lambdaUpdate(VendorPersonnelAdmission.class)
.eq(VendorPersonnelAdmission::getId, admission.getId())
.set(VendorPersonnelAdmission::getUpdateTime, new Date())
.set(VendorPersonnelAdmission::getStatus, VendorPersonnelAdmissionStatusEnum.TRAINING.getCode()));
// ApplicationCirculationInfo applicationCirculationInfo = message.getData();
Long applyId = admission.getId();
VendorPersonnelAdmissionVO vendorPersonnelAdmission = vendorPersonnelAdmissionService.getDetail(applyId);
FlowAddDTO flowAddDTO = new FlowAddDTO()
.setBusinessId(String.valueOf(admission.getId()))
.setPerson(UserInfoUtil.getUserId())
.setDept(UserInfoUtil.getUserDeptId())
// .setFileId(admission.getAttachmentIds())
.setBusinessType(BusinessTypeEnum.PERSONNEL_ACCESS.getCode())
.setRemark("")
.setFlowDesc("提交申请后,培训中")
.setState("培训中")
.setCreateTime(new Date(System.currentTimeMillis() + 1000));
flowClient.addFlow(flowAddDTO);
// 调用服务商管理微服务校验服务商
if (ObjectUtil.isNotNull(vendorPersonnelAdmission) && ObjectUtil.isNotNull(vendorPersonnelAdmission.getVendorId())) {
Result<BusiServiceProviderVO> result = busiServiceProviderClient.getInfo(vendorPersonnelAdmission.getVendorId());
BusiServiceProviderVO serviceProviderVO = result.getData();
Assert.notNull(serviceProviderVO, "服务商不存在");
// 查询服务商是否有对应的培训
List<TrainingCourse> trainingCourses = trainingCourseService.listByProviderType(serviceProviderVO.getServiceType());
if (ObjectUtil.isEmpty(trainingCourses)) {
ApplicationCirculationInfo applicationCirculationInfo = new ApplicationCirculationInfo();
applicationCirculationInfo.setApplicationId(applyId);
HandlerUtil.handle(TrainingCompletedHandler.class, applicationCirculationInfo);
return;
}
trainingCourses.forEach(trainingCourse -> {
List<VendorAdmissionPersonnelVO> personnelList = vendorPersonnelAdmission.getPersonnelList();
for (VendorAdmissionPersonnelVO vendorAdmissionPersonnelVO : personnelList) {
// 添加用户培训课程
TrainingUserCourse trainingUserCourse = userCourseService.addUserCourse(trainingCourse.getId(),
vendorAdmissionPersonnelVO.getUserId(), String.valueOf(applyId));
// 添加用户考试
if (ObjectUtil.equals(trainingCourse.getIsAssessment(), 1)) {
List<ExamPaper> examPapers = examPaperService.listByPlanId(trainingCourse.getId());
examPapers.forEach(examPaper -> {
userExamInfoService.addUserExams(vendorAdmissionPersonnelVO.getUserId(),
vendorAdmissionPersonnelVO.getName(), examPaper.getId(),
trainingUserCourse.getCourseId(), trainingUserCourse.getId(), applyId);
});
}
}
});
}
}
@Override
public String getStatus() {
return VendorPersonnelAdmissionStatusEnum.DRAFT.getCode();
}
@Override
protected String getStatusName() {
return VendorPersonnelAdmissionStatusEnum.DRAFT.getDesc();
}
}
@Slf4j
@Component
public class ApplyDraftState extends AbstractVendorPersonnelAdmissionState {
@Resource
private VendorPersonnelAdmissionService vendorPersonnelAdmissionService;
@Resource
private FlowClient flowClient;
@Override
public void submit(VendorPersonnelAdmission admission) {
log.info("草稿状态提交申请,开始培训");
admission.setStatus(VendorPersonnelAdmissionStatusEnum.COMMIT.getCode());
vendorPersonnelAdmissionService.update(Wrappers.lambdaUpdate(VendorPersonnelAdmission.class)
.eq(VendorPersonnelAdmission::getId, admission.getId())
.set(VendorPersonnelAdmission::getUpdateTime, new Date())
.set(VendorPersonnelAdmission::getStatus, VendorPersonnelAdmissionStatusEnum.COMMIT.getCode()));
// VendorPersonnelAdmission byId = vendorPersonnelAdmissionService.getById(admission.getId());
ApplicationCirculationInfo applicationCirculationInfo = new ApplicationCirculationInfo();
applicationCirculationInfo.setApplicationId(admission.getId());
FlowAddDTO flowAddDTO = new FlowAddDTO()
.setBusinessId(String.valueOf(admission.getId()))
.setPerson(UserInfoUtil.getUserId())
.setDept(UserInfoUtil.getUserDeptId())
.setFileId(admission.getAttachmentIds())
.setBusinessType(BusinessTypeEnum.PERSONNEL_ACCESS.getCode())
.setRemark("")
.setFlowDesc("发起准入申请")
.setState("发起申请");
flowClient.addFlow(flowAddDTO);
HandlerUtil.handle(VendorPersonnelAdmissionInitiateHandler.class, applicationCirculationInfo);
}
@Override
public String getStatus() {
return VendorPersonnelAdmissionStatusEnum.DRAFT.getCode();
}
@Override
protected String getStatusName() {
return VendorPersonnelAdmissionStatusEnum.DRAFT.getDesc();
}
}
@Slf4j
@Component
public class ApplyPendingApprovalState extends AbstractVendorPersonnelAdmissionState {
@Resource
private VendorPersonnelAdmissionService vendorPersonnelAdmissionService;
@Resource
private VendorAdmissionPersonnelService vendorAdmissionPersonnelService;
@Resource
private FlowClient flowClient;
@Override
public void approve(VendorPersonnelAdmission admission) {
log.info("审批通过");
// 更新实体状态
admission.setStatus(VendorPersonnelAdmissionStatusEnum.APPROVED.getCode());
vendorPersonnelAdmissionService.update(Wrappers.lambdaUpdate(VendorPersonnelAdmission.class)
.eq(VendorPersonnelAdmission::getId, admission.getId())
.set(VendorPersonnelAdmission::getUpdateTime, new Date())
.set(VendorPersonnelAdmission::getStatus, VendorPersonnelAdmissionStatusEnum.APPROVED.getCode()));
vendorAdmissionPersonnelService.update(Wrappers.lambdaUpdate(VendorAdmissionPersonnel.class)
.eq(VendorAdmissionPersonnel::getApplicationId, admission.getId())
.set(VendorAdmissionPersonnel::getUpdateTime, new Date())
.set(VendorAdmissionPersonnel::getAdmissionStatus, AdmissionStatusEnum.PENDING_ADMISSION.getCode()));
FlowAddDTO flowAddDTO = new FlowAddDTO()
.setBusinessId(String.valueOf(admission.getId()))
.setPerson(UserInfoUtil.getUserId())
.setDept(UserInfoUtil.getUserDeptId())
// .setFileId(admission.getAttachmentIds())
.setBusinessType(BusinessTypeEnum.PERSONNEL_ACCESS.getCode())
.setRemark("")
// .setFlowDesc(UserInfoUtil.getUserName() + "审批通过")
.setFlowDesc("审批通过")
.setState("审批通过");
flowClient.addFlow(flowAddDTO);
}
@Override
public void reject(VendorPersonnelAdmission admission) {
log.info("审批驳回");
// 更新实体状态
admission.setStatus(VendorPersonnelAdmissionStatusEnum.REJECTED.getCode());
vendorPersonnelAdmissionService.update(Wrappers.lambdaUpdate(VendorPersonnelAdmission.class)
.eq(VendorPersonnelAdmission::getId, admission.getId())
.set(VendorPersonnelAdmission::getUpdateTime, new Date())
.set(VendorPersonnelAdmission::getStatus, VendorPersonnelAdmissionStatusEnum.REJECTED.getCode()));
FlowAddDTO flowAddDTO = new FlowAddDTO()
.setBusinessId(String.valueOf(admission.getId()))
.setPerson(UserInfoUtil.getUserId())
.setDept(UserInfoUtil.getUserDeptId())
// .setFileId(admission.getAttachmentIds())
.setBusinessType(BusinessTypeEnum.PERSONNEL_ACCESS.getCode())
.setRemark(admission.getRejectReason())
.setFlowDesc(UserInfoUtil.getUserName() + "审批不通过")
.setState("审批不通过");
flowClient.addFlow(flowAddDTO);
}
@Override
public String getStatus() {
return VendorPersonnelAdmissionStatusEnum.PENDING_APPROVAL.getCode();
}
@Override
protected String getStatusName() {
return VendorPersonnelAdmissionStatusEnum.PENDING_APPROVAL.getDesc();
}
}
@Component
@Slf4j
public class TrainingCompletedStartExamHandler implements TrainingCompletedHandler {
@Resource
private ExamUserExamInfoService userExamInfoService;
@Resource
private UserCourseService userCourseService;
@Resource
private VendorPersonnelAdmissionService vendorPersonnelAdmissionService;
@Override
public void handle(Message<ApplicationCirculationInfo> message) {
ApplicationCirculationInfo applicationCirculationInfo = message.getData();
Long trainingId = applicationCirculationInfo.getTrainingId();
// 不存在培训信息、就没有考试,直接执行考试完成
if (ObjectUtil.isNull(trainingId)) {
VendorPersonnelAdmission vendorPersonnelAdmission = vendorPersonnelAdmissionService.getById(applicationCirculationInfo.getApplicationId());
VendorPersonnelStateFactory.getState(vendorPersonnelAdmission).submitApproval(vendorPersonnelAdmission);
return;
}
TrainingUserCourse userCourse = userCourseService.getById(trainingId);
List<ExamUserExamInfo> examUserExamInfos = userExamInfoService.listUserExamsByTrainId(userCourse.getUserId(), Long.valueOf(userCourse.getSourceId()));
// 不存在培训信息对应的考试,直接执行考试完成
if (ObjectUtil.isEmpty(examUserExamInfos)) {
VendorPersonnelAdmission vendorPersonnelAdmission = new VendorPersonnelAdmission();
vendorPersonnelAdmission.setStatus(VendorPersonnelAdmissionStatusEnum.TRAINED.getCode());
vendorPersonnelAdmission.setId(applicationCirculationInfo.getApplicationId());
VendorPersonnelStateFactory.getState(vendorPersonnelAdmission).TrainedApproval(vendorPersonnelAdmission);
return;
}
// 存在考试,考试执行培训完成对应的操作
// for (ExamUserExamInfo examUserExamInfo : examUserExamInfos) {
VendorPersonnelAdmission vendorPersonnelAdmission = new VendorPersonnelAdmission();
vendorPersonnelAdmission.setStatus(VendorPersonnelAdmissionStatusEnum.START_EXAM.getCode());
vendorPersonnelAdmission.setId(applicationCirculationInfo.getApplicationId());
VendorPersonnelStateFactory.getState(vendorPersonnelAdmission).TrainingPlanApproval(vendorPersonnelAdmission);
// VendorPersonnelAdmission vendorPersonnelAdmission = vendorPersonnelAdmissionService.getById(applicationCirculationInfo.getApplicationId());
// VendorPersonnelStateFactory.getState(vendorPersonnelAdmission).startExam(vendorPersonnelAdmission);
userExamInfoService.completeTraining(examUserExamInfos.get(0).getId());
// }
}
@Override
public int order() {
return 1;
}
}