فهرست منبع

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

Dun.Jason 6 ماه پیش
والد
کامیت
8a92b47e16

+ 34 - 21
src/Hotline.Application.Tests/DefaultHttpContextAccessor.cs

@@ -2,6 +2,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Security.Authentication;
 using System.Security.Claims;
 using System.Text;
 using System.Threading.Tasks;
@@ -24,31 +25,43 @@ public class DefaultHttpContextAccessor : IHttpContextAccessor, ISessionContext
     }
     public HttpContext? HttpContext { get => GetContext(); set => _content = value; }
 
-    public string? UserId => throw new NotImplementedException();
+  
 
-    public string RequiredUserId => throw new NotImplementedException();
-
-    public string? UserName => throw new NotImplementedException();
-
-    public string? Phone => throw new NotImplementedException();
-
-    public string[] Roles => throw new NotImplementedException();
-
-    public string? OrgId { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-
-    public string RequiredOrgId => throw new NotImplementedException();
+    public string? OpenId { get; set; }
 
-    public string? OrgName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-    public int OrgLevel { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-    public string? OrgAreaCode { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-    public bool OrgIsCenter { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
-    public string? OrgAreaName { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+    /// <summary>
+    /// Id of current tenant or null for host
+    /// </summary>
+    public string? UserId { get; set; }
 
-    public string? AreaId => throw new NotImplementedException();
+    /// <summary>
+    /// Id of current user or throw Exception for guest
+    /// </summary>
+    /// <exception cref="AuthenticationException"></exception>
+    public string RequiredUserId { get; }
+    public string? UserName { get; set; }
+    public string? Phone { get; set; }
 
-    public string? ClientId => throw new NotImplementedException();
+    /// <summary>
+    /// Roles
+    /// </summary>
+    public string[] Roles { get; set; }
+    public string? OrgId { get; set; }
+    public string RequiredOrgId { get; }
+    public string? OrgName { get; set; }
+    public int OrgLevel { get; set; }
+    public string? OrgAreaCode { get; set; }
+    public bool OrgIsCenter { get; set; }
 
-    public string? StaffNo => throw new NotImplementedException();
+    /// <summary>
+    /// 部门行政区划名称
+    /// </summary>
+    public string? OrgAreaName { get; set; }
+    public string? AreaId { get; set; }
+    public string? ClientId { get; set; }
 
-    public string? OpenId { get; set; }
+    /// <summary>
+    /// 工号
+    /// </summary>
+    public string? StaffNo { get; set; }
 }

+ 1 - 1
src/Hotline.Application.Tests/Startup.cs

@@ -105,7 +105,7 @@ public class Startup
 
             services.AddScoped<IDataPermissionFilterBuilder, DataPermissionFilterBuilder>();
             services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>));
-            services.AddScoped<IHttpContextAccessor, DefaultHttpContextAccessor>();
+            //services.AddScoped<IHttpContextAccessor, DefaultHttpContextAccessor>();
             services.AddCache(d =>
             {
                 var cacheConfig = configuration.GetSection("Cache").Get<CacheOptions>();

+ 1 - 1
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -391,7 +391,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException(string.Join(',', validationResult.Errors));
 
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
-           withTraces: true, cancellationToken: cancellationToken);
+           withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
 
         //await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
 

+ 2 - 0
src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs

@@ -153,6 +153,8 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
                             order.CenterToOrgTime.Value, now,
                         order.ProcessType is EProcessType.Zhiban)
                         : 0;
+                    creationTimeHandleDurationWorkday = creationTimeHandleDurationWorkday <= 0 ? 10 : creationTimeHandleDurationWorkday;
+                    centerToOrgHandleDurationWorkday = centerToOrgHandleDurationWorkday <= 0 ? 10 : centerToOrgHandleDurationWorkday;
 
                     order.File(now, handleDuration, fileDuration, allDuration, creationTimeHandleDurationWorkday, centerToOrgHandleDurationWorkday);
                     order.FileUserId = notification.Trace.HandlerId;

+ 5 - 3
src/Hotline.Application/Handlers/FlowEngine/WorkflowRecallHandler.cs

@@ -33,7 +33,7 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
     private readonly IRepository<OrderVisitDetail> _orderVisitedDetailRepository;
     private readonly IRepository<OrderPublishHistory> _orderPublishHistoryRepository;
 
-	public WorkflowRecallHandler(
+    public WorkflowRecallHandler(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowDomainService workflowDomainService,
@@ -43,7 +43,7 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
         IRepository<OrderVisit> orderVisitRepository,
         IRepository<OrderVisitDetail> orderVisitedDetailRepository,
         IRepository<OrderPublishHistory> orderPublishHistoryRepository,
-		ICapPublisher capPublisher,
+        ICapPublisher capPublisher,
         IMapper mapper,
         ILogger<WorkflowRecallHandler> logger)
     {
@@ -57,7 +57,7 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
         _orderVisitRepository = orderVisitRepository;
         _orderVisitedDetailRepository = orderVisitedDetailRepository;
         _orderPublishHistoryRepository = orderPublishHistoryRepository;
-		_mapper = mapper;
+        _mapper = mapper;
         _logger = logger;
     }
 
@@ -104,6 +104,8 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
                         //    //order.BackToUnsign();
                         //}
                     }
+
+                    order.Status = EOrderStatus.Special;
                     await _orderRepository.UpdateAsync(order, false, cancellationToken);
                     var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == order.Id, cancellationToken);
                     if (publish != null)

+ 30 - 27
src/Hotline.Application/Orders/OrderApplication.cs

@@ -96,7 +96,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
 
 
-	public OrderApplication(
+    public OrderApplication(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowDomainService workflowDomainService,
@@ -158,7 +158,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _orderVisitedDetailRepository = orderVisitedDetailRepository;
         _appOptions = appOptions;
         _cityBaseConfiguration = cityBaseConfiguration;
-	}
+    }
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -263,6 +263,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             order.ProcessType is EProcessType.Zhiban)
             : 0;
 
