Selaa lähdekoodia

Merge branch 'release' of http://git.12345lm.cn/Fengwo/hotline into release

Dun.Jason 6 kuukautta sitten
vanhempi
commit
0fee91f438

+ 5 - 83
src/Hotline.Api/Controllers/OrderController.cs

@@ -3720,7 +3720,10 @@ public class OrderController : BaseController
         var (total, items) = await _orderRepository
             .Queryable(hasHandled: isHandled, isAdmin: isAdmin)
             .Includes(d => d.OrderSpecials)
-            .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept && d.Status != EOrderStatus.HandOverToUnAccept)
+            .Where(d => d.Status != EOrderStatus.WaitForAccept && 
+                        d.Status != EOrderStatus.BackToUnAccept && 
+                        d.Status != EOrderStatus.SpecialToUnAccept && 
+                        d.Status != EOrderStatus.HandOverToUnAccept)
             .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword))
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
@@ -3741,59 +3744,7 @@ public class OrderController : BaseController
             .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent!.Value)
             .OrderByDescending(d => new { d.IsUrgent, d.StartTime })
             .ToPagedListAsync(dto, HttpContext.RequestAborted);
-
-        //if (isHandled)
-        //{
-        //    var (total, items) = await _orderRepository
-        //        .Queryable(hasHandled: isHandled)
-        //        .Includes(d => d.OrderSpecials)
-        //        .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
-        //        .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword))
-        //        .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
-        //        .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
-        //        .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => !d.CounterSignType.HasValue)
-        //        .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == true, d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) || (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //超期 未办
-        //        .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == false, d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now)//即将超期 未办
-        //        .Where(d => d.Source < ESource.MLSQ || d.Source > ESource.WZSC)
-        //        .Where(d => d.Status != EOrderStatus.BackToProvince && d.Status < EOrderStatus.Filed)
-        //        //.Where(d => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == d.Id).NotAny())
-        //        .Where(d => d.OrderSpecials.Any() == false || d.OrderSpecials.Any(s => s.State > 0))
-        //        .WhereIF(dto.StartTime.HasValue, d => d.StartTime >= dto.StartTime)
-        //        .WhereIF(dto.EndTime.HasValue, d => d.StartTime <= dto.EndTime)
-        //        .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
-        //        .OrderByDescending(d => d.StartTime)
-        //        .ToPagedListAsync(dto, HttpContext.RequestAborted);
-        //    return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
-        //}
-        //else
-        //{
-        //    var (total, items) = await _orderRepository.Queryable()
-        //        .Where(d => SqlFunc.Subqueryable<WorkflowTrace>()
-        //            .Where(step => step.ExternalId == d.Id && step.TraceState != EWorkflowTraceState.StepRemoveByPrevious &&
-        //                                                         ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
-        //                                                          (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
-        //                                                          (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))) &&
-        //                                                         step.Status == EWorkflowStepStatus.Handled).Any())
-        //        .Includes(d => d.OrderSpecials)
-        //        .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
-        //        .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword))
-        //        .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
-        //        .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
-        //        .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => !d.CounterSignType.HasValue)
-        //        .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == true, d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) || (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //超期 未办
-        //        .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == false, d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now)//即将超期 未办
-        //        .Where(d => d.Source < ESource.MLSQ || d.Source > ESource.WZSC)
-        //        .Where(d => d.Status != EOrderStatus.BackToProvince && d.Status < EOrderStatus.Filed)
-        //        //.Where(d => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == d.Id).NotAny())
-        //        .Where(d => d.OrderSpecials.Any() == false || d.OrderSpecials.Any(s => s.State > 0))
-        //        .WhereIF(dto.StartTime.HasValue, d => d.StartTime >= dto.StartTime)
-        //        .WhereIF(dto.EndTime.HasValue, d => d.StartTime <= dto.EndTime)
-        //        .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
-        //        .OrderByDescending(d => d.StartTime)
-
-        //        .ToPagedListAsync(dto, HttpContext.RequestAborted);
-        //    return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
-        //}
+        
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
 
