Bläddra i källkod

Merge branch 'test' of http://110.188.24.182:10023/Fengwo/hotline into test

qinchaoyue 3 månader sedan
förälder
incheckning
344cd13d20
32 ändrade filer med 1307 tillägg och 580 borttagningar
  1. 1 0
      Hotline.sln
  2. 120 37
      src/Hotline.Api/Controllers/OrderController.cs
  3. 8 4
      src/Hotline.Api/Controllers/QualityController.cs
  4. 4 4
      src/Hotline.Application/FlowEngine/IWorkflowApplication.cs
  5. 62 87
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  6. 1 59
      src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs
  7. 1 2
      src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs
  8. 15 5
      src/Hotline.Application/Orders/OrderApplication.cs
  9. 5 1
      src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs
  10. 2 2
      src/Hotline.Application/Quality/QualityApplication.cs
  11. 27 6
      src/Hotline.Application/Subscribers/DatasharingSubscriber.cs
  12. 4 4
      src/Hotline.Repository.SqlSugar/BaseRepositoryWorkflow.cs
  13. 8 8
      src/Hotline.Repository.SqlSugar/Extensions/DataPermissionExtensions.cs
  14. 2 2
      src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs
  15. 118 18
      src/Hotline.Share/Dtos/FlowEngine/PreviousWorkflowDto.cs
  16. 1 1
      src/Hotline.Share/Dtos/FlowEngine/RecallDto.cs
  17. 1 1
      src/Hotline.Share/Dtos/Order/OrderSpecialDto.cs
  18. 24 0
      src/Hotline.Share/Enums/FlowEngine/EFlowAssignType.cs
  19. 6 0
      src/Hotline.Share/Enums/FlowEngine/EHandleMode.cs
  20. 1 1
      src/Hotline/CallCenter/Tels/TelDomainService.cs
  21. 0 14
      src/Hotline/FlowEngine/FlowAssignInfo.cs
  22. 1 0
      src/Hotline/FlowEngine/Notifications/WorkflowNotify.cs
  23. 31 11
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  24. 67 1
      src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs
  25. 2 1
      src/Hotline/FlowEngine/Workflows/WorkflowCountersignMember.cs
  26. 555 192
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  27. 2 2
      src/Hotline/KnowledgeBase/KnowledgeDomainService.cs
  28. 13 1
      src/Hotline/Orders/IOrderDomainService.cs
  29. 144 12
      src/Hotline/Orders/OrderDomainService.cs
  30. 1 1
      src/Hotline/Quality/QualityItem.cs
  31. 78 78
      src/XF.Domain.Repository/Entity.cs
  32. 2 25
      src/XF.Domain/Entities/IWorkflow.cs

+ 1 - 0
Hotline.sln

@@ -203,6 +203,7 @@ Global
 		{75215667-65AF-4B7B-85E7-3140239B30CC} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{3AB75B51-A69D-4145-A564-1D9D1695992E} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{31855124-4EFC-47B9-A4D5-64822DE036E6} = {08D63205-1445-430F-A4AB-EF1744E3AC11}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution

+ 120 - 37
src/Hotline.Api/Controllers/OrderController.cs

@@ -4119,19 +4119,6 @@ public class OrderController : BaseController
 
         try
         {
-            // // 平均派单
-            // var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-            // if (dto.Workflow.BusinessType == EBusinessType.Send && averageSendOrder)
-            // {
-            //     var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-            //     dto.Workflow.NextHandlers = new List<FlowStepHandler> { handler };
-            // }
-            //
-            // var startDto = _mapper.Map<StartWorkflowDto>(dto.Workflow);
-            // startDto.DefinitionModuleCode = WorkflowModuleConsts.OrderHandle;
-            // startDto.Title = order.Title;
-            // await _workflowApplication.StartWorkflowAsync(startDto, order.Id, order.ExpiredTime, HttpContext.RequestAborted);
-
             var startDto = _mapper.Map<StartWorkflowDto>(dto.Workflow);
             startDto.DefinitionModuleCode = WorkflowModuleConsts.OrderHandle;
             startDto.Title = order.Title;
@@ -5136,17 +5123,39 @@ public class OrderController : BaseController
         audit.InitId();
         if (dto.Files.Any())
             audit.FileJson = await _fileRepository.AddFileAsync(dto.Files, audit.Id, "", HttpContext.RequestAborted);
+        FlowStepHandler? handler = null;
         if (_appOptions.Value.IsZiGong && prevStep.BusinessType == EBusinessType.Send)
         {
             // 平均派单
             var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
             if (averageSendOrder)
             {
-                var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                dto.Handler = handler;
+                handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                //dto.Handler = handler;
             }
         }
 
+        dto.ReverseFlowStepAssignInfo =
+            _orderDomainService.GetOrderPreviousAssignInfo(prevStep.BusinessType, handler is null ? null : new StepAssignInfo
+            {
+                FlowAssignType = EFlowAssignType.User,
+                UserId = handler.UserId,
+                Username = handler.Username,
+                OrgId = handler.OrgId,
+                OrgName = handler.OrgName,
+                RoleId = handler.RoleId,
+                RoleName = handler.RoleName
+            });
+        audit.SendBackData.ReverseFlowStepAssignInfo = dto.ReverseFlowStepAssignInfo;
+
+        //todo 需整理各个地市退回业务的需求(指派类型及指派对象)
+        //if (workflow.FlowType == EFlowType.Handle) //该逻辑需放在退回操作前依据业务判断
+        //{
+        //    prevStep.FlowAssignType = prevStep.BusinessType is EBusinessType.Seat ? EFlowAssignType.Role :
+        //        prevStep.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Org;
+        //}
+
+
         if (oneSendBack || twoSendBack)
         {
             var sendBack = await _orderSendBackAuditRepository.Queryable()
@@ -5179,11 +5188,12 @@ public class OrderController : BaseController
                 audit.State = ESendBackAuditState.End;
                 audit.AuditUser = "默认通过";
                 audit.AuditTime = DateTime.Now;
-                if (prevStep.BusinessType == EBusinessType.Send && dto.Handler != null)
+                if (prevStep.BusinessType == EBusinessType.Send && handler != null)
                 {
-                    await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = dto.Handler.UserId, CenterToOrgHandlerName = dto.Handler.Username })
+                    await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = handler.UserId, CenterToOrgHandlerName = handler.Username })
                         .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
                 }
+
                 var flowDirection = await _workflowApplication.PreviousAsync(dto, HttpContext.RequestAborted);
                 var processType = flowDirection == EFlowDirection.OrgToCenter || flowDirection == EFlowDirection.CenterToCenter
                     ? EProcessType.Zhiban
@@ -5200,9 +5210,9 @@ public class OrderController : BaseController
             audit.State = ESendBackAuditState.End;
             audit.AuditUser = "默认通过";
             audit.AuditTime = DateTime.Now;
-            if (prevStep.BusinessType == EBusinessType.Send && dto.Handler != null)
+            if (prevStep.BusinessType == EBusinessType.Send && handler != null)
             {
-                await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = dto.Handler.UserId, CenterToOrgHandlerName = dto.Handler.Username })
+                await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = handler.UserId, CenterToOrgHandlerName = handler.Username })
                     .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
             }
             var flowDirection = await _workflowApplication.PreviousAsync(dto, HttpContext.RequestAborted);
@@ -5251,16 +5261,33 @@ public class OrderController : BaseController
                 var (currentStep, prevStep, isOrgToCenter, isSecondToFirstOrgLevel) = await _workflowApplication.GetPreviousInformationAsync(
                     order.WorkflowId, sendBack.WorkflowUserId, sendBack.WorkflowOrgId, sendBack.WorkflowRoleIds.ToArray(),
                     HttpContext.RequestAborted);
+
                 if (prevStep.BusinessType == EBusinessType.Send)
                 {
+                    FlowStepHandler? handler = null;
                     // 平均派单
                     var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                     if (averageSendOrder)
                     {
-                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                        sendBack.SendBackData.Handler = handler;
+                        handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                        //sendBack.SendBackData.Handler = handler;
+                        sendBack.SendBackData.ReverseFlowStepAssignInfo =
+                            _orderDomainService.GetOrderPreviousAssignInfo(prevStep.BusinessType, handler is null ? null : new StepAssignInfo
+                            {
+                                FlowAssignType = EFlowAssignType.User,
+                                UserId = handler.UserId,
+                                Username = handler.Username,
+                                OrgId = handler.OrgId,
+                                OrgName = handler.OrgName,
+                                RoleId = handler.RoleId,
+                                RoleName = handler.RoleName
+                            });
                     }
-                    await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = sendBack.SendBackData.Handler.UserId, CenterToOrgHandlerName = sendBack.SendBackData.Handler.Username })
+                    await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
+                    {
+                        CenterToOrgHandlerId = handler.UserId,
+                        CenterToOrgHandlerName = handler.Username
+                    })
                         .Where(o => o.Id == sendBack.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
                 }
             }
@@ -5325,14 +5352,30 @@ public class OrderController : BaseController
                         HttpContext.RequestAborted);
                     if (prevStep.BusinessType == EBusinessType.Send)
                     {
+                        FlowStepHandler? handler = null;
                         // 平均派单
                         var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                         if (averageSendOrder)
                         {
-                            var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                            sendBack.SendBackData.Handler = handler;
+                            handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                            //sendBack.SendBackData.Handler = handler;
+                            sendBack.SendBackData.ReverseFlowStepAssignInfo =
+                                _orderDomainService.GetOrderPreviousAssignInfo(prevStep.BusinessType, handler is null ? null : new StepAssignInfo
+                                {
+                                    FlowAssignType = EFlowAssignType.User,
+                                    UserId = handler.UserId,
+                                    Username = handler.Username,
+                                    OrgId = handler.OrgId,
+                                    OrgName = handler.OrgName,
+                                    RoleId = handler.RoleId,
+                                    RoleName = handler.RoleName
+                                });
                         }
-                        await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = sendBack.SendBackData.Handler.UserId, CenterToOrgHandlerName = sendBack.SendBackData.Handler.Username })
+                        await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
+                        {
+                            CenterToOrgHandlerId = handler.UserId,
+                            CenterToOrgHandlerName = handler.Username
+                        })
                             .Where(o => o.Id == sendBack.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
                     }
                 }
