using Hotline.Caching.Interfaces; using Hotline.File; using Hotline.FlowEngine.Definitions; using Hotline.FlowEngine.Notifications; using Hotline.FlowEngine.WorkflowModules; using Hotline.SeedData; using Hotline.Settings; using Hotline.Share.Dtos; using Hotline.Share.Dtos.FlowEngine; using Hotline.Share.Dtos.FlowEngine.Definition; using Hotline.Share.Enums.FlowEngine; using Hotline.Users; using MapsterMapper; using Microsoft.Extensions.Logging; using SqlSugar; using Hotline.EventBus; using XF.Domain.Authentications; using XF.Domain.Dependency; using XF.Domain.Entities; using XF.Domain.Exceptions; using XF.Domain.Repository; using System.Text; using FluentValidation; using Hotline.Configurations; using Hotline.Share.Dtos.File; using Hotline.Share.Dtos.FlowEngine.Workflow; using Hotline.Validators.FlowEngine; using Microsoft.Extensions.Options; using Microsoft.AspNetCore.Http; namespace Hotline.FlowEngine.Workflows { public class WorkflowDomainService : IWorkflowDomainService, IScopeDependency { private readonly IWorkflowRepository _workflowRepository; private readonly IRepository _workflowStepRepository; private readonly IRepository _workflowTraceRepository; private readonly IRepository _workflowCountersignRepository; private readonly IMapper _mapper; private readonly Publisher _publisher; private readonly ILogger _logger; private readonly IFileRepository _fileRepository; private readonly IRepository _userRepository; private readonly ISystemSettingCacheManager _systemSettingCacheManager; private readonly IWfModuleCacheManager _wfModuleCacheManager; private readonly ISessionContext _sessionContext; private readonly IOptionsSnapshot _appOptions; public WorkflowDomainService( IWorkflowRepository workflowRepository, IRepository workflowStepRepository, IRepository workflowTraceRepository, IRepository workflowCountersignRepository, ISystemSettingCacheManager systemSettingCacheManager, IWfModuleCacheManager wfModuleCacheManager, ISessionContext sessionContext, IMapper mapper, Publisher publisher, ILogger logger, IOptionsSnapshot appOptions, IFileRepository fileRepository) { _workflowRepository = workflowRepository; _workflowStepRepository = workflowStepRepository; _workflowTraceRepository = workflowTraceRepository; _workflowCountersignRepository = workflowCountersignRepository; _mapper = mapper; _publisher = publisher; _logger = logger; _fileRepository = fileRepository; _systemSettingCacheManager = systemSettingCacheManager; _wfModuleCacheManager = wfModuleCacheManager; _sessionContext = sessionContext; _appOptions = appOptions; } public async Task CreateWorkflowAsync(WorkflowModule wfModule, string title, string userId, string orgId, string? externalId = null, CancellationToken cancellationToken = default) { var definition = wfModule.Definition; if (definition is null) throw new UserFriendlyException("无效流程模板"); var workflow = new Workflow { Title = title, ModuleId = wfModule.Id, ModuleName = wfModule.Name, ModuleCode = wfModule.Code, DefinitionId = definition.Id, Status = EWorkflowStatus.Runnable, Steps = new(), Traces = new(), WorkflowDefinition = definition, ExternalId = externalId ?? string.Empty, // //FlowedOrgIds = new List { orgId }, // FlowedUserIds = new List { userId }, FlowType = definition.FlowType, }; await _workflowRepository.AddAsync(workflow, cancellationToken); return workflow; } // /// // /// 流程开始 // /// // public async Task StartAsync(Workflow workflow, WorkflowStep startStep, BasicWorkflowDto dto, // StepDefine firstStepDefine, bool isNextDynamic, FlowAssignInfo flowAssignInfo, // ECounterSignType? counterSignType, DateTime? expiredTime, // CancellationToken cancellationToken) // { // if (firstStepDefine.StepType is EStepType.End) // { // await _publisher.PublishAsync( // new StartWorkflowNotify(workflow, dto, flowAssignInfo.FlowAssignType, startStep.WorkflowTrace), // PublishStrategy.ParallelWhenAll, cancellationToken); // // //firstStep是否为end,t: 实际办理节点为startStep, 并且handlerId赋值 f: 实际办理节点为firstStep, handlerId未赋值 // workflow.UpdateActualStepWhenHandle(startStep, _sessionContext.OrgAreaCode, // _sessionContext.OrgAreaName, _sessionContext.OrgLevel); // // workflow.UpdateCurrentStepWhenHandle(startStep, _sessionContext.OrgAreaCode, // _sessionContext.OrgAreaName, _sessionContext.OrgLevel); // // var endTrace = await EndAsync(_sessionContext, workflow, dto, firstStepDefine, startStep, expiredTime, // cancellationToken); // return; // } // // var assigner = new UserInfo( // _sessionContext.UserId, // _sessionContext.UserName, // _sessionContext.OrgId, // _sessionContext.OrgName, // _sessionContext.OrgIsCenter // ); // // //firststeps // var firstSteps = await CreateNextStepsAsync(workflow, startStep, dto, firstStepDefine, assigner, // isNextDynamic, expiredTime, dto.IsStartCountersign, cancellationToken: cancellationToken); // // await _workflowStepRepository.UpdateAsync(startStep, cancellationToken); // // //handle trace // var trace = await NextTraceAsync(workflow, dto, startStep, cancellationToken); // // //指派实际办理节点 // UpdateActualStep(workflow, dto, firstStepDefine, firstSteps); // // //更新实际办理节点 // UpdateCurrentStep(workflow, dto, firstStepDefine, firstSteps); // // //发起会签时记录顶层会签节点(必须在update currentStep之后) // if (dto.IsStartCountersign && !workflow.IsInCountersign) // workflow.StartCountersign(startStep.Id, counterSignType); // // await _workflowRepository.UpdateAsync(workflow, cancellationToken); // // //publish // await _publisher.PublishAsync(new StartWorkflowNotify(workflow, dto, flowAssignInfo.FlowAssignType, trace), // PublishStrategy.ParallelWhenAll, cancellationToken); // } /// /// new(开启流程并停留在开始节点,开始节点为待办节点,指派给当前操作人) /// public async Task<(Workflow, WorkflowStep)> StartAsync(StartWorkflowDto dto, string externalId, DateTime? expiredTime = null, Action? newStepConfig = null, CancellationToken cancellationToken = default) { // var validator = new StartWorkflowDtoValidator(); // var validResult = await validator.ValidateAsync(dto, cancellationToken); // if (!validResult.IsValid) // throw new UserFriendlyException( // $"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}"); var wfModule = await GetWorkflowModuleAsync(dto.DefinitionModuleCode, cancellationToken); var definition = wfModule.Definition; if (definition == null) throw new UserFriendlyException("无效模板编码"); if (definition.Status is not EDefinitionStatus.Enable) throw new UserFriendlyException("该模板不可用"); //如果发起会签需检查是否支持发起会签 var startStepDefine = definition.FindStartStepDefine(); //下一节点是否为动态节点 var isNextDynamic = startStepDefine.InstanceMode is EInstanceMode.Dynamic && !DynamicShouldTerminal(startStepDefine, _sessionContext.OrgLevel); var firstStepDefine = isNextDynamic ? startStepDefine : definition.FindStepDefine(dto.NextStepCode); if (firstStepDefine is null) throw new UserFriendlyException("未查询到下一步节点配置"); //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选 if (firstStepDefine.HandlerType is not EHandlerType.Role && !dto.NextHandlers.Any()) throw UserFriendlyException.SameMessage("未指派办理人"); if (dto.IsStartCountersign) { if (!startStepDefine.CanStartCountersign) throw new UserFriendlyException("当前节点不支持发起会签"); //if (startStepDefine.HandlerType is EHandlerType.Role) // throw new UserFriendlyException("当前节点不支持发起会签"); //即使当前节点支持发起会签,但下一节点为信息汇总节点、结束节点时也不可发起会签 if (firstStepDefine.StepType is EStepType.Summary or EStepType.End) throw new UserFriendlyException("下一节点不允许发起会签"); //下一节点是会签汇总节点也不允许发起会签 if (dto.BackToCountersignEnd) throw new UserFriendlyException("下一节点不允许发起会签"); } var workflow = CreateWorkflow(wfModule, dto.Title, externalId); var defineHandler = startStepDefine.HandlerTypeItems.First(); var assigner = new UserInfo( _sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, _sessionContext.OrgIsCenter ); var handler = new StepAssignInfo { Key = _sessionContext.RequiredUserId, Value = _sessionContext.UserName, UserId = _sessionContext.UserId, Username = _sessionContext.UserName, OrgId = _sessionContext.RequiredOrgId, OrgName = _sessionContext.OrgName, RoleId = defineHandler.Key, RoleName = defineHandler.Value, FlowAssignType = EFlowAssignType.User }; var startStep = CreateStartStep(workflow, startStepDefine, dto, assigner, handler, expiredTime, newStepConfig); if (dto.Files.Any()) startStep.FileJson = await _fileRepository.AddFileAsync(dto.Files, workflow.ExternalId, startStep.Id, cancellationToken); await _workflowStepRepository.AddAsync(startStep, cancellationToken); workflow.Steps.Add(startStep); //starttrace // var startTrace = _mapper.Map(startStep); // startTrace.StepId = startStep.Id; // startTrace.TraceType = EWorkflowTraceType.Normal; // await _workflowTraceRepository.AddAsync(startTrace, cancellationToken); // workflow.Traces.Add(startTrace); //starttrace var startTrace = await CreateTraceAsync(workflow, startStep, cancellationToken: cancellationToken); startStep.WorkflowTrace = startTrace; //更新受理人信息 workflow.UpdateAcceptor( _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.StaffNo, _sessionContext.RequiredOrgId, _sessionContext.OrgName); workflow.UpdateActualStepWhenAssign(startStep, handler); workflow.UpdateCurrentStepWhenAssign(startStep, handler); await _workflowRepository.AddAsync(workflow, cancellationToken); // var flowAssignInfo = // await GetNextStepFlowAssignInfoAsync(workflow, startStep, dto, firstStepDefine, isNextDynamic, cancellationToken); //publish await _publisher.PublishAsync(new StartWorkflowNotify(workflow, dto, startTrace), PublishStrategy.ParallelWhenAll, cancellationToken); return (workflow, startStep); } /// /// new /// workflow, currentStep, nextStepDefine, nextSteps /// public async Task<(Workflow, WorkflowStep, StepDefine, List)> NextAsync( NextWorkflowDto dto, EHandleMode handleMode = EHandleMode.Normal, DateTime? expiredTime = null, bool isAutoFillSummaryOpinion = false, Action? currentStepConfig = null, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true, cancellationToken: cancellationToken); CheckWhetherRunnable(workflow.Status); var currentStep = workflow.Steps.FirstOrDefault(d => d.Id == dto.StepId); if (currentStep == null) throw new UserFriendlyException( $"未找到对应节点, workflowId: {dto.WorkflowId}, stepId: {dto.StepId}", "未找到对应节点"); if (currentStep.Status is EWorkflowStepStatus.Handled) throw new UserFriendlyException("该状态不支持继续办理"); //todo 校验currentStep 能否由 current 办理 var currentStepDefine = GetStepDefine(workflow.WorkflowDefinition, currentStep.Code); //下一节点是否为动态节点 var isNextDynamic = (string.IsNullOrEmpty(dto.NextStepCode) || dto.NextStepCode.ToLower() != "end") && currentStepDefine.InstanceMode is EInstanceMode.Dynamic && !DynamicShouldTerminal(currentStepDefine, _sessionContext.OrgLevel); StepDefine nextStepDefine; if (isNextDynamic || (workflow.IsInCountersign && currentStep.IsInCountersign() && !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)) || dto.IsStartCountersign) { //下一步配置为当前节点配置 nextStepDefine = currentStepDefine; } else { //if dto.NextStepCode is empty, find nextCode, but if not only one throw it if (string.IsNullOrEmpty(dto.NextStepCode)) { if (currentStepDefine?.NextSteps?.Count == 0) throw new UserFriendlyException( $"未选择下一办理节点且未配置下一节点, workflowId: {workflow.Id}, stepDefineName: {currentStepDefine.Name}", "未选择下一办理节点"); if (currentStepDefine?.NextSteps?.Count > 1) throw new UserFriendlyException( $"未选择下一办理节点且配置有多个节点, workflowId: {workflow.Id}, stepDefineName: {currentStepDefine.Name}", "未选择下一办理节点且配置有多个节点"); dto.NextStepCode = currentStepDefine.NextSteps.First().Code; } //下一步配置为下一步节点配置 nextStepDefine = GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode); } // //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签) // if (!dto.NextHandlers.Any() && // nextStepDefine.HandlerType != EHandlerType.Role && // nextStepDefine.StepType != EStepType.End && // nextStepDefine.StepType != EStepType.Summary // ) // throw new UserFriendlyException("未指定节点处理者"); if (dto.IsStartCountersign) { if (!currentStepDefine.CanStartCountersign) throw UserFriendlyException.SameMessage("当前节点不支持发起会签"); //即使当前节点支持发起会签,但下一节点为信息汇总节点、结束节点时也不可发起会签 if (nextStepDefine.StepType is EStepType.Summary or EStepType.End) throw UserFriendlyException.SameMessage("下一汇总节点不允许发起会签"); //下一节点是会签汇总节点也不允许发起会签 if (dto.BackToCountersignEnd) throw UserFriendlyException.SameMessage("下一会签汇总节点不允许发起会签"); } // var flowAssignInfo = // await GetNextStepFlowAssignInfoAsync(workflow, currentStep, dto, nextStepDefine, isNextDynamic, cancellationToken); dto.FlowAssignType ??= SetNextStepAssignInfo(workflow, currentStep, dto, nextStepDefine, isNextDynamic); if (!dto.NextHandlers.Any() && nextStepDefine.StepType != EStepType.End) { //普通节点往汇总节点办理时可以不选,不选的场景主动去查之前的办理对象 if (nextStepDefine.StepType is EStepType.Summary) { var handler = GetSummaryTargetFlowStepHandler(workflow, nextStepDefine.SummaryTargetCode); dto.NextHandlers = new List { handler }; } //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签) else if (nextStepDefine.HandlerType == EHandlerType.Role) { var handler = nextStepDefine.HandlerTypeItems.First(); if (nextStepDefine.BusinessType is EBusinessType.DepartmentLeader) { dto.NextHandlers.Add(new StepAssignInfo { Key = handler.Key, Value = handler.Value, RoleId = handler.Key, RoleName = handler.Value, OrgId = _sessionContext.RequiredOrgId, OrgName = _sessionContext.OrgName, FlowAssignType = EFlowAssignType.OrgAndRole }); } else { //todo 指派给配置的角色 //dto.FlowAssignType = EFlowAssignType.Role; dto.NextHandlers.Add(new StepAssignInfo { Key = handler.Key, Value = handler.Value, RoleId = handler.Key, RoleName = handler.Value, FlowAssignType = EFlowAssignType.Role }); } } } if (!dto.NextHandlers.Any() && nextStepDefine.StepType != EStepType.End) throw new UserFriendlyException("未指定节点处理者"); #region 办理当前节点 if (dto.Files != null && dto.Files.Any()) currentStep.FileJson = await _fileRepository.AddFileAsync( dto.Files, workflow.ExternalId, currentStep.Id, cancellationToken); var counterSignType = GetCounterSignType(dto.IsStartCountersign, currentStep.BusinessType); var updateSteps = new List { currentStep }; //结束当前会签流程 if (currentStep.IsCountersignEndStep) { var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId); if (countersignStartStep is null) throw new UserFriendlyException( $"未查询到会签开始step, workflowId: {workflow.Id}, currentStepId: {currentStep.Id}", "未查询到会签开始节点"); if (countersignStartStep.IsStartCountersign) { var currentCountersign = workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId); if (currentCountersign is null) throw new UserFriendlyException( $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}", "无效会签编号"); //结束step会签信息 countersignStartStep.CountersignEnd(); updateSteps.Add(countersignStartStep); //结束会签 currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType, _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName); await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken); } } await HandleStepAsync(currentStep, workflow, dto, counterSignType, expiredTime, handleMode, cancellationToken); currentStep.IsActualHandled = CheckIsActualHandle(workflow, currentStep, nextStepDefine, dto); _mapper.Map(dto, workflow); //会签办理节点办理时更新会签members字段 if (currentStep.CountersignPosition is ECountersignPosition.Direct or ECountersignPosition.Indirect) { if (!string.IsNullOrEmpty(currentStep.CountersignId)) { //会签中正常办理节点,更新会签members办理状态 var countersign = workflow.Countersigns.FirstOrDefault(d => !d.IsCompleted() && d.Id == currentStep.CountersignId); if (countersign is not null) { //throw new UserFriendlyException( // $"会签数据异常, workflowId: {currentStep.WorkflowId}, countersignId: {currentStep.CountersignId}", // "会签数据异常"); countersign.MemberHandled(_sessionContext.UserId, _sessionContext.OrgId); //update cs await _workflowCountersignRepository.UpdateNav(countersign) .Include(d => d.Members) .ExecuteCommandAsync(); } } } await Task.Run(() => currentStepConfig?.Invoke(workflow, currentStep, nextStepDefine), cancellationToken); await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken); //更新traces var updateTraces = new List(); foreach (var updateStep in updateSteps) { var updateTrace = workflow.Traces.First(d => d.Id == updateStep.Id); _mapper.Map(updateStep, updateTrace); updateTraces.Add(updateTrace); } await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken); if (workflow.ActualHandleStepId == currentStep.Id) { //更新实际办理节点信息 workflow.UpdateActualStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel); } if (workflow.CurrentStepId == currentStep.Id) { workflow.UpdateCurrentStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel); } //var trace = await NextTraceAsync(workflow, dto, currentStep, cancellationToken); #endregion #region 处理流程 //检查会签是否结束,并更新当前会签节点字段 var isCountersignOver = false; if (workflow.IsInCountersign && currentStep.IsCountersignEndStep) { isCountersignOver = workflow.CheckIfCountersignOver(); if (isCountersignOver) workflow.EndCountersign(); } //检查是否流转到流程终点 if (nextStepDefine.StepType is EStepType.End) { if (string.IsNullOrEmpty(workflow.ActualHandlerId) || string.IsNullOrEmpty(workflow.ActualHandleOrgCode)) //开始流程直接归档 { //更新实际办理节点信息 workflow.UpdateActualStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel); workflow.ActualHandleStepAcceptTime = currentStep.AcceptTime; workflow.ActualHandleTime = currentStep.HandleTime; } var (endStep, _) = await EndAsync(_sessionContext, workflow, dto, nextStepDefine, currentStep, expiredTime, cancellationToken); return (workflow, currentStep, nextStepDefine, new List { endStep }); } var isStartCountersign = currentStep.CountersignPosition switch { ECountersignPosition.None => dto.IsStartCountersign, ECountersignPosition.Direct => !dto.BackToCountersignEnd, ECountersignPosition.Indirect => !dto.BackToCountersignEnd, ECountersignPosition.End => !dto.BackToCountersignEnd && (workflow.IsInCountersign || dto.IsStartCountersign), _ => throw new ArgumentOutOfRangeException() }; var currentTrace = workflow.Traces.First(d => d.Id == currentStep.Id); //创建会签数据 if (isStartCountersign) { var exists = workflow.Countersigns.Any(d => !d.IsCompleted() && !string.IsNullOrEmpty(_sessionContext.UserId) && d.StarterId == _sessionContext.UserId); if (exists) throw new UserFriendlyException($"该用户在当前流程存在未结束会签, workflowId: {workflow.Id}, userId: {_sessionContext.UserId}"); var countersign = await StartCountersignAsync(_sessionContext, workflow, currentStep, dto, counterSignType, expiredTime, cancellationToken); currentTrace.StartCountersign(countersign.Id); await _workflowStepRepository.UpdateAsync(currentStep, cancellationToken); await _workflowTraceRepository.UpdateAsync(currentTrace, cancellationToken); } //发起会签时记录顶层会签节点 if (dto.IsStartCountersign && !workflow.IsInCountersign) workflow.StartCountersign(currentStep.Id, counterSignType); var assigner = new UserInfo( _sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, _sessionContext.OrgIsCenter ); //创建下一/N个节点(会签汇总节点:会签未全部办理时不创建,最后一个会签办理节点创建会签汇总节点) var nextSteps = await CreateNextStepsAsync(workflow, currentStep, dto, nextStepDefine, assigner, isNextDynamic, expiredTime, dto.IsStartCountersign, isAutoFillSummaryOpinion, newStepConfig, cancellationToken); // //更新办理对象(nextSteps无元素表示当前节点为会签办理节点且当前会签没有全部办理完成) // workflow.UpdateHandlers(current.RequiredUserId, current.RequiredOrgId, // flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects, nextSteps.Any()); //指派实际办理节点 UpdateActualStep(workflow, dto, nextStepDefine, nextSteps); //更新实际办理节点 UpdateCurrentStep(workflow, dto, nextStepDefine, nextSteps); //更新会签实际办理对象信息 if (currentStep.IsActualHandled) workflow.AddCsActualHandler(_sessionContext.UserId, _sessionContext.OrgId); await _workflowRepository.UpdateAsync(workflow, cancellationToken); #endregion #region 流转记录 //var trace = await NextTraceAsync(workflow, dto, currentStep, cancellationToken); #endregion await _publisher.PublishAsync( new NextStepNotify(workflow, dto, currentTrace, nextStepDefine, _sessionContext.OrgId, expiredTime.HasValue), PublishStrategy.ParallelWhenAll, cancellationToken); return (workflow, currentStep, nextStepDefine, nextSteps); } /// /// 开启流程并办理至第一个节点 /// public async Task> StartToFirstStepAsync(StartWorkflowDto dto, string externalId, DateTime? expiredTime = null, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var (workflow, startStep) = await StartAsync(dto, externalId, expiredTime, newStepConfig, cancellationToken); var nextDto = _mapper.Map(dto); nextDto.WorkflowId = workflow.Id; nextDto.StepId = startStep.Id; var (_, _, _, nextSteps) = await NextAsync(nextDto, expiredTime: expiredTime, isAutoFillSummaryOpinion: true, newStepConfig: newStepConfig, cancellationToken: cancellationToken); return nextSteps; } public async Task GetWorkflowAsync(string workflowId, bool withDefine = false, bool withSteps = false, bool withTraces = false, bool withTracesTree = false, bool withSupplements = false, bool withCountersigns = false, CancellationToken cancellationToken = default) { if (withTraces && withTracesTree) throw new UserFriendlyException("traces只能在集合与树形集合结构中二选一"); var query = _workflowRepository.Queryable().Where(d => d.Id == workflowId); if (withDefine) query = query.Includes(d => d.WorkflowDefinition); // if (withSupplements) // query = query.Includes(d => d.Supplements); //if (withAssigns) // query = query.Includes(d => d.Assigns); if (withCountersigns) query = query.Includes(d => d.Countersigns, x => x.Members); if (withSteps) query = query.Includes(d => d.Steps); if (withTraces) query = query.Includes(d => d.Traces); var workflow = await query.FirstAsync(cancellationToken); if (workflow is null) throw new UserFriendlyException("无效workflowId"); if (withTracesTree) { workflow.Traces = await _workflowTraceRepository.Queryable() .Where(d => d.WorkflowId == workflow.Id) .OrderBy(d => d.CreationTime) .ToTreeAsync(d => d.Traces, d => d.ParentId, null); } return workflow; } /// /// 查询用户对于当前流程权限 /// public async Task<(Workflow Workflow, string? CountersignId, bool CanHandle, bool CanPrevious, WorkflowTrace? Trace)> GetWorkflowHandlePermissionAsync( string workflowId, string userId, string orgId, string[] roleIds, CancellationToken cancellationToken = default) { var workflow = await GetWorkflowAsync(workflowId, withSteps: true, withTraces: true, withCountersigns: true, cancellationToken: cancellationToken); var canHandle = workflow.IsCanHandle(userId, orgId, roleIds); var canPrevious = false; if (canHandle) { var currentStep = FindCurrentStepWaitForHandle(workflow, userId, orgId, roleIds); if (currentStep.Status is not EWorkflowStepStatus.Handled) { canPrevious = !(currentStep.IsInCountersign() && !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)); } } var unhandlePreviousTrace = workflow.Traces.FirstOrDefault(d => d.Status is not EWorkflowStepStatus.Handled ); var unCompletedCountersign = workflow.Countersigns .FirstOrDefault(d => !d.IsCompleted() && d.StarterOrgId == orgId); if (unCompletedCountersign is null) return (workflow, null, canHandle, canPrevious, unhandlePreviousTrace); //var existCountersignEndStep = workflow.Steps.Exists(d => // d.IsCountersignEndStep && d.CountersignStartStepId == unCompletedCountersign.StartStepId); //return (workflow, existCountersignEndStep ? null : unCompletedCountersign.Id, canPrevious); return (workflow, unCompletedCountersign?.Id ?? null, canHandle, canPrevious, unhandlePreviousTrace); } /// /// 受理(接办) /// public async Task AcceptAsync(Workflow workflow, string userId, string? userName, string orgId, string? orgName, string? orgAreaCode, string? orgAreaName, CancellationToken cancellationToken) { if (!workflow.IsCanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, _sessionContext.Roles)) return null; //工单完成以后查看的场景 if (workflow.Status != EWorkflowStatus.Runnable) return null; var currentStep = GetUnHandleStep(workflow.Steps, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId, _sessionContext.Roles); if (currentStep.Status is not EWorkflowStepStatus.WaitForAccept) return null; //if (currentStep.Handlers.All(d => d.Key != orgId && d.Key != userId)) return null; if (currentStep.StepType is EStepType.End) throw new UserFriendlyException("当前流程已流转到最终步骤"); currentStep.Accept(userId, userName, orgId, orgName, orgAreaCode, orgAreaName); var trace = workflow.Traces.First(d => d.Id == currentStep.Id); _mapper.Map(currentStep, trace); currentStep.WorkflowTrace = trace; await _workflowStepRepository.UpdateNav(currentStep) .Include(d => d.WorkflowTrace) .ExecuteCommandAsync(); workflow.ActualHandleStepAcceptTime = currentStep.AcceptTime; workflow.CurrentStepAcceptTime = currentStep.AcceptTime; await _workflowRepository.Updateable(workflow).ExecuteCommandAsync(cancellationToken); return workflow.ActualHandleStepAcceptTime; } ///// ///// 退回(返回前一节点) ///// ///// //public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, // string applicantId, string applicantName, // string applicantOrgId, string applicantOrgName, // string applicantOrgAreaCode, string applicantOrgAreaName, // bool applicantIsCenter, string[] applicantRoleIds, // CancellationToken cancellationToken) //{ // //ValidatePermission(workflow, operater.OrgId, operater.Id); // var (currentStep, prevStep, countersignStartStep) = // GetPreviousStep(workflow, applicantId, applicantOrgId, applicantRoleIds); // //保存附件 // if (dto.Files.Any()) // currentStep.FileJson = await _fileRepository.AddFileAsync( // dto.Files, workflow.ExternalId, currentStep.Id, cancellationToken); // // add prev current to remove list // var removeSteps = new List { currentStep, prevStep }; // if (countersignStartStep is not null) // { // //add cs steps to remove list // SearchCountersignSteps(countersignStartStep, workflow.Steps, ref removeSteps); // //end cs // var currentCountersign = // workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId); // if (currentCountersign is null) // throw new UserFriendlyException( // $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}", // "无效会签编号"); // //结束step会签信息 // countersignStartStep.CountersignEnd(); // await _workflowStepRepository.UpdateAsync(countersignStartStep, cancellationToken); // //updateSteps.Add(countersignStartStep); // //结束会签 // //currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType, // // current.RequiredUserId, current.UserName, // // current.RequiredOrgId, current.OrgName, // // current.OrgAreaCode, current.OrgAreaName); // currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType, // applicantId, applicantName, // applicantOrgId, applicantOrgName, // applicantOrgAreaCode, applicantOrgAreaName); // await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken); // //update workflow cs status // if (workflow.CheckIfCountersignOver()) // workflow.EndCountersign(); // } // //update trace // //var trace = await PreviousTraceAsync(workflow.Id, dto, currentStep, // // applicantId, applicantName, // // applicantOrgId, applicantOrgName, // // applicantOrgAreaCode, applicantOrgAreaName, // // applicantIsCenter, cancellationToken); // var trace = workflow.Traces.First(t => t.StepId == currentStep.Id); // // _mapper.Map(dto, trace); // trace.FileJson = currentStep.FileJson; // trace.IsSms = dto.AcceptSms; // trace.Opinion = dto.Opinion; // //HandleTrace(trace, dto.Opinion, current); // trace.Handle(applicantId, applicantName, // applicantOrgId, applicantOrgName, // applicantOrgAreaCode, applicantOrgAreaName, // applicantIsCenter, EHandleMode.Previous, dto.Opinion); // //await _workflowTraceRepository.UpdateAsync(trace, cancellationToken); // //如果有传入期满时间 新节点为传入的期满时间 // if (dto.ExpiredTime.HasValue) // prevStep.StepExpiredTime = dto.ExpiredTime; // if (workflow.FlowType == EFlowType.Handle) // { // prevStep.FlowAssignType = prevStep.BusinessType is EBusinessType.Seat ? EFlowAssignType.Role : // prevStep.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Org; // } // //甄别退回到最开始节点到部门 // if (workflow.FlowType == EFlowType.Review && workflow.ModuleCode == WorkflowModuleConsts.OrderScreen) // { // prevStep.FlowAssignType = prevStep.StepType == EStepType.Start ? EFlowAssignType.Org : prevStep.FlowAssignType; // } // //复制上一个节点为待接办 // // var newPrevStep = // // await DuplicateStepWithTraceAsync(workflow, prevStep, EWorkflowTraceType.Previous, cancellationToken); // var newPrevStep = DuplicateStep(prevStep, EWorkflowTraceType.Previous, dto.ExpiredTime); // //退给派单组节点,需按照平均分配原则派给一个派单员 禅道299 TODO // if (dto.Handler != null) //todo 改为按策略判断 // { // var handle = dto.Handler; // newPrevStep.Assign(handle.UserId, handle.Username, handle.OrgId, handle.OrgName, handle.RoleId, handle.RoleName); // } // await _workflowStepRepository.AddAsync(newPrevStep, cancellationToken); // await CreateTraceAsync(workflow, newPrevStep, EWorkflowTraceType.Previous, cancellationToken); // //remove workflow.steps // await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken); // var stepIds = removeSteps.Select(d => d.Id).ToList(); // var updateTraces = workflow.Traces.Where(d => stepIds.Contains(d.StepId)).ToList(); // await UpdateTracesStateAsync(updateTraces, EWorkflowTraceState.StepRemoveByPrevious, cancellationToken); // if (workflow.Status is EWorkflowStatus.Completed) // workflow.SetStatusRunnable(); // //更新实际办理节点信息 // workflow.UpdateActualStepWhenAssign(newPrevStep, new FlowStepHandler // { // UserId = newPrevStep.HandlerId, // Username = newPrevStep.HandlerName, // OrgId = newPrevStep.HandlerOrgId, // OrgName = newPrevStep.HandlerOrgName, // }); // workflow.UpdateCurrentStepWhenAssign(newPrevStep, new FlowStepHandler // { // UserId = newPrevStep.HandlerId, // Username = newPrevStep.HandlerName, // OrgId = newPrevStep.HandlerOrgId, // OrgName = newPrevStep.HandlerOrgName, // }); // // //更新流程可办理对象 // // workflow.UpdatePreviousHandlers(applicantId, applicantOrgId, prevStep); // //orgToCenter会触发重新计算期满时间,1.无需审核按当前时间进行计算 2.需审核按审核通过时间计算 // var isOrgToCenter = prevStep.BusinessType is EBusinessType.Send && prevStep.IsOrigin; // await _workflowRepository.UpdateAsync(workflow, cancellationToken); // await _publisher.PublishAsync(new PreviousNotify(workflow, newPrevStep, dto, isOrgToCenter), // PublishStrategy.ParallelWhenAll, cancellationToken); // return GetFlowDirection(currentStep.BusinessType, prevStep.BusinessType); //} /// /// 退回(new) /// workflow, currentStep, prevStepDefine, prevStep, newPrevStep /// public async Task<(Workflow workflow, WorkflowStep currentStep, StepDefine prevDefine, WorkflowStep prevStep, WorkflowStep newStep, EFlowDirection flowDirection)> PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, OperatorInfo operatorInfo, EHandleMode handleMode = EHandleMode.Previous, Action? newStepConfig = null, CancellationToken cancellationToken = default) { //ValidatePermission(workflow, operater.OrgId, operater.Id); if (string.IsNullOrEmpty(operatorInfo.UserId) && string.IsNullOrEmpty(operatorInfo.OrgId) && !operatorInfo.Roles.Any()) throw new UserFriendlyException("无效当前操作人信息"); var (currentStep, prevStep, countersignStartStep) = GetPreviousStep(workflow, operatorInfo.UserId, operatorInfo.OrgId, operatorInfo.Roles); //保存附件 if (dto.Files.Any()) currentStep.FileJson = await _fileRepository.AddFileAsync(dto.Files, workflow.ExternalId, currentStep.Id, cancellationToken); // add prev current to remove list var removeSteps = new List { currentStep, prevStep }; if (countersignStartStep is not null) { //add cs steps to remove list SearchCountersignSteps(countersignStartStep, workflow.Steps, ref removeSteps); //end cs var currentCountersign = workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId); if (currentCountersign is null) throw new UserFriendlyException( $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}", "无效会签编号"); //结束step会签信息 countersignStartStep.CountersignEnd(); await _workflowStepRepository.UpdateAsync(countersignStartStep, cancellationToken); //updateSteps.Add(countersignStartStep); //结束会签 currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType, operatorInfo.UserId, operatorInfo.UserName, operatorInfo.OrgId, operatorInfo.OrgName, operatorInfo.OrgAreaCode, operatorInfo.OrgAreaName); await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken); //update workflow cs status if (workflow.CheckIfCountersignOver()) workflow.EndCountersign(); } var trace = workflow.Traces.First(t => t.StepId == currentStep.Id); // _mapper.Map(dto, trace); trace.FileJson = currentStep.FileJson; trace.IsSms = dto.AcceptSms; trace.Opinion = dto.Opinion; //HandleTrace(trace, dto.Opinion, current); trace.Handle(operatorInfo.UserId, operatorInfo.UserName, operatorInfo.OrgId, operatorInfo.OrgName, operatorInfo.OrgAreaCode, operatorInfo.OrgAreaName, operatorInfo.OrgIsCenter, handleMode, dto.Opinion); //如果有传入期满时间 新节点为传入的期满时间 if (dto.ExpiredTime.HasValue) prevStep.StepExpiredTime = dto.ExpiredTime; //if (workflow.FlowType == EFlowType.Handle) //该逻辑需放在退回操作前依据业务判断 //{ // prevStep.FlowAssignType = prevStep.BusinessType is EBusinessType.Seat ? EFlowAssignType.Role : // prevStep.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Org; //} // dto.ReverseFlowStepAssignInfo ??= new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStepUser); // //todo // var stepAssignInfo = GetStepAssignInfo(dto.ReverseFlowStepAssignInfo, prevStep, prevStepDefine); var prevStepDefine = workflow.WorkflowDefinition.FindStepDefine(prevStep.Code); //复制上一个节点为待接办 var newPrevStep = DuplicateStep(workflow, prevStepDefine, prevStep, EWorkflowTraceType.Previous, dto.ExpiredTime); newPrevStep.Assign(prevStep, EFlowAssignType.User); ////甄别退回到最开始节点到部门 todo 重构放在调用处判断 //if (workflow.FlowType == EFlowType.Review && workflow.ModuleCode == WorkflowModuleConsts.OrderScreen) //{ // newPrevStep.FlowAssignType = newPrevStep.StepType == EStepType.Start ? EFlowAssignType.Org : prevStep.FlowAssignType; //} if (dto.Handler != null) newPrevStep.Assign(dto.Handler); //await Task.Run(() => newStepConfig?.Invoke(workflow, currentStep, prevStepDefine, prevStep, newPrevStep), cancellationToken); newStepConfig?.Invoke(workflow, currentStep, prevStepDefine, prevStep, newPrevStep); await _workflowStepRepository.AddAsync(newPrevStep, cancellationToken); await CreateTraceAsync(workflow, newPrevStep, EWorkflowTraceType.Previous, cancellationToken); //remove workflow.steps await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken); var stepIds = removeSteps.Select(d => d.Id).ToList(); var updateTraces = workflow.Traces.Where(d => stepIds.Contains(d.StepId)).ToList(); await UpdateTracesStateAsync(updateTraces, EWorkflowTraceState.StepRemoveByPrevious, cancellationToken); if (workflow.Status is EWorkflowStatus.Completed) workflow.SetStatusRunnable(); //更新实际办理节点信息 workflow.UpdateActualStepWhenAssign(newPrevStep, new FlowStepHandler { UserId = newPrevStep.HandlerId, Username = newPrevStep.HandlerName, OrgId = newPrevStep.HandlerOrgId, OrgName = newPrevStep.HandlerOrgName, }); workflow.UpdateCurrentStepWhenAssign(newPrevStep, new FlowStepHandler { UserId = newPrevStep.HandlerId, Username = newPrevStep.HandlerName, OrgId = newPrevStep.HandlerOrgId, OrgName = newPrevStep.HandlerOrgName, }); // //更新流程可办理对象 // workflow.UpdatePreviousHandlers(applicantId, applicantOrgId, prevStep); //orgToCenter会触发重新计算期满时间,1.无需审核按当前时间进行计算 2.需审核按审核通过时间计算 var isOrgToCenter = prevStep.BusinessType is EBusinessType.Send && prevStep.IsOrigin; await _workflowRepository.UpdateAsync(workflow, cancellationToken); await _publisher.PublishAsync(new PreviousNotify(workflow, newPrevStep, dto, isOrgToCenter), PublishStrategy.ParallelWhenAll, cancellationToken); var flowDirection = GetFlowDirection(currentStep.BusinessType, prevStep.BusinessType); return (workflow, currentStep, prevStepDefine, prevStep, newPrevStep, flowDirection); } /// /// 退回 /// public async Task<(Workflow workflow, WorkflowStep currentStep, StepDefine prevDefine, WorkflowStep prevStep, WorkflowStep newStep, EFlowDirection flowDirection)> PreviousAsync(PreviousWorkflowDto dto, EHandleMode handleMode = EHandleMode.Previous, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true, cancellationToken: cancellationToken); return await PreviousAsync(workflow, dto, new OperatorInfo( _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgIsCenter, _sessionContext.Roles, _sessionContext.OrgLevel), handleMode, newStepConfig, cancellationToken); } private async Task UpdateTracesStateAsync(List traces, EWorkflowTraceState traceState, CancellationToken cancellationToken) { foreach (var trace in traces) { trace.TraceState = traceState; } await _workflowTraceRepository.UpdateRangeAsync(traces, cancellationToken); } /// /// 查询退回节点信息 /// public (WorkflowStep currentStep, WorkflowStep prevStep, WorkflowStep? countersignStartStep) GetPreviousStep( Workflow workflow, string operaterId, string operaterOrgId, string[] roleIds) { var currentStep = GetUnHandleStep(workflow.Steps, operaterOrgId, operaterId, roleIds); var isCurrentTopCountersignEndStep = workflow.IsInCountersign && currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId); if (currentStep.IsInCountersign() && !isCurrentTopCountersignEndStep) throw UserFriendlyException.SameMessage("会签节点不支持退回"); if (workflow.FlowType is EFlowType.Review && currentStep.StepType is EStepType.Start && currentStep.IsOrigin) throw UserFriendlyException.SameMessage("当前流程已退回到开始节点"); //当退回操作遇到会签时,删除所有会签节点直达topCsStep //find prevStep, update handler WorkflowStep? prevStep, countersignStartStep = null; if (isCurrentTopCountersignEndStep) { //prev is topstart's prev countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId); if (countersignStartStep is null) throw new UserFriendlyException("未查询到对应会签开始节点"); prevStep = workflow.Steps.FirstOrDefault(d => d.Id == countersignStartStep.PrevStepId); } else { prevStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.PrevStepId); } if (prevStep == null) throw UserFriendlyException.SameMessage("未查询到前一节点"); while (prevStep.IsCountersignEndStep) { countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == prevStep.CountersignStartStepId); prevStep = countersignStartStep ?? throw new UserFriendlyException("未查询到对应会签开始节点"); if (prevStep == null) throw UserFriendlyException.SameMessage("未查询到前一节点"); } return (currentStep, prevStep, countersignStartStep); } /// /// 开启流程直接归档 /// public async Task StartToEndAsync(StartWorkflowDto dto, string externalId, DateTime? expiredTime = null, CancellationToken cancellationToken = default) { var wfModule = await GetWorkflowModuleAsync(dto.DefinitionModuleCode, cancellationToken); var definition = wfModule.Definition; if (definition == null) throw new UserFriendlyException("无效模板编码"); if (definition.Status is not EDefinitionStatus.Enable) throw new UserFriendlyException("该模板不可用"); var endStepDefine = definition.FindEndStepDefine(); dto.NextStepCode = endStepDefine.Code; dto.NextStepName = endStepDefine.Name; dto.FlowDirection = EFlowDirection.CenterToFile; await StartAsync(dto, externalId, expiredTime, cancellationToken: cancellationToken); } /// /// 查询派单池中流程节点id /// public async Task> GetUnhandleStepIdsFromSendPoolAsync(string sendPoolId, CancellationToken cancellationToken) { return await _workflowStepRepository.Queryable() .Where(d => SqlFunc.JsonListObjectAny(d.Handlers, "Key", sendPoolId)) .Select(d => d.Id) .ToListAsync(cancellationToken); } /// /// 查询归属某用户的所有流程节点 /// public async Task> GetStepsBelongsToAsync(string userId, CancellationToken cancellationToken) { return await _workflowStepRepository.Queryable() .Includes(d => d.WorkflowTrace) .Where(d => d.HandlerId == userId) .OrderBy(d => d.CreationTime) .ToListAsync(cancellationToken); } /// /// 批量修改工单办理对象 /// public async Task ChangeHandlerBatchAsync( IReadOnlyList<(string userId, string username, string orgId, string orgName, string? roleId, string? roleName, ICollection steps)> handlers, CancellationToken cancellationToken) { foreach (var handler in handlers) { foreach (var step in handler.steps) { step.FlowAssignType = EFlowAssignType.User; step.Assign(handler.userId, handler.username, handler.orgId, handler.orgName, handler.roleId, handler.roleName); if (step.WorkflowTrace is null) throw new UserFriendlyException("未查询节点对应快照信息"); step.WorkflowTrace.FlowAssignType = EFlowAssignType.User; step.WorkflowTrace.Assign(handler.userId, handler.username, handler.orgId, handler.orgName, handler.roleId, handler.roleName); //更新节点CreationTime 派单量统计 待派单数据使用 step.CreationTime = DateTime.Now; } } var steps = handlers.SelectMany(d => d.steps).ToList(); //await _workflowStepRepository.UpdateRangeAsync(steps, cancellationToken); await _workflowStepRepository.UpdateNav(steps) .Include(d => d.WorkflowTrace) .ExecuteCommandAsync(); } /// /// 查询工单办理中的一级部门 /// public async Task> GetLevelOneOrgsAsync(string workflowId, CancellationToken cancellation) { var traces = await _workflowTraceRepository.Queryable() .LeftJoin((t, o) => t.HandlerOrgId == o.Id) .Where((t, o) => t.WorkflowId == workflowId && !string.IsNullOrEmpty(t.HandlerOrgId) && o.Level == 1) .ToListAsync(cancellation); //var handlers = await _workflowStepHandlerRepository.Queryable() // .InnerJoin((wsh, wt) => wsh.WorkflowStepId == wt.StepId) // .LeftJoin((wsh, wt, o) => wsh.OrgId == o.Id) // .Where((wsh, wt, o) => wsh.WorkflowId == workflowId && // //wt.BusinessType == EBusinessType.Department && // //wt.HandlerType == EHandlerType.OrgLevel && // !string.IsNullOrEmpty(wsh.OrgId) && // o.Level == 1) // .ToListAsync(cancellation); //var orgs = handlers.Select(d => new Kv(d.OrgId, d.OrgName)) // .DistinctBy(d => d.Key) // .ToList(); var orgs = traces .DistinctBy(d => d.HandlerOrgId) .Select(d => new Kv(d.HandlerOrgId, d.HandlerOrgName)) .ToList(); return orgs; //var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellation); //var list = workflow.Steps.Distinct().Where(d => d.BusinessType == EBusinessType.Department && // d.HandlerType == EHandlerType.OrgLevel && // d.StepHandlers.Any(d => // !string.IsNullOrEmpty(d.OrgId) && d.OrgId.CheckIfOrgLevelIs(1))) // .Select(d => new Kv(d.StepHandlers.First().OrgId, d.StepHandlers.First().OrgName)) // .ToList(); //return list.Where((x, i) => list.FindIndex(z => z.Key == x.Key) == i).ToList(); } /// /// 更新未办理节点的期满时间 /// public async Task UpdateUnhandleExpiredTimeAsync(string workflowId, DateTime expiredTime, CancellationToken cancellation) { var steps = await _workflowStepRepository.Queryable() .Includes(d => d.WorkflowTrace) .Where(d => d.WorkflowId == workflowId && d.Status < EWorkflowStepStatus.Handled) .ToListAsync(cancellation); foreach (var step in steps) { step.StepExpiredTime = expiredTime; step.WorkflowTrace.StepExpiredTime = expiredTime; } await _workflowStepRepository.UpdateNav(steps) .Include(d => d.WorkflowTrace) .ExecuteCommandAsync(); } /// /// 查询该部门最后办理节点 /// /// public async Task FindLastHandleStepAsync(string workflowId, string orgId, CancellationToken cancellation) { return await _workflowStepRepository.Queryable() .Where(d => d.WorkflowId == workflowId && d.HandlerOrgId == orgId && d.StepType != EStepType.End && d.StepType != EStepType.Summary && !d.IsCountersignEndStep) //.Where(d => d.StepHandlers.Any(sh => sh.OrgId == orgId) && d.WorkflowId == workflowId) .OrderByDescending(d => d.HandleTime) .FirstAsync(cancellation); } /// /// 部门会签工单获取流程最顶级办理节点 /// /// public async Task FindTopHandleStepAsync(string workflowId, CancellationToken cancellation) { var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellation); return workflow.Steps.FirstOrDefault(x => x.Id == workflow.TopCountersignStepId); } /// /// 查询流转方向 /// public EFlowDirection GetFlowDirection(EBusinessType sourceStepBusinessType, EBusinessType directionStepBusinessType) { switch (sourceStepBusinessType) { case EBusinessType.Seat: case EBusinessType.Send: case EBusinessType.CenterMonitor: case EBusinessType.CenterLeader: return directionStepBusinessType switch { EBusinessType.Seat => EFlowDirection.CenterToCenter, EBusinessType.Send => EFlowDirection.CenterToCenter, EBusinessType.CenterMonitor => EFlowDirection.CenterToCenter, EBusinessType.CenterLeader => EFlowDirection.CenterToCenter, EBusinessType.Department => EFlowDirection.CenterToOrg, EBusinessType.DepartmentLeader => EFlowDirection.CenterToOrg, EBusinessType.File => EFlowDirection.CenterToFile, _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType), directionStepBusinessType, null) }; case EBusinessType.Department: case EBusinessType.DepartmentLeader: return directionStepBusinessType switch { EBusinessType.Seat => EFlowDirection.OrgToCenter, EBusinessType.Send => EFlowDirection.OrgToCenter, EBusinessType.CenterMonitor => EFlowDirection.OrgToCenter, EBusinessType.CenterLeader => EFlowDirection.OrgToCenter, EBusinessType.Department => EFlowDirection.OrgToOrg, EBusinessType.DepartmentLeader => EFlowDirection.OrgToOrg, EBusinessType.File => EFlowDirection.OrgToFile, _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType), directionStepBusinessType, null) }; case EBusinessType.File: return directionStepBusinessType switch { EBusinessType.Seat => EFlowDirection.FiledToCenter, EBusinessType.Send => EFlowDirection.FiledToCenter, EBusinessType.Department => EFlowDirection.FiledToOrg, EBusinessType.DepartmentLeader => EFlowDirection.FiledToOrg, EBusinessType.CenterMonitor => EFlowDirection.FiledToCenter, EBusinessType.CenterLeader => EFlowDirection.FiledToCenter, _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType), directionStepBusinessType, null) }; case EBusinessType.Unknown: case EBusinessType.Publish: case EBusinessType.Visit: case EBusinessType.TrashEnd: default: throw new ArgumentOutOfRangeException(nameof(sourceStepBusinessType), sourceStepBusinessType, null); } } /// /// 流程被签收至某个用户(更新流转对象,办理对象,节点办理对象以及stepHandlers) /// public async Task SignToSomebodyAsync(string workflowId, string userId, string username, string orgId, string orgName, CancellationToken cancellationToken) { var workflow = await GetWorkflowAsync(workflowId, withSteps: true, withTraces: true, cancellationToken: cancellationToken); var startStep = workflow.Steps.First(d => d.StepType == EStepType.Start && d.IsOrigin); startStep.Handlers = new List { new(userId, username) }; startStep.AcceptorId = userId; startStep.AcceptorName = username; startStep.AcceptTime = DateTime.Now; startStep.AcceptorOrgId = orgId; startStep.AcceptorOrgName = orgName; startStep.FlowAssignType = EFlowAssignType.User; startStep.Assign(userId, username, orgId, orgName); startStep.WorkflowTrace = workflow.Traces.First(d => d.Id == startStep.Id); _mapper.Map(startStep, startStep.WorkflowTrace); await _workflowStepRepository.UpdateNav(startStep) .Include(d => d.WorkflowTrace) .ExecuteCommandAsync(); await _workflowRepository.UpdateAsync(workflow, cancellationToken); return workflow; } /// /// 非节点办理人员查询待办节点 /// /// public async Task> GetUnhandleStepsByOthersAsync(string workflowId, CancellationToken cancellationToken) { return await _workflowStepRepository.Queryable() .Where(d => d.WorkflowId == workflowId && d.Status != EWorkflowStepStatus.Handled) .ToListAsync(cancellationToken); } /// /// 根据汇总对象id找到被汇总节点,生成指派到用户的办理对象 /// public StepAssignInfo GetSummaryTargetFlowStepHandler(Workflow workflow, string summaryTargetStepCode) { //根据汇总对象id找到被汇总节点 var summaryTargetStep = workflow.Steps.FirstOrDefault(d => d.StepType == EStepType.Normal && d.Code == summaryTargetStepCode && d.Status == EWorkflowStepStatus.Handled && d.IsOrigin); if (summaryTargetStep is null) throw UserFriendlyException.SameMessage("未查询到汇总对象节点"); var handler = new StepAssignInfo { Key = summaryTargetStep.HandlerId, Value = summaryTargetStep.HandlerName, UserId = summaryTargetStep.HandlerId, Username = summaryTargetStep.HandlerName, OrgId = summaryTargetStep.HandlerOrgId, OrgName = summaryTargetStep.HandlerOrgName, RoleId = summaryTargetStep.RoleId, RoleName = summaryTargetStep.RoleName, FlowAssignType = EFlowAssignType.User }; return handler; } /// /// 追加归档信息(接收ds推送12315归档信息) /// public async Task AppendFileOpinionAsync(string workflowId, string opinion, List files, CancellationToken cancellationToken) { //归档意见,附件追加在归档节点前一节点上 var workflow = await GetWorkflowAsync(workflowId, withSteps: true, withTraces: true, cancellationToken: cancellationToken); var endStep = workflow.Steps.FirstOrDefault(d => d.StepType == EStepType.End); if (endStep is null) throw new UserFriendlyException($"该流程还未归档, workflowId: {workflowId}"); var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == endStep.PrevStepId); if (prevStep is null) throw new UserFriendlyException($"未找到归档节点的前一节点, workflowId: {workflowId}, endStepId: {endStep.Id}"); prevStep.Opinion = opinion; if (files != null && files.Any()) { var filejsons = await _fileRepository.AddFileAsync(files, workflow.ExternalId, prevStep.Id, cancellationToken); prevStep.FileJson.AddRange(filejsons); } await _workflowStepRepository.UpdateAsync(prevStep, cancellationToken); var endTrace = workflow.Traces.FirstOrDefault(p => p.Id == prevStep.Id); if (endTrace != null) { endTrace.Opinion = prevStep.Opinion; endTrace.FileJson = prevStep.FileJson; await _workflowTraceRepository.UpdateAsync(endTrace, cancellationToken); } } /// /// 跳转至结束节点(无视流程模板配置以及当前办理对象,直接跳至结束节点) /// public async Task JumpToEndAsync(ISessionContext current, string workflowId, string opinion, List files, DateTime? expiredTime, EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default) { var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true, cancellationToken: cancellationToken); await JumpToEndAsync(current, workflow, opinion, files, expiredTime, reviewResult, cancellationToken); } /// /// 跳转至结束节点(无视流程模板配置以及当前办理对象,直接跳至结束节点) /// public async Task JumpToEndAsync(ISessionContext current, Workflow workflow, string opinion, List files, DateTime? expiredTime, EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default) { var endStepDefine = workflow.WorkflowDefinition.FindEndStepDefine(); if (endStepDefine is null) throw new UserFriendlyException("未正确配置结束节点"); var dto = new BasicWorkflowDto { NextStepCode = endStepDefine.Code, NextStepName = endStepDefine.Name, FlowDirection = EFlowDirection.OrgToFile, BusinessType = endStepDefine.BusinessType, ReviewResult = reviewResult, Opinion = opinion, Files = files }; var unhandleSteps = workflow.Steps .Where(d => d.Status != EWorkflowStepStatus.Handled).ToList(); var unhandleTraces = workflow.Traces .Where(d => d.Status != EWorkflowStepStatus.Handled).ToList(); //get currentStep var currentStep = unhandleSteps.MaxBy(d => d.CreationTime) ?? workflow.Steps.MaxBy(d => d.CreationTime); foreach (var step in unhandleSteps) { await HandleStepAsync(step, workflow, dto, null, null, EHandleMode.Normal, cancellationToken); if (step.IsStartCountersign) step.CountersignEnd(); var trace = unhandleTraces.First(d => d.StepId == step.Id); _mapper.Map(dto, trace); _mapper.Map(step, trace); } await _workflowStepRepository.UpdateRangeAsync(unhandleSteps, cancellationToken); await _workflowTraceRepository.UpdateRangeAsync(unhandleTraces, cancellationToken); //结束会签 var counstersigns = workflow.Countersigns .Where(d => !d.EndTime.HasValue) .ToList(); foreach (var counstersign in counstersigns) { //结束会签 counstersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType, current.UserId, current.UserName, current.OrgId, current.OrgName, current.OrgAreaCode, current.OrgAreaName); } await _workflowCountersignRepository.UpdateRangeAsync(counstersigns, cancellationToken); //更新实际办理节点信息 if (currentStep.StepType != EStepType.Summary && currentStep.StepType != EStepType.End) { workflow.UpdateActualStepWhenHandle(currentStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel); workflow.ActualHandleStepAcceptTime = currentStep.AcceptTime; } // // workflow.UpdateCurrentStepWhenHandle(currentStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel); if (workflow.Steps.All(d => d.StepType != EStepType.End)) { await EndAsync(current, workflow, dto, endStepDefine, currentStep, expiredTime, cancellationToken); } } /// /// 查找当前会签内所有节点(含start,end) /// private void SearchCountersignSteps(WorkflowStep startStep, List steps, ref List csSteps) { if (startStep.IsStartCountersign) { var countersignSteps = steps.Where(d => d.CountersignId == startStep.StartCountersignId).ToList(); if (countersignSteps.Any()) { foreach (var countersignStep in countersignSteps) { SearchCountersignSteps(countersignStep, steps, ref csSteps); } } } csSteps.Add(startStep); } /// /// 撤回(返回到之前任意节点) /// public async Task<(Workflow, StepDefine, WorkflowStep, WorkflowStep, WorkflowStep, bool)> RecallAsync( RecallDto dto, StepAssignInfo stepAssignInfo, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true, cancellationToken: cancellationToken); var targetStepDefine = GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode); if (targetStepDefine.StepType is EStepType.End) throw UserFriendlyException.SameMessage("结束节点不支持撤回"); var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin); if (targetStep is null) throw UserFriendlyException.SameMessage("该流程尚未流转至该节点"); return await RecallAsync(workflow, dto, stepAssignInfo, targetStepDefine, targetStep, traceType, expiredTime, isOrderFiled, handleMode, newStepConfig, cancellationToken); } /// /// 撤回(返回到之前任意节点) /// public Task<(Workflow, StepDefine, WorkflowStep, WorkflowStep, WorkflowStep, bool)> RecallAsync( Workflow workflow, BasicWorkflowDto dto, StepAssignInfo stepAssignInfo, StepDefine targetStepDefine, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin); if (targetStep is null) throw UserFriendlyException.SameMessage("该流程尚未流转至该节点"); return RecallAsync(workflow, dto, stepAssignInfo, targetStepDefine, targetStep, traceType, expiredTime, isOrderFiled, handleMode, newStepConfig, cancellationToken); } public Task<(Workflow, StepDefine, WorkflowStep, WorkflowStep, WorkflowStep, bool)> RecallAsync( Workflow workflow, BasicWorkflowDto dto, StepAssignInfo stepAssignInfo, WorkflowStep targetStep, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var targetStepDefine = GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode); if (targetStepDefine.StepType is EStepType.End) throw UserFriendlyException.SameMessage("结束节点不支持撤回"); return RecallAsync(workflow, dto, stepAssignInfo, targetStepDefine, targetStep, traceType, expiredTime, isOrderFiled, handleMode, newStepConfig, cancellationToken); } /// /// 特提 /// /// workflow, targetStepDefine, currentStep, targetStep, newStep, isOrgToCenter /// private async Task<(Workflow workflow, StepDefine targetStepDefine, WorkflowStep currentStep, WorkflowStep targetStep, WorkflowStep newStep, bool isOrgToCenter)> RecallAsync( Workflow workflow, BasicWorkflowDto dto, StepAssignInfo stepAssignInfo, StepDefine targetStepDefine, WorkflowStep targetStep, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var targetIsStartStep = targetStepDefine.StepType is EStepType.Start; var updateTraces = new List(); var currentStep = workflow.Steps.Where(x => x.Status != EWorkflowStepStatus.Handled) .MaxBy(d => d.CreationTime) ?? workflow.Steps.Where(x => x.StepType == EStepType.End) .MaxBy(d => d.CreationTime) ?? workflow.Steps.MaxBy(d => d.CreationTime); //update uncomplete traces var uncompleteTraces = workflow.Traces.Where(d => d.Status != EWorkflowStepStatus.Handled && d.TraceStyle == ETraceStyle.Flow).ToList(); if (uncompleteTraces.Any()) { foreach (var trace in uncompleteTraces) { trace.Handle( _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgIsCenter, handleMode, dto.Opinion); } //await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken); updateTraces.AddRange(uncompleteTraces); } else { var endTrace = workflow.Traces.Where(d => d.StepType == EStepType.End && d.TraceStyle == ETraceStyle.Flow).MaxBy(d => d.CreationTime); if (endTrace is not null) { endTrace.Opinion += ("\r\n" + dto.Opinion); updateTraces.Add(endTrace); } } //get targetStep's previous WorkflowStep? targetPrevStep = null; if (!targetIsStartStep) { targetPrevStep = workflow.Steps.FirstOrDefault(d => d.Id == targetStep.PrevStepId); if (targetPrevStep == null) throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}"); } //查询所有目标节点之后的节点,然后删掉(包括目标节点) var removeSteps = GetStepsBehindTargetStep(workflow.Steps, targetStep); if (removeSteps.Any()) { await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken); workflow.Steps.RemoveAll(d => removeSteps.Contains(d)); //更新快照对应节点状态 var stepIds = removeSteps.Select(d => d.Id).ToList(); var traces = workflow.Traces.Where(d => stepIds.Contains(d.StepId)).ToList(); //await UpdateTracesStateAsync(updateTraces, EWorkflowTraceState.StepRemoveByRecall, cancellationToken); foreach (var trace in traces) { trace.TraceState = isOrderFiled ? EWorkflowTraceState.StepRemoveByRecallWhenFiled : EWorkflowTraceState.StepRemoveByRecall; } updateTraces.AddRange(traces); } await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken); //结束会签 var unCompleteCountersigns = workflow.Countersigns.Where(d => !d.IsCompleted()).ToList(); if (unCompleteCountersigns.Any()) { foreach (var unCompleteCountersign in unCompleteCountersigns) { unCompleteCountersign.End(null, null, EBusinessType.File, _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName); } await _workflowCountersignRepository.UpdateRangeAsync(unCompleteCountersigns, cancellationToken); } workflow.EndCountersign(); workflow.ResetOption(); if (workflow.Status is EWorkflowStatus.Completed) workflow.SetStatusRunnable(); var assigner = new UserInfo( _sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, _sessionContext.OrgIsCenter ); //var targetStepNew = targetIsStartStep // ? await CreateStartStepAsync(workflow, targetStepDefine, dto, assigner, // dto.NextHandlers.First(), traceType, expiredTime, flowAssignInfo.FlowAssignType, cancellationToken) // : (await CreateStepsAsync(workflow, targetStepDefine, targetPrevStep, dto, assigner, // flowAssignInfo.FlowAssignType, dto.NextHandlers, // null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, true, traceType, // null, expiredTime, cancellationToken: cancellationToken)).First(); //var stepAssignInfo = GetStepAssignInfo(reverseFlowStepAssignInfo, targetStep, targetStepDefine); stepAssignInfo ??= targetStep.GetWorkflowStepHandler(); WorkflowStep targetStepNew; if (targetIsStartStep) { // targetStepNew = await CreateStartStepAsync(workflow, targetStepDefine, dto, assigner, // stepAssignInfo, traceType, expiredTime, stepConfig, cancellationToken); targetStepNew = CreateStartStep(workflow, targetStepDefine, dto, assigner, stepAssignInfo, expiredTime); //await Task.Run(() => newStepConfig?.Invoke(workflow, currentStep, targetStepDefine, targetStep, targetStepNew), cancellationToken); newStepConfig?.Invoke(workflow, currentStep, targetStepDefine, targetStep, targetStepNew); await _workflowStepRepository.AddAsync(targetStepNew, cancellationToken); workflow.Steps.Add(targetStepNew); await CreateTraceAsync(workflow, targetStepNew, traceType, cancellationToken); } else { // targetStepNew = (await CreateStepsAsync(workflow, targetStepDefine, targetPrevStep, dto, assigner, [stepAssignInfo], // null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, true, traceType, // null, expiredTime, stepConfig: stepConfig, cancellationToken: cancellationToken)).First(); targetStepNew = CreateStep(workflow, targetStepDefine, targetPrevStep, stepAssignInfo, assigner, dto.NextStepCode, null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, expiredTime, dto.NextStepName, true, true, null, dto.BusinessType, dto.FlowDirection); //await Task.Run(() => newStepConfig?.Invoke(workflow, currentStep, targetStepDefine, targetStep, targetStepNew), cancellationToken); newStepConfig?.Invoke(workflow, currentStep, targetStepDefine, targetStep, targetStepNew); await _workflowStepRepository.AddAsync(targetStepNew, cancellationToken); workflow.Steps.Add(targetStepNew); await CreateTraceAsync(workflow, targetStepNew, traceType, cancellationToken); } //更新实际办理节点信息 workflow.UpdateActualStepWhenAssign(targetStepNew, new FlowStepHandler { UserId = targetStep.HandlerId, Username = targetStep.HandlerName, OrgId = targetStep.HandlerOrgId, OrgName = targetStep.HandlerOrgName }); workflow.UpdateCurrentStepWhenAssign(targetStepNew, new FlowStepHandler { UserId = targetStep.HandlerId, Username = targetStep.HandlerName, OrgId = targetStep.HandlerOrgId, OrgName = targetStep.HandlerOrgName }); //取消维护workflow得冗余字段(FlowedOrgIds, HandlerOrgs) //workflow.Assign(flowAssignInfo.FlowAssignType, flowAssignInfo.GetHandlerIds()); //workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects); await _workflowRepository.UpdateAsync(workflow, cancellationToken); //calc workflow expired time var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStep); await _publisher.PublishAsync(new RecallNotify(workflow, targetStep, dto, isOrgToCenter), PublishStrategy.ParallelWhenAll, cancellationToken); return (workflow, targetStepDefine, currentStep, targetStep, targetStepNew, isOrgToCenter); } /// /// 撤回至开始节点 /// public async Task RecallToStartStepAsync(Workflow workflow, string opinion, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, EFlowAssignType? flowAssignType, CancellationToken cancellationToken = default) { //todo 1.当前待办节点删掉 2.当前待办trace更新(status, opinion) 3.复制startStep为待办 4.更新workflow(status, csStatus, handlers) 5.publish event var startStep = workflow.Steps.Where(d => d.StepType == EStepType.Start && d.IsOrigin) .MaxBy(d => d.CreationTime); if (startStep is null) throw new UserFriendlyException($"数据异常, workflowId: {workflow.Id}", "该流程无开始节点"); //await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken); var startStepDefine = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start); var dto = new RecallDto { Opinion = opinion, NextStepCode = startStep.Code, NextStepName = startStep.Name, BusinessType = startStep.BusinessType, StepType = startStep.StepType, HandlerType = startStepDefine.HandlerType, NextHandlers = new List { flowAssignType == EFlowAssignType.User ? new() { Key = startStep.HandlerId, Value = startStep.HandlerName, RoleId = startStep.RoleId, RoleName = startStep.RoleName, UserId = startStep.HandlerId, Username = startStep.HandlerName, OrgId = startStep.HandlerOrgId, OrgName = startStep.HandlerOrgName, FlowAssignType = EFlowAssignType.User } : new() { Key = startStep.RoleId, Value = startStep.RoleName, RoleId = startStep.RoleId, RoleName = startStep.RoleName, UserId = startStep.HandlerId, Username = startStep.HandlerName, OrgId = startStep.HandlerOrgId, OrgName = startStep.HandlerOrgName, FlowAssignType = EFlowAssignType.Role } } }; // var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.HandlerType, dto.IsStartCountersign, // dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), cancellationToken); // //flowAssignInfo.FlowAssignType = EFlowAssignType.Role; // await RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, EWorkflowTraceType.Recall, expiredTime, isOrderFiled, // handleMode, cancellationToken); await RecallAsync(workflow, dto, dto.NextHandlers.First(), startStepDefine, startStep, EWorkflowTraceType.Recall, expiredTime, isOrderFiled, handleMode, cancellationToken: cancellationToken); } /// /// 撤回至派单节点 /// public async Task RecallToSendStepAsync(Workflow workflow, string opinion, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, EFlowAssignType? flowAssignType, CancellationToken cancellationToken = default) { var sendStep = workflow.Steps.Where(d => d.BusinessType == EBusinessType.Send && d.IsOrigin) .MaxBy(d => d.CreationTime); if (sendStep is null) throw new UserFriendlyException($"未找到派单节点, workflowId: {workflow.Id}", "该流程无派单节点"); //await RecallToTargetStepAsync(workflow, sendStep, opinion, current, cancellationToken); var sendStepDefine = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send); var dto = new RecallDto { Opinion = opinion, NextStepCode = sendStep.Code, NextStepName = sendStep.Name, BusinessType = sendStep.BusinessType, StepType = sendStep.StepType, HandlerType = sendStepDefine.HandlerType, NextHandlers = new List { flowAssignType == EFlowAssignType.User ? new() { Key = sendStep.HandlerId, Value = sendStep.HandlerName, RoleId = sendStep.RoleId, RoleName = sendStep.RoleName, UserId = sendStep.HandlerId, Username = sendStep.HandlerName, OrgId = sendStep.HandlerOrgId, OrgName = sendStep.HandlerOrgName, FlowAssignType = EFlowAssignType.User } : new() { Key = sendStep.RoleId, Value = sendStep.RoleName, RoleId = sendStep.RoleId, RoleName = sendStep.RoleName, UserId = sendStep.HandlerId, Username = sendStep.HandlerName, OrgId = sendStep.HandlerOrgId, OrgName = sendStep.HandlerOrgName, FlowAssignType = EFlowAssignType.Role } } }; //var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.HandlerType, dto.IsStartCountersign, // dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), cancellationToken); ////flowAssignInfo.FlowAssignType = EFlowAssignType.Role; //await RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, EWorkflowTraceType.Recall, expiredTime, isOrderFiled, // handleMode, cancellationToken); await RecallAsync(workflow, dto, dto.NextHandlers.First(), sendStepDefine, sendStep, EWorkflowTraceType.Recall, expiredTime, isOrderFiled, handleMode, cancellationToken: cancellationToken); } /// /// 特提至中心(优先派单组其次坐席) /// /// true 派单组 false 话务部 public async Task<(bool, Workflow workflow)> RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime, EHandleMode handleMode, EFlowAssignType? flowAssignType = EFlowAssignType.User, CancellationToken cancellationToken = default) { var isPaiDan = false; var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true, cancellationToken: cancellationToken); var hasSendStep = workflow.Steps.Any(d => d.BusinessType == EBusinessType.Send); if (hasSendStep) { await RecallToSendStepAsync(workflow, opinion, expiredTime, isOrderFiled, handleMode, flowAssignType, cancellationToken); isPaiDan = true; } else { await RecallToStartStepAsync(workflow, opinion, expiredTime, isOrderFiled, handleMode, flowAssignType, cancellationToken); //isPaiDan = false; } return (new(isPaiDan, workflow)); } /// /// 补充 /// /// 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 _publisher.PublishAsync(new TerminalWorkflowNotify(workflow), PublishStrategy.ParallelWhenAll, cancellationToken); } /// /// 根据stepCode查询流程配置中对应的节点 /// public StepDefine GetStepDefine(WorkflowDefinition workflowDefinition, string stepCode) { if (workflowDefinition == null) throw new ArgumentNullException(nameof(workflowDefinition)); if (string.IsNullOrEmpty(stepCode)) throw new ArgumentNullException(nameof(stepCode)); var stepDefine = workflowDefinition.FindStepDefine(stepCode); if (stepDefine == null) throw new UserFriendlyException( $"未找到流程中对应的节点,DefineCode: {workflowDefinition.Code}, stepCode: {stepCode}", "未查询到对应节点"); return stepDefine; } /// /// 查询当前待办理节点 /// public WorkflowStep FindCurrentStepWaitForHandle(Workflow workflow, string userId, string orgId, string[] roleIds) => GetUnHandleStep(workflow.Steps, orgId, userId, roleIds); /// /// 查询当前节点中最后一个节点 /// public async Task FindLastStepAsync(string workflowId, CancellationToken cancellationToken) { var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellationToken); return workflow.Steps.MaxBy(d => d.CreationTime); } /// /// 查询所有办理部门及实际办理部门 /// /// public async Task<(Kv, IReadOnlyList)> GetHandleOrgsAsync(string workflowId, CancellationToken cancellationToken) { var workflow = await GetWorkflowAsync(workflowId, withTraces: true, cancellationToken: cancellationToken); var steps = workflow.Traces .Where(d => d.StepType is EStepType.Normal) .ToList(); var items = steps.Where(d => d.TraceType == EWorkflowTraceType.Normal || d.TraceType == EWorkflowTraceType.Jump) .Select(d => new Kv(d.HandlerOrgId, d.HandlerOrgName)) .DistinctBy(d => d.Key).ToList(); return (new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), items); } /// /// 新增流程流转记录 /// public async Task AddStepsAsync(string workflowId, List steps, CancellationToken cancellationToken) { var workflow = await GetWorkflowAsync(workflowId, cancellationToken: cancellationToken); if (workflow is null) throw new UserFriendlyException("找不到该流程"); await _workflowStepRepository.AddNav(steps) .Include(d => d.WorkflowTrace) .ExecuteCommandAsync(); } /// /// 创建开始节点 /// public WorkflowStep CreateStartStep( Workflow workflow, StepDefine startStepDefine, BasicWorkflowDto dto, UserInfo assigner, StepAssignInfo handler, DateTime? expiredTime, //EFlowAssignType? flowAssignType = EFlowAssignType.User, Action? newStepConfig = null) { //startstep var nextSteps = _mapper.Map>(startStepDefine.NextSteps); if (startStepDefine.InstanceMode is EInstanceMode.Config) { var selectedStep = nextSteps.FirstOrDefault(d => d.Code == dto.NextStepCode); if (selectedStep is not null) selectedStep.Selected = true; } var startStep = _mapper.Map(startStepDefine); _mapper.Map(workflow, startStep); //startStep.FlowAssignType = flowAssignType; startStep.Handlers = new List { new(handler.Key, handler.Value) }; startStep.NextSteps = nextSteps; startStep.IsMain = true; startStep.IsOrigin = true; startStep.Status = EWorkflowStepStatus.WaitForAccept; startStep.FlowDirection = dto.FlowDirection; startStep.PrevChosenStepCode = null; if (expiredTime.HasValue) startStep.StepExpiredTime = expiredTime; startStep.Assign(handler); startStep.AssignerId = assigner.UserId; startStep.AssignerName = assigner.UserName; startStep.AssignerOrgId = assigner.OrgId; startStep.AssignerOrgName = assigner.OrgName; startStep.AssignerOrgIsCenter = assigner.OrgIsCenter; startStep.InitId(); newStepConfig?.Invoke(workflow, null, startStepDefine, startStep); return startStep; } /// /// 流程结束 /// public async Task<(WorkflowStep, WorkflowTrace)> EndAsync(ISessionContext current, Workflow workflow, BasicWorkflowDto dto, StepDefine endStepDefine, WorkflowStep currentStep, DateTime? expiredTime, CancellationToken cancellationToken) { //create endStep var endStep = await CreateEndStepAsync(workflow, endStepDefine, currentStep, dto, expiredTime, cancellationToken); //workflow.Steps.Add(endStep); //update endTrace var endTrace = await NextTraceAsync(workflow, dto, endStep, cancellationToken); //create publish trace if (workflow.FlowType is EFlowType.Handle) await CreatePublishTraceAsync(endTrace, cancellationToken); workflow.Complete(endStep, dto.ReviewResult); //需求调整:归档时当前节点显示为归档节点 workflow.UpdateCurrentStepWhenHandle(endStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel); workflow.CurrentStepAcceptTime = endStep.AcceptTime; // workflow.UpdateActualStepWhenHandle(endStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel); // workflow.ActualHandleStepAcceptTime = endStep.AcceptTime.Value; if (string.IsNullOrEmpty(workflow.OrgLevelOneCode)) workflow.UpdateLevelOneOrg(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName); await _workflowRepository.UpdateAsync(workflow, cancellationToken); await _publisher.PublishAsync(new EndWorkflowNotify(workflow, endTrace, dto), PublishStrategy.ParallelWhenAll, cancellationToken); return (endStep, endTrace); } ///// ///// 判断会签类型(中心会签或部门会签) ///// ///// ///// ///// //public ECounterSignType? GetCounterSignType(EBusinessType businessType) => // businessType switch // { // EBusinessType.Seat => ECounterSignType.Seat, // EBusinessType.Send => ECounterSignType.Seat, // EBusinessType.Department => ECounterSignType.Department, // EBusinessType.File => null, // _ => throw new ArgumentOutOfRangeException(nameof(businessType), businessType, null) // }; public ECounterSignType? GetCounterSignType(bool isStartCountersign, EBusinessType currentStepBusinessType) { if (!isStartCountersign) return null; return currentStepBusinessType switch { EBusinessType.Seat => ECounterSignType.Center, EBusinessType.Send => ECounterSignType.Center, EBusinessType.CenterMonitor => ECounterSignType.Center, EBusinessType.CenterLeader => ECounterSignType.Center, EBusinessType.Department => ECounterSignType.Department, EBusinessType.DepartmentLeader => ECounterSignType.Department, EBusinessType.File => null, _ => throw new ArgumentOutOfRangeException(nameof(currentStepBusinessType), currentStepBusinessType, null) }; // return _sessionContext.OrgIsCenter ? ECounterSignType.Center : ECounterSignType.Department; } /// /// 办理节点 /// public async Task HandleStepAsync(WorkflowStep step, Workflow workflow, BasicWorkflowDto dto, ECounterSignType? counterSignType, DateTime? expiredTime, EHandleMode handleMode, CancellationToken cancellationToken) { if (step.Status is EWorkflowStepStatus.Handled) throw UserFriendlyException.SameMessage("当前节点状态已办理"); if (step.StepType is EStepType.End) throw new UserFriendlyException("当前流程已流转到最终步骤"); if (dto.IsStartCountersign && !counterSignType.HasValue) throw new UserFriendlyException("缺少会签类型参数"); //办理参数 //_mapper.Map(dto, step); step.NextHandlers = dto.NextHandlers; step.NextMainHandler = dto.NextMainHandler; step.NextStepCode = dto.NextStepCode; step.IsSms = dto.IsSms; step.Opinion = dto.Opinion; step.Remark = dto.Remark; step.ReviewResult = dto.ReviewResult; if (workflow.FlowType is EFlowType.Review) step.HandleMode = dto.ReviewResult == EReviewResult.Approval ? EHandleMode.Approved : EHandleMode.NotApproved; //step办理状态 HandleStep(step, handleMode, dto.Opinion, dto.NextStepCode); } /// /// 查找会签循环的初始会签发起节点 /// /// /// /// /// public WorkflowStep GetCsLoopStartStep(List steps, WorkflowStep currentStep) { var startCountersignStep = steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId); if (startCountersignStep is null) throw new UserFriendlyException( $"GetCsLoopStartStep: 未查询到会签开始节点,workflowId: {currentStep.WorkflowId}, startStepId: {currentStep.CountersignStartStepId}", "未查询到会签开始节点,数据异常"); if (!startCountersignStep.IsCountersignEndStep) return startCountersignStep; return GetCsLoopStartStep(steps, startCountersignStep); } public async Task HandlePublishTraceAsync(string workflowId, string orderPublishId, UserInfo acceptor, UserInfo handler, DateTime handleTime, UserInfo visitAcceptor, string orderVisitId, CancellationToken cancellation) { if (string.IsNullOrEmpty(orderPublishId)) throw new UserFriendlyException($"参数异常,orderPublishId不能为空, workflowId: {workflowId}"); //handle pubtrace var pubTrace = await _workflowTraceRepository.Queryable() .FirstAsync(d => d.WorkflowId == workflowId && d.TraceStyle == ETraceStyle.Publish && d.Status == EWorkflowStepStatus.WaitForAccept, cancellation); if (pubTrace is not null) { pubTrace.OrderPublishId = orderPublishId; pubTrace.AcceptorId = acceptor.UserId; pubTrace.AcceptorName = acceptor.UserName; pubTrace.AcceptorOrgId = acceptor.OrgId; pubTrace.AcceptorOrgName = acceptor.OrgName; pubTrace.AcceptTime = handleTime; pubTrace.HandlerId = handler.UserId; pubTrace.HandlerName = handler.UserName; pubTrace.HandlerOrgId = handler.OrgId; pubTrace.HandlerOrgName = handler.OrgName; pubTrace.HandleTime = handleTime; pubTrace.Status = EWorkflowStepStatus.Handled; await _workflowTraceRepository.UpdateAsync(pubTrace, cancellation); //create visit trace await CreateVisitTraceAsync(pubTrace, visitAcceptor, orderVisitId, cancellation); } else { //throw new UserFriendlyException($"未查询到待办的发布节点, workflowId:{workflowId}"); } } public async Task HandleVisitTraceAsync(string orderVisitId, UserInfo visitor, DateTime visitTime, CancellationToken cancellation) { var visitTrace = await _workflowTraceRepository.GetAsync(d => d.OrderVisitId == orderVisitId, cancellationToken: cancellation); if (visitTrace is not null) { visitTrace.AcceptorId = visitor.UserId; visitTrace.AcceptorName = visitor.UserName; visitTrace.AcceptorOrgId = visitor.OrgId; visitTrace.AcceptorOrgName = visitor.OrgName; visitTrace.HandlerId = visitor.UserId; visitTrace.HandlerName = visitor.UserName; visitTrace.HandlerOrgId = visitor.OrgId; visitTrace.HandlerOrgName = visitor.OrgName; visitTrace.HandleTime = visitTime; visitTrace.Status = EWorkflowStepStatus.Handled; await _workflowTraceRepository.UpdateAsync(visitTrace, cancellation); //create append end trace await CreateTrashEndTraceAsync(visitTrace, cancellation); } else { //throw new UserFriendlyException($"未查询到待办的发布节点, orderVisitId:{orderVisitId}"); } } #region private method private StepAssignInfo GetStepAssignInfo(ReverseFlowStepAssignInfo assignInfo, WorkflowStep? targetStep = null, StepDefine? targetStepDefine = null) { switch (assignInfo.ReverseFlowStepCreationPolicy) { case EReverseFlowStepCreationPolicy.OriginStep: if (targetStep is null) throw new UserFriendlyException("参数异常:原节点信息为空"); return GetStepAssignInfo(targetStep); case EReverseFlowStepCreationPolicy.OriginStepUser: if (string.IsNullOrEmpty(targetStep?.HandlerId)) throw new UserFriendlyException("参数异常:原节点办理人为空"); return GetStepAssignInfo(targetStep, EFlowAssignType.User); case EReverseFlowStepCreationPolicy.OriginStepOrg: if (string.IsNullOrEmpty(targetStep?.HandlerOrgId)) throw new UserFriendlyException("参数异常:原节点办理部门为空"); return GetStepAssignInfo(targetStep, EFlowAssignType.Org); case EReverseFlowStepCreationPolicy.OriginStepRole: if (string.IsNullOrEmpty(targetStep?.RoleId)) throw new UserFriendlyException("参数异常:原节点办理角色为空"); return GetStepAssignInfo(targetStep, EFlowAssignType.Role); case EReverseFlowStepCreationPolicy.OriginStepOrgAndRole: if (string.IsNullOrEmpty(targetStep?.RoleId) || string.IsNullOrEmpty(targetStep?.HandlerOrgId)) throw new UserFriendlyException("参数异常:原节点办理角色或部门为空"); return GetStepAssignInfo(targetStep, EFlowAssignType.OrgAndRole); case EReverseFlowStepCreationPolicy.AssignHandler: if (assignInfo?.StepAssignInfo is null) throw new UserFriendlyException("参数异常:节点指定办理对象信息为空"); return assignInfo.StepAssignInfo; case EReverseFlowStepCreationPolicy.OriginDefinition: if (targetStepDefine is null) throw new UserFriendlyException("参数异常:节点配置信息为空"); return GetStepAssignInfo(targetStepDefine); default: throw new ArgumentOutOfRangeException(); } } private StepAssignInfo GetStepAssignInfo(StepDefine stepDefine) { var handler = stepDefine.HandlerTypeItems.FirstOrDefault(); if (handler is null) throw new UserFriendlyException($"未正确配置节点办理对象, stepcode: {stepDefine.Code}", "未正确配置节点办理对象"); var rsp = new StepAssignInfo(); switch (stepDefine.HandlerType) { case EHandlerType.Role: rsp.FlowAssignType = EFlowAssignType.Role; rsp.RoleId = handler.Key; rsp.RoleName = handler.Value; break; case EHandlerType.AssignedUser: rsp.FlowAssignType = EFlowAssignType.User; rsp.UserId = handler.Key; rsp.Username = handler.Value; break; case EHandlerType.AssignedOrg: rsp.FlowAssignType = EFlowAssignType.Org; rsp.OrgId = handler.Key; rsp.OrgName = handler.Value; break; case EHandlerType.OrgType: throw new ArgumentOutOfRangeException("部门类型不支持按配置指派"); case EHandlerType.OrgLevel: throw new ArgumentOutOfRangeException("部门等级不支持按配置指派"); default: throw new ArgumentOutOfRangeException(); } return rsp; } private StepAssignInfo GetStepAssignInfo(WorkflowStep targetStep, EFlowAssignType? flowAssignType = null) { return new StepAssignInfo { FlowAssignType = (flowAssignType ?? targetStep.FlowAssignType) ?? EFlowAssignType.User, UserId = targetStep.HandlerId, Username = targetStep.HandlerName, OrgId = targetStep.HandlerOrgId, OrgName = targetStep.HandlerOrgName, RoleId = targetStep.RoleId, RoleName = targetStep.RoleName, }; } private Workflow CreateWorkflow(WorkflowModule wfModule, string title, string? externalId = null) { var definition = wfModule.Definition; if (definition is null) throw new UserFriendlyException("无效流程模板"); var workflow = new Workflow { Title = title, ModuleId = wfModule.Id, ModuleName = wfModule.Name, ModuleCode = wfModule.Code, DefinitionId = definition.Id, Status = EWorkflowStatus.Runnable, Steps = new(), Traces = new(), WorkflowDefinition = definition, ExternalId = externalId ?? string.Empty, FlowType = definition.FlowType, }; workflow.InitId(); return workflow; } private static void UpdateCurrentStep(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepDefine, List nextSteps) { if (dto.IsStartCountersign) return; if (workflow.IsInCountersign) return; if (nextStepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send) { //坐席->派单不选办理对象时 workflow.UpdateCurrentStepWhenAssign(nextSteps.First(), new FlowStepHandler { OrgId = OrgSeedData.CenterId, OrgName = OrgSeedData.CenterName }); } else { var nextHandler = dto.NextHandlers.First(); workflow.UpdateCurrentStepWhenAssign(nextSteps.First(), nextHandler); } } private static void UpdateActualStep(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepDefine, List nextSteps) { if (dto.IsStartCountersign) return; if (workflow.IsInCountersign) return; if (nextStepDefine.StepType is EStepType.Summary or EStepType.End) return; if (nextSteps.Count > 1) return; //多个下级节点不更新workflow的实际办理信息 var nextStep = nextSteps.First(); //todo 重构为办理对象由参数传入,指派给中心?派单员?待确认 if (nextStepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send) { //坐席->派单不选办理对象时 workflow.UpdateActualStepWhenAssign(nextStep, new FlowStepHandler { Key = OrgSeedData.CenterId, Value = OrgSeedData.CenterName, OrgId = OrgSeedData.CenterId, OrgName = OrgSeedData.CenterName }); } else { var nextHandler = dto.NextHandlers.First(); workflow.UpdateActualStepWhenAssign(nextStep, nextHandler); } //与实际办理节点的接办状态保持一致 workflow.ActualHandleStepAcceptTime = nextStep.AcceptTime; } private async Task CreateStartStepAsync(Workflow workflow, StepDefine startStepDefine, BasicWorkflowDto dto, UserInfo assigner, StepAssignInfo stepAssignInfo, EWorkflowTraceType traceType, DateTime? expiredTime, Action? stepConfig = null, CancellationToken cancellationToken = default) { var startStep = CreateStartStep(workflow, startStepDefine, dto, assigner, stepAssignInfo, expiredTime, stepConfig); await _workflowStepRepository.AddAsync(startStep, cancellationToken); await CreateTraceAsync(workflow, startStep, traceType, cancellationToken); return startStep; } /// /// 创建下1/N个节点 /// private async Task> CreateNextStepsAsync(Workflow workflow, WorkflowStep currentStep, BasicWorkflowDto dto, StepDefine nextStepDefine, UserInfo assigner, bool isNextDynamic, DateTime? expiredTime, bool isStartCountersign, bool isAutoFillSummaryOpinion = false, Action? newStepConfig = null, CancellationToken cancellationToken = default) { List nextSteps = new(); if (currentStep.IsInCountersign()) { if (currentStep.IsCountersignEndStep) { // check if current is topend f: csStartStep.prev // t: check if dto.StartCs t: csconfig f: config if (currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)) { if (isStartCountersign) { //依据会签策略创建会签下一级节点 nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, expiredTime, newStepConfig, cancellationToken); } else { //创建普通节点(根据配置) nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, EWorkflowTraceType.Normal, expiredTime, isAutoFillSummaryOpinion, newStepConfig, cancellationToken); } } else { if (dto.BackToCountersignEnd) { // csStartStep.prev var csStartStep = GetRealCsStartHandleStep(workflow.Steps, currentStep.CountersignStartStepId); if (csStartStep is null) throw new UserFriendlyException("未查询到会签节点"); nextSteps = await CreateCsEndStepsByTargetPrevAsync(workflow, csStartStep, dto, expiredTime, isAutoFillSummaryOpinion, cancellationToken); } else { //依据会签策略创建会签下一级节点 nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, expiredTime, newStepConfig, cancellationToken); } } } else { if (dto.BackToCountersignEnd) { // check if cs all complete, create next nextSteps = await CreateCsEndStepsByTargetPrevAsync(workflow, currentStep, dto, expiredTime, isAutoFillSummaryOpinion, cancellationToken); } else { //依据会签策略创建会签下一级节点 nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, expiredTime, newStepConfig, cancellationToken); } } } else if (isStartCountersign) //top { //依据会签策略创建会签下一级节点 nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, expiredTime, newStepConfig, cancellationToken); } else if (isNextDynamic) { //创建动态下一级节点 nextSteps = await CreateDynamicStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, expiredTime, newStepConfig, cancellationToken); } else { //创建普通节点(根据配置) nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, EWorkflowTraceType.Normal, expiredTime, isAutoFillSummaryOpinion, newStepConfig, cancellationToken); } return nextSteps; } /// /// 查询当前会签开始的第一级办理节点(会签汇总再次发起会签得场景) /// private static WorkflowStep? GetRealCsStartHandleStep(List steps, string countersignStartStepId) { if (string.IsNullOrEmpty(countersignStartStepId)) throw new UserFriendlyException("会签汇总节点未正确赋值会签开始节点编号"); var csStartStep = steps.FirstOrDefault(d => d.Id == countersignStartStepId); if (csStartStep.IsCountersignEndStep) csStartStep = GetRealCsStartHandleStep(steps, csStartStep.CountersignStartStepId); return csStartStep; } private async Task> CreateDynamicStepsAsync( Workflow workflow, StepDefine nextStepDefine, WorkflowStep currentStep, BasicWorkflowDto dto, UserInfo assigner, DateTime? expiredTime, Action? newStepConfig = null, CancellationToken cancellationToken = default) { var handlerType = nextStepDefine.InstancePolicy switch { EDynamicPolicy.OrgUpCenterTop => EHandlerType.OrgLevel, EDynamicPolicy.OrgUp => EHandlerType.OrgLevel, EDynamicPolicy.OrgUpHandleCenterTop => EHandlerType.OrgLevel, EDynamicPolicy.OrgUpHandle => EHandlerType.OrgLevel, EDynamicPolicy.OrgUpLeadCenterTop => EHandlerType.OrgLevel, EDynamicPolicy.OrgUpLead => EHandlerType.OrgLevel, EDynamicPolicy.ArriveCenter => EHandlerType.OrgLevel, EDynamicPolicy.ArriveOneOrg => EHandlerType.OrgLevel, EDynamicPolicy.OrgDownCenterTop => EHandlerType.OrgLevel, EDynamicPolicy.OrgDown => EHandlerType.OrgLevel, null => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException() }; return await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, assigner, dto.NextHandlers, null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, false, EWorkflowTraceType.Normal, handlerType, expiredTime, newStepConfig: newStepConfig, cancellationToken: cancellationToken); } private Task> CreateCountersignStepsAsync( Workflow workflow, StepDefine stepDefine, WorkflowStep currentStep, BasicWorkflowDto dto, UserInfo assigner, DateTime? expiredTime, Action? newStepConfig = null, CancellationToken cancellationToken = default ) { //var countersignId = dto.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId; var countersignId = currentStep.StartCountersignId; //当前策略均为orglevel var handlerType = EHandlerType.OrgLevel; var nextStepCountersignPosition = dto.NextHandlers.Count > 1 ? ECountersignPosition.Direct : ECountersignPosition.Indirect; return CreateStepsAsync(workflow, stepDefine, currentStep, dto, assigner, dto.NextHandlers, countersignId, EWorkflowStepStatus.WaitForAccept, nextStepCountersignPosition, false, EWorkflowTraceType.Normal, handlerType, expiredTime, newStepConfig: newStepConfig, cancellationToken: cancellationToken); } /// /// 根据传入节点的上一节点创建会签汇总节点(汇总传入节点的前一节点) /// private async Task> CreateCsEndStepsByTargetPrevAsync(Workflow workflow, WorkflowStep step, BasicWorkflowDto dto, DateTime? expiredTime, bool isAutoFillSummaryOpinion = false, CancellationToken cancellationToken = default) { //var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == step.PrevStepId); var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.StartCountersignId == step.CountersignId); if (countersignStartStep is null) throw new UserFriendlyException("未查询到当前节点上级会签开启节点"); var nextSteps = new List(); //会签未全部办理则不创建汇总节点 //var csInnerSteps = workflow.Steps.Where(d => d.PrevStepId == countersignStartStep.Id).ToList(); //if (csInnerSteps.Any(d => // d.Status != EWorkflowStepStatus.Handled || (d.IsStartCountersign && !d.IsStartedCountersignEnd))) if (!HasStepsAllHandled(workflow.Steps, countersignStartStep)) return nextSteps; string? opinion = null; if (isAutoFillSummaryOpinion) { //依据某节点作为根节点,查找最底层办理节点无论普通还是汇总节点 var preSteps = GetLastStepsFromRootStep(workflow.Steps, countersignStartStep); var sb = new StringBuilder(); foreach (var prevStep in preSteps.OrderBy(d => d.HandleTime).ToList()) { sb.AppendLine($"【会签时间】:{prevStep.HandleTime?.ToString("yyyy-MM-dd HH:mm:ss")}"); sb.AppendLine($"【会签人】:{prevStep.HandlerName}"); sb.AppendLine($"【会签结果】:{prevStep.Opinion}"); sb.AppendLine(""); } opinion = sb.ToString(); } // 创建会签汇总节点 var countersignEndStep = await CreateCountersignEndStepAsync(workflow, countersignStartStep, dto, expiredTime, opinion, cancellationToken); nextSteps = new List { countersignEndStep }; //create trace await CreateTraceAsync(workflow, countersignEndStep, EWorkflowTraceType.Normal, cancellationToken); await _publisher.PublishAsync(new CountersignEndAssigned(workflow), PublishStrategy.ParallelWhenAll, cancellationToken); return nextSteps; } /// /// 是否会签节点都已经办理完成(包含会签嵌套的判断) /// private bool HasStepsAllHandled(List steps, WorkflowStep countersignStartStep) { var countersignSteps = GetStepsCascade(steps, countersignStartStep); return countersignSteps.All(d => d.Status == EWorkflowStepStatus.Handled); } private List GetStepsCascade(List steps, WorkflowStep countersignStartStep) { _logger.LogWarning($"wfId: {countersignStartStep.WorkflowId}, stepId: {countersignStartStep.Id}"); var rsp = new List { countersignStartStep }; if (!countersignStartStep.IsStartCountersign) return rsp; var innerSteps = steps.Where(d => !string.IsNullOrEmpty(d.CountersignId) && d.CountersignId == countersignStartStep.StartCountersignId).ToList(); _logger.LogWarning($"wfId: {countersignStartStep.WorkflowId}, innerSteps count: {innerSteps.Count}"); if (!innerSteps.Any()) return rsp; foreach (var innerStep in innerSteps) { rsp.AddRange(GetStepsCascade(steps, innerStep)); } return rsp; } /// /// 以某一节点作为根节点开始查找最底层办理节点(需求:自动填充汇总节点意见) /// private List GetLastStepsFromRootStep(List steps, WorkflowStep rootStep) { var lastSteps = new List(); var nextSteps = steps.Where(d => d.PrevStepId == rootStep.Id).ToList(); foreach (var nextStep in nextSteps) { if (nextStep.IsStartCountersign) { //find last csend var lastCsEndStep = GetLastCountersignEndStep(steps, nextStep); if (lastCsEndStep != null) lastSteps.Add(lastCsEndStep); } else { lastSteps.Add(nextStep); } } return lastSteps; } private WorkflowStep? GetLastCountersignEndStep(List steps, WorkflowStep nextStep) { var csEndStep = steps.FirstOrDefault(d => d.IsCountersignEndStep && d.CountersignStartStepId == nextStep.Id); if (csEndStep == null) return null; return !csEndStep.IsStartCountersign ? csEndStep : GetLastCountersignEndStep(steps, csEndStep); } private async Task CreateCountersignEndStepAsync( Workflow workflow, WorkflowStep countersignStartStep, BasicWorkflowDto dto, DateTime? expiredTime, string? opinion = null, CancellationToken cancellationToken = default) { var csEndStep = _mapper.Map(countersignStartStep); csEndStep.Status = EWorkflowStepStatus.WaitForAccept; csEndStep.PrevStepId = null; csEndStep.PrevStepCode = null; csEndStep.IsOrigin = false; csEndStep.CountersignId = countersignStartStep.StartCountersignId; csEndStep.CountersignPosition = ECountersignPosition.End; //csEndStep.CountersignSteps = new(); csEndStep.IsCountersignEndStep = true; csEndStep.CountersignStartStepId = countersignStartStep.Id; csEndStep.Name = dto.NextStepName; //csEndStep.TimeLimit = GetTimeLimit(""); csEndStep.StepExpiredTime = expiredTime; csEndStep.BusinessType = dto.BusinessType; csEndStep.Handlers = countersignStartStep.Handlers .Where(d => d.Key == countersignStartStep.HandlerId || d.Key == countersignStartStep.HandlerOrgId) .ToList(); //需求调整:汇总节点指派给发起人部门办理 //todo 待重构 csEndStep.FlowAssignType = EFlowAssignType.Org; csEndStep.Reset(); csEndStep.ResetParameters(); if (!string.IsNullOrEmpty(opinion)) csEndStep.Opinion = opinion; await _workflowStepRepository.AddAsync(csEndStep, cancellationToken); workflow.Steps.Add(csEndStep); //await _workflowStepRepository.AddNav(csEndStep) // .Include(d => d.StepHandlers) // .ExecuteCommandAsync(); return csEndStep; } private bool CheckIsActualHandle(Workflow workflow, WorkflowStep step, StepDefine nextStepDefine, BasicWorkflowDto dto) { //1. workflow是否为办理类型 2. 非会签:当前是否为普通节点and下一节点是否为汇总 or endStep //3. 会签:当前操作为汇总还是继续往下办理?thk: 汇总以后但未回到top又往下办理的场景,前面实际办理部门也算作办理部门 if (workflow.FlowType is not EFlowType.Handle) return false; if (workflow.IsInCountersign) { return !step.IsCountersignEndStep && dto.BackToCountersignEnd; } else { return step.StepType is EStepType.Normal && nextStepDefine.StepType is EStepType.Summary or EStepType.End; } } /// /// 办理节点(赋值节点的办理对象信息) /// private void HandleStep(WorkflowStep step, EHandleMode handleMode, string opinion, string nextStepCode) { //todo 重构:ISessionContext传入 step.Handle(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgIsCenter, handleMode, opinion, nextStepCode); //var handler = step.FindActualHandler(current.Roles, current.RequiredUserId, current.RequiredOrgId); //if (handler is not null) // handler.IsActualHandler = true; } /// /// 开始会签(创建会签数据,更新currentStep会签数据) /// private async Task StartCountersignAsync(ISessionContext current, Workflow workflow, WorkflowStep startStep, BasicWorkflowDto dto, ECounterSignType? counterSignType, DateTime? expiredTime, CancellationToken cancellationToken) { var countersignId = startStep.CountersignId; if (startStep.IsCountersignEndStep) { var topStartCsStep = GetCsLoopStartStep(workflow.Steps, startStep); countersignId = topStartCsStep.CountersignId; } var countersign = await CreateCountersignAsync(current, workflow, startStep, dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), dto.FlowAssignType, counterSignType, expiredTime, countersignId, cancellationToken); startStep.StartCountersign(countersign.Id); return countersign; } /// /// 检查是否从中心流转至部门 /// private bool CheckIfFlowFromCenterToOrg(WorkflowStep sourceStep, StepDefine targetStepBoxDefine) { var isFromCenter = sourceStep.IsCenter(); if (!isFromCenter) return false; var isToOrg = targetStepBoxDefine.IsOrg(); return isFromCenter && isToOrg; } /// /// 检查是否从中心流转至部门 /// private bool CheckIfFlowFromCenterToOrg(Workflow workflow, WorkflowStep targetStepBox) { var isToOrg = targetStepBox.IsOrg(); if (!isToOrg) return false; var isFromCenter = workflow.Steps.All(d => d.BusinessType is not EBusinessType.Department); return isFromCenter && isToOrg; } /// /// 检查是否从部门流转至中心 /// private bool CheckIfFlowFromOrgToCenter(WorkflowStep sourceStepBox, StepDefine targetStepBoxDefine) { var isFromOrg = sourceStepBox.IsOrg(); if (!isFromOrg) return false; var isToCenter = targetStepBoxDefine.IsCenter(); return isFromOrg && isToCenter; } /// /// 检查是否从部门流转至中心 /// private bool CheckIfFlowFromOrgToCenter(WorkflowStep sourceStepBox, WorkflowStep targetStep) { var isFromOrg = sourceStepBox.IsOrg(); if (!isFromOrg) return false; var isToCenter = targetStep.IsCenter(); return isFromOrg && isToCenter; } /// /// 检查是否从部门流转至中心 /// private bool CheckIfFlowFromOrgToCenter(Workflow workflow, WorkflowStep targetStep) { var isToCenter = targetStep.IsCenter(); if (!isToCenter) return false; var isFromOrg = workflow.Steps.Any(d => d.BusinessType is EBusinessType.Department or EBusinessType.DepartmentLeader); return isFromOrg && isToCenter; } /// /// 复制一个节点为待接办 /// private async Task DuplicateStepWithTraceAsync(Workflow workflow, StepDefine stepDefine, WorkflowStep step, EWorkflowTraceType traceType, DateTime expiredTime, CancellationToken cancellationToken) { var newStep = DuplicateStep(workflow, stepDefine, step, traceType, expiredTime); await _workflowStepRepository.AddAsync(newStep, cancellationToken); await CreateTraceAsync(workflow, newStep, traceType, cancellationToken); return newStep; } //private WorkflowStep DuplicateStep(WorkflowStep step, EWorkflowTraceType traceType, DateTime? expiredTime) //{ // var newStep = _mapper.Map(step); // newStep.Reset(); // newStep.Status = EWorkflowStepStatus.WaitForAccept; // newStep.PrevStepId = step.PrevStepId; // newStep.IsMain = step.IsMain; // newStep.IsOrigin = step.IsOrigin; // //newStep.ParentId = step.ParentId; // newStep.Handlers = step.Handlers; // newStep.StartCountersignId = step.StartCountersignId; // newStep.CountersignId = step.CountersignId; // newStep.IsStartedCountersignEnd = step.IsStartedCountersignEnd; // newStep.StepExpiredTime = expiredTime; // newStep.InitId(); // newStep.Assign(step); // //退回场景:指派给原办理人,其余场景:按照原节点原始指派方式复制 //todo 重构为参数传入办理对象 // if (traceType is EWorkflowTraceType.Previous) // { // newStep.FlowAssignType = step.FlowAssignType; // // //newStep.FlowAssignType = EFlowAssignType.User; // // // 是否中心 临时紧急修改 后续在流程模版定义是否原办理人退回类型 来实现流程 禅道200 // // //newStep.FlowAssignType = step.BusinessType is EBusinessType.Seat or EBusinessType.Send // // // ? step.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Role // // // : EFlowAssignType.Org; // // //if (newStep is { FlowAssignType: EFlowAssignType.Role, BusinessType: EBusinessType.Send }) // // // newStep.FlowAssignType = EFlowAssignType.User; // newStep.Assign(step.HandlerId, step.HandlerName, step.HandlerOrgId, step.HandlerOrgName, step.RoleId, step.RoleName); // } // return newStep; //} //new private WorkflowStep DuplicateStep( Workflow workflow, StepDefine stepDefine, WorkflowStep step, EWorkflowTraceType traceType, DateTime? expiredTime) { var newStep = _mapper.Map(step); newStep.Reset(); newStep.Status = EWorkflowStepStatus.WaitForAccept; newStep.PrevStepId = step.PrevStepId; newStep.IsMain = step.IsMain; newStep.IsOrigin = step.IsOrigin; //newStep.ParentId = step.ParentId; newStep.Handlers = step.Handlers; newStep.StartCountersignId = step.StartCountersignId; newStep.CountersignId = step.CountersignId; newStep.IsStartedCountersignEnd = step.IsStartedCountersignEnd; newStep.StepExpiredTime = expiredTime; newStep.InitId(); newStep.Assign(step); // //退回场景:指派给原办理人,其余场景:按照原节点原始指派方式复制 //todo 重构为参数传入办理对象 // if (traceType is EWorkflowTraceType.Previous) // { // newStep.FlowAssignType = step.FlowAssignType; // // //newStep.FlowAssignType = EFlowAssignType.User; // // // 是否中心 临时紧急修改 后续在流程模版定义是否原办理人退回类型 来实现流程 禅道200 // // //newStep.FlowAssignType = step.BusinessType is EBusinessType.Seat or EBusinessType.Send // // // ? step.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Role // // // : EFlowAssignType.Org; // // //if (newStep is { FlowAssignType: EFlowAssignType.Role, BusinessType: EBusinessType.Send }) // // // newStep.FlowAssignType = EFlowAssignType.User; // // newStep.Assign(step.HandlerId, step.HandlerName, step.HandlerOrgId, step.HandlerOrgName, step.RoleId, step.RoleName); // } return newStep; } private async Task CreateCountersignAsync( ISessionContext current, Workflow workflow, WorkflowStep startStep, List handlers, EFlowAssignType? flowAssignType, ECounterSignType? counterSignType, DateTime? expiredTime, string? parentId = null, CancellationToken cancellationToken = default) { var members = handlers.Select(d => new WorkflowCountersignMember { Key = d.Key, Value = d.Value, FlowAssignType = flowAssignType }).ToList(); var countersign = new WorkflowCountersign { WorkflowId = workflow.Id, StartStepId = startStep.Id, StartStepCode = startStep.Code, StartStepBusiType = startStep.BusinessType, StarterId = current.UserId, StarterName = current.UserName ?? string.Empty, StarterOrgId = current.OrgId, StarterOrgName = current.OrgName, StarterOrgAreaCode = current.OrgAreaCode ?? string.Empty, StarterOrgAreaName = current.OrgAreaName ?? string.Empty, ParentId = parentId, Members = members, FlowAssignType = flowAssignType, CounterSignType = counterSignType, ExpiredTime = expiredTime, //ExternalId = workflow.ExternalId, }; //await _workflowCountersignRepository.AddAsync(countersign, cancellationToken); await _workflowCountersignRepository.AddNav(countersign) .Include(d => d.Members) .ExecuteCommandAsync(); return countersign; } //private async Task JumpTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken) //{ // //未办理的traces // var uncompleteTraces = // await _workflowTraceRepository.QueryAsync(d => // d.WorkflowId == workflowId && string.IsNullOrEmpty(d.HandlerId)); // foreach (var trace in uncompleteTraces) // { // HandleTrace(trace, dto.Opinion); // } // await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken); //} //private async Task RecallTraceAsync(List traces, string opinion, ISessionContext current, // CancellationToken cancellationToken) //{ // //未办理的traces // //var uncompleteTraces = // // await _workflowTraceRepository.QueryAsync(d => // // d.WorkflowId == workflowId && string.IsNullOrEmpty(d.HandlerId)); // var uncompleteTraces = traces.Where(d => d.Status != EWorkflowStepStatus.Handled).ToList(); // if (uncompleteTraces.Any()) // { // foreach (var trace in uncompleteTraces) // { // trace.Handle( // current.RequiredUserId, current.UserName, // current.RequiredOrgId, current.OrgName, // current.OrgAreaCode, current.OrgAreaName, // current.OrgIsCenter, opinion); // } // await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken); // } //} //private async Task PreviousTraceAsync(string workflowId, // PreviousWorkflowDto dto, WorkflowStep step, // string applicantId, string applicantName, // string applicantOrgId, string applicantOrgName, // string applicantOrgAreaCode, string applicantOrgAreaName, // bool applicantIsCenter, CancellationToken cancellationToken) //{ // var trace = await GetWorkflowTraceAsync(workflowId, step.Id, cancellationToken); // _mapper.Map(dto, trace); // //HandleTrace(trace, dto.Opinion, current); // trace.Handle(applicantId, applicantName, // applicantOrgId, applicantOrgName, // applicantOrgAreaCode, applicantOrgAreaName, // applicantIsCenter, dto.Opinion); // await _workflowTraceRepository.UpdateAsync(trace, cancellationToken); // return trace; //} //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); var trace = workflow.Traces.FirstOrDefault(d => d.Id == step.Id); if (trace == null) throw new UserFriendlyException($"未找到对应trace, workflowId: {workflow.Id}, stepId: {step.Id}"); _mapper.Map(dto, trace); _mapper.Map(step, trace); await _workflowTraceRepository.UpdateAsync(trace, cancellationToken); return trace; } //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 step, EWorkflowTraceType traceType = EWorkflowTraceType.Normal, CancellationToken cancellationToken = default) { var sendHandleTimes = 0; if (step.BusinessType == EBusinessType.Send) { var sendHandleCount = workflow.Traces.Count(d => d.StepType == EStepType.Normal && d.BusinessType == EBusinessType.Send); sendHandleTimes = sendHandleCount + 1; } var trace = _mapper.Map(step); trace.TraceType = traceType; trace.SendHandleTimes = sendHandleTimes; if (workflow.IsInCountersign && step.IsInCountersign()) { if (step.IsCountersignEndStep) { //var startTrace = await GetWorkflowTraceAsync(workflow.Id, step.CountersignStartStepId, cancellationToken); var startTrace = workflow.Traces.FirstOrDefault(d => d.Id == step.CountersignStartStepId); if (startTrace == null) throw new UserFriendlyException( $"未找到startTrace, workflowId: {workflow.Id}, step.CountersignStartStepId: {step.CountersignStartStepId}"); trace.ParentId = startTrace.ParentId; } else { //if (step.CountersignPosition is ECountersignPosition.Direct) //{ // var prevTrace = await GetWorkflowTraceAsync(workflow.Id, step.PrevStepId, cancellationToken); // trace.ParentId = prevTrace.Id; //} //else if (step.CountersignPosition is ECountersignPosition.Single) //{ // var prevTrace = await GetWorkflowTraceAsync(workflow.Id, step.PrevStepId, cancellationToken); // trace.ParentId = prevTrace.ParentId; //} //var prevTrace = await GetWorkflowTraceAsync(workflow.Id, step.PrevStepId, cancellationToken); var prevTrace = workflow.Traces.FirstOrDefault(d => d.Id == step.PrevStepId); if (prevTrace == null) throw new UserFriendlyException($"未找到prevTrace, workflowId: {workflow.Id}, step.PrevStepId: {step.PrevStepId}"); trace.ParentId = prevTrace.Id; } } await _workflowTraceRepository.AddAsync(trace, cancellationToken); workflow.Traces.Add(trace); return trace; } 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, BasicWorkflowDto dto, FlowAssignInfo flowAssignInfo, // StepDefine targetStepDefine, WorkflowStep targetStep, EWorkflowTraceType traceType, // DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, CancellationToken cancellationToken) // { // var targetIsStartStep = targetStepDefine.StepType is EStepType.Start; // var updateTraces = new List(); // // //update uncomplete traces // var uncompleteTraces = workflow.Traces.Where(d => d.Status != EWorkflowStepStatus.Handled && d.TraceStyle == ETraceStyle.Flow).ToList(); // if (uncompleteTraces.Any()) // { // foreach (var trace in uncompleteTraces) // { // trace.Handle( // _sessionContext.RequiredUserId, _sessionContext.UserName, // _sessionContext.RequiredOrgId, _sessionContext.OrgName, // _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, // _sessionContext.OrgIsCenter, handleMode, dto.Opinion); // } // // //await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken); // updateTraces.AddRange(uncompleteTraces); // } // else // { // var endTrace = workflow.Traces.Where(d => d.StepType == EStepType.End && d.TraceStyle == ETraceStyle.Flow).MaxBy(d => d.CreationTime); // if (endTrace is not null) // { // endTrace.Opinion += ("\r\n" + dto.Opinion); // updateTraces.Add(endTrace); // } // } // // //get targetStep's previous // WorkflowStep? targetPrevStep = null; // if (!targetIsStartStep) // { // targetPrevStep = workflow.Steps.FirstOrDefault(d => d.Id == targetStep.PrevStepId); // if (targetPrevStep == null) // throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}"); // } // // //查询所有目标节点之后的节点,然后删掉(包括目标节点) // var removeSteps = GetStepsBehindTargetStep(workflow.Steps, targetStep); // if (removeSteps.Any()) // { // await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken); // workflow.Steps.RemoveAll(d => removeSteps.Contains(d)); // // //更新快照对应节点状态 // var stepIds = removeSteps.Select(d => d.Id).ToList(); // var traces = workflow.Traces.Where(d => stepIds.Contains(d.StepId)).ToList(); // //await UpdateTracesStateAsync(updateTraces, EWorkflowTraceState.StepRemoveByRecall, cancellationToken); // foreach (var trace in traces) // { // trace.TraceState = isOrderFiled // ? EWorkflowTraceState.StepRemoveByRecallWhenFiled // : EWorkflowTraceState.StepRemoveByRecall; // } // // updateTraces.AddRange(traces); // } // // await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken); // // //结束会签 // var unCompleteCountersigns = workflow.Countersigns.Where(d => !d.IsCompleted()).ToList(); // if (unCompleteCountersigns.Any()) // { // foreach (var unCompleteCountersign in unCompleteCountersigns) // { // unCompleteCountersign.End(null, null, EBusinessType.File, // _sessionContext.RequiredUserId, _sessionContext.UserName, // _sessionContext.RequiredOrgId, _sessionContext.OrgName, // _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName); // } // // await _workflowCountersignRepository.UpdateRangeAsync(unCompleteCountersigns, cancellationToken); // } // // workflow.EndCountersign(); // workflow.ResetOption(); // if (workflow.Status is EWorkflowStatus.Completed) // workflow.SetStatusRunnable(); // // var assigner = new UserInfo( // _sessionContext.UserId, // _sessionContext.UserName, // _sessionContext.OrgId, // _sessionContext.OrgName, // _sessionContext.OrgIsCenter // ); // // var targetStepNew = targetIsStartStep // ? await CreateStartStepAsync(workflow, targetStepDefine, dto, assigner, // dto.NextHandlers.First(), traceType, expiredTime, flowAssignInfo.FlowAssignType, cancellationToken) // : (await CreateStepsAsync(workflow, targetStepDefine, targetPrevStep, dto, assigner, dto.NextHandlers, // null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, true, traceType, // null, expiredTime, cancellationToken: cancellationToken)).First(); // // //更新实际办理节点信息 // workflow.UpdateActualStepWhenAssign(targetStepNew, new FlowStepHandler // { // UserId = targetStep.HandlerId, // Username = targetStep.HandlerName, // OrgId = targetStep.HandlerOrgId, // OrgName = targetStep.HandlerOrgName // }); // // workflow.UpdateCurrentStepWhenAssign(targetStepNew, new FlowStepHandler // { // UserId = targetStep.HandlerId, // Username = targetStep.HandlerName, // OrgId = targetStep.HandlerOrgId, // OrgName = targetStep.HandlerOrgName // }); // // //取消维护workflow得冗余字段(FlowedOrgIds, HandlerOrgs) // //workflow.Assign(flowAssignInfo.FlowAssignType, flowAssignInfo.GetHandlerIds()); // //workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects); // // //calc workflow expired time // var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStep); // // return isOrgToCenter; // } private List GetStepsBehindTargetStep(List steps, WorkflowStep targetStep) { var behindSteps = new List { targetStep }; if (!steps.Any()) return behindSteps; var nextSteps = targetStep.IsStartCountersign ? steps.Where(d => d.CountersignId == targetStep.StartCountersignId).ToList() : steps.Where(d => d.PrevStepId == targetStep.Id).ToList(); //var nextSteps = steps.Where(d => d.PrevStepId == targetStep.Id).ToList(); if (!nextSteps.Any()) return behindSteps; foreach (var nextStep in nextSteps) { var leftSteps = steps.Except(behindSteps).ToList(); behindSteps.AddRange(GetStepsBehindTargetStep(leftSteps, nextStep)); } return behindSteps; } private static void CheckWhetherRunnable(EWorkflowStatus status) { if (status != EWorkflowStatus.Runnable) throw UserFriendlyException.SameMessage("当前流程状态不可继续流转"); } private void ValidatePermission(Workflow workflow, string OrgId, string UserId, string[] roleIds) { if (!workflow.IsCanHandle(UserId, OrgId, roleIds)) throw UserFriendlyException.SameMessage("无办理权限"); } private async Task CreateEndStepAsync( Workflow workflow, StepDefine endStepDefine, WorkflowStep prevStep, BasicWorkflowDto dto, DateTime? expiredTime, CancellationToken cancellationToken) { if (workflow.Steps.Any(d => d.StepType == EStepType.End)) throw UserFriendlyException.SameMessage("无法重复创建结束节点"); var handler = new StepAssignInfo { Key = _sessionContext.UserId, Value = _sessionContext.UserName, UserId = _sessionContext.RequiredUserId, Username = _sessionContext.UserName, OrgId = _sessionContext.OrgId, OrgName = _sessionContext.OrgName, FlowAssignType = EFlowAssignType.User, }; var assigner = new UserInfo( _sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, _sessionContext.OrgIsCenter ); var step = CreateStep(workflow, endStepDefine, prevStep, handler, assigner, null, null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, expiredTime, endStepDefine.Name, true, businessType: EBusinessType.File, flowDirection: dto.FlowDirection); //step.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName, // _sessionContext.RequiredOrgId, _sessionContext.OrgName, // _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName); var opinion = workflow.FlowType == EFlowType.Handle ? $"流程归档。承办意见:{dto.Opinion}" : "流程归档"; HandleStep(step, EHandleMode.Normal, opinion, string.Empty); await _workflowStepRepository.AddAsync(step, cancellationToken); workflow.Steps.Add(step); //end trace await CreateTraceAsync(workflow, step, cancellationToken: cancellationToken); return step; } public async Task> CreateConfigStepsAsync( Workflow workflow, StepDefine stepDefine, WorkflowStep currentStep, BasicWorkflowDto dto, UserInfo assigner, EWorkflowTraceType traceType, DateTime? expiredTime, bool isAutoFillSummaryOpinion = false, Action? newStepConfig = null, CancellationToken cancellationToken = default) { string? opinion = null; if (isAutoFillSummaryOpinion && stepDefine.StepType is EStepType.Summary) opinion = currentStep.Opinion; return await CreateStepsAsync(workflow, stepDefine, currentStep, dto, assigner, dto.NextHandlers, null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, true, traceType, null, expiredTime, opinion, newStepConfig, cancellationToken); } private async Task> CreateStepsAsync( Workflow workflow, StepDefine stepDefine, WorkflowStep currentStep, BasicWorkflowDto dto, //bool isStartCountersign, UserInfo assigner, List handlers, string? countersignId, EWorkflowStepStatus stepStatus, ECountersignPosition csPosition, bool isOrigin, EWorkflowTraceType traceType, EHandlerType? handlerType = null, DateTime? expiredTime = null, string? opinion = null, Action? newStepConfig = null, CancellationToken cancellationToken = default ) { List steps = new(); foreach (var handler in handlers) { var isMain = handlers.Count == 1 || (handlers.Count > 1 && handler.Key == dto.NextMainHandler); var step = CreateStep(workflow, stepDefine, currentStep, handler, assigner, dto.NextStepCode, countersignId, stepStatus, csPosition, expiredTime, dto.NextStepName, isOrigin, isMain, handlerType, dto.BusinessType, dto.FlowDirection, opinion); //workflow, currentStep, nextStepDefine, nextStep await Task.Run(() => newStepConfig?.Invoke(workflow, currentStep, stepDefine, step), cancellationToken); // if (stepConfig != null) // { // var stepAssignInfo = stepConfig(workflow, stepDefine, prevStep); // var validator = new StepAssignInfoValidator(); // await validator.ValidateAndThrowAsync(stepAssignInfo, cancellationToken); // step.Assign(stepAssignInfo); // } steps.Add(step); } await _workflowStepRepository.AddRangeAsync(steps, cancellationToken); workflow.Steps.AddRange(steps); //create traces todo add range traces foreach (var step in steps) { await CreateTraceAsync(workflow, step, traceType, cancellationToken); } return steps; } private async Task CreatePublishTraceAsync(WorkflowTrace endTrace, CancellationToken cancellation) { var pubTrace = _mapper.Map(endTrace); pubTrace.TraceStyle = ETraceStyle.Publish; pubTrace.Name = "中心发布"; pubTrace.Status = EWorkflowStepStatus.WaitForAccept; pubTrace.Code = "publish"; pubTrace.CreationTime = endTrace.HandleTime ?? DateTime.Now; pubTrace.PrevStepId = endTrace.Id; pubTrace.PrevStepCode = endTrace.Code; pubTrace.PrevStepName = endTrace.Name; pubTrace.AcceptorId = null; pubTrace.AcceptorName = null; pubTrace.AcceptorOrgId = null; pubTrace.AcceptorOrgName = null; pubTrace.AcceptorOrgAreaCode = null; pubTrace.AcceptorOrgAreaName = null; pubTrace.AcceptTime = null; pubTrace.HandlerId = null; pubTrace.HandlerName = null; pubTrace.HandlerOrgId = null; pubTrace.HandlerOrgName = null; pubTrace.HandlerOrgAreaCode = null; pubTrace.HandlerOrgAreaName = null; pubTrace.HandleTime = null; pubTrace.StepExpiredTime = null; pubTrace.CreationTime = endTrace.CreationTime.AddSeconds(1); await _workflowTraceRepository.AddAsync(pubTrace, cancellation); return pubTrace; } private async Task CreateVisitTraceAsync(WorkflowTrace pubTrace, UserInfo acceptor, string orderVisitId, CancellationToken cancellation) { if (string.IsNullOrEmpty(orderVisitId)) throw new UserFriendlyException($"参数异常,orderVisitId不能为空, pubTraceId: {pubTrace.Id}"); var visitTrace = _mapper.Map(pubTrace); visitTrace.OrderPublishId = null; visitTrace.OrderVisitId = orderVisitId; visitTrace.TraceStyle = ETraceStyle.Visit; visitTrace.Name = "中心回访"; visitTrace.Status = EWorkflowStepStatus.WaitForAccept; visitTrace.Code = "visit"; visitTrace.CreationTime = pubTrace.HandleTime ?? DateTime.Now; visitTrace.PrevStepId = pubTrace.Id; visitTrace.PrevStepCode = pubTrace.Code; visitTrace.PrevStepName = pubTrace.Name; visitTrace.AssignerId = pubTrace.HandlerId; visitTrace.AssignerName = pubTrace.HandlerName; visitTrace.AssignerOrgId = pubTrace.HandlerOrgId; visitTrace.AssignerOrgName = pubTrace.HandlerOrgName; visitTrace.AssignerOrgIsCenter = pubTrace.HandlerOrgIsCenter ?? false; visitTrace.HandleTime = null; visitTrace.AcceptorId = acceptor.UserId; visitTrace.AcceptorName = acceptor.UserName; visitTrace.AcceptorOrgId = acceptor.OrgId; visitTrace.AcceptorOrgName = acceptor.OrgName; visitTrace.StepExpiredTime = null; await _workflowTraceRepository.AddAsync(visitTrace, cancellation); return visitTrace; } private async Task CreateTrashEndTraceAsync(WorkflowTrace visitTrace, CancellationToken cancellation) { var now = DateTime.Now; var TrashEndTrace = _mapper.Map(visitTrace); TrashEndTrace.StepId = null; TrashEndTrace.OrderPublishId = null; TrashEndTrace.OrderVisitId = null; TrashEndTrace.TraceStyle = ETraceStyle.TrashEnd; TrashEndTrace.Name = "结束"; TrashEndTrace.Status = EWorkflowStepStatus.Handled; TrashEndTrace.Code = "trashend"; TrashEndTrace.CreationTime = visitTrace.HandleTime ?? now; TrashEndTrace.PrevStepId = visitTrace.Id; TrashEndTrace.PrevStepCode = visitTrace.Code; TrashEndTrace.PrevStepName = visitTrace.Name; TrashEndTrace.AssignerId = visitTrace.HandlerId; TrashEndTrace.AssignerName = visitTrace.HandlerName; TrashEndTrace.AssignerOrgId = visitTrace.HandlerOrgId; TrashEndTrace.AssignerOrgName = visitTrace.HandlerOrgName; TrashEndTrace.AssignerOrgIsCenter = visitTrace.HandlerOrgIsCenter ?? false; TrashEndTrace.AcceptorId = visitTrace.HandlerId; TrashEndTrace.AcceptorName = visitTrace.HandlerName; TrashEndTrace.AcceptorOrgId = visitTrace.HandlerOrgId; TrashEndTrace.AcceptorOrgName = visitTrace.HandlerOrgName; TrashEndTrace.AcceptTime = visitTrace.HandleTime ?? now; TrashEndTrace.HandlerId = visitTrace.HandlerId; TrashEndTrace.HandlerName = visitTrace.HandlerName; TrashEndTrace.HandlerOrgId = visitTrace.HandlerOrgId; TrashEndTrace.HandlerOrgName = visitTrace.HandlerOrgName; TrashEndTrace.HandleTime = visitTrace.HandleTime ?? now; TrashEndTrace.StepExpiredTime = null; await _workflowTraceRepository.AddAsync(TrashEndTrace, cancellation); return TrashEndTrace; } /// /// 查询未完成节点 /// public WorkflowStep GetUnHandleStep(List steps, string orgId, string userId, string[] roleIds) { //var step = GetStep(steps, orgCode, userId, d => d != EWorkflowStepStatus.Handled); var step = steps.FirstOrDefault(d => d.IsCanHandle(userId, orgId, roleIds)); if (step == null) throw new UserFriendlyException( $"未找到对应节点, workflowId: {steps.FirstOrDefault()?.WorkflowId} orgCode:{orgId}, userId: {userId}, roleIds: {string.Join(',', roleIds)}", "未找到对应节点"); return step; } /// /// 检查当前办理节点是否为开始节点 /// /// /// 当前办理人Id /// 当前办理人orgId /// /// public async Task CheckCurrentIsStartStepAsync(string workflowId, string userId, string orgId, CancellationToken cancellationToken) { var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellationToken); var currentStep = GetStep(workflow.Steps, orgId, userId, d => d != EWorkflowStepStatus.Handled); if (currentStep is null) return false; return workflow.Steps.Count == 1 && currentStep.StepType is EStepType.Start && currentStep.IsOrigin; } /// /// 检查动态节点是否该终止 /// public bool DynamicShouldTerminal(StepDefine currentStepDefine, int currentOrgLevel) { if (currentStepDefine.InstanceMode is not EInstanceMode.Dynamic) throw new UserFriendlyException("非动态节点"); switch (currentStepDefine.InstancePolicy) { case EDynamicPolicy.OrgUpCenterTop: case EDynamicPolicy.OrgUp: case EDynamicPolicy.ArriveCenter: case EDynamicPolicy.ArriveOneOrg: case EDynamicPolicy.OrgUpHandleCenterTop: case EDynamicPolicy.OrgUpHandle: if (!int.TryParse(currentStepDefine.TerminalDynamicMark, out var tMark)) throw new UserFriendlyException( $"TerminalDynamicMark parse to int failed, tMark: {currentStepDefine.TerminalDynamicMark}"); return currentOrgLevel <= tMark; case EDynamicPolicy.OrgUpLeadCenterTop: case EDynamicPolicy.OrgUpLead: if (!int.TryParse(currentStepDefine.TerminalDynamicMark, out var tMark2)) throw new UserFriendlyException( $"TerminalDynamicMark parse to int failed, tMark: {currentStepDefine.TerminalDynamicMark}"); var leadRoleCode = _systemSettingCacheManager.GetSetting(SettingConstants.RoleLingDao)?.SettingValue[0]; var isLead = _sessionContext.Roles.Any(x => x == leadRoleCode); return (currentOrgLevel <= tMark2) && (isLead || _sessionContext.OrgIsCenter); case EDynamicPolicy.OrgDownCenterTop: case EDynamicPolicy.OrgDown: if (!int.TryParse(currentStepDefine.TerminalDynamicMark, out var tMark1)) throw new UserFriendlyException( $"TerminalDynamicMark parse to int failed, tMark: {currentStepDefine.TerminalDynamicMark}"); return currentOrgLevel >= tMark1; default: throw new ArgumentOutOfRangeException(); } } /// /// 终止会签 /// /// /// /// public async Task TerminalCountersignAsync(string countersignId, DateTime expireTime, CancellationToken cancellationToken) { var countersign = await _workflowCountersignRepository.GetAsync(countersignId, cancellationToken); if (countersign is null) throw new UserFriendlyException("无效会签编号"); //1. 检查会签是否已结束 t: return 2.检查是否有嵌套会签 t: 一起结束 3.结束会签 4.trace 5.检查workflow会签状态,如果会签全结束需更新状态 6.cp会签发起节点变为待办节点 if (countersign.IsCompleted()) throw new UserFriendlyException("该会签已结束"); return await TerminalCountersignAsync(countersign, expireTime, cancellationToken); } /// /// 终止会签 /// public async Task TerminalCountersignAsync(WorkflowCountersign countersign, DateTime expireTime, CancellationToken cancellationToken) { // var countersign = await _workflowCountersignRepository.GetAsync(countersignId, cancellationToken); // if (countersign is null) // throw new UserFriendlyException("无效会签编号"); //1. 检查会签是否已结束 t: return 2.检查是否有嵌套会签 t: 一起结束 3.结束会签 4.trace 5.检查workflow会签状态,如果会签全结束需更新状态 6.cp会签发起节点变为待办节点 if (countersign.IsCompleted()) throw new UserFriendlyException("该会签已结束"); var workflow = await GetWorkflowAsync(countersign.WorkflowId, withSteps: true, withTraces: true, withDefine: true, withCountersigns: true, cancellationToken: cancellationToken); if (!workflow.IsInCountersign) throw new UserFriendlyException("该流程未处于会签中"); countersign = workflow.Countersigns.First(d => d.Id == countersign.Id); var startCountersignStep = workflow.Steps.Find(d => d.StartCountersignId == countersign.Id); if (startCountersignStep is null) throw new UserFriendlyException("未查询到发起会签节点"); if (startCountersignStep.IsStartedCountersignEnd) throw new UserFriendlyException("该会签已汇总"); var updateCountersigns = new List(); EndCountersignWithCascade(countersign, workflow.Countersigns, startCountersignStep.BusinessType, ref updateCountersigns); if (updateCountersigns.Any()) { var updateSteps = new List(); var updateTraces = new List(); HandleStepsByTerminalCs(startCountersignStep, workflow.Steps, workflow.Traces, ref updateSteps, ref updateTraces); if (updateSteps.Any()) await _workflowStepRepository.RemoveRangeAsync(updateSteps, cancellationToken); //await _workflowStepRepository.RemoveNav(updateSteps) // .Include(d => d.StepHandlers) // .ExecuteCommandAsync(); if (updateTraces.Any()) await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken); await _workflowCountersignRepository.UpdateRangeAsync(updateCountersigns, cancellationToken); //cp会签发起节点变为待办节点 //1. create terminal trace 2. 撤回至startStep var stepToDuplicate = startCountersignStep.IsCountersignEndStep ? GetCsLoopStartStep(workflow.Steps, startCountersignStep) : startCountersignStep; // var stepAssignInfo = GetStepAssignInfo(new ReverseFlowStepAssignInfo // { // ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginStep // }, stepToDuplicate); // var stepDefine = workflow.WorkflowDefinition.FindStepDefine(stepToDuplicate.Code); var newStep = await DuplicateStepWithTraceAsync(workflow, stepDefine, stepToDuplicate, EWorkflowTraceType.Normal, expireTime, cancellationToken); //当topcsStep结束cs时,实际办理节点应该更新为newStep if (startCountersignStep.Id == workflow.TopCountersignStepId) { workflow.UpdateActualStepWhenAssign(newStep, new FlowStepHandler { UserId = startCountersignStep.HandlerId, Username = startCountersignStep.HandlerName, OrgId = startCountersignStep.HandlerOrgId, OrgName = startCountersignStep.HandlerOrgName, RoleId = startCountersignStep.RoleId, RoleName = startCountersignStep.RoleName }); workflow.UpdateCurrentStepWhenAssign(newStep, new FlowStepHandler { UserId = startCountersignStep.HandlerId, Username = startCountersignStep.HandlerName, OrgId = startCountersignStep.HandlerOrgId, OrgName = startCountersignStep.HandlerOrgName, RoleId = startCountersignStep.RoleId, RoleName = startCountersignStep.RoleName }); } // //csEndStep又开启了cs,在结束会签时,如果该节点是topcs的end节点, workflow.topcsStep应该更新为前一cs开启stepId // if (startCountersignStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)) // workflow.TopCountersignStepId = startCountersignStep.CountersignStartStepId; if (workflow.CheckIfCountersignOver()) workflow.EndCountersign(); await _workflowRepository.UpdateAsync(workflow, cancellationToken); } return workflow; } private void HandleStepsByTerminalCs(WorkflowStep step, List steps, List traces, ref List updateSteps, ref List updateTraces) { if (step.IsStartCountersign) { var countersignSteps = steps.Where(d => d.CountersignId == step.StartCountersignId).ToList(); if (countersignSteps.Any()) { foreach (var countersignStep in countersignSteps) { HandleStepsByTerminalCs(countersignStep, steps, traces, ref updateSteps, ref updateTraces); } } } EndStepByTerminalCs(step, traces, ref updateSteps, ref updateTraces); } private void EndStepByTerminalCs(WorkflowStep step, List traces, ref List updateSteps, ref List updateTraces) { var isHandled = step.Status is EWorkflowStepStatus.Handled; var opinion = $"会签未办理完成,由 {_sessionContext.OrgName} 的 {_sessionContext.UserName} 终止办理"; if (step.IsStartCountersign) step.CountersignEnd(); if (step.Status is not EWorkflowStepStatus.Handled) { step.Handle(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgIsCenter, EHandleMode.Normal, opinion); } updateSteps.Add(step); if (isHandled) return; var trace = traces.FirstOrDefault(d => d.StepId == step.Id); if (trace != null) { _mapper.Map(step, trace); updateTraces.Add(trace); } } /// /// 结束会签(包含子项) /// /// /// private void EndCountersignWithCascade(WorkflowCountersign countersign, List countersigns, EBusinessType businessType, ref List updateCountersigns) { if (countersign is null) return; var childCountersigns = countersigns.Where(d => d.ParentId == countersign.Id).ToList(); if (childCountersigns.Any()) { foreach (var childCountersign in childCountersigns) { EndCountersignWithCascade(childCountersign, countersigns, businessType, ref updateCountersigns); } } EndCountersign(countersign, countersigns, businessType, ref updateCountersigns); } private void EndCountersign(WorkflowCountersign countersign, List countersigns, EBusinessType businessType, ref List updateCountersigns) { //todo 1. trace? 先确定展现形式 2. end cs countersign.End(null, null, businessType, _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName); /* * //结束step会签信息 countersignStartStep.CountersignEnd(); updateSteps.Add(countersignStartStep); //结束会签 currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType, _sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId, _sessionContext.OrgName, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName); await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken); */ updateCountersigns.Add(countersign); } private WorkflowStep? GetStep(List steps, string orgCode, string userId, Func predicate) => steps.FirstOrDefault(d => predicate(d.Status) && d.Handlers.Any(x => x.Key == orgCode || x.Key == userId)); private WorkflowStep CreateStep( Workflow workflow, StepDefine stepDefine, WorkflowStep prevStep, // EFlowAssignType? flowAssignType, StepAssignInfo handler, UserInfo assigner, string nextStepCode, string? countersignId, EWorkflowStepStatus stepStatus, ECountersignPosition countersignPosition, DateTime? expiredTime, string stepName, bool isOrigin, bool isMainHandler = false, EHandlerType? handlerType = null, //动态节点依据动态策略判断 EBusinessType? businessType = null, EFlowDirection? flowDirection = null, string? opinion = null ) { var step = _mapper.Map(stepDefine); _mapper.Map(workflow, step); // step.FlowAssignType = flowAssignType; step.Handlers = new List { new(handler.Key, handler.Value) }; //step.StepHandlers = stepHandlers; step.NextStepCode = step.StepType is EStepType.End ? string.Empty : nextStepCode; step.IsMain = isMainHandler; step.PrevStepId = prevStep.Id; step.PrevStepCode = prevStep.Code; step.PrevStepName = prevStep.Name; step.CountersignId = countersignId; step.Status = stepStatus; step.CountersignPosition = countersignPosition; step.FlowDirection = flowDirection; if (expiredTime.HasValue) step.StepExpiredTime = expiredTime; //step.TimeLimit = GetTimeLimit(""); step.IsOrigin = isOrigin; if (!string.IsNullOrEmpty(stepName)) step.Name = stepName; //新增需求: 部门汇总节点由部门办理 //todo 待确认中心由部门处理还是由之前办理人办理 待重构 if (step.StepType == EStepType.Summary && step.BusinessType == EBusinessType.Department) step.FlowAssignType = EFlowAssignType.Org; step.Assign(handler); if (handlerType.HasValue) step.HandlerType = handlerType.Value; if (businessType.HasValue) step.BusinessType = businessType.Value; if (!string.IsNullOrEmpty(opinion)) step.Opinion = opinion; step.AssignerId = assigner.UserId; step.AssignerName = assigner.UserName; step.AssignerOrgId = assigner.OrgId; step.AssignerOrgName = assigner.OrgName; step.AssignerOrgIsCenter = assigner.OrgIsCenter; return step; } public async Task GetNextStepFlowAssignInfoByDefineAsync(StepDefine nextStepDefine, EHandlerType handlerType, bool isStartCountersign, List handlers, CancellationToken cancellationToken) { switch (handlerType) { case EHandlerType.Role: if (!handlers.Any()) { //var roles = await _roleRepository.Queryable() // .Includes(d => d.Accounts, x => x.User) // .Where(d => nextStepDefine.HandlerTypeItems.Select(x => x.Key).Contains(d.Name)) // .ToListAsync(cancellationToken); //handlers = roles.SelectMany(d => d.Accounts).Distinct() // .Select(d => new Kv(d.Id, d.User.Name)) // .ToList(); handlers = nextStepDefine.HandlerTypeItems; return FlowAssignInfo.Create(EFlowAssignType.Role, handlers, isStartCountersign); } return FlowAssignInfo.Create(EFlowAssignType.User, handlers, isStartCountersign); case EHandlerType.OrgLevel: case EHandlerType.OrgType: case EHandlerType.AssignedOrg: return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign); case EHandlerType.AssignedUser: return FlowAssignInfo.Create(EFlowAssignType.User, handlers, isStartCountersign); //case EHandlerType.AssignedOrgOrRole: // return FlowAssignInfo.Create(EFlowAssignType.OrgAndRole, handlers, isStartCountersign); default: throw new ArgumentOutOfRangeException(); } } //new /// /// 查询流程业务模块 /// /// /// /// /// private async Task GetWorkflowModuleAsync(string code, CancellationToken cancellationToken) { var wfModule = await _wfModuleCacheManager.GetWorkflowModuleAsync(code, cancellationToken); if (wfModule == null) throw UserFriendlyException.SameMessage("无效流程模块编码"); if (wfModule.Definition is null) throw new UserFriendlyException($"{code} 未配置流程模板", "未配置流程模板"); return wfModule; } /// /// 查询下一节点办理对象类型(user or org)及实际办理对象 /// private async Task GetNextStepFlowAssignInfoAsync(Workflow workflow, WorkflowStep currentStep, BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic, CancellationToken cancellationToken) { if (nextStepDefine.StepType is EStepType.End) return new(); var isStartCountersign = dto.IsStartCountersign; var handlers = dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(); if (isStartCountersign) { var assignType = FlowAssignInfo.GetAssignType(dto.HandlerType, dto.NextHandlers.Any()); //按会签策略判断,目前所有策略为org return FlowAssignInfo.Create(assignType, handlers, isStartCountersign); } //if (currentStep.IsInCountersign() && !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)) // return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign); if (currentStep.IsInCountersign()) { if (currentStep.IsCountersignEndStep) { //汇总节点(非顶级) if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)) { if (dto.BackToCountersignEnd) { var csStartStep = GetCsLoopStartStep(workflow.Steps, currentStep); var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == csStartStep.PrevStepId); if (prevStep is null) throw new UserFriendlyException("未查询到目标节点的前一节点"); return FlowAssignInfo.Create(prevStep.FlowAssignType.Value, prevStep.Handlers, isStartCountersign); } } } else { if (dto.BackToCountersignEnd) { var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.PrevStepId); if (prevStep is null) throw new UserFriendlyException($"未查询到当前节点的上级节点"); return FlowAssignInfo.Create(prevStep.FlowAssignType.Value, prevStep.Handlers, isStartCountersign); } else { var assignType = FlowAssignInfo.GetAssignType(dto.HandlerType, dto.NextHandlers.Any()); //按会签策略判断,目前所有策略为org return FlowAssignInfo.Create(assignType, handlers, isStartCountersign); } } } if (isNextDynamic) { switch (currentStep.InstancePolicy) { case EDynamicPolicy.OrgUpCenterTop: case EDynamicPolicy.OrgUp: case EDynamicPolicy.OrgDownCenterTop: case EDynamicPolicy.OrgDown: case EDynamicPolicy.ArriveCenter: case EDynamicPolicy.ArriveOneOrg: return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign); case EDynamicPolicy.OrgUpHandleCenterTop: case EDynamicPolicy.OrgUpHandle: case EDynamicPolicy.OrgUpLeadCenterTop: case EDynamicPolicy.OrgUpLead: return FlowAssignInfo.Create(EFlowAssignType.OrgAndRole, handlers, isStartCountersign); default: throw new ArgumentOutOfRangeException(); } } return await GetNextStepFlowAssignInfoByDefineAsync(nextStepDefine, dto.HandlerType, isStartCountersign, handlers, cancellationToken); } private EFlowAssignType SetNextStepAssignInfo(Workflow workflow, WorkflowStep currentStep, BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic) { if (nextStepDefine.StepType is EStepType.End) return EFlowAssignType.User; if (dto.IsStartCountersign) return GetFlowStepAssignType(dto.HandlerType, dto.NextHandlers.Any()); if (currentStep.IsInCountersign()) { if (currentStep.IsCountersignEndStep) { //汇总节点(非顶级) if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId)) { if (dto.BackToCountersignEnd) { var csStartStep = GetCsLoopStartStep(workflow.Steps, currentStep); var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == csStartStep.PrevStepId); if (prevStep is null) throw new UserFriendlyException("未查询到目标节点的前一节点"); return prevStep.FlowAssignType ?? EFlowAssignType.User; } } } else { if (dto.BackToCountersignEnd) { var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.PrevStepId); if (prevStep is null) throw new UserFriendlyException($"未查询到当前节点的上级节点"); return prevStep.FlowAssignType ?? EFlowAssignType.User; } else { return GetFlowStepAssignType(dto.HandlerType, dto.NextHandlers.Any()); } } } if (isNextDynamic) { switch (currentStep.InstancePolicy) { case EDynamicPolicy.OrgUpCenterTop: case EDynamicPolicy.OrgUp: case EDynamicPolicy.OrgDownCenterTop: case EDynamicPolicy.OrgDown: case EDynamicPolicy.ArriveCenter: case EDynamicPolicy.ArriveOneOrg: return EFlowAssignType.Org; case EDynamicPolicy.OrgUpHandleCenterTop: case EDynamicPolicy.OrgUpHandle: case EDynamicPolicy.OrgUpLeadCenterTop: case EDynamicPolicy.OrgUpLead: return EFlowAssignType.OrgAndRole; default: throw new ArgumentOutOfRangeException(); } } return GetFlowStepAssignType(dto.HandlerType, dto.NextHandlers.Any()); } public EFlowAssignType GetFlowStepAssignType(EHandlerType handlerType, bool hasNextHandlers) { return handlerType switch { EHandlerType.Role => hasNextHandlers ? EFlowAssignType.User : EFlowAssignType.Role, EHandlerType.OrgLevel => EFlowAssignType.Org, EHandlerType.OrgType => EFlowAssignType.Org, EHandlerType.AssignedUser => EFlowAssignType.User, EHandlerType.AssignedOrg => EFlowAssignType.Org, _ => throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, null) }; } #endregion } }