@@ -3855,35 +3806,6 @@ public class OrderController : BaseController
              .OrderByIF(dto.IsHandled == false, d => new { IsUrgent = d.IsUrgent, CreationTime = d.CreationTime }, OrderByType.Desc)
             .ToPagedListAsync(dto, HttpContext.RequestAborted);
 
-        //var (total, items) = await _orderRepository.Queryable()
-        //    .LeftJoin<WorkflowStep>((d, step) => d.Id == step.ExternalId)
-        //    .Where((d, step) =>
-        //        ((string.IsNullOrEmpty(d.WorkflowId) && (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)) ||
-        //        (!string.IsNullOrEmpty(d.WorkflowId) &&
-        //        ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
-        //         (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
-        //         (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))) &&
-        //         (((dto.IsHandled.HasValue && dto.IsHandled == false) && step.Status != EWorkflowStepStatus.Handled) ||
-        //        ((dto.IsHandled.HasValue && dto.IsHandled == true) && step.Status == EWorkflowStepStatus.Handled)))))
-        //    .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
-        //    .WhereIF(dto.IsHandled.HasValue, d => handleStatuses.Contains(d.Status))
-        //    .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword!))
-        //    .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
-        //    .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
-        //    .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => !d.CounterSignType.HasValue)
-        //    .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == true, d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) || (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //超期 未办
-        //    .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == false, d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now)//即将超期 未办
-        //    .WhereIF(dto.StartTime.HasValue, d => d.CreationTime >= dto.StartTime)
-        //    .WhereIF(dto.EndTime.HasValue, d => d.CreationTime <= dto.EndTime)
-        //    .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
-        //    .Where(d => d.Source < ESource.MLSQ || d.Source > ESource.WZSC)
-        //    .Where(d => d.Status != EOrderStatus.BackToProvince && d.Status < EOrderStatus.Filed)
-        //    .OrderBy(d => d.Status)
-        //    .OrderByIF(dto.IsHandled == true, d => d.StartTime, OrderByType.Desc)
-        //    .OrderByIF(dto.IsHandled == false, d => d.CreationTime, OrderByType.Desc)
-        //    .Select((d, step) => d)
-        //    .ToPagedListAsync(dto, HttpContext.RequestAborted);
-
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
 

+ 6 - 0
src/Hotline.Application/FlowEngine/IWorkflowApplication.cs

@@ -22,6 +22,12 @@ namespace Hotline.Application.FlowEngine
         Task<string> StartWorkflowAsync(StartWorkflowDto dto, ISessionContext current, string externalId, DateTime? expiredTime = null,
             CancellationToken cancellationToken = default);
 
+        /// <summary>
+        /// 开始流程并停留在开始节点(开始节点作为待办节点)
+        /// </summary>
+        Task<string> StartWorkflowToStartStepAsync(StartWorkflowDto dto, ISessionContext current, string externalId, DateTime? expiredTime = null,
+            CancellationToken cancellationToken = default);
+        
         /// <summary>
         /// 查询下一节点办理对象类型(user or org)及实际办理对象
         /// </summary>

+ 69 - 3
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -212,6 +212,75 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         return workflow.Id;
     }
 