@@ -5714,13 +5757,14 @@ public class OrderController : BaseController
         await _orderSpecialRepository.AddAsync(model, HttpContext.RequestAborted);
         if (model.State == 1)
         {
+            var handler = dto.NextHandlers.FirstOrDefault();
             if (_appOptions.Value.IsZiGong && dto.BusinessType == EBusinessType.Send)
             {
                 // 平均派单
                 var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                 if (averageSendOrder)
                 {
-                    var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                    handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
                     dto.NextHandlers = new List<FlowStepHandler> { handler };
                     await _orderSpecialRepository.Updateable().SetColumns(x => new OrderSpecial { NextHandlers = dto.NextHandlers }).Where(x => x.Id == model.Id).ExecuteCommandAsync();
                 }
@@ -5779,9 +5823,6 @@ public class OrderController : BaseController
                 expiredTime.NearlyExpiredTimeOne = order.NearlyExpiredTimeOne.Value;
             }
 
-            //var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now,
-            //	ETimeType.WorkDay,
-            //	dto.TimeLimit.Value, order.AcceptTypeCode);
             await _orderRepository.Updateable().SetColumns(o => new Orders.Order()
             {
                 ExpiredTime = expiredTime.ExpiredTime,
@@ -5800,8 +5841,16 @@ public class OrderController : BaseController
                 await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { CenterToOrgHandlerId = flowStepHandler.UserId, CenterToOrgHandlerName = flowStepHandler.Username })
                     .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
             }
-            //}
-            await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Recall,
+
+            StepAssignInfo? stepAssignInfo = null;
+            if(handler is not null)
+            {
+                stepAssignInfo = _mapper.Map<StepAssignInfo>(handler);
+                stepAssignInfo.FlowAssignType = EFlowAssignType.User;
+            }
+            var reverseFlowStepAssignInfo = _orderDomainService.GetOrderRecallAssignInfo(dto.BusinessType, stepAssignInfo);
+            await _workflowDomainService.RecallAsync(recall, reverseFlowStepAssignInfo, EWorkflowTraceType.Recall,
+                expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EHandleMode.Recall,
                 HttpContext.RequestAborted);
 
             if (order.Status >= EOrderStatus.Filed)
@@ -5925,13 +5974,14 @@ public class OrderController : BaseController
 
         if (model.State == 1)
         {
+            var handler = dto.NextHandlers.FirstOrDefault();
             if (_appOptions.Value.IsZiGong && dto.BusinessType == EBusinessType.Send)
             {
                 // 平均派单
                 var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                 if (averageSendOrder)
                 {
-                    var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                    handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
                     dto.NextHandlers = new List<FlowStepHandler> { handler };
                     await _orderSpecialRepository.Updateable().SetColumns(x => new OrderSpecial { NextHandlers = dto.NextHandlers }).Where(x => x.Id == model.Id).ExecuteCommandAsync();
                 }
@@ -6003,8 +6053,19 @@ public class OrderController : BaseController
                     .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
             }
 
-            await _workflowApplication.RecallAsync(recall, endTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Redo,
+            StepAssignInfo? stepAssignInfo = null;
+            if (handler is not null)
+            {
+                stepAssignInfo = _mapper.Map<StepAssignInfo>(handler);
+                stepAssignInfo.FlowAssignType = EFlowAssignType.User;
+            }
+            var reverseFlowStepAssignInfo = _orderDomainService.GetOrderRecallAssignInfo(dto.BusinessType, stepAssignInfo);
+            await _workflowDomainService.RecallAsync(recall, reverseFlowStepAssignInfo, EWorkflowTraceType.Redo,
+                endTime, order.Status >= EOrderStatus.Filed, EHandleMode.Redo,
                 HttpContext.RequestAborted);
+
+            //await _workflowApplication.RecallAsync(recall, endTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Redo,
+            //    HttpContext.RequestAborted);
             //var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == dto.OrderId);
             //if (publish != null)
             //{
@@ -6072,13 +6133,14 @@ public class OrderController : BaseController
         var order = await _orderRepository.GetAsync(x => x.Id == special.OrderId);
         if (special.State == 1)
         {
+            var handler = dto.NextHandlers.FirstOrDefault();
             if (_appOptions.Value.IsZiGong && special.BusinessType == EBusinessType.Send)
             {
                 // 平均派单
                 var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                 if (averageSendOrder)
                 {
-                    var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                    handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
                     special.NextHandlers = new List<FlowStepHandler> { handler };
                     await _orderSpecialRepository.Updateable().SetColumns(x => new OrderSpecial { NextHandlers = special.NextHandlers }).Where(x => x.Id == special.Id).ExecuteCommandAsync();
                 }
@@ -6155,8 +6217,18 @@ public class OrderController : BaseController
 
             //todo 特提重办,按审批通过时间依据中心派至部门的规则计算期满时间,更新order
 
-            await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Recall,
+            StepAssignInfo? stepAssignInfo = null;
+            if (handler is not null)
+            {
+                stepAssignInfo = _mapper.Map<StepAssignInfo>(handler);
+                stepAssignInfo.FlowAssignType = EFlowAssignType.User;
+            }
+            var reverseFlowStepAssignInfo = _orderDomainService.GetOrderRecallAssignInfo(dto.BusinessType, stepAssignInfo);
+            await _workflowDomainService.RecallAsync(recall, reverseFlowStepAssignInfo, EWorkflowTraceType.Recall,
+                expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EHandleMode.Recall,
                 HttpContext.RequestAborted);
+            //await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Recall,
+            //    HttpContext.RequestAborted);
 
 
             if (order.Status >= EOrderStatus.Filed)
@@ -6229,13 +6301,14 @@ public class OrderController : BaseController
             var order = await _orderRepository.GetAsync(x => x.Id == special.OrderId);
             if (special.State == 1)
             {
+                var handler = dto.NextHandlers.FirstOrDefault();
                 if (_appOptions.Value.IsZiGong && special.BusinessType == EBusinessType.Send)
                 {
                     // 平均派单
                     var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                     if (averageSendOrder)
                     {
-                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                        handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
                         special.NextHandlers = new List<FlowStepHandler> { handler };
                         await _orderSpecialRepository.Updateable().SetColumns(x => new OrderSpecial { NextHandlers = special.NextHandlers }).Where(x => x.Id == special.Id).ExecuteCommandAsync();
                     }
@@ -6310,8 +6383,18 @@ public class OrderController : BaseController
                 }
                 //todo 特提重办,按审批通过时间依据中心派至部门的规则计算期满时间,更新order
 
-                await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Recall,
+                StepAssignInfo? stepAssignInfo = null;
+                if (handler is not null)
+                {
+                    stepAssignInfo = _mapper.Map<StepAssignInfo>(handler);
+                    stepAssignInfo.FlowAssignType = EFlowAssignType.User;
+                }
+                var reverseFlowStepAssignInfo = _orderDomainService.GetOrderRecallAssignInfo(dto.BusinessType, stepAssignInfo);
+                await _workflowDomainService.RecallAsync(recall, reverseFlowStepAssignInfo, EWorkflowTraceType.Recall,
+                    expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EHandleMode.Recall,
                     HttpContext.RequestAborted);
+                //await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Recall,
+                //    HttpContext.RequestAborted);
                 if (order.Status >= EOrderStatus.Filed)
                 {
                     var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == special.OrderId);

+ 8 - 4
src/Hotline.Api/Controllers/QualityController.cs

@@ -674,13 +674,17 @@ namespace Hotline.Api.Controllers
                 if ((DateTime.Now - transfer.TransferTime.Value).TotalMinutes >= 30)
                 {
                     await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Lose }).Where(x => x.Id == transfer.Id).ExecuteCommandAsync();
-                }
+					await _qualitey.Updateable().SetColumns(x => new Hotline.Quality.Quality { TransferState = EQualityTransferState.Lose }).Where(x => x.Id == transfer.QualityId).ExecuteCommandAsync();
+				}
                 return;
             }
             else {
-				var notStarted = await _qualityTransferRecordsRepository.Queryable().Where(x => x.IsFinished == false && x.TransferState == EQualityTransferState.NotStarted).OrderBy(x => x.TransferTime).FirstAsync();
-				await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Translating , TransferTime = DateTime.Now }).Where(x => x.Id == notStarted.Id).ExecuteCommandAsync();
-				Task.Run(async () => await _qualityApplication.Transfer_XT(notStarted.QualityId, HttpContext.RequestAborted));
+                var trany = await _qualityTransferRecordsRepository.Queryable().Where(x => x.IsFinished == false && x.TransferState == EQualityTransferState.NotStarted).AnyAsync();
+                if (trany) {
+					var notStarted = await _qualityTransferRecordsRepository.Queryable().Where(x => x.IsFinished == false && x.TransferState == EQualityTransferState.NotStarted).OrderBy(x => x.TransferTime).FirstAsync();
+					await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Translating, TransferTime = DateTime.Now }).Where(x => x.Id == notStarted.Id).ExecuteCommandAsync();
+					Task.Run(async () => await _qualityApplication.Transfer_XT(notStarted.QualityId, HttpContext.RequestAborted));
+				}
 			}
 		}
 

+ 4 - 4
src/Hotline.Application/FlowEngine/IWorkflowApplication.cs

@@ -54,10 +54,10 @@ namespace Hotline.Application.FlowEngine
         Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, string applicantId, string applicantOrgId, string[] applicantRoleIds,
             CancellationToken cancellationToken);
 
-        /// <summary>
-        /// 撤回至任意节点
-        /// </summary>
-        Task RecallAsync(RecallDto dto, DateTime? expiredTime, bool isOrderFiled, EWorkflowTraceType traceType, CancellationToken cancellationToken);
+        ///// <summary>
+        ///// 撤回至任意节点
+        ///// </summary>
+        //Task RecallAsync(RecallDto dto, FlowAssignInfo flowAssignInfo, DateTime? expiredTime, bool isOrderFiled, EWorkflowTraceType traceType, CancellationToken cancellationToken);
 
         /// <summary>
         /// 办理至结束节点(无视流程模板配置,操作人需是当前节点办理对象)

+ 62 - 87
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -347,14 +347,21 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
             withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
-        return await _workflowDomainService.PreviousAsync(workflow, dto,
+        //return await _workflowDomainService.PreviousAsync(workflow, dto,
+        //    _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+        //    _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+        //    _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName,
+        //    _sessionContextProvider.SessionContext.OrgIsCenter, _sessionContextProvider.SessionContext.Roles,
+        //    cancellationToken);
+
+        return await _workflowDomainService.PreviousAsync(workflow, dto, new OperatorInfo(
             _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
             _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
             _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName,
             _sessionContextProvider.SessionContext.OrgIsCenter, _sessionContextProvider.SessionContext.Roles,
-            cancellationToken);
+            _sessionContextProvider.SessionContext.OrgLevel), cancellationToken);
     }
 
     /// <summary>
@@ -363,66 +370,73 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     public async Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, string applicantId, string applicantOrgId, string[] applicantRoleIds,
         CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
             withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
         var user = await _userRepository.Queryable()
             .Includes(x => x.Organization)
             .FirstAsync(x => x.Id == applicantId, cancellationToken);
-        return await _workflowDomainService.PreviousAsync(workflow, dto,
+        //return await _workflowDomainService.PreviousAsync(workflow, dto,
+        //    applicantId, user.Name,
+        //    applicantOrgId, user.Organization.Name,
+        //    user.Organization.AreaCode, user.Organization.AreaName,
+        //    user.Organization.IsCenter, applicantRoleIds, cancellationToken);
+
+        return await _workflowDomainService.PreviousAsync(workflow, dto, new OperatorInfo(
             applicantId, user.Name,
             applicantOrgId, user.Organization.Name,
             user.Organization.AreaCode, user.Organization.AreaName,
-            user.Organization.IsCenter, applicantRoleIds, cancellationToken);
+            user.Organization.IsCenter, applicantRoleIds,
+            user.Organization.Level), cancellationToken);
     }
 
-    /// <summary>
-    /// 撤回至之前任意节点
-    /// </summary>
-    public async Task RecallAsync(RecallDto dto, DateTime? expiredTime, bool isOrderFiled, EWorkflowTraceType traceType,
-        CancellationToken cancellationToken)
-    {
-        var validator = new RecallDtoValidator();
-        var validationResult = await validator.ValidateAsync(dto, cancellationToken);
-        if (!validationResult.IsValid)
-            throw new UserFriendlyException(string.Join(',', validationResult.Errors));
+    ///// <summary>
+    ///// 撤回至之前任意节点
+    ///// </summary>
+    //public async Task RecallAsync(RecallDto dto, FlowAssignInfo flowAssignInfo, DateTime? expiredTime, bool isOrderFiled, EWorkflowTraceType traceType,
+    //    CancellationToken cancellationToken)
+    //{
+    //    var validator = new RecallDtoValidator();
+    //    var validationResult = await validator.ValidateAsync(dto, cancellationToken);
+    //    if (!validationResult.IsValid)
+    //        throw new UserFriendlyException(string.Join(',', validationResult.Errors));
 
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
-            withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
+    //    var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
+    //        withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
 
-        //await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
+    //    //await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
 
-        var targetStepDefine = _workflowDomainService.GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode);
-        if (targetStepDefine.StepType is EStepType.End)
-            throw UserFriendlyException.SameMessage("结束节点不支持撤回");
-        //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
-        var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin);
-        if (targetStep is null)
-            throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
+    //    var targetStepDefine = _workflowDomainService.GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+    //    if (targetStepDefine.StepType is EStepType.End)
+    //        throw UserFriendlyException.SameMessage("结束节点不支持撤回");
+    //    //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
+    //    var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin);
+    //    if (targetStep is null)
+    //        throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
 
-        ///退回到派单组 没有下一步办理人 获取之前节点办理人
-        if (!dto.NextHandlers.Any())
-        {
-            dto.NextHandlers.Add(new FlowStepHandler()
-            {
-                UserId = targetStep.HandlerId,
-                Username = targetStep.HandlerName,
-                OrgId = targetStep.HandlerOrgId,
-                OrgName = targetStep.HandlerOrgName,
-                Key = targetStep.HandlerId,
-                Value = targetStep.HandlerName,
-                RoleId = targetStep.RoleId,
-                RoleName = targetStep.RoleName
-            });
-        }
+    //    //退回到派单组 没有下一步办理人 获取之前节点办理人
+    //    if (!dto.NextHandlers.Any())
+    //    {
+    //        dto.NextHandlers.Add(new FlowStepHandler()
+    //        {
+    //            UserId = targetStep.HandlerId,
+    //            Username = targetStep.HandlerName,
+    //            OrgId = targetStep.HandlerOrgId,
+    //            OrgName = targetStep.HandlerOrgName,
+    //            Key = targetStep.HandlerId,
+    //            Value = targetStep.HandlerName,
+    //            RoleId = targetStep.RoleId,
+    //            RoleName = targetStep.RoleName
+    //        });
+    //    }
 