+        creationTimeHandleDurationWorkday = creationTimeHandleDurationWorkday <= 0 ? 10 : creationTimeHandleDurationWorkday;
+        centerToOrgHandleDurationWorkday = centerToOrgHandleDurationWorkday <= 0 ? 10 : centerToOrgHandleDurationWorkday;
+
         order.File(now, handleDuration, fileDuration, allDuration, creationTimeHandleDurationWorkday, centerToOrgHandleDurationWorkday);
         await _orderRepository.UpdateAsync(order, cancellationToken);
     }
@@ -285,13 +288,13 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         var IsCenter = _sessionContext.OrgIsCenter;
 
         return _orderRepository.Queryable(canView: !IsCenter).Includes(d => d.OrderDelays)
-	        .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
-		        .Where(step => step.ExternalId == d.Id &&
-		                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
-		                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
-		                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
-		        .Any())
-			.WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
+            .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
+                .Where(step => step.ExternalId == d.Id &&
+                               ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
+                                (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
+                                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
+                .Any())
+            .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No.Contains(dto.No!))
             .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Title.Contains(dto.Title!))
             .WhereIF(dto.Delay.HasValue && dto.Delay == 1, d => d.OrderDelays.Any() == true)
@@ -707,7 +710,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             visit.JudgeState = EJudgeState.Judging;
         }
 
-        for (int i = 0;i < visit.OrderVisitDetails.Count;i++)
+        for (int i = 0; i < visit.OrderVisitDetails.Count; i++)
         {
             var detail = visit.OrderVisitDetails[i];
             var detaildto = dto.VisitDetails.FirstOrDefault(x => x.Id == detail.Id);
@@ -777,7 +780,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         var orderVisitList = await _orderVisitRepository.Queryable()
             .Includes(d => d.Order)
             .Where(d => dto.Ids.Contains(d.Id) && d.VisitState == EVisitState.WaitForVisit)
-            .Select(d => new { d.Id, d.Order.SourceChannelCode,  d.Order.Contact, d.Order.Password, d.No, d.OrderId, d.Order.Title, d.Order.FromName })
+            .Select(d => new { d.Id, d.Order.SourceChannelCode, d.Order.Contact, d.Order.Password, d.No, d.OrderId, d.Order.Title, d.Order.FromName })
             .ToListAsync(cancellationToken);
 
         foreach (var item in orderVisitList)
@@ -828,30 +831,30 @@ public class OrderApplication : IOrderApplication, IScopeDependency
              .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), d => d.ReceiveProvinceNo == dto.ReceiveProvinceNo) //省编号
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No) //工单编码
             .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType)//受理类型
-            //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
+                                                                                                    //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
             .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannelCode == dto.Channel)
             //.WhereIF(dto.Channels.Any(), d => dto.Channels.Contains(d.SourceChannelCode)) //来源渠道
             //.WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId)) //热点类型
             .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.HotspotSpliceName != null && d.HotspotSpliceName.Contains(dto.Hotspot))
             .WhereIF(!string.IsNullOrEmpty(dto.TransferPhone), d => d.TransferPhone == dto.TransferPhone!) //转接号码
-            //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
-            //.WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.ActualHandleOrgCode)) //接办部门
-            //.WhereIF(!string.IsNullOrEmpty(dto.OrgId), d => d.CurrentHandleOrgId == dto.OrgId)//接办部门
-            .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName),d=>d.OrgLevelOneName.Contains(dto.OrgLevelOneName)) //一级部门
-            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName),d=>d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
+                                                                                                           //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
+                                                                                                           //.WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.ActualHandleOrgCode)) //接办部门
+                                                                                                           //.WhereIF(!string.IsNullOrEmpty(dto.OrgId), d => d.CurrentHandleOrgId == dto.OrgId)//接办部门
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), d => d.OrgLevelOneName.Contains(dto.OrgLevelOneName)) //一级部门
+            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
             .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
-            //.WhereIF(dto.EmergencyLevels.Any(), d => dto.EmergencyLevels.Contains(d.EmergencyLevel))  //紧急程度
+                                                                                               //.WhereIF(dto.EmergencyLevels.Any(), d => dto.EmergencyLevels.Contains(d.EmergencyLevel))  //紧急程度
             .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), d => d.FromPhone == dto.FromPhone) //来电号码
             .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.Contact == dto.PhoneNo!) //联系电话
-            //.WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), d => d.PushTypeCode == dto.PushTypeCode) //推送分类
+                                                                                         //.WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), d => d.PushTypeCode == dto.PushTypeCode) //推送分类
             .WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), x => x.OrderPushTypes.Any(opt => opt.PushTypeCode == dto.PushTypeCode))//推送分类
             .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart) //超期时间开始
             .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.ExpiredTime <= dto.ExpiredTimeEnd) //超期时间结束
-            //.WhereIF(dto.Statuses.Any(), d => dto.Statuses.Contains(d.Status))  //工单状态
+                                                                                            //.WhereIF(dto.Statuses.Any(), d => dto.Statuses.Contains(d.Status))  //工单状态
             .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)//工单状态
-            //.WhereIF(dto.Statuses.Any(d => d == EOrderStatus.SpecialToUnAccept), d => d.Status <= EOrderStatus.SpecialToUnAccept)
+                                                                      //.WhereIF(dto.Statuses.Any(d => d == EOrderStatus.SpecialToUnAccept), d => d.Status <= EOrderStatus.SpecialToUnAccept)
             .WhereIF(!string.IsNullOrEmpty(dto.ActualHandlerName), d => d.ActualHandlerName == dto.ActualHandlerName) //接办人
             .WhereIF(dto.IsScreen == true, d => d.OrderScreens.Any(x => x.Status != EScreenStatus.Refuse)) //有甄别
             .WhereIF(dto.IsScreen == false, d => !d.OrderScreens.Any(x => x.Status != EScreenStatus.Refuse)) //无甄别