+    /// <summary>
+    /// 开始流程并停留在开始节点(开始节点作为待办节点)
+    /// </summary>
+    public async Task<string> StartWorkflowToStartStepAsync(StartWorkflowDto dto, ISessionContext current, string externalId, DateTime? expiredTime = 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 workflow = await _workflowDomainService.CreateWorkflowAsync(wfModule, dto.Title,
+            current.RequiredUserId, current.RequiredOrgId,
+            externalId, cancellationToken);
+        
+        var defineHandler = startStepDefine.HandlerTypeItems.First();
+        
+        //todo 需求:所有坐席都可以办,临时方案,后期重构:依据策略决定指派对象
+        var startStep = _workflowDomainService.CreateStartStep(workflow, startStepDefine, dto,
+            new FlowStepHandler
+            {
+                Key = current.RequiredUserId,
+                Value = current.UserName,
+                UserId = current.UserId,
+                Username = current.UserName,
+                OrgId = current.RequiredOrgId,
+                OrgName = current.OrgName,
+                RoleId = defineHandler.Key,
+                RoleName = defineHandler.Value,
+            }, expiredTime,
+            EFlowAssignType.Role);
+        
+        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<WorkflowTrace>(startStep);
+        startTrace.StepId = startStep.Id;
+        startTrace.TraceType = EWorkflowTraceType.Normal;
+        //_mapper.Map(dto, startTrace);
+        await _workflowTraceRepository.AddAsync(startTrace, cancellationToken);
+        workflow.Traces.Add(startTrace);
+        startStep.WorkflowTrace = startTrace;
+
+        //更新受理人信息
+        workflow.UpdateAcceptor(
+            current.RequiredUserId,
+            current.UserName,
+            current.StaffNo,
+            current.RequiredOrgId,
+            current.OrgName);
+        
+        return workflow.Id;
+    }
+
     /// <summary>
     /// 流转至下一节点(节点办理)
     /// </summary>
@@ -286,9 +355,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
             withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
-        //var user = await _userRepository.Queryable()
-        //    .Includes(x => x.Organization)
-        //    .FirstAsync(x => x.Id == _sessionContext.RequiredUserId, cancellationToken);
         return await _workflowDomainService.PreviousAsync(workflow, dto,
             _sessionContext.RequiredUserId, _sessionContext.UserName,
             _sessionContext.RequiredOrgId, _sessionContext.OrgName,

+ 3 - 10
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -249,7 +249,7 @@ namespace Hotline.Application.Subscribers
             };
             await _orderRevokeRepository.AddAsync(orderRevoke, cancellationToken);
 
-            //宜宾需求:特提至中心(派单组?),由派单员归档
+            //宜宾需求:特提至中心(优先派单组无派单组节点就特提至坐席),由派单员归档
             var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
             if (string.IsNullOrEmpty(order?.WorkflowId))
             {
@@ -259,18 +259,11 @@ namespace Hotline.Application.Subscribers
                     Title = order.Title,
                     Opinion = dto.Opinion,
                 };
-                //await _workflowApplication.StartToEndAsync(startDto, current, order.Id, order.ExpiredTime,
-                //    cancellationToken);
-                await _workflowApplication.StartWorkflowAsync(startDto, current, order.Id, order.ExpiredTime,
-                    cancellationToken);
+                await _workflowApplication.StartWorkflowToStartStepAsync(startDto, current, order.Id, order.ExpiredTime, cancellationToken);
             }
             else
             {
-                //await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, dto.Opinion, null,
-                //    cancellationToken: cancellationToken);
-                //await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Opinion, null, order.ExpiredTime,
-                //    cancellationToken: cancellationToken);
-                await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, dto.Opinion, current, cancellationToken);
+                await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion, current, cancellationToken);
             }
         }
 

+ 6 - 0
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -79,6 +79,12 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         Task RecallToSendStepAsync(string workflowId, string opinion, ISessionContext current, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 特提至中心(优先派单组其次坐席)
+        /// </summary>
+        /// <returns></returns>
+        Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, ISessionContext current, CancellationToken cancellationToken);
+        
         ///// <summary>
         ///// 跳转(直接将流程跳转至任意节点)
         ///// </summary>

+ 59 - 34
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -111,7 +111,7 @@ namespace Hotline.FlowEngine.Workflows
 
                 //firstStep是否为end,t: 实际办理节点为startStep, 并且handlerId赋值 f: 实际办理节点为firstStep, handlerId未赋值
                 workflow.UpdateActualStepWhenHandle(startStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel);