-        var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.HandlerType, dto.IsStartCountersign,
-            dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), cancellationToken);
+    //    //var flowAssignInfo = await _workflowDomainService.GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.HandlerType, dto.IsStartCountersign,
+    //    //    dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), cancellationToken);
 
-        //var stepHandlers = await GetNextStepHandlersAsync(workflow, targetStepDefine, dto, cancellationToken);
+    //    //var stepHandlers = await GetNextStepHandlersAsync(workflow, targetStepDefine, dto, cancellationToken);
 
-        await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, traceType, expiredTime, isOrderFiled,
-          EHandleMode.Recall, cancellationToken);
-    }
+    //    await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, traceType, expiredTime, isOrderFiled,
+    //      EHandleMode.Recall, cancellationToken);
+    //}
 
     /// <summary>
     /// 无视流程模板配置直接将当前节点办理至结束节点
@@ -1713,49 +1727,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             }
         }
 
-        return await GetNextStepFlowAssignInfoByDefineAsync(nextStepDefine, dto.HandlerType, isStartCountersign, handlers,
+        return await _workflowDomainService.GetNextStepFlowAssignInfoByDefineAsync(nextStepDefine, dto.HandlerType, isStartCountersign, handlers,
             cancellationToken);
     }
 
-    /// <summary>
-    /// 按流程模板配置创建下一步办理对象
-    /// </summary>
-    private async Task<FlowAssignInfo> GetNextStepFlowAssignInfoByDefineAsync(StepDefine nextStepDefine,
-        EHandlerType handlerType, bool isStartCountersign, List<Kv> 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();
-        }
-    }
-
     public ISugarQueryable<WorkflowCountersign, Workflow, Order> QueryOrderCountersigns(QueryOrderCountersignDto dto, ISessionContext _sessionContext)
     {
         var Role = _sessionContext.Roles;

+ 1 - 59
src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs

@@ -259,65 +259,7 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
                     knowledge.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
                     await _knowledgeRepository.UpdateAsync(knowledge, cancellationToken);
                     break;
-                //case WorkflowModuleConsts.OrderScreen:
-                //    var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order)
-                //        .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
-                //    if (screen != null)
-                //    {
-                //        screen.Status = EScreenStatus.Approval;
-                //        screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
-                //        //如果下个节点是省审批,则修改为省甄别
-                //        if (nextTag is not null && nextTag.Type == TagDefaults.TagType.Org && nextTag.Value == TagDefaults.TagValue.Province)
-                //            screen.IsProScreen = true;
-                //        await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
-                //    }
-
-                //    if (nextTag is not null && nextTag.Type == TagDefaults.TagType.Org)
-                //    {
-                //        switch (nextTag.Value)
-                //        {
-                //            case TagDefaults.TagValue.Province:
-                //                if (screen != null)
-                //                {
-                //                    var screenDto = _mapper.Map<OrderScreenListDto>(screen);
-                //                    if (screen.Order != null && screen.Order.Source == ESource.ProvinceStraight)
-                //                    {
-                //                        var screenOrderDto = _mapper.Map<OrderDto>(screen.Order);
-                //                        //推省上
-                //                        _capPublisher.Publish(EventNames.HotlineOrderScreenApply, new PublishScreenDto()
-                //                        {
-                //                            Order = screenOrderDto,
-                //                            Screen = screenDto,
-                //                            ClientGuid = ""
-                //                        });
-                //                        //try
-                //                        //{
-                //                        //    await _provinceService.ScreenCaseInfoSend(new PublishScreenDto()
-                //                        //    {
-                //                        //        Order = screenOrderDto,
-                //                        //        Screen = screenDto,
-                //                        //        ClientGuid = ""
-                //                        //    }, cancellationToken);
-                //                        //}
-                //                        //catch (Exception e)
-                //                        //{
-                //                        //    _logger.LogError(
-                //                        //        "_provinceService.ScreenCaseInfoSend throw exception: {ex}", e.Message);
-                //                        //}
-                //                    }
-                //                }
-
-                //                break;
-                //        }
-                //    }
-
-                //OrderScreenDetail detail = new OrderScreenDetail
-                //{
-                //	ScreenId = screen.Id
-                //};
-                //detail.Audit(_sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, 1);
-                //await _orderScreenDetailRepository.AddAsync(detail, cancellationToken);
-                //break;
+                
                 case WorkflowModuleConsts.OrderDelay:
                     var orderDelay = await _orderDelayRepository.Queryable().Includes(x => x.Order)
                         .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);

+ 1 - 2
src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs

@@ -2,7 +2,6 @@
 using Hotline.Application.CallCenter;
 using Hotline.Application.Quality;
 using Hotline.Caching.Interfaces;
-using Hotline.CallCenter.Configs;
 using Hotline.Configurations;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
@@ -14,6 +13,7 @@ using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Enums.CallCenter;
+using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Enums.Quality;
 using Hotline.Share.Mq;
@@ -23,7 +23,6 @@ using MapsterMapper;
 using MediatR;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using XF.Domain.Entities;
 using XF.Domain.Repository;
 
 namespace Hotline.Application.Handlers.FlowEngine

+ 15 - 5
src/Hotline.Application/Orders/OrderApplication.cs

@@ -3638,21 +3638,31 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             //特提(撤回至发起)
             if (!string.IsNullOrEmpty(order.WorkflowId))
             {
-                var nextHandler = new List<FlowStepHandler>();
+                FlowStepHandler? handler = null;
                 if (_appOptions.Value.IsZiGong)
                 {
                     // 平均派单
                     var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                     if (averageSendOrder)
                     {
-                        var handler = await _orderDomainService.AverageOrder(cancellationToken);
-                        nextHandler = new List<FlowStepHandler> { handler };
+                        handler = await _orderDomainService.AverageOrder(cancellationToken);
                     }
                 }
 
+                StepAssignInfo? stepAssignInfo = null;
+                if (handler is not null)
+                {
+                    stepAssignInfo = _mapper.Map<StepAssignInfo>(handler);
+                    stepAssignInfo.FlowAssignType = EFlowAssignType.User;
+                }
+
+                var reverseFlowStepAssignInfo = stepAssignInfo is null
+                    ? new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStep)
+                    : new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStepUser, stepAssignInfo);
                 //await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, order.Status >= EOrderStatus.Filed, cancellationToken);
-                var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, "省工单重派", order.Status >= EOrderStatus.Filed,
-                    order.ExpiredTime, nextHandler, EHandleMode.Redo, cancellationToken);
+                var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, "省工单重派", reverseFlowStepAssignInfo,
+                    order.Status >= EOrderStatus.Filed, order.ExpiredTime, EHandleMode.Redo, cancellationToken);
+                
                 order.FileEmpty();
 
                 order.CurrentStepName = workflow.CurrentStepName;

+ 5 - 1
src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs

@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
 using System.Threading;
 using Hotline.Configurations;
+using Hotline.FlowEngine;
 using Hotline.Repository.SqlSugar.Extensions;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
@@ -218,7 +219,10 @@ namespace Hotline.Application.Orders
 					}
                 }
 
-                await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, isOrderFiled, EWorkflowTraceType.SecondHandle, cancellationToken);
+                var reverseFlowStepAssignInfo = new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStep);
+                await _workflowDomainService.RecallAsync(recall, reverseFlowStepAssignInfo, EWorkflowTraceType.SecondHandle, 
+                    expiredTime.ExpiredTime, isOrderFiled,  EHandleMode.SecondaryHandle, cancellationToken);
+
                 visit.VisitState = EVisitState.None;
                 await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
 			}

+ 2 - 2
src/Hotline.Application/Quality/QualityApplication.cs

@@ -404,7 +404,7 @@ namespace Hotline.Application.Quality
 
 			var quality = await _qualityRepository.Queryable().Includes(x => x.Order).Where(x => x.Id == Id).FirstAsync();
 			var call = await _callApplication.GetCallAsync(quality.Order.CallId, cancellationToken);
-			var items = await _qualiteyItem.Queryable().Where(x => x.IsEnable == 0 && x.IsDeleted == true && x.IsIntelligent == 1).ToListAsync();
+			var items = await _qualiteyItem.Queryable().Where(x => x.IsEnable == 1 && x.IsDeleted == false && x.IsIntelligent == 1).ToListAsync();
 
 			var transfers = new List<Transfer>();
 			if (!string.IsNullOrEmpty(call?.AudioFile))
@@ -448,7 +448,7 @@ namespace Hotline.Application.Quality
 										};
                                         details.Add(detail);
 									}
-									model.Prohibited = string.Join(",", hitWord);
+									model.Prohibited = string.Join(",", hitWord.Select(x=>x.Name));
                                     await _qualiteyDetail.AddRangeAsync(details);
 								}
 							}

+ 27 - 6
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -304,6 +304,7 @@ namespace Hotline.Application.Subscribers
             }
             else
             {
+                FlowStepHandler? handler = null;
                 var nextHandler = new List<FlowStepHandler>();
                 if (_appOptions.Value.IsZiGong)
                 {
@@ -311,12 +312,27 @@ namespace Hotline.Application.Subscribers
                     var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                     if (averageSendOrder)
                     {
-                        var handler = await _orderDomainService.AverageOrder(cancellationToken);
+                        handler = await _orderDomainService.AverageOrder(cancellationToken);
                         nextHandler = new List<FlowStepHandler> { handler };
                     }
                 }
-                var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, 
-                    dto.Opinion, order.Status >= EOrderStatus.Filed, order.ExpiredTime, nextHandler, EHandleMode.Recall, cancellationToken);
+
+                StepAssignInfo? stepAssignInfo = null;
+                if (handler is not null)
+                {
+                    stepAssignInfo = _mapper.Map<StepAssignInfo>(handler);
+                    stepAssignInfo.FlowAssignType = EFlowAssignType.User;
+                }
+
+                var reverseFlowStepAssignInfo = stepAssignInfo is null
+                    ? new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStep)
+                    : new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStepUser, stepAssignInfo);
+
+                //var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, 
+                //    dto.Opinion, order.Status >= EOrderStatus.Filed, order.ExpiredTime, nextHandler, EHandleMode.Recall, cancellationToken);
+                var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion, reverseFlowStepAssignInfo,
+                    order.Status >= EOrderStatus.Filed, order.ExpiredTime, EHandleMode.Recall, cancellationToken);
+
                 order.FiledTime = null;
                 order.Status = isPaiDan ? EOrderStatus.Special : EOrderStatus.WaitForAccept;
 
@@ -838,9 +854,14 @@ namespace Hotline.Application.Subscribers
                     //退回:撤回至发起人
                     //await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, dto.Opinion, order.Status >= EOrderStatus.Filed,
                     //    order.ExpiredTime, cancellationToken); //todo think是否需要保存附件至省平台办理节点?
-                    var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(
-                        order.WorkflowId, dto.Opinion, order.Status >= EOrderStatus.Filed, 
-                        order.ExpiredTime, null, EHandleMode.Recall, cancellationToken);
+                    
+                    //var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(
+                    //    order.WorkflowId, dto.Opinion, order.Status >= EOrderStatus.Filed, 
+                    //    order.ExpiredTime, null, EHandleMode.Recall, cancellationToken);
+
+                    var reverseFlowStepAssignInfo = new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStep);
+                    var (isPaiDan, workflow) = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion, reverseFlowStepAssignInfo,
+                        order.Status >= EOrderStatus.Filed, order.ExpiredTime, EHandleMode.Recall, cancellationToken);
 
                     order.Status = isPaiDan ? EOrderStatus.Special : EOrderStatus.WaitForAccept;
                     order.CurrentStepName = workflow.CurrentStepName;

+ 4 - 4
src/Hotline.Repository.SqlSugar/BaseRepositoryWorkflow.cs