@@ -862,10 +865,10 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .WhereIF(dto.IsOverTime == false, d => (d.ExpiredTime > DateTime.Now && d.Status < EOrderStatus.Filed) || (d.ExpiredTime > d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //否 超期
             .WhereIF(dto.IdentityType != null, d => d.IdentityType == dto.IdentityType) //来电主体
             .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName == dto.FromName) //来电人姓名
-            //.WhereIF(dto.AreaCodes.Any(), d => dto.AreaCodes.Contains(d.AreaCode)) //区域
-            //.WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode)//区域
-            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00")>0,d=>d.AreaCode.StartsWith(SqlFunc.Substring(dto.AreaCode,0,4)))
-            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00")<=0,d=>d.AreaCode.StartsWith(dto.AreaCode))
+                                                                                           //.WhereIF(dto.AreaCodes.Any(), d => dto.AreaCodes.Contains(d.AreaCode)) //区域
+                                                                                           //.WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode)//区域
+            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00") > 0, d => d.AreaCode.StartsWith(SqlFunc.Substring(dto.AreaCode, 0, 4)))
+            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00") <= 0, d => d.AreaCode.StartsWith(dto.AreaCode))
             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, d => d.Source == ESource.ProvinceStraight)
             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, d => d.Source != ESource.ProvinceStraight)
             .WhereIF(!string.IsNullOrEmpty(dto.SensitiveWord), d => SqlFunc.JsonArrayAny(d.Sensitive, dto.SensitiveWord))
@@ -2066,8 +2069,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             //特提(撤回至发起)
             if (!string.IsNullOrEmpty(order.WorkflowId))
             {
-                current = SessionContextCreator.CreateSessionContext("province",_cityBaseConfiguration.Value);
-                await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, cancellationToken);
+                current = SessionContextCreator.CreateSessionContext(_sessionContext, "province", _cityBaseConfiguration.Value);
+                await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, order.Status >= EOrderStatus.Filed, cancellationToken);
             }
         }
         return _mapper.Map<AddOrderResponse>(order);

+ 50 - 48
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -69,6 +69,7 @@ namespace Hotline.Application.Subscribers
         private readonly IRepository<OrderPublish> _orderPublishRepository;
         private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
         private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
+        private readonly ISessionContext _sessionContext;
 
         public DataSharingSubscriber(
             IRepository<OrderVisit> orderVisitRepository,
@@ -100,7 +101,8 @@ namespace Hotline.Application.Subscribers
             ICallApplication callApplication,
             IRepository<OrderPublish> orderPublishRepository,
             ISystemDicDataCacheManager sysDicDataCacheManager,
-            IOptions<CityBaseConfiguration> cityBaseConfiguration)
+            IOptions<CityBaseConfiguration> cityBaseConfiguration,
+            ISessionContext sessionContext)
         {
             _orderSendBackRepository = orderSendBackRepository;
             _workflowApplication = workflowApplication;
@@ -132,6 +134,7 @@ namespace Hotline.Application.Subscribers
             _orderPublishRepository = orderPublishRepository;
             _sysDicDataCacheManager = sysDicDataCacheManager;
             _cityBaseConfiguration = cityBaseConfiguration;
+            _sessionContext = sessionContext;
         }
 
         /// <summary>
@@ -178,48 +181,48 @@ namespace Hotline.Application.Subscribers
 
                 //if (dto.Result is 1)
                 //{
-                    //var now = DateTime.Now;
-                    //var handleDuration = order.StartTime.HasValue
-                    //    ? _timeLimitDomainService.CalcWorkTime(order.StartTime.Value,
-                    //        now, order.ProcessType is EProcessType.Zhiban)
-                    //    : 0;
-                    //var fileDuration = order.CenterToOrgTime.HasValue
-                    //    ? _timeLimitDomainService.CalcWorkTime(order.CenterToOrgTime.Value,
-                    //        now, order.ProcessType is EProcessType.Zhiban)
-                    //    : 0;
-                    //var allDuration = order.StartTime.HasValue
-                    //    ? _timeLimitDomainService.CalcWorkTime(order.StartTime.Value, now,
-                    //        order.ProcessType is EProcessType.Zhiban)
-                    //    : 0;
-                    //order.File(now, handleDuration, fileDuration, allDuration);
-                    //await _orderRepository.UpdateAsync(order, cancellationToken);
-
-                    //var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
-                    //if (string.IsNullOrEmpty(order.WorkflowId))
-                    //{
-                    //    var startDto = new StartWorkflowDto
-                    //    {
-                    //        DefinitionModuleCode = WorkflowModuleConsts.OrderHandle,
-                    //        Title = order.Title,
-                    //        Opinion = dto.Reason ?? "省工单同意退回",
-                    //    };
-                    //    await _workflowApplication.StartToEndAsync(startDto, current, order.Id, order.ExpiredTime,
-                    //        cancellationToken);
-                    //}
-                    //else
-                    //{
-                    //    //await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, "省工单同意退回", null,
-                    //    //    EReviewResult.Approval, cancellationToken);
-                    //    await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Reason ?? "省工单同意退回",
-                    //        null, order.ExpiredTime, cancellationToken: cancellationToken);
-                    //}
-
-
-                    //TODO 工单终止
+                //var now = DateTime.Now;
+                //var handleDuration = order.StartTime.HasValue
+                //    ? _timeLimitDomainService.CalcWorkTime(order.StartTime.Value,
+                //        now, order.ProcessType is EProcessType.Zhiban)
+                //    : 0;
+                //var fileDuration = order.CenterToOrgTime.HasValue
+                //    ? _timeLimitDomainService.CalcWorkTime(order.CenterToOrgTime.Value,
+                //        now, order.ProcessType is EProcessType.Zhiban)
+                //    : 0;
+                //var allDuration = order.StartTime.HasValue
+                //    ? _timeLimitDomainService.CalcWorkTime(order.StartTime.Value, now,
+                //        order.ProcessType is EProcessType.Zhiban)
+                //    : 0;
+                //order.File(now, handleDuration, fileDuration, allDuration);
+                //await _orderRepository.UpdateAsync(order, cancellationToken);
+
+                //var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
+                //if (string.IsNullOrEmpty(order.WorkflowId))
+                //{
+                //    var startDto = new StartWorkflowDto
+                //    {
+                //        DefinitionModuleCode = WorkflowModuleConsts.OrderHandle,
+                //        Title = order.Title,
+                //        Opinion = dto.Reason ?? "省工单同意退回",
+                //    };
+                //    await _workflowApplication.StartToEndAsync(startDto, current, order.Id, order.ExpiredTime,
+                //        cancellationToken);
                 //}
                 //else
                 //{
