using Hotline.FlowEngine.Definitions; using Hotline.FlowEngine.Notifications; using Hotline.SeedData; using Hotline.Share.Dtos.FlowEngine; using Hotline.Share.Enums.FlowEngine; using Hotline.Users; using MapsterMapper; using MediatR; using Microsoft.Extensions.Logging; using SqlSugar; using XF.Domain.Authentications; using XF.Domain.Dependency; using XF.Domain.Entities; 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 IWorkflowAssignRepository _workflowAssignRepository; private readonly IWorkflowCountersignRepository _workflowCountersignRepository; private readonly ISessionContext _sessionContext; private readonly IMapper _mapper; private readonly IMediator _mediator; private readonly ILogger _logger; public WorkflowDomainService( IWorkflowRepository workflowRepository, IWorkflowStepRepository workflowStepRepository, IWorkflowTraceRepository workflowTraceRepository, IWorkflowAssignRepository workflowAssignRepository, IWorkflowSupplementRepository workflowSupplementRepository, IWorkflowCountersignRepository workflowCountersignRepository, ISessionContext sessionContext, IMapper mapper, IMediator mediator, ILogger logger) { _workflowRepository = workflowRepository; _workflowStepRepository = workflowStepRepository; _workflowTraceRepository = workflowTraceRepository; _workflowAssignRepository = workflowAssignRepository; _workflowSupplementRepository = workflowSupplementRepository; _workflowCountersignRepository = workflowCountersignRepository; _sessionContext = sessionContext; _mapper = mapper; _mediator = mediator; _logger = logger; } public async Task CreateWorkflowAsync(Definition definition, string title, string userId, string userCode, string? externalId = null, CancellationToken cancellationToken = default) { var workflow = new Workflow { Title = title, ModuleId = definition.ModuleId, ModuleName = definition.ModuleName, ModuleCode = definition.ModuleCode, DefinitionId = definition.Id, Status = EWorkflowStatus.Runnable, TimeLimit = GetTimeLimit(definition.Code), ExpiredTime = GenerateExpiredTime(definition.Code), StepBoxes = new(), Traces = new(), Definition = definition, AssignTime = DateTime.Now, ExternalId = externalId, AssignOrgCodes = new List { userCode }, AssignUserIds = new List { userId }, }; await _workflowRepository.AddAsync(workflow, cancellationToken); return workflow; } /// /// 流程开始 /// /// /// /// /// public async Task StartAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken) { //var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode); //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选 if (nextStepBoxDefine.HandlerType is not EHandlerType.Role && !dto.NextHandlers.Any()) throw UserFriendlyException.SameMessage("未指派办理人"); //开始节点 var (startStepBox, startStep) = await CreateStartStepAsync(workflow, dto, cancellationToken); //var isStartCountersign = startStep.ShouldStartCountersign(dto.NextHandlers.Count); //检查是否支持会签 //if (isStartCountersign && startStep.CountersignMode == ECountersignMode.UnSupport) // throw new UserFriendlyException($"当前节点不支持发起会签, stepId: {startStep.Id}", "当前节点不支持发起会签"); if (isStartCountersign) { //创建会签数据 var countersign = await StartCountersignAsync(workflow.Id, startStep, startStepBox.CountersignEndStepCode, dto.NextHandlers.Count, startStep.CountersignId, cancellationToken); startStep.StartCountersignId = countersign.Id; await _workflowStepRepository.UpdateAsync(startStep, cancellationToken); } ////开始节点trace //await AcceptTraceAsync(workflow, startStepBox, startStep, cancellationToken); await NextTraceAsync(workflow, dto, startStep, cancellationToken); //第二节点(创建即为 已指派/待接办 状态) var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned, startStepBox, startStep, EWorkflowTraceStatus.Normal, cancellationToken); //更新当前节点名称、时间、会签节点code 等字段 workflow.SetWorkflowCurrentStepInfo(isStartCountersign, nextStepBox); workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects); //更新指派信息 workflow.Assign(flowAssignMode.FlowAssignType, flowAssignMode.GetHandlers()); await _workflowRepository.UpdateAsync(workflow, cancellationToken); //publish await _mediator.Publish(new StartWorkflowNotify(workflow, dto, isStartCountersign, flowAssignMode), cancellationToken); } public async Task GetWorkflowAsync(string workflowId, bool withDefine = false, bool withSteps = false, bool withTraces = false, bool withSupplements = false, bool withAssigns = false, bool withCountersigns = false, CancellationToken cancellationToken = default) { var query = _workflowRepository.Queryable().Where(d => d.Id == workflowId); if (withDefine) query = query.Includes(d => d.Definition); if (withSupplements) query = query.Includes(d => d.Supplements, d => d.Creator); if (withAssigns) query = query.Includes(d => d.Assigns); if (withCountersigns) query = query.Includes(d => d.Countersigns); var workflow = await query.FirstAsync(); if (workflow is null) throw new UserFriendlyException("无效workflowId"); if (withSteps) { var steps = await _workflowStepRepository.Queryable() .Where(d => d.WorkflowId == workflow.Id) .OrderBy(d => d.CreationTime) .ToTreeAsync(d => d.Steps, d => d.ParentId, null); workflow.StepBoxes = steps; } if (withTraces) { var traces = await _workflowTraceRepository.Queryable() .Where(d => d.WorkflowId == workflow.Id) .OrderBy(d => d.CreationTime) .ToTreeAsync(d => d.Traces, d => d.ParentId, null); workflow.Traces = traces; } return workflow; } /// /// 受理(接办) /// public async Task AcceptAsync(Workflow workflow, string userId, string userName, string orgCode, string orgName, CancellationToken cancellationToken) { if (!workflow.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode)) return; //工单完成以后查看的场景 if (workflow.Status != EWorkflowStatus.Runnable && workflow.Status != EWorkflowStatus.Marked) return; var (currentStepBox, currentStep) = GetUnCompleteStepOrDefault(workflow.StepBoxes, orgCode, userId); if (currentStep is null) return; if (currentStep.Status is EWorkflowStepStatus.Accepted) return; if (currentStep.HandlerType is EHandlerType.AssignUser or EHandlerType.Role) { //userId if (currentStep.Handlers.All(d => d.Id != userId)) return; } else { //orgId if (currentStep.Handlers.All(d => d.Id != orgCode)) return; } if (currentStep.StepType is EStepType.End) throw new UserFriendlyException("当前流程已流转到最终步骤"); var changedSteps = new List { currentStep }; if (currentStepBox.Status is EWorkflowStepStatus.Assigned) { currentStepBox.Status = EWorkflowStepStatus.Accepted; changedSteps.Add(currentStepBox); } currentStep.Accept(userId, userName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName); //接办时非会签并且有多个接办部门时需更新接办部门 if (!workflow.IsInCountersign()) { var assigns = await _workflowAssignRepository.QueryAsync(d => d.WorkflowId == workflow.Id); if (assigns.Count > 1) { await _workflowAssignRepository.RemoveRangeAsync(assigns, cancellationToken); var assign = WorkflowAssign.Create(workflow.Id, orgCode, orgName); await _workflowAssignRepository.AddAsync(assign, cancellationToken); } } await _workflowStepRepository.UpdateRangeAsync(changedSteps, cancellationToken); await AcceptTraceAsync(workflow, currentStep, cancellationToken); await _mediator.Publish(new AcceptWorkflowNotify(workflow), cancellationToken); } /// /// 办理(流转至下一节点) /// public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken) { ValidatePermission(workflow); CheckWhetherRunnable(workflow.Status); #region 办理当前节点 var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId); if (currentStep.Status is EWorkflowStepStatus.Completed or EWorkflowStepStatus.Created) throw UserFriendlyException.SameMessage("当前节点状态无法办理"); //var isStartCountersign = currentStep.ShouldStartCountersign(dto.NextHandlers.Count); //检查是否支持发起会签 //if (isStartCountersign && currentStep.CountersignMode == ECountersignMode.UnSupport) // throw new UserFriendlyException($"当前节点不支持发起会签, stepId: {currentStep.Id}", "当前节点不支持发起会签"); if (isStartCountersign && nextStepBoxDefine.StepType is EStepType.CountersignEnd) throw new UserFriendlyException($"汇总节点不支持办理会签, stepId: {currentStep.Id}", "汇总节点不支持办理会签"); if (currentStep.Status is EWorkflowStepStatus.Assigned) await AcceptAsync(workflow, _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName, cancellationToken); if (currentStep.StepType is EStepType.End) throw new UserFriendlyException("当前流程已流转到最终步骤"); if (isStartCountersign) { //创建会签数据 var countersign = await StartCountersignAsync(workflow.Id, currentStep, currentStepBox.CountersignEndStepCode, dto.NextHandlers.Count, currentStep.CountersignId, cancellationToken); currentStep.StartCountersign(countersign.Id); } _mapper.Map(dto, currentStep); //step办理状态 currentStep.Complete( _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName, dto.NextStepCode); //stepBox办理状态 currentStepBox.CheckStepBoxStatusAndUpdate(); var updateSteps = new List { currentStepBox, currentStep }; //结束当前会签流程 if (currentStep.StepType is EStepType.CountersignEnd && currentStep.IsInCountersign) { var currentCountersign = workflow.Countersigns.FirstOrDefault(d => d.Id == currentStep.CountersignId); if (currentCountersign is null) throw new UserFriendlyException( $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}", "无效会签编号"); //todo 1.根据当前节点配置查找结束节点对应开始节点 2.如该开始节点与当前会签开始节点吻合说明可以结束 //如果!=,说明未发起会签而是继承的外层会签 if (currentStepBox.CountersignStartStepCode == currentCountersign.StartStepCode) { //结束step会签信息 var countersignStartStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == currentCountersign.StartStepCode); if (countersignStartStepBox is null) throw new UserFriendlyException( $"未查询到会签开始stepBox, workflowId: {workflow.Id}, startStepCode: {currentCountersign.StartStepCode}", "未查询到会签开始节点"); var countersignStartStep = countersignStartStepBox.Steps.FirstOrDefault(d => d.HasStartedCountersign && d.Id == currentCountersign.StartStepId); if (countersignStartStep is null) throw new UserFriendlyException( $"未查询到会签开始step, workflowId: {workflow.Id}, startStepId: {currentCountersign.StartStepId}", "未查询到会签开始节点"); countersignStartStep.CountersignComplete(); updateSteps.Add(countersignStartStep); //结束会签 currentCountersign.Complete(currentStep.Id, currentStep.Code); await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken); } } await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken); #endregion #region 处理流程 //检查会签是否结束,并更新当前会签节点字段 var isCountersignOver = false; if (currentStep.StepType is EStepType.CountersignEnd && workflow.IsInCountersign()) { isCountersignOver = workflow.CheckIfCountersignOver(); if (isCountersignOver) workflow.EndCountersign(); } //检查是否流转到流程终点 if (nextStepBoxDefine.StepType is EStepType.End && !workflow.IsInCountersign()) { //create endStep var (_, endStep) = await CreateEndStepAsync(workflow, nextStepBoxDefine, currentStepBox, currentStep, cancellationToken); //update endTrace await NextTraceAsync(workflow, dto, endStep, cancellationToken); workflow.Complete(); await _workflowRepository.UpdateAsync(workflow, cancellationToken); await _mediator.Publish(new EndWorkflowNotify(workflow), cancellationToken); return; } //是否从中心流转出去,重新计算expiredTime if (isOutOfCallCenter) { workflow.IsStraight = false; workflow.ExpiredTime = GenerateExpiredTime(workflow.Definition.Code); workflow.AssignTime = DateTime.Now; } //最终办理意见与时间处理(解决工单业务需求,如果流程配置普通节点后未配置汇总节点则不会运行到此处) var normalSteps = workflow.StepBoxes .Where(d => d.StepType is EStepType.Normal) .SelectMany(d => d.Steps); var isAllCompleted = normalSteps.All(d => d.Status is EWorkflowStepStatus.Completed); if (isAllCompleted) { //最终办理 workflow.Opinion = dto.Opinion; workflow.Status = EWorkflowStatus.Marked; await _mediator.Publish(new OrderFinalManageNotify(workflow), cancellationToken); } else if (workflow.Status is EWorkflowStatus.Marked) { //汇总以后又重新指派到非汇总节点办理 workflow.ResetOption(); workflow.Status = EWorkflowStatus.Runnable; await _mediator.Publish(new OrderRecallFinalManageNotify(workflow), cancellationToken); } //创建下一节点(会签汇总节点不重复创建) var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Created, currentStepBox, currentStep, EWorkflowTraceStatus.Normal, cancellationToken); //下一节点为汇总节点时,检查下一节点是否可办理 var nextStepCanHandle = true; if (nextStepBox.StepType is EStepType.CountersignEnd) { if (currentStep.IsInCountersign) { //同一会签Id,非汇总节点 var steps = await _workflowStepRepository.QueryAsync(d => d.WorkflowId == workflow.Id && d.CountersignId == currentStep.CountersignId && d.StepType != EStepType.End); //(当前办理节点所处同一会签内的所有step全都办理完成并且如果开启了会签的step,必须会签结束) var unComplete = steps.Any(d => d.Status != EWorkflowStepStatus.Completed || (d.HasStartedCountersign && !(d.IsStartedCountersignComplete ?? false))); nextStepCanHandle = !unComplete; } } if (nextStepCanHandle) { //将下一节点处理为已指派/可接办 await SetNextCountersignEndAssignedAsync(nextStepBox, currentStep, cancellationToken); workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects); _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken); } //更新workflow当前节点名称、时间、会签节点code 等字段 workflow.SetWorkflowCurrentStepInfo(isStartCountersign, nextStepBox); //更新指派信息 workflow.Assign(flowAssignMode.FlowAssignType, flowAssignMode.GetHandlers()); await _workflowRepository.UpdateAsync(workflow, cancellationToken); #endregion #region 流转记录 await NextTraceAsync(workflow, dto, currentStep, cancellationToken); #endregion await _mediator.Publish(new NextStepNotify(workflow, dto, isStartCountersign, isCountersignOver, flowAssignMode), cancellationToken); } /// /// 退回(返回前一节点) /// /// public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, CancellationToken cancellationToken) { ValidatePermission(workflow); CheckWhetherRunnable(workflow.Status); if (workflow.IsInCountersign()) throw UserFriendlyException.SameMessage("会签流程不支持退回"); var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId); if (currentStepBox.StepType is EStepType.Start) throw UserFriendlyException.SameMessage("当前流程已退回到开始节点"); //find prevStep, update handler var prevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == currentStepBox.PreviousId); if (prevStepBox == null) throw UserFriendlyException.SameMessage("未查询到上级节点"); var prevStep = prevStepBox.Steps.FirstOrDefault(d => d.Id == currentStep.PreviousId); if (prevStep == null) throw UserFriendlyException.SameMessage("未查询到前一节点"); //检查并重置上级stepbox状态为待接办 await ResetStepBoxStatusAsync(prevStepBox, cancellationToken); //复制一个节点为待接办 var newPrevStep = await CreateByAsync(prevStep, cancellationToken); //remove workflow.steps await _workflowStepRepository.RemoveRangeAsync(new List { prevStep, currentStep }, cancellationToken); //更新流程可办理对象 workflow.UpdatePreviousHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, newPrevStep); await _workflowRepository.UpdateAsync(workflow, cancellationToken); //update trace await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken); await _mediator.Publish(new PreviousNotify(workflow, dto), cancellationToken); } /// /// 撤回(返回到之前任意节点) /// public async Task RecallAsync(Workflow workflow, NextWorkflowDto dto, StepDefine targetStepDefine, bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken) { //ValidatePermission(workflow); CheckWhetherRunnable(workflow.Status); //var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId); //if (currentStepBox.StepType is EStepType.Start) // throw UserFriendlyException.SameMessage("当前流程已退回到开始节点"); var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode); if (targetStepBox is null) throw UserFriendlyException.SameMessage("该流程尚未流转至该节点"); await RecallAsync(workflow, dto, targetStepDefine, targetStepBox, isStartCountersign, cancellationToken); workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects); await _workflowRepository.UpdateAsync(workflow, cancellationToken); await _mediator.Publish(new RecallNotify(workflow, dto), cancellationToken); } /// /// 跳转(直接将流程跳转至任意节点) /// public async Task JumpAsync(Workflow workflow, NextWorkflowDto dto, StepDefine targetStepDefine, bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken) { CheckWhetherRunnable(workflow.Status); //var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId); //if (currentStepBox.StepType is EStepType.Start) // throw UserFriendlyException.SameMessage("当前流程已退回到开始节点"); //if (currentStepBox.StepType is EStepType.End) // throw UserFriendlyException.SameMessage("当前流程已流转到结束节点"); //update uncompleted traces await JumpTraceAsync(workflow.Id, dto, cancellationToken); var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode); if (targetStepBox == null) { //向后跳转 //此场景并非按配置流转,默认最靠后的节点做为targetStep的prevStep var lastStepBox = workflow.StepBoxes.MaxBy(d => d.CreationTime); if (lastStepBox is null || lastStepBox.StepType is EStepType.End) throw new UserFriendlyException($"流程流转数据异常,未结束流程出现endStep, flowId: {workflow.Id}", "流程流转数据异常"); targetStepBox = await CreateStepAsync(isStartCountersign, workflow, targetStepDefine, dto, EWorkflowStepStatus.Assigned, lastStepBox, lastStepBox.Steps.First(), EWorkflowTraceStatus.Jump, cancellationToken); await ResetWorkflowCurrentStepInfo(workflow, dto, targetStepBox, isStartCountersign, cancellationToken); #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, targetStepDefine, targetStepBox, isStartCountersign, cancellationToken); } workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects); await _workflowRepository.UpdateAsync(workflow, cancellationToken); await _mediator.Publish(new JumpNotify(workflow, dto, flowAssignMode), cancellationToken); } /// /// 补充 /// /// public async Task SupplementAsync(Workflow workflow, EndWorkflowDto dto, CancellationToken cancellationToken) { CheckWhetherRunnable(workflow.Status); //todo 检查当前办理人是否为该流程中的办理人 var supplement = _mapper.Map(dto); await _workflowSupplementRepository.AddAsync(supplement, cancellationToken); } /// /// 终止流程 /// public async Task TerminateAsync(TerminateDto dto, CancellationToken cancellationToken) { var workflow = await _workflowRepository.GetAsync(dto.WorkflowId, cancellationToken); if (workflow == null) throw UserFriendlyException.SameMessage("无效的流程编号"); workflow.Terminate(dto.Opinion); await _workflowRepository.UpdateAsync(workflow, cancellationToken); await _mediator.Publish(new TerminalWorkflowNotify(workflow)); } /// /// 根据stepCode查询流程配置中对应的节点 /// public StepDefine GetStepBoxDefine(Definition definition, string stepCode) { if (definition == null) throw new ArgumentNullException(nameof(definition)); if (string.IsNullOrEmpty(stepCode)) throw new ArgumentNullException(nameof(stepCode)); var stepDefine = definition.FindStep(stepCode); if (stepDefine == null) throw new UserFriendlyException($"未找到流程中对应的节点,DefineCode: {definition.Code}, stepCode: {stepCode}", "未查询到对应节点"); return stepDefine; } /// /// 查询当前待办节点的下一级节点配置(办理参数) /// public IReadOnlyList GetNextStepDefines(Workflow workflow) { var (currentStepBox, _) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId); return workflow.Definition.FindSteps(currentStepBox.NextSteps.Select(d => d.Code)); } #region private /// /// 复制一个节点为待接办 /// private async Task CreateByAsync(WorkflowStep step, CancellationToken cancellationToken) { step.Reset(); var newStep = _mapper.Map(step); newStep.Status = EWorkflowStepStatus.Assigned; newStep.PreviousId = step.PreviousId; newStep.IsMain = step.IsMain; newStep.ParentId = step.ParentId; newStep.Handlers = step.Handlers; newStep.StartCountersignId = step.StartCountersignId; newStep.CountersignId = step.CountersignId; newStep.IsStartedCountersignComplete = step.IsStartedCountersignComplete; await _workflowStepRepository.AddAsync(newStep, cancellationToken); return newStep; } /// /// 检查并重置目标stepbox状态为待接办 /// private async Task ResetStepBoxStatusAsync(WorkflowStep stepBox, CancellationToken cancellationToken) { if (stepBox.Status is EWorkflowStepStatus.Completed) { stepBox.Status = EWorkflowStepStatus.Assigned; await _workflowStepRepository.UpdateAsync(stepBox, cancellationToken); } } private async Task StartCountersignAsync(string workflowId, WorkflowStep startStep, string endStepCode, int count, string? parentId = null, CancellationToken cancellationToken = default) { var countersign = new WorkflowCountersign { WorkflowId = workflowId, StartStepId = startStep.Id, StartStepCode = startStep.Code, EndStepCode = endStepCode, Members = count, ParentId = parentId, }; await _workflowCountersignRepository.AddAsync(countersign, cancellationToken); return countersign; } /// /// 更新下级汇总节点可办理状态 /// /// /// /// /// private async Task SetNextCountersignEndAssignedAsync(WorkflowStep nextStepBox, WorkflowStep currentStep, CancellationToken cancellationToken) { var nextSteps = currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign ? nextStepBox.Steps.Where(d => d.CountersignId == currentStep.CountersignId).ToList() : nextStepBox.Steps.Where(d => d.PreviousId == currentStep.Id).ToList(); if (!nextSteps.Any()) throw new UserFriendlyException($"未查询到下一节点, currentStepId: {currentStep.Id}"); foreach (var nextStep in nextSteps) { nextStep.SetAssigned(); } await _workflowStepRepository.UpdateRangeAsync(nextSteps, cancellationToken); } private async Task JumpTraceAsync(string workflowId, NextWorkflowDto 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, dto.Opinion); } await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken); } private async Task RecallTraceAsync(string workflowId, NextWorkflowDto dto, CancellationToken cancellationToken) { //未办理的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.Previous( _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName); await _workflowTraceRepository.UpdateAsync(trace, cancellationToken); } //private async Task EndTraceAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken) //{ // var trace = _mapper.Map(step); // trace.Status = EWorkflowTraceStatus.Normal; // trace.ExpiredTime = workflow.ExpiredTime; // trace.TimeLimit = workflow.TimeLimit; // await _workflowTraceRepository.AddAsync(trace, cancellationToken); //} 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); await _workflowTraceRepository.UpdateAsync(trace, cancellationToken); } private async Task AcceptTraceAsync(Workflow workflow, WorkflowStep step, CancellationToken cancellationToken) { var trace = await GetWorkflowTraceAsync(workflow.Id, step.Id, cancellationToken); _mapper.Map(step, trace); await _workflowTraceRepository.UpdateAsync(trace, cancellationToken); } private async Task CreateTraceAsync(Workflow workflow, WorkflowStep currentStep, EWorkflowTraceStatus traceStatus = EWorkflowTraceStatus.Normal, CancellationToken cancellationToken = default) { var trace = _mapper.Map(currentStep); trace.Status = traceStatus; trace.ExpiredTime = workflow.ExpiredTime; trace.TimeLimit = workflow.TimeLimit; //1.如果是汇总节点,trace.parentId=会签开始节点对应的trace.parentId(即与会签开始节点trace同级) //2.普通节点:2.1: in 判断上级节点是否发起会签,有则赋值parentId为上级trace.Id, 2.2: outer 与上级节点trace保持同级,取值上级节点对应trace.parentId if (currentStep.StepType is EStepType.CountersignEnd) { if (currentStep.IsInCountersign) { var countersign = await _workflowCountersignRepository.GetAsync(currentStep.CountersignId!, cancellationToken); if (countersign == null) throw new UserFriendlyException( $"汇总节点处于会签中,未查询到对应会签,countersignId: {currentStep.CountersignId}"); var startTrace = await GetWorkflowTraceAsync(workflow.Id, countersign.StartStepId, cancellationToken); trace.ParentId = startTrace.ParentId; } } else if (currentStep.StepType is EStepType.Normal) { if (currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign) { var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken); trace.ParentId = prevTrace.Id; } else if (currentStep.StepCountersignStatus is EStepCountersignStatus.OuterCountersign) { var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken); trace.ParentId = prevTrace.ParentId; } } ////处于会签中的节点,其对应的trace.parentId赋值上级trace.Id //if (currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign) //{ // var parentTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken); // trace.ParentId = parentTrace.Id; //} await _workflowTraceRepository.AddAsync(trace, cancellationToken); } private async Task 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, NextWorkflowDto dto, StepDefine targetStepDefine, WorkflowStep targetStepBox, bool isStartCountersign, CancellationToken cancellationToken) { //get targetStep's previous var targetPrevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == targetStepBox.PreviousId); if (targetPrevStepBox == null) throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}, targetStepBoxPrevId: {targetPrevStepBox.PreviousId}"); //真实的前一节点并不存在(非正常流转造成的),所以取前一stepbox任意一个step替代 var targetPrevStep = targetPrevStepBox.Steps.FirstOrDefault(); if (targetPrevStep == null) throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}"); ////remove completedSteps include target stepBox //var removeSteps = GetStepsIncludeStepBox(targetStepBox); //var tempStepBox = currentStepBox; //while (tempStepBox.Code != targetStepBox.Code) //{ // removeSteps.AddRange(GetStepsIncludeStepBox(tempStepBox)); // var prevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == tempStepBox.PreviousId); // if (prevStepBox is null) // throw new UserFriendlyException($"{nameof(RecallAsync)}, 未查询到节点, workflowId: {workflow.Id}, prevStepBoxId: {tempStepBox.PreviousId}"); // tempStepBox = prevStepBox; //} // //查询所有目标节点之后的节点,然后删掉(包括目标节点) var removeSteps = GetStepsBehindTargetStepBox(workflow.StepBoxes, targetStepBox); await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken); //recreate targetStep await CreateStepAsync(isStartCountersign, workflow, targetStepDefine, dto, EWorkflowStepStatus.Assigned, targetPrevStepBox, targetPrevStep, EWorkflowTraceStatus.Recall, cancellationToken); //flow manage await ResetWorkflowCurrentStepInfo(workflow, dto, targetStepBox, isStartCountersign, cancellationToken); //update uncompleted traces await RecallTraceAsync(workflow.Id, dto, cancellationToken); } private IEnumerable GetStepsBehindTargetStepBox(List stepBoxes, WorkflowStep currentStepBox) { var steps = GetStepsIncludeStepBox(currentStepBox); var nextStepBoxs = stepBoxes.Where(d => d.PreviousId == currentStepBox.Id); if (!nextStepBoxs.Any()) return steps; foreach (var nextStepBox in nextStepBoxs) { steps.AddRange(GetStepsBehindTargetStepBox(stepBoxes, nextStepBox)); } return steps; } private List GetStepsIncludeStepBox(WorkflowStep stepBox) { var steps = new List { stepBox }; steps.AddRange(stepBox.Steps); return steps; } /// /// 重置currentStep信息 /// private async Task ResetWorkflowCurrentStepInfo(Workflow workflow, NextWorkflowDto dto, WorkflowStep stepBox, bool isStartCountersign, CancellationToken cancellationToken) { //更新当前节点名称、时间、会签节点code workflow.CloseCountersignStatus(); workflow.SetWorkflowCurrentStepInfo(isStartCountersign, stepBox); await _workflowRepository.UpdateAsync(workflow, cancellationToken); } private static void CheckWhetherRunnable(EWorkflowStatus status) { if (status != EWorkflowStatus.Runnable && status != EWorkflowStatus.Marked) throw new UserFriendlyException("当前流程状态不可继续流转"); } private void ValidatePermission(Workflow workflow) { if (!workflow.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode)) throw new UserFriendlyException("无办理权限"); } /// /// 创建开始节点(保存开始流程的办理意见,对应definition的start节点) /// private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateStartStepAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken) { if (workflow.StepBoxes.Any()) throw UserFriendlyException.SameMessage("无法重复创建开始节点"); var startStepDefinition = workflow.Definition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start); if (startStepDefinition == null) throw new UserFriendlyException($"模板未配置开始节点, defineCode: {workflow.Definition.Code}", "模板未配置开始节点"); var stepBox = CreateStepBox(workflow.Id, startStepDefinition, string.Empty); await _workflowStepRepository.AddAsync(stepBox, cancellationToken); //start节点的办理人分类默认为用户,即为当前发起流程的操作员 var handler = new IdName { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName }; var step = await CreateStartSubStepAsync(handler, dto, stepBox, cancellationToken); //开始节点trace await CreateTraceAsync(workflow, step, cancellationToken: cancellationToken); return (stepBox, step); } private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateEndStepAsync( Workflow workflow, StepDefine endStepDefine, WorkflowStep prevStepBox, WorkflowStep prevStep, CancellationToken cancellationToken) { if (workflow.StepBoxes.Any(d => d.StepType == EStepType.End)) throw UserFriendlyException.SameMessage("无法重复创建结束节点"); var stepBox = CreateStepBox(workflow.Id, endStepDefine, prevStepBox.Id); await _workflowStepRepository.AddAsync(stepBox, cancellationToken); var handler = new IdName { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName }; var step = await CreateEndSubStepAsync(handler, stepBox, prevStep, cancellationToken); //end trace await CreateTraceAsync(workflow, step, cancellationToken: cancellationToken); return (stepBox, step); } /// /// 创建节点(不含开始、结束节点) /// private async Task CreateStepAsync( bool isPrevStartCountersign, Workflow workflow, StepDefine stepBoxDefine, BasicWorkflowDto dto, EWorkflowStepStatus status, WorkflowStep prevStepBox, WorkflowStep prevStep, EWorkflowTraceStatus traceStatus, CancellationToken cancellationToken = default) { if (stepBoxDefine.StepType is EStepType.Start or EStepType.End) throw new UserFriendlyException("该方法不支持创建开始或结束节点"); var stepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == stepBoxDefine.Code); if (stepBox == null) { stepBox = CreateStepBox(workflow.Id, stepBoxDefine, prevStepBox.Id); await _workflowStepRepository.AddAsync(stepBox, cancellationToken); } else if (stepBox.Status != EWorkflowStepStatus.Created) { stepBox.Status = EWorkflowStepStatus.Created; await _workflowStepRepository.UpdateAsync(stepBox, cancellationToken); } //下一节点为汇总节点时,同一会签只需要创建一次汇总节点 if (stepBoxDefine.StepType is EStepType.CountersignEnd && prevStep.StepCountersignStatus == EStepCountersignStatus.InCountersign) { var step = stepBox.Steps.FirstOrDefault(d => d.IsInCountersign && d.CountersignId == prevStep.CountersignId); if (step != null) return stepBox; } await CreateSubStepsAsync(workflow, isPrevStartCountersign, stepBoxDefine, dto, stepBox, status, prevStep, traceStatus, cancellationToken); return stepBox; } private async Task CreateStartSubStepAsync( IdName handler, BasicWorkflowDto dto, WorkflowStep stepBox, CancellationToken cancellationToken) { //开始节点既不发起会签,也不处于会签中 var subStep = CreateSubStep(stepBox, new List { handler }, dto.NextStepCode, dto.NextMainHandler, null, null, EWorkflowStepStatus.Completed, EStepCountersignStatus.None); subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName); //step办理状态 subStep.Complete( _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName, dto.NextStepCode); _mapper.Map(dto, subStep); stepBox.Steps.Add(subStep); await _workflowStepRepository.AddAsync(subStep, cancellationToken); return subStep; } private async Task CreateEndSubStepAsync( IdName handler, WorkflowStep currentStepBox, WorkflowStep prevStep, CancellationToken cancellationToken) { var subStep = CreateSubStep(currentStepBox, new List { handler }, null, null, prevStep.Id, null, EWorkflowStepStatus.Completed, EStepCountersignStatus.None); subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName); subStep.Complete(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName, string.Empty); currentStepBox.Steps.Add(subStep); await _workflowStepRepository.AddAsync(subStep, cancellationToken); return subStep; } private async Task CreateSubStepsAsync( Workflow workflow, bool isPrevStartCountersign, StepDefine stepBoxDefine, BasicWorkflowDto dto, WorkflowStep stepBox, EWorkflowStepStatus stepStatus, WorkflowStep prevStep, EWorkflowTraceStatus traceStatus, CancellationToken cancellationToken = default) { var countersignStatus = stepBoxDefine.StepType is EStepType.CountersignEnd ? prevStep.IsInCountersign ? EStepCountersignStatus.InCountersign : EStepCountersignStatus.None : prevStep.GetNextStepCountersignStatus(); var countersignId = isPrevStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId; List subSteps; if (stepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.AssignOrg) { subSteps = CreateSubSteps(isPrevStartCountersign, stepBox, stepBox.HandlerClassifies, dto.NextStepCode, dto.NextMainHandler, prevStep?.Id, countersignId, stepStatus, countersignStatus); } else { if (stepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any()) throw new UserFriendlyException("未指定节点处理者"); subSteps = CreateSubSteps(isPrevStartCountersign, stepBox, dto.NextHandlers, dto.NextStepCode, dto.NextMainHandler, prevStep?.Id, countersignId, stepStatus, countersignStatus); } stepBox.Steps.AddRange(subSteps); await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken); //create traces foreach (var step in subSteps) { await CreateTraceAsync(workflow, step, traceStatus, cancellationToken); } } /// /// 查询未完成节点 /// /// /// /// /// private (WorkflowStep, WorkflowStep) GetUnCompleteStep(List stepBoxes, string orgCode, string userId) { var (stepBox, step) = GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed); if (step == null) throw new UserFriendlyException( $"未找到对应节点, workflowId: {stepBoxes.FirstOrDefault()?.WorkflowId} orgCode:{orgCode}, userId: {userId}", "未找到对应节点"); return (stepBox, step); } private (WorkflowStep, WorkflowStep) GetUnCompleteStepOrDefault(List stepBoxes, string orgCode, string userId) => GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed); private (WorkflowStep, WorkflowStep) GetStep(List stepBoxes, string orgCode, string userId, Func predicate) { if (!stepBoxes.Any()) throw new UserFriendlyException("该流程中暂无节点"); foreach (var stepBox in stepBoxes) { foreach (var step in stepBox.Steps) { if (predicate(step.Status) && (step.Handlers.Any(d => d.Id == orgCode) || step.Handlers.Any(d => d.Id == userId))) return (stepBox, step); } } return new(); } private WorkflowStep CreateStepBox(string workflowId, StepDefine stepDefine, string prevStepBoxId) { var stepBox = _mapper.Map(stepDefine); stepBox.WorkflowId = workflowId; stepBox.PreviousId = prevStepBoxId; stepBox.NextStepCode = string.Empty; stepBox.Opinion = string.Empty; stepBox.CountersignStartStepCode = stepDefine.CountersignStartStepCode; stepBox.CountersignEndStepCode = stepDefine.CountersignEndStepCode; return stepBox; } private List CreateSubSteps( bool isPrevStartCountersign, WorkflowStep stepBox, List handlers, string nextStepCode, string? nextMainHandler, string? prevStepId, string? countersignId, EWorkflowStepStatus stepStatus, EStepCountersignStatus countersignStatus) { if (countersignStatus is EStepCountersignStatus.None && !string.IsNullOrEmpty(countersignId)) throw UserFriendlyException.SameMessage("非法参数"); if (countersignStatus is not EStepCountersignStatus.None && string.IsNullOrEmpty(countersignId)) throw UserFriendlyException.SameMessage("非法参数"); //依据是否发起会签创建step,发起会签表示一个handler创建一个step,未发起会签表示多人处理同一个节点,只创建一个step var steps = new List(); if (isPrevStartCountersign) { foreach (var handler in handlers) { var step = CreateSubStep(stepBox, new List { handler }, nextStepCode, nextMainHandler, prevStepId, countersignId, stepStatus, countersignStatus); steps.Add(step); } } else { var step = CreateSubStep(stepBox, handlers, nextStepCode, nextMainHandler, prevStepId, countersignId, stepStatus, countersignStatus); steps.Add(step); } return steps; } private WorkflowStep CreateSubStep( WorkflowStep stepBox, List handlers, string nextStepCode, string? nextMainHandler, string? prevStepId, string? countersignId, EWorkflowStepStatus stepStatus, EStepCountersignStatus countersignStatus) { if (!handlers.Any()) throw new UserFriendlyException("非法参数"); var step = _mapper.Map(stepBox); var handlerIds = handlers.Select(d => d.Id).ToList(); var isMain = handlers.Count == 1 || (handlers.Count > 1 || handlerIds.First() == nextMainHandler); step.ParentId = stepBox.Id; step.Handlers = handlers; step.NextStepCode = step.StepType is EStepType.End ? string.Empty : nextStepCode; step.IsMain = isMain; step.PreviousId = prevStepId; step.CountersignId = countersignId; step.Status = stepStatus; step.StepCountersignStatus = countersignStatus; return step; } /// /// 依据配置生成过期时间 /// /// private DateTime GenerateExpiredTime(string defineCode) { //GetConfig(string defineCode).Time return DateTime.Now.AddDays(7); //todo 依据配置生成, Think about 工作日 } private string GetTimeLimit(string defineCode) { //return GetConfig(string defineCode).Description; return "7个工作日"; } //private ConfigInCludeDescriptionAndTime GetConfig(string defineCode) //{ // throw new NotImplementedException(); //} #endregion } }