@@ -42,8 +42,8 @@ public class BaseRepositoryWorkflow<TEntity> : BaseRepository<TEntity>, IReposit
 
     public async Task<string> AddAsync(TEntity entity, CancellationToken cancellationToken = default)
     {
-        entity.InitDatePermission(_dataPermissionFilterBuilder.DataPermissionManager)
-            .AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager);
+        entity.InitDatePermission(_dataPermissionFilterBuilder.DataPermissionManager);
+            //.AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager);
         var excEntity = await Db.Insertable(entity).ExecuteReturnEntityAsync();
         return excEntity.Id;
     }
@@ -56,8 +56,8 @@ public class BaseRepositoryWorkflow<TEntity> : BaseRepository<TEntity>, IReposit
     /// <returns></returns>
     public async Task AddRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
     {
-        entities.ForEach(d => d.InitDatePermission(_dataPermissionFilterBuilder.DataPermissionManager)
-                .AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager));
+        entities.ForEach(d => d.InitDatePermission(_dataPermissionFilterBuilder.DataPermissionManager));
+                //.AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager));
         await Db.Insertable(entities).ExecuteCommandAsync();
     }
 }

+ 8 - 8
src/Hotline.Repository.SqlSugar/Extensions/DataPermissionExtensions.cs

@@ -131,13 +131,13 @@ namespace Hotline.Repository.SqlSugar.Extensions
             return entity;
         }
 
-        public static TEntity AssignToCreator<TEntity>(this TEntity entity,
-            IDataPermissionManager dataPermissionManager)
-            where TEntity : class, IEntity<string>, IDataPermission, IWorkflow, new()
-        {
-            var (creatorId, _, _, _, _, _) = dataPermissionManager.GetDataPermissionOptions();
-            entity.Assign(EFlowAssignType.User, creatorId);
-            return entity;
-        }
+        //public static TEntity AssignToCreator<TEntity>(this TEntity entity,
+        //    IDataPermissionManager dataPermissionManager)
+        //    where TEntity : class, IEntity<string>, IDataPermission, IWorkflow, new()
+        //{
+        //    var (creatorId, _, _, _, _, _) = dataPermissionManager.GetDataPermissionOptions();
+        //    entity.Assign(EFlowAssignType.User, creatorId);
+        //    return entity;
+        //}
     }
 }

+ 2 - 2
src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs

@@ -71,8 +71,8 @@ namespace Hotline.Repository.SqlSugar.Orders
 
         public async Task<string> AddOrderNavAsync(Order order, CancellationToken cancellationToken)
         {
-            //弥补AddNav方法没有自动指派到创建人
-            order.AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager);
+            ////弥补AddNav方法没有自动指派到创建人
+            //order.AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager);
 
             await AddNav(order).Include(d => d.OrderExtension).Include(d => d.OrderTags).ExecuteCommandAsync();
 

+ 118 - 18
src/Hotline.Share/Dtos/FlowEngine/PreviousWorkflowDto.cs

@@ -1,4 +1,7 @@
-namespace Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.FlowEngine;
+using System.Security.Authentication;
+
+namespace Hotline.Share.Dtos.FlowEngine;
 
 public class PreviousWorkflowDto : EndWorkflowIdDto
 {
@@ -11,40 +14,137 @@ public class PreviousWorkflowDto : EndWorkflowIdDto
     /// 期满时间
     /// </summary>
     public DateTime? ExpiredTime { get; set; }
-    
+
+    ///// <summary>
+    ///// 指定办理对象
+    ///// </summary>
+    //public FlowStepHandler? Handler { get; set; }
+
     /// <summary>
-    /// 指定办理对象
+    /// 逆向流程,节点指派方式
+    /// 退回、特提
     /// </summary>
-    public FlowStepHandler? Handler { get; set; }
+    public ReverseFlowStepAssignInfo ReverseFlowStepAssignInfo { get; set; }
 }
 
 /// <summary>
-/// 目标节点指派策略信息(流程流转时,指派的目标节点)
+/// 逆向流程,节点指派方式
 /// </summary>
-public class TargetStepAssignPolicyInfo
+public class ReverseFlowStepAssignInfo
 {
-    public ETargetStepAssignPolicy TargetStepAssignPolicy { get; set; }
-    
+    public ReverseFlowStepAssignInfo()
+    {
+        
+    }
+
+    public ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy reverseFlowStepCreationPolicy, StepAssignInfo? stepAssignInfo = null)
+    {
+        ReverseFlowStepCreationPolicy = reverseFlowStepCreationPolicy;
+        StepAssignInfo = stepAssignInfo;
+    }
+
+    public EReverseFlowStepCreationPolicy ReverseFlowStepCreationPolicy { get; set; } =
+        EReverseFlowStepCreationPolicy.OriginStep;
+
     /// <summary>
-    /// 指定办理对象(非指定办理对象时无效)
+    /// 节点指定办理对象(非指定办理对象时无效)
     /// </summary>
-    public FlowStepHandler? Handler { get; set; }
+    public StepAssignInfo? StepAssignInfo { get; set; }
 }
 
-public enum ETargetStepAssignPolicy
+/// <summary>
+/// 逆向流程,节点创建策略
+/// </summary>
+public enum EReverseFlowStepCreationPolicy
 {
     /// <summary>
-    /// 按配置
+    /// 保持原节点的办理对象不变
     /// </summary>
-    Config = 0,
-    
+    OriginStep = 1,
+
     /// <summary>
-    /// 保持目标节点的办理对象不变
+    /// 指派给原节点办理人
     /// </summary>
-    TargetStep = 1,
-    
+    OriginStepUser = 2,
+
+    /// <summary>
+    /// 指派给原节点办理部门
+    /// </summary>
+    OriginStepOrg = 3,
+
+    /// <summary>
+    /// 指派给原节点办理角色
+    /// </summary>
+    OriginStepRole = 4,
+
+    /// <summary>
+    /// 指派给原节点办理部门及角色
+    /// </summary>
+    OriginStepOrgAndRole = 5,
+
     /// <summary>
     /// 指定办理对象
     /// </summary>
-    AssignHandler = 2,
+    AssignHandler = 20,
+
+    /// <summary>
+    /// 保持原节点配置(指派给角色)
+    /// </summary>
+    OriginDefinition = 30,
+}
+
+public class StepAssignInfo : FlowStepHandler
+{
+    public EFlowAssignType FlowAssignType { get; set; }
+}
+
+/// <summary>
+/// 操作人信息
+/// </summary>
+public class OperatorInfo
+{
+    public OperatorInfo()
+    {
+
+    }
+
+    public OperatorInfo(
+        string? userId, string? userName,
+        string? orgId, string? orgName,
+        string? orgAreaCode, string orgAreaName,
+        bool orgIsCenter, string[] roles,
+        int orglevel)
+    {
+        UserId = userId;
+        UserName = userName;
+        OrgId = orgId;
+        OrgName = orgName;
+        OrgLevel = orglevel;
+        OrgIsCenter = orgIsCenter;
+        OrgAreaCode = orgAreaCode;
+        Roles = roles;
+    }
+
+    public string? UserId { get; init; }
+
+    public string? UserName { get; init; }
+
+    public string? OrgId { get; init; }
+
+    public string? OrgName { get; init; }
+    public int OrgLevel { get; init; }
+    public bool OrgIsCenter { get; init; }
+
+    public string? OrgAreaCode { get; init; }
+
+    /// <summary>
+    /// 部门行政区划名称
+    /// </summary>
+    public string? OrgAreaName { get; init; }
+
+    /// <summary>
+    /// Roles
+    /// </summary>
+    public string[] Roles { get; init; }
+
 }

+ 1 - 1
src/Hotline.Share/Dtos/FlowEngine/RecallDto.cs

@@ -1,7 +1,7 @@
 namespace Hotline.Share.Dtos.FlowEngine;
 
 /// <summary>
-/// 撤回
+/// 特提
 /// </summary>
 public class RecallDto : BasicWorkflowDto
 {

+ 1 - 1
src/Hotline.Share/Dtos/Order/OrderSpecialDto.cs

@@ -156,7 +156,7 @@ namespace Hotline.Share.Dtos.Order
 		/// <summary>
 		/// 模板配置节点业务类型
 		/// </summary>
-		public EBusinessType? BusinessType { get; set; } 
+		public EBusinessType BusinessType { get; set; } 
 
 	}
 

+ 24 - 0
src/Hotline.Share/Enums/FlowEngine/EFlowAssignType.cs

@@ -0,0 +1,24 @@
+namespace Hotline.Share.Enums.FlowEngine;
+
+public enum EFlowAssignType
+{
+    /// <summary>
+    /// 指派到部门
+    /// </summary>
+    Org = 0,
+
+    /// <summary>
+    /// 指派到用户
+    /// </summary>
+    User = 1,
+
+    /// <summary>
+    /// 指派到角色
+    /// </summary>
+    Role = 2,
+
+    /// <summary>
+    /// 指派到指定部门的指定角色
+    /// </summary>
+    OrgAndRole = 3,
+}

+ 6 - 0
src/Hotline.Share/Enums/FlowEngine/EHandleMode.cs

@@ -42,4 +42,10 @@ public enum EHandleMode
     /// </summary>
     [Description("重办")]
     Redo = 201,
+
+    /// <summary>
+    /// 二次办理
+    /// </summary>
+    [Description("二次办理")]
+    SecondaryHandle = 202,
 }

+ 1 - 1
src/Hotline/CallCenter/Tels/TelDomainService.cs

@@ -80,7 +80,7 @@ public class TelDomainService : ITelDomainService, IScopeDependency
         if (telRest == null)
             throw new UserFriendlyException($"无效分机休息编号, telRestId: {telRestId}", "无效分机休息编号");
         telRest.WorkflowId = workflowId;
-        telRest.Assign(assignInfo.FlowAssignType, assignInfo.GetHandlerIds());
+        //telRest.Assign(assignInfo.FlowAssignType, assignInfo.GetHandlerIds());
         await _telRestRepository.UpdateAsync(telRest, cancellationToken);
     }
 

+ 0 - 14
src/Hotline/FlowEngine/FlowAssignInfo.cs

@@ -84,18 +84,4 @@ public class FlowAssignInfo
             EHandlerType.AssignedOrg => EFlowAssignType.Org,
             _ => throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, null)
         };
-}
-
-public class StepAssignInfo
-{
-    /// <summary>
-    /// 流程指派类型
-    /// </summary>
-    public EFlowAssignType FlowAssignType { get; set; }
-
-    /// <summary>
-    /// 办理对象
-    /// </summary>
-    public FlowStepHandler Handler { get; set; }
-    
 }

+ 1 - 0
src/Hotline/FlowEngine/Notifications/WorkflowNotify.cs

@@ -1,6 +1,7 @@
 using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.FlowEngine;
 using MediatR;
 using XF.Domain.Entities;
 

+ 31 - 11
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -74,30 +74,47 @@ namespace Hotline.FlowEngine.Workflows
             bool applicantIsCenter, string[] applicantRoleIds,
             CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 退回(new)
+        /// </summary>
+        Task<EFlowDirection> PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, OperatorInfo operatorInfo, CancellationToken cancellationToken);
+
         /// <summary>
         /// 撤回(返回到之前任意节点)
         /// </summary>
-        Task RecallAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine, FlowAssignInfo flowAssignInfo,
+        Task<bool> RecallAsync(RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode,
+            CancellationToken cancellationToken);
+
+        Task<bool> RecallAsync(Workflow workflow, RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo, StepDefine targetStepDefine,
             EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, CancellationToken cancellationToken);
 
+        Task<bool> RecallAsync(Workflow workflow, RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            StepDefine targetStepDefine, WorkflowStep targetStep, EWorkflowTraceType traceType,
+            DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, CancellationToken cancellationToken);
+
+        Task<bool> RecallAsync(Workflow workflow, RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            WorkflowStep targetStep, EWorkflowTraceType traceType,
+            DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, CancellationToken cancellationToken);
+
         /// <summary>
         /// 撤回至开始节点
         /// </summary>
-        Task RecallToStartStepAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime, EHandleMode handleMode,
-            CancellationToken cancellationToken);
+        Task RecallToStartStepAsync(Workflow workflow, string opinion, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, CancellationToken cancellationToken);
 
         /// <summary>
         /// 特提至派单节点(无派单节点会抛异常)
         /// </summary>