-                   
+                //    //await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, "省工单同意退回", null,
+                //    //    EReviewResult.Approval, cancellationToken);
+                //    await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Reason ?? "省工单同意退回",
+                //        null, order.ExpiredTime, cancellationToken: cancellationToken);
+                //}
+
+
+                //TODO 工单终止
+                //}
+                //else
+                //{
+
                 //}
                 await _orderSendBackRepository.UpdateAsync(sendBack, cancellationToken);
             }
@@ -257,7 +260,7 @@ namespace Hotline.Application.Subscribers
             await _orderRevokeRepository.AddAsync(orderRevoke, cancellationToken);
 
             //宜宾需求:特提至中心(优先派单组无派单组节点就特提至坐席),由派单员归档
-            var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
+            var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
             if (string.IsNullOrEmpty(order?.WorkflowId))
             {
                 var startDto = new StartWorkflowDto
@@ -266,12 +269,11 @@ namespace Hotline.Application.Subscribers
                     Title = order.Title,
                     Opinion = dto.Opinion,
                 };
-              order.WorkflowId =  await _workflowApplication.StartWorkflowToStartStepAsync(startDto, current, order.Id, order.ExpiredTime, cancellationToken);
-              await _orderRepository.UpdateAsync(order, cancellationToken);
+                await _workflowApplication.StartWorkflowToStartStepAsync(startDto, current, order.Id, order.ExpiredTime, cancellationToken);
             }
             else
             {
-                await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion, current, cancellationToken);
+                await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion, current, order.Status >= EOrderStatus.Filed, cancellationToken);
             }
         }
 
@@ -431,7 +433,7 @@ namespace Hotline.Application.Subscribers
                             x.Status == EScreenStatus.Approval)
                 .FirstAsync(cancellationToken);
 
-            var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
+            var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
             await _workflowApplication.HandleToEndAsync(current,
                 orderScreen.WorkflowId, "省上推送甄别结果", null,
                 dto.ProvinceScreenResult.AuditResult
@@ -677,7 +679,7 @@ namespace Hotline.Application.Subscribers
                         orderDelay.FileJson = await _fileRepository.AddFileAsync(dto.Files, orderDelay.Id, orderDelay.WorkflowId, cancellationToken);
                     await _orderDelayRepository.UpdateAsync(orderDelay, cancellationToken);
 
-                    var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
+                    var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
                     await _workflowApplication.HandleToEndAsync(current, orderDelay.WorkflowId, dto.Opinion, dto.Files,
                         dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed, cancellationToken);
                 }
@@ -703,12 +705,12 @@ namespace Hotline.Application.Subscribers
             //    await _orderRepository.FileAsync(order, cancellationToken);
             //}
 
-            var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
+            var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
             switch (dto.FinishType)
             {
                 case "0":
                     //退回:撤回至发起人
-                    await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, dto.Opinion, current, cancellationToken);//todo think是否需要保存附件至省平台办理节点?
+                    await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, dto.Opinion, current, order.Status >= EOrderStatus.Filed, cancellationToken);//todo think是否需要保存附件至省平台办理节点?
                     break;
                 case "1":
                     //办结:归档

+ 7 - 7
src/Hotline/Authentications/Police110SessionContext.cs

@@ -17,19 +17,19 @@ namespace Hotline.Authentications
         /// <summary>
         /// Id of current tenant or null for host
         /// </summary>
-        public string? UserId { get; }
+        public string? UserId { get; set; }
 
         /// <summary>
         /// Id of current user or throw Exception for guest
         /// </summary>
         public string RequiredUserId => UserId ?? throw new ArgumentNullException();
-        public string? UserName { get; }
-        public string? Phone { get; }
+        public string? UserName { get; set; }
+        public string? Phone { get; set; }
 
         /// <summary>
         /// Roles
         /// </summary>
-        public string[] Roles { get; }
+        public string[] Roles { get; set; }
         public string? OrgId { get; set; }
         public string RequiredOrgId => OrgId ?? throw new ArgumentNullException();
         public string? OrgName { get; set; }
@@ -41,12 +41,12 @@ namespace Hotline.Authentications
         /// 部门行政区划名称
         /// </summary>
         public string? OrgAreaName { get; set; }
-        public string? AreaId { get; }
-        public string? ClientId { get; }
+        public string? AreaId { get; set; }
+        public string? ClientId { get; set; }
 
         /// <summary>
         /// 工号
         /// </summary>
-        public string? StaffNo { get; }
+        public string? StaffNo { get; set; }
     }
 }

+ 7 - 7
src/Hotline/Authentications/ProvinceSessionContext.cs

@@ -18,7 +18,7 @@ namespace Hotline.Authentications
         /// <summary>
         /// Id of current tenant or null for host
         /// </summary>
-        public string? UserId { get; }
+        public string? UserId { get; set; }
 
         /// <summary>
         /// Id of current user or throw Exception for guest
@@ -26,13 +26,13 @@ namespace Hotline.Authentications
         /// <exception cref="AuthenticationException"></exception>
         public string RequiredUserId => UserId ?? throw new ArgumentNullException();
         //取消写入
-		public string? UserName { get; }
-        public string? Phone { get; }
+		public string? UserName { get; set; }
+        public string? Phone { get; set; }
 
         /// <summary>
         /// Roles
         /// </summary>
-        public string[] Roles { get; }
+        public string[] Roles { get; set; }
         public string? OrgId { get; set; }
         public string RequiredOrgId => OrgId ?? throw new ArgumentNullException();
         public string? OrgName { get; set; }
@@ -44,12 +44,12 @@ namespace Hotline.Authentications
         /// 部门行政区划名称
         /// </summary>
         public string? OrgAreaName { get; set; }
-        public string? AreaId { get; }
-        public string? ClientId { get; }
+        public string? AreaId { get; set; }
+        public string? ClientId { get; set; }
 
         /// <summary>
         /// 工号
         /// </summary>
-        public string? StaffNo { get; }
+        public string? StaffNo { get; set; }
     }
 }

+ 11 - 2
src/Hotline/Authentications/SessionContextCreator.cs

@@ -5,18 +5,27 @@ using System.Text;
 using System.Threading.Tasks;
 using Hotline.Configurations;
 using Hotline.Share.Enums.Order;