-                
+
                 workflow.UpdateCurrentStepWhenHandle(startStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel);
 
                 var endTrace = await EndAsync(workflow, dto, firstStepDefine, startStep, current, expiredTime, cancellationToken);
@@ -258,7 +258,7 @@ namespace Hotline.FlowEngine.Workflows
 
             var unhandlePreviousTrace = workflow.Traces.FirstOrDefault(d =>
                     d.Status is not EWorkflowStepStatus.Handled
-            //&& d.TraceType is EWorkflowTraceType.Previous
+                //&& d.TraceType is EWorkflowTraceType.Previous
             );
             //var previousOpinion = unhandlePreviousTrace?.Opinion ?? null;
 
@@ -458,7 +458,6 @@ namespace Hotline.FlowEngine.Workflows
 
             //var trace = await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
 
-
             #endregion
 
             #region 处理流程
@@ -622,17 +621,18 @@ namespace Hotline.FlowEngine.Workflows
                 applicantOrgAreaCode, applicantOrgAreaName,
                 applicantIsCenter, dto.Opinion);
 
-			//await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
+            //await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
 
-			//如果有传入期满时间 新节点为传入的期满时间
-			if (dto.ExpiredTime.HasValue)
+            //如果有传入期满时间 新节点为传入的期满时间
+            if (dto.ExpiredTime.HasValue)
                 prevStep.StepExpiredTime = dto.ExpiredTime;
-			//退给派单组节点,需按照平均分配原则派给一个派单员 禅道299 TODO
-			if (dto.NextHandlers.Any())
+            //退给派单组节点,需按照平均分配原则派给一个派单员 禅道299 TODO
+            if (dto.NextHandlers.Any())
             {
                 var handle = dto.NextHandlers.FirstOrDefault();
                 prevStep.Assign(handle.UserId, handle.Username, handle.OrgId, handle.OrgName, handle.RoleId, handle.RoleName);
-			}
+            }
+
             //复制上一个节点为待接办
             var newPrevStep =
                 await DuplicateStepWithTraceAsync(workflow, prevStep, EWorkflowTraceType.Previous, cancellationToken);
@@ -651,7 +651,7 @@ namespace Hotline.FlowEngine.Workflows
                 workflow.SetStatusRunnable();
 
             //更新实际办理节点信息