-        Task RecallToSendStepAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime, EHandleMode handleMode,
-            CancellationToken cancellationToken);
+        Task RecallToSendStepAsync(Workflow workflow, string opinion, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo, 
+            DateTime? expiredTime, bool isOrderFiled,  EHandleMode handleMode, CancellationToken cancellationToken);
 
         /// <summary>
         /// 特提至中心(优先派单组其次坐席)
         /// </summary>
         /// <returns></returns>
-        Task<(bool, Workflow workflow)> RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime, List<FlowStepHandler> handlers,
-            EHandleMode handleMode, CancellationToken cancellationToken);
+        Task<(bool, Workflow workflow)> RecallToCenterFirstToSendAsync(string workflowId, string opinion, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo, 
+            bool isOrderFiled, DateTime? expiredTime, EHandleMode handleMode, CancellationToken cancellationToken);
 
         ///// <summary>
         ///// 跳转(直接将流程跳转至任意节点)
@@ -283,16 +300,16 @@ namespace Hotline.FlowEngine.Workflows
         /// 根据汇总对象id找到被汇总节点,生成指派到用户的办理对象
         /// </summary>
         FlowStepHandler GetSummaryTargetFlowStepHandler(Workflow workflow, string summaryTargetStepCode);
-        
+
         /// <summary>
         /// 追加归档信息(接收ds推送12315归档信息)
         /// </summary>
         Task AppendFileOpinionAsync(string workflowId, string opinion, List<FileDto> files, CancellationToken cancellationToken);
-        
+
         /// <summary>
         /// 跳转至结束节点(无视流程模板配置以及当前办理对象,直接跳至结束节点)
         /// </summary>
-        Task JumpToEndAsync(ISessionContext current,string workflowId, string opinion, List<FileDto> files, DateTime? expiredTime,
+        Task JumpToEndAsync(ISessionContext current, string workflowId, string opinion, List<FileDto> files, DateTime? expiredTime,
             EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default);
 
         Task JumpToEndAsync(ISessionContext current, Workflow workflow, string opinion, List<FileDto> files, DateTime? expiredTime,
@@ -303,9 +320,12 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         WorkflowStep GetCsLoopStartStep(List<WorkflowStep> steps, WorkflowStep currentStep);
 
-        Task HandlePublishTraceAsync(string workflowId, string orderPublishId, UserInfo acceptor, UserInfo handler, DateTime handleTime, 
+        Task HandlePublishTraceAsync(string workflowId, string orderPublishId, UserInfo acceptor, UserInfo handler, DateTime handleTime,
             UserInfo visitAcceptor, string orderVisitId, CancellationToken cancellation);
 
         Task HandleVisitTraceAsync(string orderVisitId, UserInfo visitor, DateTime visitTime, CancellationToken cancellation);
+
+        Task<FlowAssignInfo> GetNextStepFlowAssignInfoByDefineAsync(StepDefine nextStepDefine,
+            EHandlerType handlerType, bool isStartCountersign, List<Kv> handlers, CancellationToken cancellationToken);
     }
 }

+ 67 - 1
src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs

@@ -1,4 +1,5 @@
-using Hotline.Share.Dtos;
+using Hotline.FlowEngine.Definitions;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.FlowEngine.Definition;
@@ -7,6 +8,7 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using SqlSugar;
 using XF.Domain.Entities;
+using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 
 namespace Hotline.FlowEngine.Workflows;
@@ -501,6 +503,70 @@ public abstract class StepBasicEntity : CreationEntity
         RoleName = roleName;
     }
 
+    public void Assign(
+        EFlowAssignType flowAssignType,
+        string? handlerId, string? handlerName,
+        string? handlerOrgId, string? handlerOrgName,
+        string? roleId = null, string? roleName = null)
+    {
+        FlowAssignType = flowAssignType;
+        Assign(handlerId, handlerName, handlerOrgId, handlerOrgName, roleId, roleName);
+    }
+
+    public void Assign(WorkflowStep step, EFlowAssignType? flowAssignType = null)
+    {
+        FlowAssignType = flowAssignType ?? step.FlowAssignType;
+        HandlerId = step.HandlerId;
+        HandlerName = step.HandlerName;
+        HandlerOrgId = step.HandlerOrgId;
+        HandlerOrgName = step.HandlerOrgName;
+        RoleId = step.RoleId;
+        RoleName = step.RoleName;
+    }
+
+    public void Assign(StepAssignInfo stepAssignInfo)
+    {
+        FlowAssignType = stepAssignInfo?.FlowAssignType;
+        HandlerId = stepAssignInfo?.UserId;
+        HandlerName = stepAssignInfo?.Username;
+        HandlerOrgId = stepAssignInfo?.OrgId;
+        HandlerOrgName = stepAssignInfo?.OrgName;
+        RoleId = stepAssignInfo?.RoleId;
+        RoleName = stepAssignInfo?.RoleName;
+    }
+
+    public void Assign(StepDefine stepDefine)
+    {
+        var handler = stepDefine.HandlerTypeItems.FirstOrDefault();
+        if (handler is null)
+            throw new UserFriendlyException($"未正确配置节点办理对象, stepcode: {stepDefine.Code}", "未正确配置节点办理对象");
+        switch (stepDefine.HandlerType)
+        {
+            case EHandlerType.Role:
+                FlowAssignType = EFlowAssignType.Role;
+                RoleId = handler.Key;
+                RoleName = handler.Value;
+                break;
+            case EHandlerType.AssignedUser:
+                FlowAssignType = EFlowAssignType.User;
+                HandlerId = handler.Key;
+                HandlerName = handler.Value;
+                break;
+            case EHandlerType.AssignedOrg:
+                FlowAssignType = EFlowAssignType.Org;
+                HandlerOrgId = handler.Key;
+                HandlerOrgName = handler.Value;
+                break;
+            case EHandlerType.OrgType:
+                throw new ArgumentOutOfRangeException("部门类型不支持按配置指派");
+            case EHandlerType.OrgLevel:
+                throw new ArgumentOutOfRangeException("部门等级不支持按配置指派");
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
+
+    }
+
     /// <summary>
     /// 是否处于会签流程中(不包含顶层发起会签节点)
     /// </summary>

+ 2 - 1
src/Hotline/FlowEngine/Workflows/WorkflowCountersignMember.cs

@@ -1,4 +1,5 @@
-using SqlSugar;
+using Hotline.Share.Enums.FlowEngine;
+using SqlSugar;
 using XF.Domain.Entities;
 using XF.Domain.Repository;
 

+ 555 - 192
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -1004,18 +1004,161 @@ namespace Hotline.FlowEngine.Workflows
                 prevStep.FlowAssignType = prevStep.StepType == EStepType.Start ? EFlowAssignType.Org : prevStep.FlowAssignType;
             }
 
+            dto.ReverseFlowStepAssignInfo ??= new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStepUser);
+            var prevStepDefine = workflow.WorkflowDefinition.FindStepDefine(prevStep.Code);
+            var stepAssignInfo = GetStepAssignInfo(dto.ReverseFlowStepAssignInfo, prevStep, prevStepDefine);
 
             //复制上一个节点为待接办
             // 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 newPrevStep = DuplicateStep(prevStep, EWorkflowTraceType.Previous, stepAssignInfo, dto.ExpiredTime);
+            ////退给派单组节点,需按照平均分配原则派给一个派单员 禅道299 TODO
+            //if (dto.ReverseFlowStepAssignInfo != null) //todo 改为按策略判断
+            //{
+            //    var handle = dto.ReverseFlowStepAssignInfo.StepAssignInfo;
+            //    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
             {
-                var handle = dto.Handler;
-                newPrevStep.Assign(handle.UserId, handle.Username, handle.OrgId, handle.OrgName, handle.RoleId, handle.RoleName);
+                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);
+        }
+
+        /// <summary>
+        /// 退回(new)
+        /// </summary>
+        public async Task<EFlowDirection> PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, OperatorInfo operatorInfo,
+            CancellationToken cancellationToken)
+        {
+            //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<WorkflowStep> { 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, EHandleMode.Previous, 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);
+            var prevStepDefine = workflow.WorkflowDefinition.FindStepDefine(prevStep.Code);
+            var stepAssignInfo = GetStepAssignInfo(dto.ReverseFlowStepAssignInfo, prevStep, prevStepDefine);
+
+            //复制上一个节点为待接办
+            var newPrevStep = DuplicateStep(prevStep, EWorkflowTraceType.Previous, stepAssignInfo, dto.ExpiredTime);
+            ////退给派单组节点,需按照平均分配原则派给一个派单员 禅道299 TODO
+            //if (dto.ReverseFlowStepAssignInfo != null) //todo 改为按策略判断
+            //{
+            //    var handle = dto.ReverseFlowStepAssignInfo.StepAssignInfo;
+            //    newPrevStep.Assign(handle.UserId, handle.Username, handle.OrgId, handle.OrgName, handle.RoleId, handle.RoleName);
+            //}
+
+            //dto.ReverseFlowStepAssignInfo ??= new ReverseFlowStepAssignInfo
+            //{
+            //    ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginStepUser
+            //};
+            //var prevStepDefine = workflow.WorkflowDefinition.FindStepDefine(prevStep.Code);
+            //ReverseFlowAssignStepHandler(dto.ReverseFlowStepAssignInfo, newPrevStep, prevStep, prevStepDefine);
+
             await _workflowStepRepository.AddAsync(newPrevStep, cancellationToken);
             await CreateTraceAsync(workflow, newPrevStep, EWorkflowTraceType.Previous, cancellationToken);
 
@@ -1141,64 +1284,6 @@ namespace Hotline.FlowEngine.Workflows
                 .ToListAsync(cancellationToken);
         }
 
-        ///// <summary>
-        ///// 批量改变办理对象
-        ///// </summary>
-        //public async Task<ICollection<string>> ChangeHandlerRangeAsync(string sendPoolId,
-        //    IReadOnlyList<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers,
-        //    CancellationToken cancellationToken)
-        //{
-        //    var stepsIds = handlers.SelectMany(d => d.stepIds).ToList();
-        //    var steps = await _workflowStepRepository.Queryable()
-        //        .Includes(d => d.Workflow)
-        //        .Includes(d => d.WorkflowTrace)
-        //        //.Includes(d => d.StepHandlers)
-        //        .Where(d => stepsIds.Contains(d.Id))
-        //        .ToListAsync(cancellationToken);
-        //    foreach (var handler in handlers)
-        //    {
-        //        var thisHandlers = new List<Kv> { new(handler.userId, handler.username) };
-        //        var thisHandlerGroup = new HandlerGroupItem
-        //        {
-        //            GroupId = Guid.NewGuid().ToString(),
-        //            Key = handler.userId,
-        //            Value = handler.username,
-        //        };
-        //        var thisSteps = steps.Where(d => handler.stepIds.Contains(d.Id)).ToList();
-        //        foreach (var thisStep in thisSteps)
-        //        {
-        //            //var stepHandler = WorkflowStepHandler.Create(thisStep.Workflow.Id, thisStep.Workflow.ExternalId,
-        //            //    thisStep.FlowAssignType ?? EFlowAssignType.User, handler.userId, handler.username, handler.orgId, handler.orgName);
-        //            //thisStep.StepHandlers.Clear();
-        //            //thisStep.StepHandlers.Add(stepHandler);
-
-        //            thisStep.Handlers = thisHandlers;
-
-        //            //update trace
-        //            thisStep.WorkflowTrace.Handlers = thisStep.Handlers;
-
-        //            // update workflow
-        //            thisStep.Workflow.FlowedUserIds.Remove(sendPoolId);
-        //            thisStep.Workflow.FlowedUserIds.Add(handler.userId);
-        //            thisStep.Workflow.UpdateHandlers(sendPoolId, null, EFlowAssignType.User,
-        //                new List<HandlerGroupItem> { thisHandlerGroup }, true);
-
-        //            var handlerUser = thisStep.Workflow.HandlerUsers.FirstOrDefault(d => d.Key == sendPoolId);
-        //            if (handlerUser == null) continue;
-        //            handlerUser.Key = handler.userId;
-        //            handlerUser.Value = handler.username;
-        //        }
-        //    }
-
-        //    await _workflowStepRepository.UpdateNav(steps)
-        //        .Include(d => d.WorkflowTrace)
-        //        .Include(d => d.Workflow)
-        //        //.Include(d => d.StepHandlers)
-        //        .ExecuteCommandAsync();
-
-        //    return steps.Select(d => d.WorkflowId).ToList();
-        //}
-
         /// <summary>
         /// 批量修改工单办理对象
         /// </summary>