+using Hotline.Users;
 using XF.Domain.Authentications;
+using static Org.BouncyCastle.Math.EC.ECCurve;
 
 namespace Hotline.Authentications
 {
     public class SessionContextCreator
     {
-        public static ISessionContext CreateSessionContext(string source, CityBaseConfiguration cityBase)
+        public static ISessionContext CreateSessionContext(ISessionContext session, string source, CityBaseConfiguration cityBase)
         {
             switch (source)
             {
                 case "province":
-                    return new ProvinceSessionContext(cityBase.CityProvince);
+                    
+                    session.UserId = cityBase.CityProvince.UserId;
+                    session.UserName = cityBase.CityProvince.UserName;
+                    session.OrgId = cityBase.CityProvince.OrgId;
+                    session.OrgName = cityBase.CityProvince.OrgName;
+                    session.OrgLevel = 1;
+
+                    return session;
                 case "110":
                     return new Police110SessionContext(cityBase.PublicSecurity);
                 case "yb-enterprise":

+ 7 - 7
src/Hotline/Authentications/YbEnterpriseSessionContext.cs

@@ -17,20 +17,20 @@ namespace Hotline.Authentications
         /// <summary>
         /// Id of current tenant or null for host
         /// </summary>
-        public string? UserId { get; }
+        public string? UserId { get; set; }
 
         /// <summary>
         /// Id of current user or throw Exception for guest
         /// </summary>
         /// <exception cref="AuthenticationException"></exception>
         public string RequiredUserId => UserId ?? throw new ArgumentNullException();
-        public string? UserName { get; }
-        public string? Phone { get; }
+        public string? UserName { get; set; }
+        public string? Phone { get; set; }
 
         /// <summary>
         /// Roles
         /// </summary>
-        public string[] Roles { get; }
+        public string[] Roles { get; set; }
         public string? OrgId { get; set; }
         public string RequiredOrgId => OrgId ?? throw new ArgumentNullException();
         public string? OrgName { get; set; }
@@ -42,12 +42,12 @@ namespace Hotline.Authentications
         /// 部门行政区划名称
         /// </summary>
         public string? OrgAreaName { get; set; }
-        public string? AreaId { get; }
-        public string? ClientId { get; }
+        public string? AreaId { get; set; }
+        public string? ClientId { get; set; }
 
         /// <summary>
         /// 工号
         /// </summary>
-        public string? StaffNo { get; }
+        public string? StaffNo { get; set; }
     }
 }

+ 7 - 7
src/Hotline/Authentications/ZzptSessionContext.cs

@@ -17,19 +17,19 @@ namespace Hotline.Authentications
         /// <summary>
         /// Id of current tenant or null for host
         /// </summary>
-        public string? UserId { get; }
+        public string? UserId { get; set; }
 
         /// <summary>
         /// Id of current user or throw Exception for guest
         /// </summary>
         public string RequiredUserId => UserId ?? throw new ArgumentNullException();
-        public string? UserName { get; }
-        public string? Phone { get; }
+        public string? UserName { get; set; }
+        public string? Phone { get; set; }
 
         /// <summary>
         /// Roles
         /// </summary>
-        public string[] Roles { get; }
+        public string[] Roles { get; set; }
         public string? OrgId { get; set; }
         public string RequiredOrgId => OrgId ?? throw new ArgumentNullException();
         public string? OrgName { get; set; }
@@ -41,12 +41,12 @@ namespace Hotline.Authentications
         /// 部门行政区划名称
         /// </summary>
         public string? OrgAreaName { get; set; }
-        public string? AreaId { get; }
-        public string? ClientId { get; }
+        public string? AreaId { get; set; }
+        public string? ClientId { get; set; }
 
         /// <summary>
         /// 工号
         /// </summary>
-        public string? StaffNo { get; }
+        public string? StaffNo { get; set; }
     }
 }

+ 1 - 1
src/Hotline/FlowEngine/FlowAssignInfo.cs

@@ -14,7 +14,7 @@ public class FlowAssignInfo
     /// <summary>
     /// 办理对象(UserIds/OrgCodes)
     /// </summary>
-    public List<HandlerGroupItem> HandlerObjects { get; set; }
+    public List<HandlerGroupItem> HandlerObjects { get; set; } = new();
 
     /// <summary>
     /// 创建mode对象实例,自动生成一个groupId

+ 3 - 3
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -72,18 +72,18 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 撤回至开始节点
         /// </summary>
-        Task RecallToStartStepAsync(string workflowId, string opinion, ISessionContext current, CancellationToken cancellationToken);
+        Task RecallToStartStepAsync(string workflowId, string opinion, ISessionContext current, bool isOrderFiled, CancellationToken cancellationToken);
 
         /// <summary>
         /// 特提至派单节点(无派单节点会抛异常)
         /// </summary>
-        Task RecallToSendStepAsync(string workflowId, string opinion, ISessionContext current, CancellationToken cancellationToken);
+        Task RecallToSendStepAsync(string workflowId, string opinion, ISessionContext current, bool isOrderFiled, CancellationToken cancellationToken);
 
         /// <summary>
         /// 特提至中心(优先派单组其次坐席)
         /// </summary>
         /// <returns></returns>
-        Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, ISessionContext current, CancellationToken cancellationToken);
+        Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, ISessionContext current, bool isOrderFiled, CancellationToken cancellationToken);
         
         ///// <summary>
         ///// 跳转(直接将流程跳转至任意节点)

+ 6 - 3
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -110,7 +110,7 @@ public partial class Workflow : CreationEntity
     /// 实际办理部门等级
     /// </summary>
     public int? ActualHandleOrgLevel { get; set; }
-    
+
     /// <summary>
     /// 实际办理部门行政区划编码
     /// </summary>
@@ -415,7 +415,7 @@ public partial class Workflow
         if (FlowType is EFlowType.Review && ReviewResult is EReviewResult.Unknown)
             ReviewResult = reviewResult;
 
-        
+
 
         ClearHandlers();
 
@@ -522,7 +522,7 @@ public partial class Workflow
         ActualHandleOrgAreaCode = handleOrgAreaCode;
         ActualHandleOrgAreaName = handleOrgAreaName;
         ActualHandleOrgLevel = handlerOrgLevel;