-            workflow.UpdateActualStepWhenAssign(newPrevStep,new FlowStepHandler
+            workflow.UpdateActualStepWhenAssign(newPrevStep, new FlowStepHandler
             {
                 UserId = prevStep.HandlerId,
                 Username = prevStep.HandlerName,
@@ -839,7 +839,7 @@ namespace Hotline.FlowEngine.Workflows
                     if (step.WorkflowTrace is null)
                         throw new UserFriendlyException("未查询节点对应快照信息");
                     step.WorkflowTrace.FlowAssignType = EFlowAssignType.User;
-					step.WorkflowTrace.Assign(handler.userId, handler.username,
+                    step.WorkflowTrace.Assign(handler.userId, handler.username,
                         handler.orgId, handler.orgName, handler.roleId, handler.roleName);
                 }
             }
@@ -924,7 +924,7 @@ namespace Hotline.FlowEngine.Workflows
             CancellationToken cancellation)
         {
             return await _workflowStepRepository.Queryable()
-                .Where(d => d.WorkflowId == workflowId && d.HandlerOrgId == orgId && d.StepType != EStepType.End && d.StepType !=  EStepType.Summary)
+                .Where(d => d.WorkflowId == workflowId && d.HandlerOrgId == orgId && d.StepType != EStepType.End && d.StepType != EStepType.Summary)
                 //.Where(d => d.StepHandlers.Any(sh => sh.OrgId == orgId) && d.WorkflowId == workflowId)
                 .OrderByDescending(d => d.HandleTime)
                 .FirstAsync(cancellation);
@@ -937,13 +937,13 @@ namespace Hotline.FlowEngine.Workflows
         public async Task<WorkflowStep> FindTopHandleStepAsync(string workflowId, CancellationToken cancellation)
         {
             var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellation);
-			return workflow.Steps.FirstOrDefault(x => x.Id == workflow.TopCountersignStepId);
+            return workflow.Steps.FirstOrDefault(x => x.Id == workflow.TopCountersignStepId);
         }
 
-		/// <summary>
-		/// 查询流转方向
-		/// </summary>
-		public EFlowDirection GetFlowDirection(EBusinessType sourceStepBusinessType,
+        /// <summary>
+        /// 查询流转方向
+        /// </summary>
+        public EFlowDirection GetFlowDirection(EBusinessType sourceStepBusinessType,
             EBusinessType directionStepBusinessType)
         {
             switch (sourceStepBusinessType)
@@ -1092,7 +1092,8 @@ namespace Hotline.FlowEngine.Workflows
             var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true,
                 cancellationToken: cancellationToken);
             var startStep = workflow.Steps.First(d => d.StepType == EStepType.Start);
-
+            if(startStep is null)
+                throw new UserFriendlyException($"数据异常, workflowId: {workflowId}", "该流程无开始节点");
 
             await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
         }
@@ -1103,14 +1104,36 @@ namespace Hotline.FlowEngine.Workflows
         public async Task RecallToSendStepAsync(string workflowId, string opinion, ISessionContext current,
             CancellationToken cancellationToken)
         {
-            //todo 1.当前待办节点删掉 2.当前待办trace更新(status, opinion) 3.复制startStep为待办 4.更新workflow(status, csStatus, handlers) 5.publish event
             var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true,
                 cancellationToken: cancellationToken);
-            var startStep = workflow.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
-            if (startStep is null)
+            var sendStep = workflow.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
+            if (sendStep is null)
                 throw new UserFriendlyException($"未找到派单节点, workflowId: {workflowId}", "该流程无派单节点");
 
-            await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
+            await RecallToTargetStepAsync(workflow, sendStep, opinion, current, cancellationToken);
+        }
+
+        /// <summary>
+        /// 特提至中心(优先派单组其次坐席)
+        /// </summary>
+        /// <returns></returns>
+        public async Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, ISessionContext current,
+            CancellationToken cancellationToken)
+        {
+            var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true,
+                cancellationToken: cancellationToken);
+            var sendStep = workflow.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
+            if (sendStep is not null)
+            {
+                await RecallToTargetStepAsync(workflow, sendStep, opinion, current, cancellationToken);
+            }
+            else
+            {
+                var startStep = workflow.Steps.First(d => d.StepType == EStepType.Start);
+                if(startStep is null)
+                    throw new UserFriendlyException($"数据异常, workflowId: {workflowId}", "该流程无开始节点");
+                await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
+            }
         }
 
         private async Task RecallToTargetStepAsync(Workflow workflow, WorkflowStep targetStep, string opinion, ISessionContext current,
@@ -1130,7 +1153,7 @@ namespace Hotline.FlowEngine.Workflows
             var newStartStep =
                 await DuplicateStepWithTraceAsync(workflow, targetStep, EWorkflowTraceType.Recall, cancellationToken);
 
-            workflow.UpdateActualStepWhenAssign(targetStep,new FlowStepHandler
+            workflow.UpdateActualStepWhenAssign(targetStep, new FlowStepHandler
             {
                 UserId = targetStep.HandlerId,
                 Username = targetStep.HandlerName,
@@ -1364,7 +1387,8 @@ namespace Hotline.FlowEngine.Workflows
             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))
+            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);
         }
@@ -1500,11 +1524,11 @@ namespace Hotline.FlowEngine.Workflows
             workflow.UpdateCurrentStepWhenHandle(endStep,
                 current.OrgAreaCode, current.OrgAreaName, current.OrgLevel);
             workflow.UpdateCurrentStepAcceptTime(endStep.AcceptTime.Value);