@@ -1300,8 +1385,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 查询该部门最后办理节点
         /// </summary>
         /// <returns></returns>
-        public async Task<WorkflowStep> FindLastHandleStepAsync(string workflowId, string orgId,
-            CancellationToken cancellation)
+        public async Task<WorkflowStep> FindLastHandleStepAsync(string workflowId, string orgId, CancellationToken cancellation)
         {
             return await _workflowStepRepository.Queryable()
                 .Where(d => d.WorkflowId == workflowId
@@ -1606,42 +1690,220 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 撤回(返回到之前任意节点)
         /// </summary>
-        public async Task RecallAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine,
-            FlowAssignInfo flowAssignInfo, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled,
+        public async Task<bool> RecallAsync(RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode,
+            CancellationToken cancellationToken)
+        {
+            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, reverseFlowStepAssignInfo, targetStepDefine, targetStep, traceType,
+                expiredTime, isOrderFiled, handleMode, cancellationToken);
+        }
+
+        /// <summary>
+        /// 撤回(返回到之前任意节点)
+        /// </summary>
+        public Task<bool> RecallAsync(Workflow workflow, RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            StepDefine targetStepDefine, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled,
             EHandleMode handleMode, CancellationToken cancellationToken)
         {
             var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin);
             if (targetStep is null)
                 throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
 
-            //update uncompleted traces
-            //await RecallTraceAsync(workflow.Traces, dto.Opinion, _sessionContext, cancellationToken);
+            //var isOrgToCenter = await RecallAsync(workflow, dto, reverseFlowStepAssignInfo, targetStepDefine, targetStep,
+            //    traceType, expiredTime, isOrderFiled, handleMode, cancellationToken);
+
+            //await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
-            var isOrgToCenter = await RecallAsync(workflow, dto, flowAssignInfo, targetStepDefine, targetStep,
+            //await _publisher.PublishAsync(new RecallNotify(workflow, targetStep, dto, isOrgToCenter),
+            //    PublishStrategy.ParallelWhenAll, cancellationToken);
+
+            //return isOrgToCenter;
+
+            return RecallAsync(workflow, dto, reverseFlowStepAssignInfo, targetStepDefine, targetStep,
                 traceType, expiredTime, isOrderFiled, handleMode, cancellationToken);
+        }
+
+        public Task<bool> RecallAsync(Workflow workflow, RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            WorkflowStep targetStep, EWorkflowTraceType traceType, DateTime? expiredTime, bool isOrderFiled,
+            EHandleMode handleMode, CancellationToken cancellationToken)
+        {
+            var targetStepDefine = GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+            if (targetStepDefine.StepType is EStepType.End)
+                throw UserFriendlyException.SameMessage("结束节点不支持撤回");
+
+            return RecallAsync(workflow, dto, reverseFlowStepAssignInfo,
+                targetStepDefine, targetStep, traceType,
+                expiredTime, isOrderFiled, handleMode, cancellationToken);
+        }
+
+        public async Task<bool> RecallAsync(Workflow workflow, RecallDto dto, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+          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<WorkflowTrace>();
+
+            //update uncomplete traces
+            var uncompleteTraces = workflow.Traces.Where(d => d.Status != EWorkflowStepStatus.Handled).ToList();
+            if (uncompleteTraces.Any())
+            {
+                foreach (var trace in uncompleteTraces)
+                {
+                    trace.Handle(
+                        _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                        _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                        _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName,
+                        _sessionContextProvider.SessionContext.OrgIsCenter, handleMode, dto.Opinion);
+                }
+
+                //await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
+                updateTraces.AddRange(uncompleteTraces);
+            }
+            else
+            {
+                var endTrace = workflow.Traces.Where(d => d.StepType == EStepType.End).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,
+                        _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                        _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                        _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName);
+                }
+
+                await _workflowCountersignRepository.UpdateRangeAsync(unCompleteCountersigns, cancellationToken);
+            }
+
+            workflow.EndCountersign();
+            workflow.ResetOption();
+            if (workflow.Status is EWorkflowStatus.Completed)
+                workflow.SetStatusRunnable();
+
+            var assigner = new UserInfo(
+                _sessionContextProvider.SessionContext.UserId,
+                _sessionContextProvider.SessionContext.UserName,
+                _sessionContextProvider.SessionContext.OrgId,
+                _sessionContextProvider.SessionContext.OrgName,
+                _sessionContextProvider.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);
+            var targetStepNew = targetIsStartStep
+                ? await CreateStartStepAsync(workflow, targetStepDefine, dto, assigner,
+                    stepAssignInfo, traceType, expiredTime, stepAssignInfo.FlowAssignType, cancellationToken)
+                : (await CreateStepsAsync(workflow, targetStepDefine, targetPrevStep, dto, assigner,
+                    stepAssignInfo.FlowAssignType, [stepAssignInfo],
+                    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);
 
             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 isOrgToCenter;
         }
 
         /// <summary>
         /// 撤回至开始节点
         /// </summary>
-        public async Task RecallToStartStepAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime,
-           EHandleMode handleMode, CancellationToken cancellationToken)
+        public async Task RecallToStartStepAsync(Workflow workflow, string opinion, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, 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, withCountersigns: true,
-                cancellationToken: cancellationToken);
-            var startStep = workflow.Steps.First(d => d.StepType == EStepType.Start);
+            var startStep = workflow.Steps.Where(d => d.StepType == EStepType.Start && d.IsOrigin)
+                .MaxBy(d => d.CreationTime);
             if (startStep is null)
-                throw new UserFriendlyException($"数据异常, workflowId: {workflowId}", "该流程无开始节点");
+                throw new UserFriendlyException($"数据异常, workflowId: {workflow.Id}", "该流程无开始节点");
 
             //await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
 
-            var targetStepDefine = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
+            var startStepDefine = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
             var dto = new RecallDto
             {
                 Opinion = opinion,
@@ -1649,7 +1911,7 @@ namespace Hotline.FlowEngine.Workflows
                 NextStepName = startStep.Name,
                 BusinessType = startStep.BusinessType,
                 StepType = startStep.StepType,
-                HandlerType = targetStepDefine.HandlerType,
+                HandlerType = startStepDefine.HandlerType,
                 NextHandlers = new List<FlowStepHandler>
                 {
                     new FlowStepHandler
@@ -1665,28 +1927,30 @@ namespace Hotline.FlowEngine.Workflows
                     }
                 }
             };
-            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);
+            //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, reverseFlowStepAssignInfo, startStepDefine, startStep,
+                EWorkflowTraceType.Recall, expiredTime, isOrderFiled,
+                handleMode, cancellationToken);
         }
 
         /// <summary>
         /// 撤回至派单节点
         /// </summary>
-        public async Task RecallToSendStepAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime,
-           EHandleMode handleMode, CancellationToken cancellationToken)
+        public async Task RecallToSendStepAsync(Workflow workflow, string opinion, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            DateTime? expiredTime, bool isOrderFiled, EHandleMode handleMode, CancellationToken cancellationToken)
         {
-            var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true,
-                cancellationToken: cancellationToken);
-            var sendStep = workflow.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
+            var sendStep = workflow.Steps.Where(d => d.BusinessType == EBusinessType.Send && d.IsOrigin)
+                .MaxBy(d => d.CreationTime);
             if (sendStep is null)
-                throw new UserFriendlyException($"未找到派单节点, workflowId: {workflowId}", "该流程无派单节点");
+                throw new UserFriendlyException($"未找到派单节点, workflowId: {workflow.Id}", "该流程无派单节点");
 
             //await RecallToTargetStepAsync(workflow, sendStep, opinion, current, cancellationToken);
 
-            var targetStepDefine = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
+            var sendStepDefine = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
             var dto = new RecallDto
             {
                 Opinion = opinion,
@@ -1694,7 +1958,7 @@ namespace Hotline.FlowEngine.Workflows
                 NextStepName = sendStep.Name,
                 BusinessType = sendStep.BusinessType,
                 StepType = sendStep.StepType,
-                HandlerType = targetStepDefine.HandlerType,
+                HandlerType = sendStepDefine.HandlerType,
                 NextHandlers = new List<FlowStepHandler>
                 {
                     new FlowStepHandler
@@ -1710,10 +1974,14 @@ namespace Hotline.FlowEngine.Workflows
                     }
                 }
             };
-            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,
+            //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, reverseFlowStepAssignInfo, sendStepDefine, sendStep,
+                EWorkflowTraceType.Recall, expiredTime, isOrderFiled,
                 handleMode, cancellationToken);
         }
 
@@ -1721,87 +1989,93 @@ namespace Hotline.FlowEngine.Workflows
         /// 特提至中心(优先派单组其次坐席)
         /// </summary>
         /// <returns>true 派单组  false 话务部</returns>
-        public async Task<(bool, Workflow workflow)> RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled,
-            DateTime? expiredTime, List<FlowStepHandler>? handlers, EHandleMode handleMode, CancellationToken cancellationToken)
+        public async Task<(bool, Workflow workflow)> RecallToCenterFirstToSendAsync(string workflowId, string opinion, ReverseFlowStepAssignInfo reverseFlowStepAssignInfo,
+            bool isOrderFiled, DateTime? expiredTime, EHandleMode handleMode, CancellationToken cancellationToken)
         {
-            bool isPaiDan = true;
+            var isPaiDan = false;
             var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true,
                 cancellationToken: cancellationToken);
-            var sendStep = workflow.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
-            if (sendStep is not null)
+            var hasSendStep = workflow.Steps.Any(d => d.BusinessType == EBusinessType.Send);
+            if (hasSendStep)
             {
-                var targetStepDefine = 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 = targetStepDefine.HandlerType,
-                    NextHandlers = new List<FlowStepHandler>
-                    {
-                        new FlowStepHandler
-                        {
-                            Key = sendStep.RoleId,
-                            Value = sendStep.RoleName,
-                            RoleId = sendStep.RoleId,
-                            RoleName = sendStep.RoleName,
-                            UserId = sendStep.HandlerId,
-                            Username = sendStep.HandlerName,
-                            OrgId = sendStep.HandlerOrgId,
-                            OrgName = sendStep.HandlerOrgName,
-                        }
-                    }
-                };
-                if (_appOptions.Value.IsZiGong && handlers != null && handlers.Any())
-                {
-                    dto.NextHandlers = handlers;
-                }
-
-                var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.HandlerType, dto.IsStartCountersign,
-                    dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), cancellationToken);
-                await RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, EWorkflowTraceType.Recall, expiredTime, isOrderFiled,
-                   handleMode, cancellationToken);
+                //var targetStepDefine = 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 = targetStepDefine.HandlerType,
+                //    NextHandlers = new List<FlowStepHandler>
+                //    {
+                //        new FlowStepHandler
+                //        {
+                //            Key = sendStep.RoleId,
+                //            Value = sendStep.RoleName,
+                //            RoleId = sendStep.RoleId,
+                //            RoleName = sendStep.RoleName,
+                //            UserId = sendStep.HandlerId,
+                //            Username = sendStep.HandlerName,
+                //            OrgId = sendStep.HandlerOrgId,
+                //            OrgName = sendStep.HandlerOrgName,
+                //        }
+                //    }
+                //};
+                //if (_appOptions.Value.IsZiGong && handlers != null && handlers.Any())
+                //{
+                //    dto.NextHandlers = handlers;
+                //}
+
+                //var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.HandlerType, dto.IsStartCountersign,
+                //    dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList(), cancellationToken);
+                //await RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, EWorkflowTraceType.Recall, expiredTime, isOrderFiled,
+                //   handleMode, cancellationToken);
+
+                await RecallToSendStepAsync(workflow, opinion, reverseFlowStepAssignInfo, expiredTime, isOrderFiled,
+                    handleMode, cancellationToken);
+                isPaiDan = true;
             }
             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);