-        
+
         //坐席->派单存在不选办理对象的场景,所以要补赋值
         ActualHandleStepId = step.Id;
         ActualHandleStepCode = step.Code;
@@ -810,6 +810,9 @@ public partial class Workflow
 
     public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
     {
+        if (!handlers.Any()) return;
+        FlowedUserIds ??= new();
+        FlowedOrgIds ??= new();
         handlers = handlers.Select(d => d.ToLower());
         switch (type)
         {

+ 195 - 180
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -22,6 +22,7 @@ using XF.Domain.Entities;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using System.Reflection.Metadata;
+using System.Security.AccessControl;
 
 namespace Hotline.FlowEngine.Workflows
 {
@@ -627,7 +628,7 @@ namespace Hotline.FlowEngine.Workflows
             //如果有传入期满时间 新节点为传入的期满时间
             if (dto.ExpiredTime.HasValue)
                 prevStep.StepExpiredTime = dto.ExpiredTime;
-            
+
             //复制上一个节点为待接办
             var newPrevStep =
                 await DuplicateStepWithTraceAsync(workflow, prevStep, EWorkflowTraceType.Previous, cancellationToken);
@@ -1087,236 +1088,201 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 撤回至开始节点
         /// </summary>
-        public async Task RecallToStartStepAsync(string workflowId, string opinion, ISessionContext current,
+        public async Task RecallToStartStepAsync(string workflowId, string opinion, ISessionContext current, bool isOrderFiled,
             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,
+            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);
             if (startStep is null)
                 throw new UserFriendlyException($"数据异常, workflowId: {workflowId}", "该流程无开始节点");
 
-            await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
+            //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
+                    }
+                }
+            };
+            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, null, isOrderFiled, cancellationToken);
         }
 
         /// <summary>
         /// 撤回至派单节点
         /// </summary>
-        public async Task RecallToSendStepAsync(string workflowId, string opinion, ISessionContext current,
+        public async Task RecallToSendStepAsync(string workflowId, string opinion, ISessionContext current, bool isOrderFiled,
             CancellationToken cancellationToken)
         {
-            var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true,
+            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 null)
                 throw new UserFriendlyException($"未找到派单节点, workflowId: {workflowId}", "该流程无派单节点");
 
-            await RecallToTargetStepAsync(workflow, sendStep, opinion, current, cancellationToken);
+            //await RecallToTargetStepAsync(workflow, sendStep, opinion, current, 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
+                    }
+                }
+            };
+            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, null, isOrderFiled, cancellationToken);
         }
 
         /// <summary>
         /// 特提至中心(优先派单组其次坐席)
         /// </summary>
         /// <returns></returns>
-        public async Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, ISessionContext current,
+        public async Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, ISessionContext current, bool isOrderFiled,
             CancellationToken cancellationToken)
         {
-            var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true,
+            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)
             {
-                await RecallToTargetStepAsync(workflow, sendStep, opinion, current, 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,
+                        }
+                    }
+                };
+                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, null, isOrderFiled, cancellationToken);
             }
             else
             {
                 var startStep = workflow.Steps.First(d => d.StepType == EStepType.Start);
                 if (startStep is null)
                     throw new UserFriendlyException($"数据异常, workflowId: {workflowId}", "该流程无开始节点");
-                await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
-            }
-        }
 
-        private async Task RecallToTargetStepAsync(Workflow workflow, WorkflowStep targetStep, string opinion, ISessionContext current,
-            CancellationToken cancellationToken)
-        {
-            //update uncompleted traces
-            await RecallTraceAsync(workflow.Traces, opinion, current, cancellationToken);
-
-            await _workflowStepRepository.RemoveRangeAsync(workflow.Steps, cancellationToken);
-            workflow.Steps.RemoveAll(_ => true);
-
-            workflow.EndCountersign();
-            workflow.ResetOption();
-            if (workflow.Status is EWorkflowStatus.Completed)
-                workflow.SetStatusRunnable();
-
-            var newStartStep =
-                await DuplicateStepWithTraceAsync(workflow, targetStep, EWorkflowTraceType.Recall, cancellationToken);
-
-            workflow.UpdateActualStepWhenAssign(targetStep, new FlowStepHandler
-            {
-                UserId = targetStep.HandlerId,
-                Username = targetStep.HandlerName,
-                OrgId = targetStep.HandlerOrgId,
-                OrgName = targetStep.HandlerOrgName,
-            });
+                //await RecallToTargetStepAsync(workflow, startStep, opinion, current, cancellationToken);
 
-            workflow.UpdateCurrentStepWhenAssign(targetStep, new FlowStepHandler
-            {
-                UserId = targetStep.HandlerId,
-                Username = targetStep.HandlerName,
-                OrgId = targetStep.HandlerOrgId,
-                OrgName = targetStep.HandlerOrgName,
-            });
-
-            var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStep);
-
-            var flowAssignInfo = FlowAssignInfo.Create(targetStep.FlowAssignType.Value, targetStep.Handlers);
-            workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
-
-            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
-
-            var dto = _mapper.Map<RecallDto>(targetStep);
-            dto.WorkflowId = workflow.Id;
-            await _publisher.PublishAsync(new RecallNotify(workflow, targetStep, dto, isOrgToCenter),
-                PublishStrategy.ParallelWhenAll, 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
+                        }
+                    }
+                };
+                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, null, isOrderFiled, cancellationToken);
+            }
         }
 
-        ///// <summary>
-        ///// 跳转(直接将流程跳转至任意节点)
-        ///// </summary>
-        //public async Task JumpAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine,
-        //    FlowAssignInfo flowAssignInfo, CancellationToken cancellationToken)
+        //private async Task RecallToTargetStepAsync(Workflow workflow, WorkflowStep targetStep, string opinion, ISessionContext current,
+        //    CancellationToken cancellationToken)
         //{
-        //    //todo 跳转至结束节点,(自动办理)
-        //    //if (targetStepDefine.StepType is EStepType.Start or EStepType.End)
-        //    //    throw UserFriendlyException.SameMessage("开始/结束节点不支持跳转");
-
         //    //update uncompleted traces