-            
+
             //workflow.UpdateActualStepWhenHandle(endStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel);
             //workflow.UpdateActualStepAcceptTime(endStep.AcceptTime.Value);
 
-            if(string.IsNullOrEmpty(workflow.OrgLevelOneCode))
+            if (string.IsNullOrEmpty(workflow.OrgLevelOneCode))
                 workflow.UpdateLevelOneOrg(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName);
 
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
@@ -1612,7 +1636,7 @@ namespace Hotline.FlowEngine.Workflows
             else
             {
                 var nextHandler = dto.NextHandlers.First();
-                workflow.UpdateActualStepWhenAssign(nextSteps.First(),nextHandler);
+                workflow.UpdateActualStepWhenAssign(nextSteps.First(), nextHandler);
             }
 
             //if ( /*workflow.FlowType is EFlowType.Handle &&*/
@@ -1803,10 +1827,10 @@ namespace Hotline.FlowEngine.Workflows
 
             var handlerType = stepDefine.CountersignPolicy switch
             {
-	            EDynamicPolicyCountersign.OrgUpCenterTop => EHandlerType.OrgLevel,
-	            EDynamicPolicyCountersign.OrgUp => EHandlerType.OrgLevel,
-	            EDynamicPolicyCountersign.OrgDownCenterTop => EHandlerType.OrgLevel,
-	            EDynamicPolicyCountersign.OrgDown => EHandlerType.OrgLevel,
+                EDynamicPolicyCountersign.OrgUpCenterTop => EHandlerType.OrgLevel,
+                EDynamicPolicyCountersign.OrgUp => EHandlerType.OrgLevel,
+                EDynamicPolicyCountersign.OrgDownCenterTop => EHandlerType.OrgLevel,
+                EDynamicPolicyCountersign.OrgDown => EHandlerType.OrgLevel,
                 null => throw new ArgumentOutOfRangeException(),
                 _ => throw new ArgumentOutOfRangeException()
             };
@@ -2022,7 +2046,9 @@ namespace Hotline.FlowEngine.Workflows
             {
                 //newStep.FlowAssignType = EFlowAssignType.User;
                 // 是否中心  临时紧急修改 后续在流程模版定义是否原办理人退回类型 来实现流程 禅道200
-                newStep.FlowAssignType = step.HandlerOrgIsCenter!.Value ? step.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Role : EFlowAssignType.Org;
+                newStep.FlowAssignType = step.HandlerOrgIsCenter!.Value
+                    ? step.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Role
+                    : EFlowAssignType.Org;
                 //if (newStep is { FlowAssignType: EFlowAssignType.Role, BusinessType: EBusinessType.Send })
                 //    newStep.FlowAssignType = EFlowAssignType.User;
 
@@ -2309,7 +2335,7 @@ namespace Hotline.FlowEngine.Workflows
                     null, expiredTime, cancellationToken: cancellationToken)).First();
 
             //更新实际办理节点信息
-            workflow.UpdateActualStepWhenAssign(targetStepNew,new FlowStepHandler
+            workflow.UpdateActualStepWhenAssign(targetStepNew, new FlowStepHandler
             {
                 UserId = targetStep.HandlerId,
                 Username = targetStep.HandlerName,
@@ -2797,5 +2823,4 @@ namespace Hotline.FlowEngine.Workflows
 
         #endregion
     }
-
 }

+ 2 - 2
src/Hotline/FlowEngine/Workflows/WorkflowTrace.cs

@@ -60,12 +60,12 @@ public enum EWorkflowTraceState
     StepRemoveByPrevious = 10,
 
     /// <summary>
-    /// 对应节点因撤回被删除(工单未归档)
+    /// 对应节点因特提被删除(工单未归档)
     /// </summary>
     StepRemoveByRecall = 20,
 
     /// <summary>
-    /// 对应节点因撤回被删除(工单已归档)
+    /// 对应节点因特提被删除(工单已归档)
     /// </summary>
     StepRemoveByRecallWhenFiled = 21,
 }