-
-                var targetStepDefine = 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 = targetStepDefine.HandlerType,
-                    NextHandlers = new List<FlowStepHandler>
-                    {
-                        new FlowStepHandler
-                        {
-                            Key = startStep.RoleId,
-                            Value = startStep.RoleName,
-                            RoleId = startStep.RoleId,
-                            RoleName = startStep.RoleName,
-                            UserId = startStep.HandlerId,
-                            Username = startStep.HandlerName,
-                            OrgId = startStep.HandlerOrgId,
-                            OrgName = startStep.HandlerOrgName,
-                        }
-                    }
-                };
-                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);
-                isPaiDan = false;
+                //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);
+
+                //var targetStepDefine = 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 = targetStepDefine.HandlerType,
+                //    NextHandlers = new List<FlowStepHandler>
+                //    {
+                //        new FlowStepHandler
+                //        {
+                //            Key = startStep.RoleId,
+                //            Value = startStep.RoleName,
+                //            RoleId = startStep.RoleId,
+                //            RoleName = startStep.RoleName,
+                //            UserId = startStep.HandlerId,
+                //            Username = startStep.HandlerName,
+                //            OrgId = startStep.HandlerOrgId,
+                //            OrgName = startStep.HandlerOrgName,
+                //        }
+                //    }
+                //};
+                //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 RecallToStartStepAsync(workflow, opinion, reverseFlowStepAssignInfo, expiredTime, isOrderFiled,
+                      handleMode, cancellationToken);
+                //isPaiDan = false;
             }
 
             return (new(isPaiDan, workflow));
@@ -2177,6 +2451,92 @@ namespace Hotline.FlowEngine.Workflows
 
         #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;
@@ -2687,7 +3047,7 @@ namespace Hotline.FlowEngine.Workflows
             var isToCenter = targetStep.IsCenter();
             if (!isToCenter) return false;
 
-            var isFromOrg = workflow.Steps.Any(d => d.BusinessType is EBusinessType.Department);
+            var isFromOrg = workflow.Steps.Any(d => d.BusinessType is EBusinessType.Department or EBusinessType.DepartmentLeader);
             return isFromOrg && isToCenter;
         }
 
@@ -2695,22 +3055,18 @@ namespace Hotline.FlowEngine.Workflows
         /// 复制一个节点为待接办
         /// </summary>
         private async Task<WorkflowStep> DuplicateStepWithTraceAsync(Workflow workflow, WorkflowStep step,
-            EWorkflowTraceType traceType, DateTime expiredTime, CancellationToken cancellationToken)
+            EWorkflowTraceType traceType, StepAssignInfo? stepAssignInfo, DateTime expiredTime, CancellationToken cancellationToken)
         {
-            var newStep = DuplicateStep(step, traceType, expiredTime);
+            var newStep = DuplicateStep(step, traceType, stepAssignInfo, expiredTime);
 
             await _workflowStepRepository.AddAsync(newStep, cancellationToken);
-            //await _workflowStepRepository.AddNav(newStep)
-            //    .Include(d => d.StepHandlers)
-            //    .ExecuteCommandAsync();
-
 
             await CreateTraceAsync(workflow, newStep, traceType, cancellationToken);
 
             return newStep;
         }
 
-        private WorkflowStep DuplicateStep(WorkflowStep step, EWorkflowTraceType traceType, DateTime? expiredTime)
+        private WorkflowStep DuplicateStep(WorkflowStep step, EWorkflowTraceType traceType, StepAssignInfo? stepAssignInfo, DateTime? expiredTime)
         {
             var newStep = _mapper.Map<WorkflowStep>(step);
             newStep.Reset();
@@ -2726,20 +3082,23 @@ namespace Hotline.FlowEngine.Workflows
             newStep.StepExpiredTime = expiredTime;
             newStep.InitId();
 
-            //退回场景:指派给原办理人,其余场景:按照原节点原始指派方式复制 //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;
+            if (stepAssignInfo is not null)
+                newStep.Assign(stepAssignInfo);
 
-                newStep.Assign(step.HandlerId, step.HandlerName, step.HandlerOrgId, step.HandlerOrgName, step.RoleId, step.RoleName);
-            }
+            ////退回场景:指派给原办理人,其余场景:按照原节点原始指派方式复制 //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;
         }
@@ -3478,7 +3837,11 @@ namespace Hotline.FlowEngine.Workflows
                 var stepToDuplicate = startCountersignStep.IsCountersignEndStep
                     ? GetCsLoopStartStep(workflow.Steps, startCountersignStep)
                     : startCountersignStep;
-                var newStep = await DuplicateStepWithTraceAsync(workflow, stepToDuplicate, EWorkflowTraceType.Normal, expireTime, cancellationToken);
+                var stepAssignInfo = GetStepAssignInfo(new ReverseFlowStepAssignInfo
+                {
+                    ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginStep
+                }, stepToDuplicate);
+                var newStep = await DuplicateStepWithTraceAsync(workflow, stepToDuplicate, EWorkflowTraceType.Normal, stepAssignInfo, expireTime, cancellationToken);
 
                 //当topcsStep结束cs时,实际办理节点应该更新为newStep
                 if (startCountersignStep.Id == workflow.TopCountersignStepId)
@@ -3687,7 +4050,7 @@ namespace Hotline.FlowEngine.Workflows
             return step;
         }
 
-        private async Task<FlowAssignInfo> GetNextStepFlowAssignInfoByDefineAsync(StepDefine nextStepDefine,
+        public async Task<FlowAssignInfo> GetNextStepFlowAssignInfoByDefineAsync(StepDefine nextStepDefine,
             EHandlerType handlerType, bool isStartCountersign, List<Kv> handlers, CancellationToken cancellationToken)
         {
             switch (handlerType)

+ 2 - 2
src/Hotline/KnowledgeBase/KnowledgeDomainService.cs

@@ -173,14 +173,14 @@ namespace Hotline.KnowledgeBase
             if (knowledgeFlow == null)
                 throw new UserFriendlyException($"无效知识编号, knowledgeFlowId: {knowledgeFlowId}", "指派失败");
 
-            knowledgeFlow.Assign(assignInfo.FlowAssignType, assignInfo.GetHandlerIds());
+            //knowledgeFlow.Assign(assignInfo.FlowAssignType, assignInfo.GetHandlerIds());
             await _knowledgeWorkFlowRepository.UpdateAsync(knowledgeFlow, cancellationToken);
 
             //知识主表
             var knowledge = await _knowledgeRepository.GetAsync(p => p.WorkflowId == knowledgeFlow.WorkflowId, cancellationToken);
             if (knowledge != null)
             {
-                knowledge.Assign(assignInfo.FlowAssignType, assignInfo.GetHandlerIds());
+                //knowledge.Assign(assignInfo.FlowAssignType, assignInfo.GetHandlerIds());
                 await _knowledgeRepository.UpdateAsync(knowledge, cancellationToken);
             }
         }

+ 13 - 1
src/Hotline/Orders/IOrderDomainService.cs

@@ -1,6 +1,8 @@
-using Hotline.Schedulings;
+using Hotline.FlowEngine.Definitions;
+using Hotline.Schedulings;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.FlowEngine;
 
 namespace Hotline.Orders
 {
@@ -112,5 +114,15 @@ namespace Hotline.Orders
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         Task VisitNoneByCancelPublishAsync(string orderId, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 查询退回操作目标节点的指派方式
+        /// </summary>
+        ReverseFlowStepAssignInfo GetOrderPreviousAssignInfo(EBusinessType targetStepBusinessType, StepAssignInfo? handler);
+
+        /// <summary>
+        /// 查询特提操作目标节点的指派方式
+        /// </summary>
+        ReverseFlowStepAssignInfo GetOrderRecallAssignInfo(EBusinessType targetStepBusinessType, StepAssignInfo? handler);
     }
 }

+ 144 - 12
src/Hotline/Orders/OrderDomainService.cs

@@ -35,6 +35,8 @@ using Microsoft.Extensions.Options;
 using Hotline.Share.Tools;
 using Hotline.Orders.Notifications;
 using Hotline.Share.Mq;
+using System.Security.Cryptography;
+using Hotline.FlowEngine.Definitions;
 using Hotline.Snapshot.Interfaces;
 
 namespace Hotline.Orders;
@@ -232,7 +234,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             orderVisit.EmployeeId = string.Empty;
         }
 