-        //    await JumpTraceAsync(workflow.Id, dto, cancellationToken);
+        //    await RecallTraceAsync(workflow.Traces, opinion, current, cancellationToken);
 
-        //    bool isOrgToCenter = false, isCenterToOrg = false;
-        //    var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin);
-        //    if (targetStep == null)
-        //    {
-        //        //向后跳转
-
-        //        //此场景并非按配置流转,默认最靠后的节点做为targetStep的prevStep
-        //        var lastStep = workflow.Steps.Where(d => d.IsOrigin).MaxBy(d => d.CreationTime);
-        //        if (lastStep is null || lastStep.StepType is EStepType.End)
-        //            throw new UserFriendlyException($"流程流转数据异常,未结束流程出现endStep, flowId: {workflow.Id}", "流程流转数据异常");
-
-        //        var targetSteps = await CreateConfigStepsAsync(workflow, targetStepDefine, lastStep, dto,
-        //            flowAssignInfo, EWorkflowTraceStatus.Jump, cancellationToken);
-        //        targetStep = targetSteps.First();
-
-        //        workflow.EndCountersign();
-        //        workflow.ResetOption();
-
-        //        ////更新当前办理节点信息
-        //        //workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign,
-        //        //    _sessionContext.RequiredUserId, _sessionContext.UserName,
-        //        //    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-        //        //    _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-        //        //    nextStep: targetStep);
-
-        //        //calc workflow expired time
-        //        isCenterToOrg = CheckIfFlowFromCenterToOrg(workflow, targetStep);
-        //        //if (isCenterToOrg)
-        //        //    workflow.ExpiredTime = CalculateExpiredTime("");//todo calc expiredTime
-
-        //        #region 补充中间节点处理方案(暂不需要)
-
-        //        //var completeStepCodes = workflow.StepBoxes.Select(d => d.Code);
-        //        //var uncompleteStepDefines = workflow.Definition.Steps.Where(d => !completeStepCodes.Contains(d.Code));
-        //        //创建当前节点与目标节点中间节点
-        //        //var jumpDto = new BasicWorkflowDto
-        //        //{
-        //        //    Opinion = "跳转补充"
-        //        //};
-
-        //        //foreach (var stepDefine in uncompleteStepDefines)
-        //        //{
-        //        //    var previousStepId = lastStepBox.Steps.Count > 1 ? lastStepBox.Id : lastStepBox.Steps.First().Id;
-        //        //    if (dto.TargetStepCode == stepDefine.Code)
-        //        //    {
-        //        //        await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
-        //        //        break;
-        //        //    }
-
-        //        //    //jump业务下,如果当前节点为会签节点,第一个补充节点的subStep.PreviousId无法确定从哪个子节点跳转过来,统一处理为当前节点的stepBox.Id
-        //        //    lastStepBox = await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
-        //        //} 
-
-        //        #endregion
-        //    }
-        //    else
-        //    {
-        //        //返回之前节点
-        //        isOrgToCenter = await RecallAsync(workflow, dto, flowAssignInfo, targetStepDefine, targetStep,
-        //            EWorkflowTraceStatus.Jump, cancellationToken);
-        //    }
+        //    await _workflowStepRepository.RemoveRangeAsync(workflow.Steps, cancellationToken);
+        //    workflow.Steps.RemoveAll(_ => true);
 
-        //    workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
-        //    await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+        //    workflow.EndCountersign();
+        //    workflow.ResetOption();
+        //    if (workflow.Status is EWorkflowStatus.Completed)
+        //        workflow.SetStatusRunnable();
 
-        //    await _mediator.Publish(
-        //        new JumpNotify(workflow, targetStep, dto, flowAssignInfo, isCenterToOrg, isOrgToCenter),
-        //        cancellationToken);
-        //}
+        //    var newStartStep =
+        //        await DuplicateStepWithTraceAsync(workflow, targetStep, EWorkflowTraceType.Recall, cancellationToken);
 
-        ///// <summary>
-        ///// 重办
-        ///// </summary>
-        //public async Task RedoAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine,
-        //    FlowAssignInfo flowAssignInfo, CancellationToken cancellationToken)
-        //{
-        //    if (targetStepDefine.StepType is EStepType.Start or EStepType.End)
-        //        throw UserFriendlyException.SameMessage("开始/结束节点不支持重办");
+        //    workflow.UpdateActualStepWhenAssign(targetStep, new FlowStepHandler
+        //    {
+        //        UserId = targetStep.HandlerId,
+        //        Username = targetStep.HandlerName,
+        //        OrgId = targetStep.HandlerOrgId,
+        //        OrgName = targetStep.HandlerOrgName,
+        //    });
 
-        //    var targetStepBox = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode);
-        //    if (targetStepBox is null)
-        //        throw UserFriendlyException.SameMessage("未找到该节点配置");
+        //    workflow.UpdateCurrentStepWhenAssign(targetStep, new FlowStepHandler
+        //    {
+        //        UserId = targetStep.HandlerId,
+        //        Username = targetStep.HandlerName,
+        //        OrgId = targetStep.HandlerOrgId,
+        //        OrgName = targetStep.HandlerOrgName,
+        //    });
 
-        //    var isOrgToCenter = await RecallAsync(workflow, dto, flowAssignInfo, targetStepDefine, targetStepBox,
-        //        EWorkflowTraceStatus.Redo, cancellationToken);
+        //    var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStep);
 
-        //    workflow.Redo();
+        //    var flowAssignInfo = FlowAssignInfo.Create(targetStep.FlowAssignType.Value, targetStep.Handlers);
         //    workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
 
-        //    //todo calc expiredTime
-        //    //dto.Extension.TimeLimitCount
-
-
         //    await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
-        //    await _mediator.Publish(new RedoNotify(workflow, dto, isOrgToCenter), cancellationToken);
-        //}
+        //    var dto = _mapper.Map<RecallDto>(targetStep);
+        //    dto.WorkflowId = workflow.Id;
+        //    await _publisher.PublishAsync(new RecallNotify(workflow, targetStep, dto, isOrgToCenter),
+        //        PublishStrategy.ParallelWhenAll, cancellationToken);
 
