|
@@ -1,195 +1,505 @@
|
|
|
using Hotline.FlowEngine.Definitions;
|
|
|
using Hotline.FlowEngine.Notifies;
|
|
|
+using Hotline.SeedData;
|
|
|
using Hotline.Share.Dtos.FlowEngine;
|
|
|
using Hotline.Share.Enums.FlowEngine;
|
|
|
+using Hotline.Users;
|
|
|
using MapsterMapper;
|
|
|
using MediatR;
|
|
|
+using XF.Domain.Authentications;
|
|
|
using XF.Domain.Dependency;
|
|
|
using XF.Domain.Exceptions;
|
|
|
+using XF.Utility.SequentialId;
|
|
|
|
|
|
namespace Hotline.FlowEngine.Workflows
|
|
|
{
|
|
|
public class WorkflowDomainService : IWorkflowDomainService, IScopeDependency
|
|
|
{
|
|
|
private readonly IWorkflowRepository _workflowRepository;
|
|
|
+ private readonly IWorkflowStepRepository _workflowStepRepository;
|
|
|
+ private readonly IWorkflowTraceRepository _workflowTraceRepository;
|
|
|
+ private readonly IWorkflowSupplementRepository _workflowSupplementRepository;
|
|
|
+ private readonly ISessionContext _sessionContext;
|
|
|
private readonly IMapper _mapper;
|
|
|
private readonly IMediator _mediator;
|
|
|
|
|
|
public WorkflowDomainService(
|
|
|
IWorkflowRepository workflowRepository,
|
|
|
+ IWorkflowStepRepository workflowStepRepository,
|
|
|
+ IWorkflowTraceRepository workflowTraceRepository,
|
|
|
+ IWorkflowSupplementRepository workflowSupplementRepository,
|
|
|
+ ISessionContext sessionContext,
|
|
|
IMapper mapper,
|
|
|
IMediator mediator)
|
|
|
{
|
|
|
_workflowRepository = workflowRepository;
|
|
|
+ _workflowStepRepository = workflowStepRepository;
|
|
|
+ _workflowTraceRepository = workflowTraceRepository;
|
|
|
+ _workflowSupplementRepository = workflowSupplementRepository;
|
|
|
+
|
|
|
+ _sessionContext = sessionContext;
|
|
|
_mapper = mapper;
|
|
|
_mediator = mediator;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 返回前一级
|
|
|
+ /// 根据操作人获取当前未完成节点
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
- public async Task PreviousAsync(PreviousDto dto, CancellationToken cancellationToken)
|
|
|
+ public async Task<(WorkflowStep, WorkflowStep)> GetStepAsync(string workflowId, string orgCode, string userId, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- //var workflow = await _workflowRepository.GetAsync(dto.WorkflowId, cancellationToken);
|
|
|
- var workflow = await GetWorkflowAsync(dto.WorkflowId, cancellationToken);
|
|
|
-
|
|
|
- var (stepBox, currentStep) = GetStep(workflow.StepBoxes, dto.DepCode, dto.UserId);
|
|
|
- if (currentStep.StepType is EStepType.Start)
|
|
|
- throw new UserFriendlyException("当前流程已流转到开始步骤");
|
|
|
-
|
|
|
- //traces新增记录
|
|
|
- var trace = CreateTrace(workflow.Id, currentStep, dto, true);
|
|
|
- workflow.Traces.Add(trace);
|
|
|
+ var workflow = await GetWorkflowAsync(workflowId, cancellationToken);
|
|
|
+ return GetUnCompleteStep(workflow.StepBoxes, orgCode, userId);
|
|
|
+ }
|
|
|
|
|
|
- //取消上一节点stepBox.endtime
|
|
|
- var (previousStepBox, previousStep) = GetStep(workflow.StepBoxes, currentStep.PreviousId);
|
|
|
- previousStep.EndTime = null;
|
|
|
- previousStepBox.EndTime = null;
|
|
|
+ /// <summary>
|
|
|
+ /// 获取可能前往的下一节点
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public async Task<IReadOnlyList<NextStepDefine>> GetNextStepsAsync(QueryWorkflowDto dto, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var (_, currentStep) = await GetStepAsync(dto.WorkflowId, dto.OrgCode, dto.UserId, cancellationToken);
|
|
|
+ //获取人工判定的所有下一节点
|
|
|
+ return currentStep.NextSteps.Where(d => string.IsNullOrEmpty(d.Predicate)).ToList();
|
|
|
+ }
|
|
|
|
|
|
- //删除steps节点
|
|
|
- stepBox.Steps.Remove(currentStep);
|
|
|
+ public async Task<Workflow> CreateWorkflowAsync(Definition definition, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var workflow = new Workflow
|
|
|
+ {
|
|
|
+ DefinitionId = definition.Id,
|
|
|
+ Status = EWorkflowStatus.Runnable,
|
|
|
+ ExpiredTime = GenerateExpiredTime(definition.Code),
|
|
|
+ StepBoxes = new(),
|
|
|
+ Traces = new(),
|
|
|
+ Definition = definition
|
|
|
+ };
|
|
|
|
|
|
- await _workflowRepository.UpdateNavigateAsync(workflow, cancellationToken);
|
|
|
+ await _workflowRepository.AddAsync(workflow, cancellationToken);
|
|
|
|
|
|
- await _mediator.Publish(new PreviousStepNotify(dto), cancellationToken);
|
|
|
+ return workflow;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 前往下一节点
|
|
|
+ /// 进行流程的开始节点
|
|
|
/// </summary>
|
|
|
+ /// <param name="workflow"></param>
|
|
|
+ /// <param name="dto"></param>
|
|
|
+ /// <param name="cancellationToken"></param>
|
|
|
/// <returns></returns>
|
|
|
- public async Task NextAsync(ApproverDto dto, CancellationToken cancellationToken)
|
|
|
+ public async Task StartAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ if (dto.Handlers.Count > 1)
|
|
|
+ {
|
|
|
+ var startStepDefine = workflow.Definition.Steps.First(d => d.StepType == EStepType.Start);
|
|
|
+ //检查是否支持会签
|
|
|
+ if (startStepDefine.CountersignMode == ECountersignMode.UnSupport)
|
|
|
+ throw UserFriendlyException.SameMessage($"当前开始节点不支持开启会签, defineCode: {workflow.Definition.Code}");
|
|
|
+ }
|
|
|
+
|
|
|
+ var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
|
|
|
+
|
|
|
+ //第二节点的previousId is string.Empty
|
|
|
+ await CreateStepAsync(workflow, nextStepBoxDefine, dto, string.Empty, string.Empty, cancellationToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<Workflow> GetWorkflowAsync(string workflowId, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- var workflow = await _workflowRepository.GetAsync(dto.WorkflowId, cancellationToken);
|
|
|
+ var workflow = await _workflowRepository.Queryable()
|
|
|
+ .Includes(d => d.Definition)
|
|
|
+ .FirstAsync(d => d.Id == workflowId);
|
|
|
if (workflow is null)
|
|
|
throw new UserFriendlyException("无效workflowId");
|
|
|
|
|
|
- var (stepBox, currentStep) = GetStep(workflow.StepBoxes, dto.DepCode, dto.UserId);
|
|
|
+ var steps = await _workflowStepRepository.Queryable()
|
|
|
+ .Where(d => d.WorkflowId == workflowId)
|
|
|
+ .OrderBy(d => d.CreationTime)
|
|
|
+ .ToTreeAsync(d => d.Steps, d => d.ParentId, null);
|
|
|
+
|
|
|
+ //var traces = await _workflowTraceRepository.Queryable()
|
|
|
+ // .Where(d => d.WorkflowId == workflowId)
|
|
|
+ // .ToTreeAsync(d => d.Traces, d => d.ParentId, null);
|
|
|
+
|
|
|
+ workflow.StepBoxes = steps;
|
|
|
+ //workflow.Traces = traces;
|
|
|
+
|
|
|
+ return workflow;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 受理
|
|
|
+ /// </summary>
|
|
|
+ public async Task AcceptAsync(Workflow workflow, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ //工单完成以后查看的场景
|
|
|
+ if (workflow.Status is not EWorkflowStatus.Runnable) return;
|
|
|
+
|
|
|
+ var (currentStepBox, currentStep) = GetUnCompleteStepOrDefault(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
|
|
|
+ if (currentStep is null) return;
|
|
|
+ if (currentStep.Status is EWorkflowStepStatus.Accepted) return;
|
|
|
+ if (currentStep.StepType is EStepType.End)
|
|
|
+ throw new UserFriendlyException("当前流程已流转到最终步骤");
|
|
|
+
|
|
|
+ if (currentStepBox.Status is EWorkflowStepStatus.Assigned)
|
|
|
+ currentStepBox.Status = EWorkflowStepStatus.Accepted;
|
|
|
+ currentStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName);
|
|
|
+
|
|
|
+ await AcceptTraceAsync(workflow, currentStepBox, currentStep, cancellationToken);
|
|
|
+
|
|
|
+ //todo publish accept
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 办理(流转至下一节点)
|
|
|
+ /// </summary>
|
|
|
+ public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ CheckWhetherRunnable(workflow.Status);
|
|
|
+
|
|
|
+ //赋值当前节点
|
|
|
+ var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
|
|
|
+ if (currentStep.Status is EWorkflowStepStatus.Assigned)
|
|
|
+ await AcceptAsync(workflow, cancellationToken);
|
|
|
if (currentStep.StepType is EStepType.End)
|
|
|
throw new UserFriendlyException("当前流程已流转到最终步骤");
|
|
|
|
|
|
- currentStep.EndTime = DateTime.Now;
|
|
|
+ //下一节点是否发起会签
|
|
|
+ var isCountersign = dto.Handlers.Count > 1;
|
|
|
+ //检查是否支持开启会签
|
|
|
+ if (isCountersign && currentStep.CountersignMode == ECountersignMode.UnSupport)
|
|
|
+ throw UserFriendlyException.SameMessage($"当前节点不支持开启会签, code: {currentStep.Code}");
|
|
|
+
|
|
|
_mapper.Map(dto, currentStep);
|
|
|
|
|
|
- var selectedStep = currentStep.NextSteps.First(d => d.Code == dto.NextStepCode);
|
|
|
- selectedStep.Selected = true;
|
|
|
- if (stepBox.Steps.All(d => d.EndTime.HasValue))
|
|
|
- stepBox.EndTime = currentStep.EndTime;
|
|
|
+ currentStep.Complete(
|
|
|
+ _sessionContext.RequiredUserId,
|
|
|
+ _sessionContext.UserName,
|
|
|
+ _sessionContext.RequiredOrgCode,
|
|
|
+ _sessionContext.OrgName,
|
|
|
+ dto.NextStepCode);
|
|
|
|
|
|
- //创建下一节点
|
|
|
- //依据nextStepCode,检查box是否存在,不存在则在模板中找到对应define创建box,存在则在box中新增step
|
|
|
- var stepDefine = workflow.Definition.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode);
|
|
|
- if (stepDefine is null)
|
|
|
- throw UserFriendlyException.SameMessage($"模板配置中未找到对应节点, code: {dto.NextStepCode}");
|
|
|
- var existsStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode);
|
|
|
- if (existsStepBox == null)
|
|
|
+ if (currentStepBox.Status is not EWorkflowStepStatus.Completed &&
|
|
|
+ currentStepBox.Steps.All(d => d.Status is EWorkflowStepStatus.Completed))
|
|
|
{
|
|
|
- var steps = CreateStepByStepDefine(stepDefine, dto.Handlers, currentStep.Id);
|
|
|
- existsStepBox = CreateStepBox(stepDefine, steps);
|
|
|
- workflow.StepBoxes.Add(existsStepBox);
|
|
|
+ currentStepBox.Status = EWorkflowStepStatus.Completed;
|
|
|
+ currentStepBox.CompleteTime = currentStepBox.Steps.Max(d => d.CompleteTime);
|
|
|
}
|
|
|
- else
|
|
|
+ await _workflowStepRepository.UpdateRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep }, cancellationToken);
|
|
|
+
|
|
|
+ //检查会签是否结束,并更新当前会签节点
|
|
|
+ workflow.CheckCountersignStatus();
|
|
|
+
|
|
|
+ //检查是否流转到流程终点
|
|
|
+ //var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
|
|
|
+ if (nextStepBoxDefine.StepType is EStepType.End)
|
|
|
{
|
|
|
- var steps = CreateStepByStepDefine(stepDefine, dto.Handlers, currentStep.Id);
|
|
|
- existsStepBox.Steps.AddRange(steps);
|
|
|
+ workflow.Complete();
|
|
|
+ await _workflowRepository.UpdateAsync(workflow, cancellationToken);
|
|
|
+
|
|
|
+ //todo publish workflow end
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
+ //判断是否从中心流转出去,重新计算expiredTime
|
|
|
+ if (isOutOfCallCenter)
|
|
|
+ workflow.ExpiredTime = GenerateExpiredTime(workflow.Definition.Code);
|
|
|
+
|
|
|
+ //创建下一节点
|
|
|
+ var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, currentStepBox.Id, currentStep.Id, cancellationToken);
|
|
|
+
|
|
|
+ //不在会签中的流程,更新当前节点名称、时间、会签节点code
|
|
|
+ if (!workflow.IsInCountersign())
|
|
|
+ SetWorkflowCurrentStepInfo(workflow, isCountersign, nextStepBox);
|
|
|
+
|
|
|
+ await _workflowRepository.UpdateAsync(workflow, cancellationToken);
|
|
|
+
|
|
|
//trace
|
|
|
- var trace = CreateTrace(workflow.Id, currentStep, dto);
|
|
|
- workflow.Traces.Add(trace);
|
|
|
+ await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
|
|
|
|
|
|
- await _workflowRepository.UpdateNavigateAsync(workflow, cancellationToken);
|
|
|
-
|
|
|
- await _mediator.Publish(new NextStepNotify(dto), cancellationToken);
|
|
|
+ await _mediator.Publish(new NextStepNotify(workflow.Id, dto), cancellationToken);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 根据操作人获取当前节点
|
|
|
+ /// 退回(返回前一节点)
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
- public async Task<(WorkflowStep, WorkflowStep)> GetStepAsync(string workflowId, string depCode, string userId, CancellationToken cancellationToken)
|
|
|
+ public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- var workflow = await _workflowRepository.GetAsync(workflowId, cancellationToken);
|
|
|
- if (workflow is null)
|
|
|
- throw new UserFriendlyException("无效workflowId");
|
|
|
+ CheckWhetherRunnable(workflow.Status);
|
|
|
+ var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
|
|
|
+ if (currentStepBox.StepType is EStepType.Start)
|
|
|
+ throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
|
|
|
+ if (currentStepBox.Steps.Count > 1)
|
|
|
+ throw UserFriendlyException.SameMessage("会签流程不支持退回");
|
|
|
|
|
|
- return GetStep(workflow.StepBoxes, depCode, userId);
|
|
|
+ //update trace
|
|
|
+ await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken);
|
|
|
+
|
|
|
+ //remove workflow.steps
|
|
|
+ await _workflowStepRepository.RemoveRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep },
|
|
|
+ cancellationToken);
|
|
|
+
|
|
|
+ //todo publish
|
|
|
}
|
|
|
|
|
|
- public (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string depCode, string userId)
|
|
|
+ /// <summary>
|
|
|
+ /// 撤回(返回到之前任意节点)
|
|
|
+ /// </summary>
|
|
|
+ public async Task RecallAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- foreach (var stepBox in stepBoxes)
|
|
|
+ CheckWhetherRunnable(workflow.Status);
|
|
|
+ var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.TargetStepCode);
|
|
|
+ if (targetStepBox is null)
|
|
|
+ throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
|
|
|
+
|
|
|
+ await RecallAsync(workflow, dto, targetStepBox, cancellationToken);
|
|
|
+
|
|
|
+ //todo publish
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 跳转(直接将流程跳转至任意节点)
|
|
|
+ /// </summary>
|
|
|
+ public async Task JumpAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ CheckWhetherRunnable(workflow.Status);
|
|
|
+ //update uncompleted traces
|
|
|
+ await JumpTraceAsync(workflow.Id, dto, cancellationToken);
|
|
|
+
|
|
|
+ var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.TargetStepCode);
|
|
|
+ if (targetStepBox == null)
|
|
|
{
|
|
|
- foreach (var step in stepBox.Steps)
|
|
|
- {
|
|
|
- if (step.HandlerId == depCode || step.HandlerId == userId)
|
|
|
- return (stepBox, step);
|
|
|
- }
|
|
|
+ //向后跳转
|
|
|
+ var lastStepBox = workflow.StepBoxes.OrderBy(d => d.CreationTime).Last();
|
|
|
+ if (lastStepBox.StepType is EStepType.Start or EStepType.End)
|
|
|
+ throw UserFriendlyException.SameMessage("无法跳转至开始或结束节点");
|
|
|
+
|
|
|
+ var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
|
|
|
+ var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, lastStepBox.Id, lastStepBox.Id, cancellationToken);
|
|
|
+
|
|
|
+ workflow.CloseCountersignStatus();
|
|
|
+ var isCountersign = dto.Handlers.Count > 1;
|
|
|
+ SetWorkflowCurrentStepInfo(workflow, isCountersign, nextStepBox);
|
|
|
+
|
|
|
+ #region 补充中间节点处理方案
|
|
|
+
|
|
|
+ //var completeStepCodes = workflow.StepBoxes.Select(d => d.Code);
|
|
|
+ //var uncompleteStepDefines = workflow.Definition.Steps.Where(d => !completeStepCodes.Contains(d.Code));
|
|
|
+ //创建当前节点与目标节点中间节点
|
|
|
+ //var jumpDto = new BasicWorkflowDto
|
|
|
+ //{
|
|
|
+ // Opinion = "跳转补充"
|
|
|
+ //};
|
|
|
+
|
|
|
+ //foreach (var stepDefine in uncompleteStepDefines)
|
|
|
+ //{
|
|
|
+ // var previousStepId = lastStepBox.Steps.Count > 1 ? lastStepBox.Id : lastStepBox.Steps.First().Id;
|
|
|
+ // if (dto.TargetStepCode == stepDefine.Code)
|
|
|
+ // {
|
|
|
+ // await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
|
|
|
+ // break;
|
|
|
+ // }
|
|
|
+
|
|
|
+ // //jump业务下,如果当前节点为会签节点,第一个补充节点的subStep.PreviousId无法确定从哪个子节点跳转过来,统一处理为当前节点的stepBox.Id
|
|
|
+ // lastStepBox = await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
|
|
|
+ //}
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ //返回之前节点
|
|
|
+ await RecallAsync(workflow, dto, targetStepBox, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- throw new UserFriendlyException("未找到对应节点");
|
|
|
+ //todo publish
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// 获取可能前往的下一节点
|
|
|
+ /// 补充
|
|
|
/// </summary>
|
|
|
/// <returns></returns>
|
|
|
- public async Task<IReadOnlyList<NextStepDefine>> GetNextStepsAsync(QueryWorkflowDto dto, CancellationToken cancellationToken)
|
|
|
+ public async Task SupplementAsync(Workflow workflow, EndWorkflowDto dto, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- var (_, currentStep) = await GetStepAsync(dto.WorkflowId, dto.DepCode, dto.UserId, cancellationToken);
|
|
|
- //获取人工判定的所有下一节点
|
|
|
- return currentStep.NextSteps.Where(d => string.IsNullOrEmpty(d.Predicate)).ToList();
|
|
|
+ CheckWhetherRunnable(workflow.Status);
|
|
|
+ //todo 检查当前办理人是否为该流程中的办理人
|
|
|
+
|
|
|
+ var supplement = _mapper.Map<WorkflowSupplement>(dto);
|
|
|
+ await _workflowSupplementRepository.AddAsync(supplement, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- public Workflow CreateWorkflow(Definition definition, StartWorkflowDto dto)
|
|
|
+ /// <summary>
|
|
|
+ /// 根据stepCode查询流程配置中对应的节点
|
|
|
+ /// </summary>
|
|
|
+ public StepDefine GetStepBoxDefine(Definition definition, string stepCode)
|
|
|
{
|
|
|
- var workflow = new Workflow
|
|
|
- {
|
|
|
+ if (definition == null) throw new ArgumentNullException(nameof(definition));
|
|
|
+ if (string.IsNullOrEmpty(stepCode)) throw new ArgumentNullException(nameof(stepCode));
|
|
|
+ if (!definition.Steps.Any())
|
|
|
+ throw new UserFriendlyException($"流程未配置节点,DefineCode: {definition.Code}", "流程配置错误");
|
|
|
+ var stepDefine = definition.Steps.FirstOrDefault(d => d.Code == stepCode);
|
|
|
+ if (stepDefine == null)
|
|
|
+ throw new UserFriendlyException($"未找到流程中对应的节点,DefineCode: {definition.Code}, stepCode: {stepCode}",
|
|
|
+ "未查询到对应节点");
|
|
|
+ return stepDefine;
|
|
|
+ }
|
|
|
|
|
|
- Definition = definition,
|
|
|
- Status = EWorkflowStatus.Runnable,
|
|
|
- };
|
|
|
|
|
|
- var startStep = definition.Steps.First(d => d.StepType == EStepType.Start);
|
|
|
- if (startStep.NextSteps.Count > 1)
|
|
|
- throw new UserFriendlyException("开始节点存在多个下一节点");
|
|
|
+ #region private
|
|
|
|
|
|
- //start节点后的首个节点
|
|
|
- var firstStep = startStep.NextSteps.First();
|
|
|
- if (firstStep.HandlerType == EHandlerType.AssignUser || firstStep.HandlerType == EHandlerType.AssignDep)
|
|
|
+ /// <summary>
|
|
|
+ /// 更新workflow中当前停留节点,时间和会签开始节点code
|
|
|
+ /// </summary>
|
|
|
+ private static void SetWorkflowCurrentStepInfo(Workflow workflow, bool isCountersign, WorkflowStep stepBox)
|
|
|
+ {
|
|
|
+ //1.不在会签中,未发起会签(普通处理) 2.不在会签中,发起会签(保存会签节点),3.会签中,不更新
|
|
|
+ if (isCountersign)
|
|
|
{
|
|
|
- var steps = CreateStepByStepDefine(firstStep, firstStep.HandlerClassifies, "StartStep");
|
|
|
- workflow.StepBoxes.Add(CreateStepBox(firstStep, steps));
|
|
|
+ workflow.FLow(stepBox.Name, stepBox.CreationTime, stepBox.Code);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if (dto.Handlers.Any())
|
|
|
- {
|
|
|
- var steps = CreateStepByStepDefine(firstStep, dto.Handlers, "StartStep");
|
|
|
- workflow.StepBoxes.Add(CreateStepBox(firstStep, steps));
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- throw new UserFriendlyException("未指定节点处理者");
|
|
|
- }
|
|
|
+ workflow.FLow(stepBox.Name, stepBox.CreationTime);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- return workflow;
|
|
|
+ private async Task JumpTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ //未办理的traces
|
|
|
+ var uncompleteTraces =
|
|
|
+ await _workflowTraceRepository.QueryAsync(d =>
|
|
|
+ d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
|
|
|
+ foreach (var trace in uncompleteTraces)
|
|
|
+ {
|
|
|
+ trace.Jump(
|
|
|
+ _sessionContext.RequiredUserId,
|
|
|
+ _sessionContext.UserName,
|
|
|
+ _sessionContext.RequiredOrgCode,
|
|
|
+ _sessionContext.OrgName);
|
|
|
+ }
|
|
|
+
|
|
|
+ await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- public void AddStartTrace(Workflow workflow, StartWorkflowDto dto)
|
|
|
+ private async Task RecallTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
|
|
|
{
|
|
|
- var startStep = workflow.Definition.Steps.First(d => d.StepType == EStepType.Start);
|
|
|
- var trace = _mapper.Map<WorkflowTrace>(startStep);
|
|
|
+ //未办理的traces
|
|
|
+ var uncompleteTraces =
|
|
|
+ await _workflowTraceRepository.QueryAsync(d =>
|
|
|
+ d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
|
|
|
+ foreach (var trace in uncompleteTraces)
|
|
|
+ {
|
|
|
+ trace.Recall(
|
|
|
+ _sessionContext.RequiredUserId,
|
|
|
+ _sessionContext.UserName,
|
|
|
+ _sessionContext.RequiredOrgCode,
|
|
|
+ _sessionContext.OrgName);
|
|
|
+ }
|
|
|
+
|
|
|
+ await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task PreviousTraceAsync(string workflowId, PreviousWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var trace = await GetWorkflowTraceAsync(workflowId, step.Id, cancellationToken);
|
|
|
_mapper.Map(dto, trace);
|
|
|
- trace.WorkflowId = workflow.Id;
|
|
|
- workflow.Traces.Add(trace);
|
|
|
+ trace.Previous(
|
|
|
+ _sessionContext.RequiredUserId,
|
|
|
+ _sessionContext.UserName,
|
|
|
+ _sessionContext.RequiredOrgCode,
|
|
|
+ _sessionContext.OrgName);
|
|
|
+ await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
|
|
|
}
|
|
|
|
|
|
- #region private
|
|
|
+ private async Task NextTraceAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var trace = await GetWorkflowTraceAsync(workflow.Id, step.Id, cancellationToken);
|
|
|
+ _mapper.Map(dto, trace);
|
|
|
+ _mapper.Map(step, trace);
|
|
|
+ trace.ExpiredTime = workflow.ExpiredTime;
|
|
|
+ await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task AcceptTraceAsync(Workflow workflow, WorkflowStep currentStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var trace = _mapper.Map<WorkflowTrace>(currentStep);//todo ignore parentId, map stepId
|
|
|
+ trace.Status = EWorkflowTraceStatus.Normal;
|
|
|
+ trace.ExpiredTime = workflow.ExpiredTime;
|
|
|
+
|
|
|
+ if (!string.IsNullOrEmpty(currentStep.PreviousId) && currentStepBox.Steps.Count > 1)
|
|
|
+ {
|
|
|
+ //有会签
|
|
|
+ var parentTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken);
|
|
|
+ trace.ParentId = parentTrace.Id;
|
|
|
+ }
|
|
|
+
|
|
|
+ await _workflowTraceRepository.AddAsync(trace, cancellationToken);
|
|
|
+ }
|
|
|
|
|
|
- private (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string stepId)
|
|
|
+ private async Task<WorkflowTrace> GetWorkflowTraceAsync(string workflowId, string stepId, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var parentTrace = await _workflowTraceRepository.GetAsync(d =>
|
|
|
+ d.WorkflowId == workflowId && d.StepId == stepId, cancellationToken);
|
|
|
+ if (parentTrace == null)
|
|
|
+ throw new UserFriendlyException($"未找到对应trace, workflowId: {workflowId}, stepId: {stepId}");
|
|
|
+ return parentTrace;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ private async Task RecallAsync(Workflow workflow, RecallDto dto, WorkflowStep targetStepBox, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ //update uncompleted traces
|
|
|
+ await RecallTraceAsync(workflow.Id, dto, cancellationToken);
|
|
|
+
|
|
|
+ //remove completedSteps include target self
|
|
|
+ var completeStepBoxes = workflow.StepBoxes.Where(d =>
|
|
|
+ d.Code == dto.TargetStepCode && d.CreationTime > targetStepBox.CreationTime);
|
|
|
+ var removeSteps = new List<WorkflowStep>();
|
|
|
+ foreach (var stepBox in completeStepBoxes)
|
|
|
+ {
|
|
|
+ removeSteps.Add(stepBox);
|
|
|
+ removeSteps.AddRange(stepBox.Steps);
|
|
|
+ }
|
|
|
+
|
|
|
+ await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
|
|
|
+
|
|
|
+ //recreate targetStep
|
|
|
+ var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
|
|
|
+ await CreateStepAsync(workflow, nextStepBoxDefine, dto, targetStepBox.PreviousId, targetStepBox.Steps.First().PreviousId, cancellationToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void CheckWhetherRunnable(EWorkflowStatus status)
|
|
|
+ {
|
|
|
+ if (status is not EWorkflowStatus.Runnable)
|
|
|
+ throw new UserFriendlyException("当前流程状态不可继续流转");
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task<WorkflowStep> CreateStepAsync(Workflow workflow, StepDefine stepBoxDefine, BasicWorkflowDto dto, string previousStepBoxId, string previousStepId, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var nextStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == stepBoxDefine.Code);
|
|
|
+ nextStepBox ??= CreateStepBox(stepBoxDefine, dto, previousStepBoxId);
|
|
|
+ await _workflowStepRepository.AddAsync(nextStepBox, cancellationToken);
|
|
|
+
|
|
|
+ if (stepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.AssignDep)
|
|
|
+ {
|
|
|
+ var subSteps = CreateSubSteps(nextStepBox, nextStepBox.HandlerClassifies, dto.NextMainHandler, previousStepId);
|
|
|
+ nextStepBox.Steps.AddRange(subSteps);
|
|
|
+ await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (!dto.Handlers.Any())
|
|
|
+ throw new UserFriendlyException("未指定节点处理者");
|
|
|
+ var subSteps = CreateSubSteps(nextStepBox, dto.Handlers, dto.NextMainHandler, previousStepId);
|
|
|
+ nextStepBox.Steps.AddRange(subSteps);
|
|
|
+ await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
|
|
|
+ }
|
|
|
+
|
|
|
+ return nextStepBox;
|
|
|
+ }
|
|
|
+
|
|
|
+ private (WorkflowStep stepBox, WorkflowStep step) GetStep(List<WorkflowStep> stepBoxes, string stepId)
|
|
|
{
|
|
|
foreach (var stepBox in stepBoxes)
|
|
|
{
|
|
@@ -203,6 +513,40 @@ namespace Hotline.FlowEngine.Workflows
|
|
|
throw new UserFriendlyException("未找到对应节点");
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// 查询未完成节点
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="stepBoxes"></param>
|
|
|
+ /// <param name="orgCode"></param>
|
|
|
+ /// <param name="userId"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private (WorkflowStep, WorkflowStep) GetUnCompleteStep(List<WorkflowStep> stepBoxes, string orgCode, string userId)
|
|
|
+ {
|
|
|
+ var (stepBox, step) = GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed);
|
|
|
+ if (step == null)
|
|
|
+ throw new UserFriendlyException("未找到对应节点");
|
|
|
+ return (stepBox, step);
|
|
|
+ }
|
|
|
+
|
|
|
+ private (WorkflowStep, WorkflowStep) GetUnCompleteStepOrDefault(List<WorkflowStep> stepBoxes, string orgCode, string userId) =>
|
|
|
+ GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed);
|
|
|
+
|
|
|
+ private (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string orgCode, string userId, Func<EWorkflowStepStatus, bool> predicate)
|
|
|
+ {
|
|
|
+ if (!stepBoxes.Any()) throw new UserFriendlyException("该流程中暂无节点");
|
|
|
+ foreach (var stepBox in stepBoxes)
|
|
|
+ {
|
|
|
+ foreach (var step in stepBox.Steps)
|
|
|
+ {
|
|
|
+ if (predicate(step.Status) && (step.HandlerId == orgCode || step.HandlerId == userId))
|
|
|
+ return (stepBox, step);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return new();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
private List<WorkflowStep> CreateStepByStepDefine(StepBasic stepBasic, List<string> handlers, string previousId)
|
|
|
{
|
|
|
return handlers.Select(d =>
|
|
@@ -220,38 +564,84 @@ namespace Hotline.FlowEngine.Workflows
|
|
|
{
|
|
|
var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
|
|
|
if (steps != null && steps.Any())
|
|
|
- stepBox.Steps = steps;
|
|
|
+ stepBox.Steps.AddRange(steps);
|
|
|
return stepBox;
|
|
|
}
|
|
|
|
|
|
- private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, ApproverDto dto, bool isBackward = false)
|
|
|
+ private WorkflowStep CreateStepBox(StepDefine stepBasic, BasicWorkflowDto dto, string previousStepBoxId)
|
|
|
+ {
|
|
|
+ var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
|
|
|
+ _mapper.Map(dto, stepBox);
|
|
|
+ stepBox.PreviousId = previousStepBoxId;
|
|
|
+ return stepBox;
|
|
|
+ }
|
|
|
+
|
|
|
+ //private WorkflowStep CreateStepBox(
|
|
|
+ // StepDefine stepBasic,
|
|
|
+ // NextWorkflowDto dto,
|
|
|
+ // string workflowId,
|
|
|
+ // string previousId = "")
|
|
|
+ //{
|
|
|
+ // var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
|
|
|
+ // _mapper.Map(dto, stepBox);
|
|
|
+ // stepBox.WorkflowId = workflowId;
|
|
|
+ // if (!string.IsNullOrEmpty(previousId))
|
|
|
+ // stepBox.PreviousId = previousId;
|
|
|
+ // return stepBox;
|
|
|
+ //}
|
|
|
+
|
|
|
+ private List<WorkflowStep> CreateSubSteps(WorkflowStep stepBox, List<string> nextHandlers, string nextMainHandler, string previousStepId)
|
|
|
+ {
|
|
|
+ return nextHandlers.Select(d =>
|
|
|
+ {
|
|
|
+ var step = _mapper.Map<WorkflowStep>(stepBox);
|
|
|
+ step.ParentId = stepBox.Id;
|
|
|
+ step.HandlerId = d;
|
|
|
+ step.IsMain = d == nextMainHandler;
|
|
|
+ step.PreviousId = previousStepId;
|
|
|
+ return step;
|
|
|
+ }).ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkflowStep InitSubStepsInStepBox(WorkflowStep stepBox, List<string> nextHandlers)
|
|
|
+ {
|
|
|
+ var subSteps = nextHandlers.Select(d =>
|
|
|
+ {
|
|
|
+ var step = _mapper.Map<WorkflowStep>(stepBox);
|
|
|
+ step.ParentId = stepBox.Id;
|
|
|
+ step.HandlerId = d;
|
|
|
+ return step;
|
|
|
+ }).ToList();
|
|
|
+
|
|
|
+ stepBox.Steps = subSteps;
|
|
|
+ return stepBox;
|
|
|
+ }
|
|
|
+
|
|
|
+ private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, NextWorkflowDto dto, EWorkflowTraceStatus status = EWorkflowTraceStatus.Normal)
|
|
|
{
|
|
|
var trace = _mapper.Map<WorkflowTrace>(currentStep);
|
|
|
_mapper.Map(dto, trace);
|
|
|
trace.WorkflowId = workflowId;
|
|
|
- trace.IsBackward = isBackward;
|
|
|
+ trace.Status = status;
|
|
|
return trace;
|
|
|
}
|
|
|
|
|
|
- private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, PreviousDto dto, bool isBackward = false)
|
|
|
+ private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, PreviousWorkflowDto dto, EWorkflowTraceStatus status = EWorkflowTraceStatus.Normal)
|
|
|
{
|
|
|
var trace = _mapper.Map<WorkflowTrace>(currentStep);
|
|
|
_mapper.Map(dto, trace);
|
|
|
trace.WorkflowId = workflowId;
|
|
|
- trace.IsBackward = isBackward;
|
|
|
+ trace.Status = status;
|
|
|
return trace;
|
|
|
}
|
|
|
|
|
|
- private async Task<Workflow> GetWorkflowAsync(string workflowId, CancellationToken cancellationToken)
|
|
|
+ /// <summary>
|
|
|
+ /// 依据配置生成过期时间
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ private DateTime GenerateExpiredTime(string defineCode)
|
|
|
{
|
|
|
- var workflow = await _workflowRepository.Queryable()
|
|
|
- .Includes(d => d.Definition)
|
|
|
- .Includes(d => d.StepBoxes)
|
|
|
- .Includes(d => d.Traces)
|
|
|
- .FirstAsync(d => d.Id == workflowId);
|
|
|
- if (workflow is null)
|
|
|
- throw new UserFriendlyException("无效workflowId");
|
|
|
- return workflow;
|
|
|
+ return DateTime.Now.AddDays(7); //todo 依据配置生成, Think about 工作日
|
|
|
}
|
|
|
|
|
|
#endregion
|