-        if (order is { FileOrgIsCenter:true, CounterSignType: null } && !order.IsProvince)
+        if (order is { FileOrgIsCenter: true, CounterSignType: null } && !order.IsProvince)
         {
             orderVisit.VisitState = EVisitState.Visited;
             orderVisit.VisitTime = DateTime.Now;
@@ -266,7 +268,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         orgDetail.VisitOrgCode = order.ActualHandleOrgCode;
         orgDetail.VisitOrgName = order.ActualHandleOrgName;
         orgDetail.VisitTarget = EVisitTarget.Org;
-        if (order is { FileOrgIsCenter:true , CounterSignType: null, IsProvince: false })
+        if (order is { FileOrgIsCenter: true, CounterSignType: null, IsProvince: false })
         {
             var satisfy = new Kv() { Key = "4", Value = "满意" };
             orgDetail.OrgProcessingResults = satisfy;
@@ -275,7 +277,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
 
         visitedDetail.Add(orgDetail);
 
-        if (order is { FileOrgIsCenter:true, CounterSignType: null })
+        if (order is { FileOrgIsCenter: true, CounterSignType: null })
         {
             seatDetail.VoiceEvaluate = EVoiceEvaluate.Satisfied;
             seatDetail.SeatEvaluate = ESeatEvaluate.Satisfied;
@@ -309,7 +311,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         {
             try
             {
-                if (order.Source != ESource.ProvinceStraight && order.FileOrgIsCenter.Value  == false)
+                if (order.Source != ESource.ProvinceStraight && order.FileOrgIsCenter.Value == false)
                 {
                     var code = "";
                     //受理类型为“投诉、举报”
@@ -408,6 +410,136 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         }
     }
 
+    /// <summary>
+    /// 查询退回操作目标节点的指派方式
+    /// </summary>
+    public ReverseFlowStepAssignInfo GetOrderPreviousAssignInfo(EBusinessType targetStepBusinessType, StepAssignInfo? handler)
+    {
+        //自贡需求:
+        // 1. 工单退回、特提、重办到话务部节点时,所有坐席都可以查看和办理
+        // 2. 退回到派单组时需执行平均分配逻辑
+        //宜宾需求:
+        // 1、退回至话务部:所有坐席都可以查看和办理(除某些场景下本来就需指定办理对象,如:发布时退回……)
+        // 2、退回至派单组:默认退给之前的派单员(除某些场景下本来就需指定办理对象,如:发布时退回……)
+        // 3、话务员特提至话务部:根据特提申请时候选择来,指定的办理对象才能查看和办理
+        // 4、派单员特提至派单组:根据特提申请时候选择来,指定的办理对象才能查看和办理
+        // 5、班长特提至派单组,办理对象必选(已实现)
+        // 6、班长特提至话务部,办理对象非必选,没选择则所有坐席都可以查看和办理
+        //泸州需求:
+        /* 1、特提(仅针对特提到热线中心节点)
+           1.1、工单特提回话务部,办理对象需调整所有坐席都可以查看和办理
+           1.2、工单特提到派单组或者班长审批等其它热线中心节点,只有之前办理人才能办理
+           2、退回功能(仅针对退回到热线中心节点)
+           2.1、退回到话务部节点,只有之前受理人才能办理;
+           2.2、退回到派单组或者班长审批等其它热线中心节点,只有之前办理人才能办理  
+         */
+
+        var rsp = new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStepUser);
+
+        switch (targetStepBusinessType)
+        {
+            case EBusinessType.Seat:
+                if (_appOptions.Value.IsZiGong || _appOptions.Value.IsYiBin)
+                {
+                    rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginDefinition;
+                }
+                break;
+            case EBusinessType.Send:
+                if (_appOptions.Value.IsZiGong)
+                {
+                    if (handler is null)
+                        throw new UserFriendlyException("参数异常,退回派单组需要通过平均派单指定办理人");
+
+                    rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.AssignHandler;
+                    rsp.StepAssignInfo = handler;
+                }
+                break;
+            case EBusinessType.Department:
+                rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginStepOrg;
+                break;
+            case EBusinessType.DepartmentLeader:
+                break;
+            case EBusinessType.CenterMonitor:
+                break;
+            case EBusinessType.CenterLeader:
+                break;
+            case EBusinessType.Unknown:
+                break;
+            case EBusinessType.File:
+            default:
+                throw new ArgumentOutOfRangeException(nameof(targetStepBusinessType), targetStepBusinessType, null);
+        }
+
+        return rsp;
+    }
+
+    /// <summary>
+    /// 查询特提操作目标节点的指派方式
+    /// </summary>
+    public ReverseFlowStepAssignInfo GetOrderRecallAssignInfo(EBusinessType targetStepBusinessType, StepAssignInfo? handler)
+    {
+        //自贡需求:
+        // 1. 工单退回、特提、重办到话务部节点时,所有坐席都可以查看和办理
+        // 2. 退回到派单组时需执行平均分配逻辑
+        //宜宾需求:
+        // 1、退回至话务部:所有坐席都可以查看和办理(除某些场景下本来就需指定办理对象,如:发布时退回……)
+        // 2、退回至派单组:默认退给之前的派单员(除某些场景下本来就需指定办理对象,如:发布时退回……)
+        // 3、话务员特提至话务部:根据特提申请时候选择来,指定的办理对象才能查看和办理
+        // 4、派单员特提至派单组:根据特提申请时候选择来,指定的办理对象才能查看和办理
+        // 5、班长特提至派单组,办理对象必选(已实现)
+        // 6、班长特提至话务部,办理对象非必选,没选择则所有坐席都可以查看和办理
+        //泸州需求:
+        /* 1、特提(仅针对特提到热线中心节点)
+           1.1、工单特提回话务部,办理对象需调整所有坐席都可以查看和办理
+           1.2、工单特提到派单组或者班长审批等其它热线中心节点,只有之前办理人才能办理
+           2、退回功能(仅针对退回到热线中心节点)
+           2.1、退回到话务部节点,只有之前受理人才能办理;
+           2.2、退回到派单组或者班长审批等其它热线中心节点,只有之前办理人才能办理
+         */
+
+        var rsp = new ReverseFlowStepAssignInfo(EReverseFlowStepCreationPolicy.OriginStepUser);
+
+        switch (targetStepBusinessType)
+        {
+            case EBusinessType.Seat:
+                if (_appOptions.Value.IsYiBin && handler is not null)
+                {
+                    rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.AssignHandler;
+                    rsp.StepAssignInfo = handler;
+                }
+                else
+                {
+                    rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginDefinition;
+                }
+                break;
+            case EBusinessType.Send:
+                if (_appOptions.Value.IsZiGong || _appOptions.Value.IsYiBin)
+                {
+                    if(handler is null)
+                        throw new UserFriendlyException("参数异常,特提派单组需要指定办理人");
+                    rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.AssignHandler;
+                    rsp.StepAssignInfo = handler;
+                }
+                break;
+            case EBusinessType.Department:
+                rsp.ReverseFlowStepCreationPolicy = EReverseFlowStepCreationPolicy.OriginStepOrg;
+                break;
+            case EBusinessType.DepartmentLeader:
+                break;
+            case EBusinessType.CenterMonitor:
+                break;
+            case EBusinessType.CenterLeader:
+                break;
+            case EBusinessType.Unknown:
+                break;
+            case EBusinessType.File:
+            default:
+                throw new ArgumentOutOfRangeException(nameof(targetStepBusinessType), targetStepBusinessType, null);
+        }
+
+        return rsp;
+    }
+
     public async Task<Order> GetOrderAsync(string? orderId, bool withHotspot = false, bool withAcceptor = false,
         bool withExtension = false, CancellationToken cancellationToken = default)
     {
@@ -596,17 +728,17 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             scheduling.SendOrderNum += sendNum;
             if (!scheduling.LoginSendOrderNum.HasValue)
             {
-	            scheduling.LoginSendOrderNum = scheduling.LoginSendOrderNum.HasValue && scheduling.LoginSendOrderNum > sendNum ? scheduling.LoginSendOrderNum : sendNum;
-				await _schedulingRepository.Updateable()
-					.SetColumns(s => new Scheduling() { LoginSendOrderNum = scheduling.LoginSendOrderNum })
-					.Where(s => s.SchedulingTime == scheduling.SchedulingTime).ExecuteCommandAsync(cancellationToken);
-			}
+                scheduling.LoginSendOrderNum = scheduling.LoginSendOrderNum.HasValue && scheduling.LoginSendOrderNum > sendNum ? scheduling.LoginSendOrderNum : sendNum;
+                await _schedulingRepository.Updateable()
+                    .SetColumns(s => new Scheduling() { LoginSendOrderNum = scheduling.LoginSendOrderNum })
+                    .Where(s => s.SchedulingTime == scheduling.SchedulingTime).ExecuteCommandAsync(cancellationToken);
+            }
             sendNum = scheduling.LoginSendOrderNum.Value;
-			await _schedulingRepository.Updateable()
+            await _schedulingRepository.Updateable()
                 .SetColumns(s => new Scheduling() { SendOrderNum = scheduling.SendOrderNum, AtWork = scheduling.AtWork })
                 .Where(s => s.Id == scheduling.Id).ExecuteCommandAsync(cancellationToken);
-		
-			if (sendNum <= 0) return;
+
+            if (sendNum <= 0) return;
             var sendSteps = steps.Take(sendNum).ToList();
             await _orderRepository.Updateable().SetColumns(o => new Order()
             {

+ 1 - 1
src/Hotline/Quality/QualityItem.cs

@@ -41,7 +41,7 @@ namespace Hotline.Quality
 		/// <summary>
 		/// 启禁用
 		/// </summary>
-		[SugarColumn(ColumnDescription = "启禁用  0  启用  1 禁用")]
+		[SugarColumn(ColumnDescription = "启禁用  1  启用  0 禁用")]
 		public int IsEnable { get; set; }
 
 

+ 78 - 78
src/XF.Domain.Repository/Entity.cs

@@ -168,45 +168,45 @@ public abstract class WorkflowEntity : FullStateEntity, IWorkflow
 
     #endregion
 
-    public void Assign(EFlowAssignType type, string handler)
-    {
-        if (string.IsNullOrEmpty(handler)) return;
-        handler = handler.ToLower();
-        switch (type)
-        {
-            case EFlowAssignType.Org:
-                var orgCodes = handler.GetHigherOrgIds(true).ToList();
-                FlowedOrgIds.AddRange(orgCodes);
-                FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
-                break;
-            case EFlowAssignType.User:
-                if (!FlowedUserIds.Exists(d => d == handler))
-                    FlowedUserIds.Add(handler);
-                break;
-            default:
-                throw new ArgumentOutOfRangeException(nameof(type), type, null);
-        }
-    }
-
-    public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
-    {
-        if (handlers == null || !handlers.Any()) return;
-        handlers = handlers.Select(d => d.ToLower());
-        switch (type)
-        {
-            case EFlowAssignType.Org:
-                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
-                FlowedOrgIds.AddRange(orgCodes);
-                FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
-                break;
-            case EFlowAssignType.User:
-                FlowedUserIds.AddRange(handlers);
-                FlowedUserIds = FlowedUserIds.Distinct().ToList();
-                break;
-            default:
-                throw new ArgumentOutOfRangeException(nameof(type), type, null);
-        }
-    }
+    //public void Assign(EFlowAssignType type, string handler)
+    //{
+    //    if (string.IsNullOrEmpty(handler)) return;
+    //    handler = handler.ToLower();
+    //    switch (type)
+    //    {
+    //        case EFlowAssignType.Org:
+    //            var orgCodes = handler.GetHigherOrgIds(true).ToList();
+    //            FlowedOrgIds.AddRange(orgCodes);
+    //            FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
+    //            break;
+    //        case EFlowAssignType.User:
+    //            if (!FlowedUserIds.Exists(d => d == handler))
+    //                FlowedUserIds.Add(handler);
+    //            break;
+    //        default:
+    //            throw new ArgumentOutOfRangeException(nameof(type), type, null);
+    //    }
+    //}
+
+    //public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
+    //{
+    //    if (handlers == null || !handlers.Any()) return;
+    //    handlers = handlers.Select(d => d.ToLower());
+    //    switch (type)
+    //    {
+    //        case EFlowAssignType.Org:
+    //            var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
+    //            FlowedOrgIds.AddRange(orgCodes);
+    //            FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
+    //            break;
+    //        case EFlowAssignType.User:
+    //            FlowedUserIds.AddRange(handlers);
+    //            FlowedUserIds = FlowedUserIds.Distinct().ToList();
+    //            break;
+    //        default:
+    //            throw new ArgumentOutOfRangeException(nameof(type), type, null);
+    //    }
+    //}
 
     /// <summary>
     /// 是否可查看
@@ -340,45 +340,45 @@ public abstract class PositionWorkflowEntity : PositionEntity, IWorkflow
 
     #endregion
 
-    public void Assign(EFlowAssignType type, string handler)
-    {
-        if (string.IsNullOrEmpty(handler)) return;
-        handler = handler.ToLower();
-        switch (type)
-        {
-            case EFlowAssignType.Org:
-                var orgCodes = handler.GetHigherOrgIds(true).ToList();
-                FlowedOrgIds.AddRange(orgCodes);
-                FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
-                break;
-            case EFlowAssignType.User:
-                if (!FlowedUserIds.Exists(d => d == handler))
-                    FlowedUserIds.Add(handler);
-                break;
-            default:
-                throw new ArgumentOutOfRangeException(nameof(type), type, null);
-        }
-    }
-
-    public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
-    {
-        if (handlers == null || !handlers.Any()) return;
-        handlers = handlers.Select(d => d.ToLower());
-        switch (type)
-        {
-            case EFlowAssignType.Org:
-                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
-                FlowedOrgIds.AddRange(orgCodes);
-                FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
-                break;
-            case EFlowAssignType.User:
-                FlowedUserIds.AddRange(handlers);
-                FlowedUserIds = FlowedUserIds.Distinct().ToList();
-                break;
-            default:
-                throw new ArgumentOutOfRangeException(nameof(type), type, null);
-        }
-    }
+    //public void Assign(EFlowAssignType type, string handler)
+    //{
+    //    if (string.IsNullOrEmpty(handler)) return;
+    //    handler = handler.ToLower();
+    //    switch (type)
+    //    {
+    //        case EFlowAssignType.Org:
+    //            var orgCodes = handler.GetHigherOrgIds(true).ToList();
+    //            FlowedOrgIds.AddRange(orgCodes);
+    //            FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
+    //            break;
+    //        case EFlowAssignType.User:
+    //            if (!FlowedUserIds.Exists(d => d == handler))
+    //                FlowedUserIds.Add(handler);
+    //            break;
+    //        default:
+    //            throw new ArgumentOutOfRangeException(nameof(type), type, null);
+    //    }
+    //}
+
+    //public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
+    //{
+    //    if (handlers == null || !handlers.Any()) return;
+    //    handlers = handlers.Select(d => d.ToLower());
+    //    switch (type)
+    //    {
+    //        case EFlowAssignType.Org:
+    //            var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
+    //            FlowedOrgIds.AddRange(orgCodes);
+    //            FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
+    //            break;
+    //        case EFlowAssignType.User:
+    //            FlowedUserIds.AddRange(handlers);
+    //            FlowedUserIds = FlowedUserIds.Distinct().ToList();
+    //            break;
+    //        default:
+    //            throw new ArgumentOutOfRangeException(nameof(type), type, null);
+    //    }
+    //}
 
     /// <summary>
     /// 是否可查看

+ 2 - 25
src/XF.Domain/Entities/IWorkflow.cs

@@ -33,9 +33,9 @@ public interface IWorkflow
 
     #endregion
 
-    void Assign(EFlowAssignType type, string handler);
+    //void Assign(EFlowAssignType type, string handler);
 
-    void Assign(EFlowAssignType type, IEnumerable<string> handlers);
+    //void Assign(EFlowAssignType type, IEnumerable<string> handlers);
 
     /// <summary>
     /// 是否可查看
@@ -60,29 +60,6 @@ public interface IWorkflow
         List<HandlerGroupItem> handlerUsers, List<HandlerGroupItem> handlerOrgs);
 }
 
-public enum EFlowAssignType
-{
-    /// <summary>
-    /// 指派到部门
-    /// </summary>
-    Org = 0,
-
-    /// <summary>
-    /// 指派到用户
-    /// </summary>
-    User = 1,
-
-    /// <summary>
-    /// 指派到角色
-    /// </summary>
-    Role = 2,
-
-	/// <summary>
-	/// 指派到指定部门的指定角色
-	/// </summary>
-	OrgAndRole = 3,
-}
-
 /// <summary>
 /// 办理对象分组(以办理step分组,多人办理一个step为一组)
 /// </summary>