-        ///// <summary>
-        ///// 否决(审批流程不通过)
-        ///// </summary>
-        ///// <returns></returns>
-        //public async Task RejectAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
-        //{
-        //    var currentStep = GetUnHandleStep(workflow.Steps, _sessionContext.RequiredOrgId,
-        //        _sessionContext.RequiredUserId);
-        //    await HandleStepAsync(currentStep, workflow, dto, null,
-        //        null, null, cancellationToken);
-        //    await _workflowStepRepository.UpdateAsync(currentStep, cancellationToken);
-
-        //    workflow.UpdateActualStepWhenHandle(currentStep,
-        //        _sessionContext.RequiredUserId, _sessionContext.UserName,
-        //        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-        //        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-        //        _sessionContext.OrgLevel);
-
-        //    var endStepDefine = workflow.WorkflowDefinition.FindEndStepDefine();
-        //    var endTrace = await EndAsync(workflow, dto, endStepDefine, currentStep, cancellationToken);
-
-        //    //await _mediator.Publish(new RejectNotify(workflow, dto), cancellationToken);
         //}
 
         /// <summary>
@@ -1489,7 +1455,8 @@ namespace Hotline.FlowEngine.Workflows
             startStep.IsOrigin = true;
             startStep.Status = EWorkflowStepStatus.WaitForAccept;
             startStep.PrevChosenStepCode = null;
-            startStep.StepExpiredTime = expiredTime;
+            if (expiredTime.HasValue)
+                startStep.StepExpiredTime = expiredTime;
 
             startStep.Assign(handler.UserId, handler.Username,
                 handler.OrgId, handler.OrgName,
@@ -2302,9 +2269,6 @@ namespace Hotline.FlowEngine.Workflows
             if (removeSteps.Any())
             {
                 await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
-                //await _workflowStepRepository.RemoveNav(removeSteps)
-                //    .Include(d => d.StepHandlers)
-                //    .ExecuteCommandAsync();
                 workflow.Steps.RemoveAll(d => removeSteps.Contains(d));
 
                 //更新快照对应节点状态
@@ -2323,6 +2287,21 @@ namespace Hotline.FlowEngine.Workflows
 
             await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken);
 
+            //结束会签
+            var unCompleteCountersigns = workflow.Countersigns.Where(d => !d.IsCompleted()).ToList();
+            if (unCompleteCountersigns.Any())
+            {
+                foreach (var unCompleteCountersign in unCompleteCountersigns)
+                {
+                    unCompleteCountersign.End(null, null, EBusinessType.File,
+                        _sessionContext.RequiredUserId, _sessionContext.UserName,
+                        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                }
+
+                await _workflowCountersignRepository.UpdateRangeAsync(unCompleteCountersigns, cancellationToken);
+            }
+
             workflow.EndCountersign();
             workflow.ResetOption();
             if (workflow.Status is EWorkflowStatus.Completed)
@@ -2802,7 +2781,8 @@ namespace Hotline.FlowEngine.Workflows
             step.CountersignId = countersignId;
             step.Status = stepStatus;
             step.CountersignPosition = countersignPosition;
-            step.StepExpiredTime = expiredTime;
+            if (expiredTime.HasValue)
+                step.StepExpiredTime = expiredTime;
             //step.TimeLimit = GetTimeLimit("");
             step.IsOrigin = isOrigin;
             step.Name = stepName;
@@ -2823,6 +2803,41 @@ namespace Hotline.FlowEngine.Workflows
             return step;
         }
 
+        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();
+            }
+        }
         #endregion
     }
 }

+ 6 - 6
src/XF.Domain/Authentications/DefaultSessionContext.cs

@@ -38,7 +38,7 @@ namespace XF.Domain.Authentications
         /// <summary>
         /// Id of current tenant or null for host
         /// </summary>
-        public string? UserId { get; }
+        public string? UserId { get; set; }
 
         /// <summary>
         /// Id of current user or throw Exception for guest
@@ -46,14 +46,14 @@ namespace XF.Domain.Authentications
         /// <exception cref="AuthenticationException"></exception>
         public string RequiredUserId => UserId ?? throw new ArgumentNullException();
 
-        public string? UserName { get; }
+        public string? UserName { get; set; }
 
-        public string? Phone { get; }
+        public string? Phone { get; set; }
 
         /// <summary>
         /// Roles
         /// </summary>
-        public string[] Roles { get; }
+        public string[] Roles { get; set; }
 
         public string? OrgId { get; set; }
         public string? OrgCode { get; set; }
@@ -72,12 +72,12 @@ namespace XF.Domain.Authentications
 
         public string RequiredOrgId => OrgId ?? throw new ArgumentNullException();
         
-        public string? ClientId { get; }
+        public string? ClientId { get; set; }
 
         /// <summary>
         /// 工号
         /// </summary>
-        public string? StaffNo { get; }
+        public string? StaffNo { get; set; }
     }
 
     public static class ClaimsPrincipalExtensions

+ 7 - 7
src/XF.Domain/Authentications/ISessionContext.cs

@@ -7,7 +7,7 @@ public interface ISessionContext
     /// <summary>
     /// Id of current tenant or null for host
     /// </summary>
-    string? UserId { get; }
+    string? UserId { get; set; }
 
     /// <summary>
     /// Id of current user or throw Exception for guest
@@ -15,14 +15,14 @@ public interface ISessionContext
     /// <exception cref="AuthenticationException"></exception>
     string RequiredUserId { get; }
 
-    string? UserName { get; }
+    string? UserName { get; set; }
 
-    string? Phone { get; }
+    string? Phone { get; set; }
 
     /// <summary>
     /// Roles
     /// </summary>
-    string[] Roles { get; }
+    string[] Roles { get; set; }
 
     string? OrgId { get; set; }
     string RequiredOrgId { get; }
@@ -37,12 +37,12 @@ public interface ISessionContext
     /// </summary>
     string? OrgAreaName { get; set; }
 
-    string? AreaId { get; }
+    string? AreaId { get; set; }
 
-    string? ClientId { get; }
+    string? ClientId { get; set; }
 
     /// <summary>
     /// 工号
     /// </summary>
-    string? StaffNo { get; }
+    string? StaffNo { get; set; }
 }