Browse Source

workflow需求调整

xf 1 year ago
parent
commit
6beb1db021

+ 24 - 17
src/Hotline.Api/Controllers/OrderController.cs

@@ -78,24 +78,24 @@ public class OrderController : BaseController
     [HttpGet("publish-order-list")]
     public async Task<PagedDto<PublishDto>> PublishOrderList([FromQuery] QueryOrderPublishDto dto)
     {
-        var (total,items) = await _orderRepository.Queryable()
+        var (total, items) = await _orderRepository.Queryable()
             .Includes(d => d.OrderPublished)
-            .Includes(d=>d.Employee)
+            .Includes(d => d.Employee)
             .WhereIF(!string.IsNullOrEmpty(dto.OrderTitle), d => d.Title.Contains(dto.OrderTitle!))
-            .WhereIF(dto.PubState == EPubState.Pub, d=> d.Progress == EProgress.Published || d.Progress== EProgress.Visited)
-            .WhereIF(dto.PubState == EPubState.NoPub, d=> d.Progress == EProgress.Managing)
+            .WhereIF(dto.PubState == EPubState.Pub, d => d.Progress == EProgress.Published || d.Progress == EProgress.Visited)
+            .WhereIF(dto.PubState == EPubState.NoPub, d => d.Progress == EProgress.Managing)
             .WhereIF(!string.IsNullOrEmpty(dto.PubMan), d => d.Employee.Name.Contains(dto.PubMan!) || d.Employee.StaffNo.Contains(dto.PubMan!))
-            .WhereIF(dto.PubRange!= null,d=>d.OrderPublished.PublishState == dto.PubRange)
-            .WhereIF(dto.AcceptTypes.Any(),d=>dto.AcceptTypes.Contains(d.AcceptType))
-            .WhereIF(dto.HotspotIds.Any(),d=> dto.HotspotIds.Contains(d.HotspotId))
+            .WhereIF(dto.PubRange != null, d => d.OrderPublished.PublishState == dto.PubRange)
+            .WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptType))
+            .WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId))
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
-            .WhereIF(dto.FiledTimeStart.HasValue,d=>d.OrderPublished.CreationTime>= dto.CreationTimeStart)
-            .WhereIF(dto.FiledTimeEnd.HasValue,d=> d.OrderPublished.CreationTime <= dto.CreationTimeEnd)
+            .WhereIF(dto.FiledTimeStart.HasValue, d => d.OrderPublished.CreationTime >= dto.CreationTimeStart)
+            .WhereIF(dto.FiledTimeEnd.HasValue, d => d.OrderPublished.CreationTime <= dto.CreationTimeEnd)
             .OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
-        return new PagedDto<PublishDto>(total,_mapper.Map<IReadOnlyList<PublishDto>>(items));
+        return new PagedDto<PublishDto>(total, _mapper.Map<IReadOnlyList<PublishDto>>(items));
     }
 
     ///// <summary>
@@ -127,7 +127,8 @@ public class OrderController : BaseController
             .WhereIF(dto.Channels.Any(), d => dto.Channels.Contains(d.Channel))
             .WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId))
             .WhereIF(!string.IsNullOrEmpty(dto.TransferPhone), d => d.TransferPhone.Contains(dto.TransferPhone!))
-            .WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
+            //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
+            .WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.Workflow.ActualHandleOrgCode))
             .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.Employee.Name.Contains(dto.NameOrNo!) || d.Employee.StaffNo.Contains(dto.NameOrNo!))
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
@@ -176,19 +177,25 @@ public class OrderController : BaseController
             .FirstAsync(d => d.Id == id);
         if (order == null)
             return new();
+
+        var canHandle = false;
         if (!string.IsNullOrEmpty(order?.WorkflowId))
         {
-            order.Workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withSteps: true, withSupplements: true, withAssigns: true, cancellationToken: HttpContext.RequestAborted);
+            var (workflow, handlePermission) = await _workflowDomainService.GetWorkflowHandlePermissionAsync(
+                order.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode,
+                withSteps: true, withSupplements: true, withAssigns: true,
+                cancellationToken: HttpContext.RequestAborted);
+            order.Workflow = workflow;
+            canHandle = handlePermission;
 
-            _mediator.Publish(new GetOrderDetailNotify(order.Workflow, _sessionContext.RequiredUserId,
-                 _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName));
+            await _mediator.Publish(new GetOrderDetailNotify(order.Workflow, _sessionContext.RequiredUserId,
+                  _sessionContext.UserName, _sessionContext.RequiredOrgCode, _sessionContext.OrgName));
         }
 
         var dto = _mapper.Map<OrderDto>(order!);
 
         if (order?.Workflow != null)
-            dto.Workflow.CanHandle =
-                order.Workflow.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode);
+            dto.Workflow.CanHandle = canHandle;
         return dto;
     }
 
@@ -353,6 +360,6 @@ public class OrderController : BaseController
 
 
 
-   
+
 
 }

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

@@ -9,7 +9,7 @@ namespace Hotline.Application.FlowEngine
 {
     public interface IWorkflowApplication
     {
-        Task StartWorkflowAsync(StartWorkflowDto dto, string? externalId = null, CancellationToken cancellationToken = default);
+        Task StartWorkflowAsync(StartWorkflowDto dto, string externalId, CancellationToken cancellationToken);
 
         /// <summary>
         /// 流转至下一节点(节点办理)

+ 70 - 46
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -13,6 +13,7 @@ using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Identity;
 using Hotline.Users;
+using MapsterMapper;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
@@ -36,6 +37,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     private readonly IUserDomainService _userDomainService;
     private readonly IAccountDomainService _accountDomainService;
     private readonly ISessionContext _sessionContext;
+    private readonly IMapper _mapper;
 
     public WorkflowApplication(
         IDefinitionDomainService definitionDomainService,
@@ -48,7 +50,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         ISystemOrganizeRepository organizeRepository,
         IRoleRepository roleRepository,
         IWfModuleCacheManager wfModuleCacheManager,
-        ISessionContext sessionContext)
+        ISessionContext sessionContext,
+        IMapper mapper)
     {
         _definitionDomainService = definitionDomainService;
         _workflowDomainService = workflowDomainService;
@@ -61,22 +64,44 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         _roleRepository = roleRepository;
         _wfModuleCacheManager = wfModuleCacheManager;
         _sessionContext = sessionContext;
+        _mapper = mapper;
     }
 
-    public async Task StartWorkflowAsync(StartWorkflowDto dto, string? externalId = null, CancellationToken cancellationToken = default)
+    //public async Task StartWorkflowAsync(StartWorkflowDto dto, string? externalId = null, CancellationToken cancellationToken = default)
+    //{
+    //    var validator = new StartWorkflowDtoValidator();
+    //    var validResult = validator.Validate(dto);
+    //    if (!validResult.IsValid)
+    //        throw new UserFriendlyException($"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
+
+    //    var wfModule = await GetWorkflowModuleAsync(dto.DefinitionModuleCode, cancellationToken);
+    //    var definition = wfModule.Definition;
+    //    if (definition == null)
+    //        throw new UserFriendlyException("无效模板编码");
+    //    if (definition.Status is not EDefinitionStatus.Enable)
+    //        throw new UserFriendlyException("该模板不可用");
+
+    //    var workflow = await _workflowDomainService.CreateWorkflowAsync(wfModule, dto.Title,
+    //        _sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, externalId, cancellationToken);
+
+    //    var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(definition, dto.NextStepCode);
+    //    var isStartCountersign = nextStepBoxDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
+
+    //    var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
+
+    //    await _workflowDomainService.StartAsync(workflow, dto, nextStepBoxDefine, isStartCountersign, flowAssignMode, cancellationToken);
+
+    //    //更新接办部门(详情页面展示)
+    //    await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
+    //}
+
+    public async Task StartWorkflowAsync(StartWorkflowDto dto, string externalId, CancellationToken cancellationToken)
     {
         var validator = new StartWorkflowDtoValidator();
         var validResult = validator.Validate(dto);
         if (!validResult.IsValid)
             throw new UserFriendlyException($"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
 
-        //if (string.IsNullOrEmpty(dto.DefinitionCode) && string.IsNullOrEmpty(dto.DefinitionModuleCode))
-        //    throw new UserFriendlyException("非法参数");
-
-        //var definition = string.IsNullOrEmpty(dto.DefinitionCode)
-        //    ? await _definitionDomainService.GetLastVersionByModuleCodeAsync(dto.DefinitionModuleCode, cancellationToken)
-        //    : await _definitionDomainService.GetLastVersionAsync(dto.DefinitionCode, cancellationToken);
-
         var wfModule = await GetWorkflowModuleAsync(dto.DefinitionModuleCode, cancellationToken);
         var definition = wfModule.Definition;
         if (definition == null)
@@ -84,49 +109,21 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         if (definition.Status is not EDefinitionStatus.Enable)
             throw new UserFriendlyException("该模板不可用");
 
-        var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(definition, dto.NextStepCode);
         var workflow = await _workflowDomainService.CreateWorkflowAsync(wfModule, dto.Title,
             _sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, externalId, cancellationToken);
 
-        var isStartCountersign = nextStepBoxDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
-
-        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
+        await _workflowDomainService.StartAsync(workflow, cancellationToken);
 
-        await _workflowDomainService.StartAsync(workflow, dto, nextStepBoxDefine, isStartCountersign, flowAssignMode, cancellationToken);
-
-        //更新接办部门(详情页面展示)
-        await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
+        await NextAsync(workflow, dto, cancellationToken);
     }
 
     /// <summary>
     /// 流转至下一节点(节点办理)
     /// </summary>
-    /// <param name="dto"></param>
-    /// <param name="cancellationToken"></param>
-    /// <returns></returns>
     public async Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, withCountersigns: true, cancellationToken: cancellationToken);
-        var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
-
-        //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签)
-        if (nextStepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
-            throw new UserFriendlyException("未指定节点处理者");
-
-        //下一节点为结束节点时,无办理人等参数,只有办理意见即可
-        var isFromCenterToOrg = nextStepBoxDefine.StepType is not EStepType.End
-                                && await CheckIfFlowOutFromCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
-        if (isFromCenterToOrg)
-            await _orderDomainService.FlowFromCenterToOrgAsync(workflow.Id, cancellationToken);
-
-        var isStartCountersign = nextStepBoxDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
-
-        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
-
-        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isFromCenterToOrg, isStartCountersign, flowAssignMode, cancellationToken);
-
-        //更新接办部门(详情页面展示)
-        await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
+        await NextAsync(workflow, dto, cancellationToken);
     }
 
     /// <summary>
@@ -264,6 +261,33 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
     #region private
 
+    private async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
+    {
+        var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+
+        //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签)
+        if (nextStepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
+            throw new UserFriendlyException("未指定节点处理者");
+
+        //下一节点为结束节点时,无办理人等参数,只有办理意见即可
+        var isFromCenterToOrg = nextStepBoxDefine.StepType is not EStepType.End
+                                && await CheckIfFlowOutFromCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
+        if (isFromCenterToOrg)
+            await _orderDomainService.FlowFromCenterToOrgAsync(workflow.Id, cancellationToken);
+
+        var isStartCountersign = nextStepBoxDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
+
+        if (isStartCountersign && nextStepBoxDefine.StepType is EStepType.CountersignEnd)
+            throw UserFriendlyException.SameMessage("汇总节点不支持办理会签");
+
+        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
+
+        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isStartCountersign, flowAssignMode, cancellationToken);
+
+        //更新接办部门(详情页面展示)
+        await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
+    }
+
     /// <summary>
     /// 查询流程业务模块
     /// </summary>
@@ -317,12 +341,12 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                         //}
                         //else
                         //{
-                            var accounts = await _accountRepository.Queryable()
-                                .Includes(d => d.User, d => d.Organization)
-                                .Where(d => dto.NextHandlers.Select(d => d.Id).Contains(d.Name))
-                                .ToListAsync();
-                            assigns = accounts.Select(d => d.User.Organization).Select(d =>
-                                WorkflowAssign.Create(workflow.Id, d.OrgCode, d.OrgName)).ToList();
+                        var accounts = await _accountRepository.Queryable()
+                            .Includes(d => d.User, d => d.Organization)
+                            .Where(d => dto.NextHandlers.Select(d => d.Id).Contains(d.Name))
+                            .ToListAsync();
+                        assigns = accounts.Select(d => d.User.Organization).Select(d =>
+                            WorkflowAssign.Create(workflow.Id, d.OrgCode, d.OrgName)).ToList();
                         //}
                     }
 

+ 2 - 2
src/Hotline.Application/Handlers/FlowEngine/NextStepHandler.cs

@@ -55,8 +55,8 @@ public class NextStepHandler : INotificationHandler<NextStepNotify>
         {
             case WorkflowModuleConsts.OrderManage:
                 var orderDto = await _orderDomainService.ManageFlowNextAsync(
-                      notification.FlowAssignMode, notification.IsCountersignStart, notification.IsCountersignEnd,
-                      workflow.ExternalId, workflow.CurrentStepTime, workflow.CurrentStepName, workflow.ExpiredTime, workflow.ProcessType,
+                      notification.FlowAssignMode, notification.IsFromCenterToOrg, notification.IsCountersignStart, notification.IsCountersignEnd,
+                      workflow.ExternalId, workflow.ActualHandleStepTime, workflow.ActualHandleStepName, workflow.ExpiredTime, workflow.ProcessType,
                       cancellationToken);
 
                 await _capPublisher.PublishAsync(EventNames.HotlineOrderFlow, new OrderFlowDto

+ 1 - 1
src/Hotline.Application/Handlers/FlowEngine/StartWorkflowHandler.cs

@@ -46,7 +46,7 @@ namespace Hotline.Application.Handlers.FlowEngine
                 case WorkflowModuleConsts.OrderManage:
                     await _orderDomainService.ManageFlowStartAsync(notification.FlowAssignMode,
                         notification.IsCountersignStart, workflow.ExternalId, workflow.Id,
-                        workflow.CurrentStepTime, workflow.CurrentStepName, workflow.ExpiredTime, cancellationToken);
+                        workflow.ActualHandleStepTime, workflow.ActualHandleStepName, workflow.ExpiredTime, cancellationToken);
                     break;
                 case WorkflowModuleConsts.KnowledgeAdd:
                 case WorkflowModuleConsts.KnowledgeUpdate:

+ 3 - 3
src/Hotline.Application/Mappers/MapperConfigs.cs

@@ -74,12 +74,12 @@ namespace Hotline.Application.Mappers
                 ;
 
             config.ForType<Workflow, WorkflowDto>()
-                .IgnoreIf((s, d) => s.Assigns == null || !s.Assigns.Any(), d => d.AssignOrgs)
+                //.IgnoreIf((s, d) => s.Assigns == null || !s.Assigns.Any(), d => d.AssignOrgs)
                 .IgnoreIf((s, d) => s.Supplements == null || !s.Supplements.Any(), d => d.Supplements)
                 .IgnoreIf((s, d) => s.Traces == null || !s.Traces.Any(), d => d.Traces)
                 //.IgnoreIf((s, d) => s.StepBoxes == null || !s.StepBoxes.Any(), d => d.StepBoxes)
                 //.IgnoreIf((s, d) => s.Definition == null, d => d.Definition)
-                .Map(d => d.AssignOrgs, s => string.Join(',', s.Assigns.Select(d => d.OrgName)))
+                //.Map(d => d.AssignOrgs, s => string.Join(',', s.Assigns.Select(d => d.OrgName)))
                 .Ignore(d => d.Definition)
                 .Ignore(d => d.StepBoxes)
                 //.Ignore(d=>d.Supplements)
@@ -123,7 +123,7 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.PageView, x => x.Knowledge.PageView)
                 .Map(d => d.Status, x => x.Knowledge.Status)
                 .Map(d => d.WorkflowModuleStatus, x => x.WorkflowModuleStatus)
-                .Map(d => d.CurrentStepTime, x => x.Workflow.CurrentStepTime);
+                .Map(d => d.CurrentStepTime, x => x.Workflow.ActualHandleStepTime);
 
             config.NewConfig<KnowledgeWorkFlow, KnowledgeDeleteApplyDataDto>()
                  .Map(d => d.Id, x => x.Knowledge.Id)

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

@@ -0,0 +1,24 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.FlowEngine;
+
+public enum EBusinessProperty
+{
+    /// <summary>
+    /// 中心节点
+    /// </summary>
+    [Description("中心节点")]
+    Center = 0,
+
+    /// <summary>
+    /// 派单节点
+    /// </summary>
+    [Description("派单节点")]
+    Send = 1,
+
+    /// <summary>
+    /// 部门节点
+    /// </summary>
+    [Description("部门节点")]
+    Department = 2,
+}

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

@@ -33,4 +33,10 @@ public enum EHandlerType
     /// </summary>
     [Description("指定部门")]
     AssignOrg = 4,
+
+    /// <summary>
+    /// 自定义(用户)
+    /// </summary>
+    [Description("自定义")]
+    Custom = 5,
 }

+ 5 - 1
src/Hotline/FlowEngine/Definitions/DefinitionDomainService.cs

@@ -126,7 +126,7 @@ public class DefinitionDomainService : IDefinitionDomainService, IScopeDependenc
     }
 
     #region private
-    
+
     private void ValidateDefinition(Definition? definition)
     {
         if (definition is null)
@@ -160,6 +160,10 @@ public class DefinitionDomainService : IDefinitionDomainService, IScopeDependenc
 
         if (definition.Steps.Count(d => d.NextSteps.Any(x => x.Code == "End")) > 1)
             throw UserFriendlyException.SameMessage("结束节点只能有一个上级节点");
+
+        var startStep = startSteps.First();
+        if (startStep.NextSteps.Count > 1)
+            throw UserFriendlyException.SameMessage("开始节点有且只能有一个下级节点");
     }
 
     #endregion

+ 0 - 48
src/Hotline/FlowEngine/Definitions/Step.cs

@@ -1,48 +0,0 @@
-//using Hotline.FlowEngine.Workflows;
-
-//namespace Hotline.FlowEngine.Definitions;
-
-//public class Step : StepBasic
-//{
-//    public string Id { get; set; }
-
-//    public List<NextStep> NextSteps { get; set; }
-
-//    /// <summary>
-//    /// 被指派处理对象(依据不同指派方式可能为:depCode或userId)
-//    /// </summary>
-//    public string HandlerId { get; set; }
-
-//    public DateTime? StartTime { get; set; } = DateTime.Now;
-
-//    public DateTime? EndTime { get; set; }
-
-//    #region 审批参数
-
-//    /// <summary>
-//    /// (下一节点处理人)根据审批者类型不同,此字段为不同内容
-//    /// <example>
-//    /// 部门等级/分类为:depCodes, 角色为:userIds
-//    /// </example>
-//    /// </summary>
-//    public List<string> Handlers { get; set; }
-
-//    /// <summary>
-//    /// 下一节点code
-//    /// </summary>
-//    public string NextStepCode { get; set; }
-
-//    /// <summary>
-//    /// 是否短信通知
-//    /// </summary>
-//    public bool AcceptSms { get; set; }
-
-//    /// <summary>
-//    /// 办理意见
-//    /// </summary>
-//    public string Opinion { get; set; }
-
-//    #endregion
-
-//    public string PreviousId { get; set; }
-//}

+ 11 - 4
src/Hotline/FlowEngine/Definitions/StepBasic.cs

@@ -1,6 +1,5 @@
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
-using SqlSugar;
 
 namespace Hotline.FlowEngine.Definitions;
 
@@ -16,8 +15,16 @@ public class StepBasic
     /// </summary>
     public string Code { get; set; }
 
+    /// <summary>
+    /// 办理/汇总、开始/结束
+    /// </summary>
     public EStepType StepType { get; init; }
 
+    /// <summary>
+    /// 业务属性
+    /// </summary>
+    public EBusinessProperty BusinessProperty { get; set; }
+
     /// <summary>
     /// 办理者类型
     /// </summary>
@@ -32,17 +39,17 @@ public class StepBasic
     public List<IdName> HandlerClassifies { get; set; } = new();
 
     /// <summary>
-    /// 当前环节会签模式
+    /// 当前环节办理会签模式
     /// </summary>
     public ECountersignMode CountersignMode { get; set; }
 
     /// <summary>
-    /// 发起会签节点code(不支持发起会签节点无此字段)
+    /// 发起会签节点code
     /// </summary>
     public string? CountersignStartStepCode { get; set; }
 
     /// <summary>
-    /// 会签汇总节点code(不支持发起会签节点无此字段)
+    /// 会签汇总节点code
     /// </summary>
     public string? CountersignEndStepCode { get; set; }
 

+ 8 - 0
src/Hotline/FlowEngine/Definitions/StepDefine.cs

@@ -26,4 +26,12 @@ public class StepDefine : StepBasic
 
         return CountersignMode == ECountersignMode.Support && handlerCount > 1;
     }
+
+    #region method
+
+    public bool IsCenter() => BusinessProperty is EBusinessProperty.Center or EBusinessProperty.Send;
+
+    public bool IsOrg() => BusinessProperty is EBusinessProperty.Department;
+
+    #endregion
 }

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

@@ -9,7 +9,7 @@ public record WorkflowNotify(Workflow Workflow, BasicWorkflowDto Dto) : INotific
 
 public record StartWorkflowNotify(Workflow Workflow, BasicWorkflowDto Dto, bool IsCountersignStart, FlowAssignMode FlowAssignMode) : WorkflowNotify(Workflow, Dto);
 
-public record NextStepNotify(Workflow Workflow, BasicWorkflowDto Dto, WorkflowTrace Trace, bool IsCountersignStart, bool IsCountersignEnd, FlowAssignMode FlowAssignMode) : WorkflowNotify(Workflow, Dto);
+public record NextStepNotify(Workflow Workflow, BasicWorkflowDto Dto, WorkflowTrace Trace, bool IsFromCenterToOrg, bool IsCountersignStart, bool IsCountersignEnd, FlowAssignMode FlowAssignMode) : WorkflowNotify(Workflow, Dto);
 
 public record AcceptWorkflowNotify(Workflow Workflow) : INotification;
 

+ 9 - 1
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -16,12 +16,20 @@ namespace Hotline.FlowEngine.Workflows
         Task StartAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
             bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken);
 
+        Task StartAsync(Workflow workflow, CancellationToken cancellationToken);
+
         /// <summary>
         /// 查询工作流
         /// </summary>
         Task<Workflow> GetWorkflowAsync(string workflowId, bool withDefine = false, bool withSteps = false, bool withTraces = false,
             bool withSupplements = false, bool withAssigns = false, bool withCountersigns = false, CancellationToken cancellationToken = default);
 
+        /// <summary>
+        /// 查询工作流包含当前用户办理权限(是否可办理)
+        /// </summary>
+        Task<(Workflow, bool)> GetWorkflowHandlePermissionAsync(string workflowId, string userId, string orgCode, bool withDefine = false, bool withSteps = false, bool withTraces = false,
+            bool withSupplements = false, bool withAssigns = false, bool withCountersigns = false, CancellationToken cancellationToken = default);
+
         /// <summary>
         /// 受理,接办
         /// </summary>
@@ -31,7 +39,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 办理(流转至下一节点)
         /// </summary>
         Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
-            bool isOutFromCallCenter, bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken);
+            bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken);
 
         /// <summary>
         /// 退回(返回前一节点)

+ 17 - 4
src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs

@@ -19,6 +19,12 @@ public abstract class StepBasicEntity : CreationEntity
 
     public EStepType StepType { get; init; }
 
+    /// <summary>
+    /// 业务属性
+    /// </summary>
+    [SugarColumn(DefaultValue = "0")]
+    public EBusinessProperty BusinessProperty { get; set; }
+
     /// <summary>
     /// 办理人类型
     /// </summary>
@@ -101,18 +107,18 @@ public abstract class StepBasicEntity : CreationEntity
     /// </summary>
     [SugarColumn(IsNullable = true)]
     public string? OrgName { get; set; }
-    
+
     /// <summary>
     /// 部门行政区划代码
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? OrgAreaCode{ get; set; }
+    public string? OrgAreaCode { get; set; }
 
     /// <summary>
     /// 部门行政区划名称
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? OrgAreaName{ get; set; }
+    public string? OrgAreaName { get; set; }
 
     /// <summary>
     /// 办理完成时间
@@ -147,5 +153,12 @@ public abstract class StepBasicEntity : CreationEntity
     public DateTime? AcceptTime { get; set; }
 
     #endregion
-    
+
+    #region method
+
+    public bool IsCenter() => BusinessProperty is EBusinessProperty.Center or EBusinessProperty.Send;
+
+    public bool IsOrg() => BusinessProperty is EBusinessProperty.Department;
+
+    #endregion
 }

+ 80 - 47
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -15,23 +15,24 @@ public class Workflow : CreationEntity
 {
     public string DefinitionId { get; set; }
 
+    #region 业务模块
+
     [SugarColumn(IsNullable = true)]
     public string? ModuleId { get; set; }
 
-    /// <summary>
-    /// 业务模块名称
-    /// </summary>
     [SugarColumn(IsNullable = true)]
     public string? ModuleName { get; set; }
 
     [SugarColumn(IsNullable = true)]
     public string? ModuleCode { get; set; }
 
+    #endregion
+
     [SugarColumn(Length = 2000)]
     public string Title { get; set; }
 
     /// <summary>
-    /// 到期时间
+    /// 到期时间(期满时间)
     /// </summary>
     public DateTime ExpiredTime { get; set; }
 
@@ -40,18 +41,37 @@ public class Workflow : CreationEntity
     /// </summary>
     public DateTime? CompleteTime { get; set; }
 
+    /// <summary>
+    /// 流程状态
+    /// </summary>
     public EWorkflowStatus Status { get; set; }
 
+    #region 实际办理节点,部门
+
+    /// <summary>
+    /// 实际办理节点名称(会签状态此字段保存最外层会签办理节点名称)
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ActualHandleStepName { get; set; }
+
     /// <summary>
-    /// 当前办理节点名称(会签状态此字段保存最外层会签办理节点名称)
+    /// 到达实际办理节点时间(stepBox创建时间)
+    /// </summary>
+    public DateTime? ActualHandleStepTime { get; set; }
+
+    /// <summary>
+    /// 实际办理部门名称
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? CurrentStepName { get; set; }
+    public string? ActualHandleOrgName { get; set; }
 
     /// <summary>
-    /// 到达当前办理节点时间(stepBox创建时间)
+    /// 实际办理部门编码
     /// </summary>
-    public DateTime? CurrentStepTime { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? ActualHandleOrgCode { get; set; }
+
+    #endregion
 
     /// <summary>
     /// 会签办理节点code,嵌套会签为最外层会签办理节点code(不处于会签状态则无值)
@@ -108,8 +128,7 @@ public class Workflow : CreationEntity
     /// <summary>
     /// 外部业务唯一标识
     /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? ExternalId { get; set; }
+    public string ExternalId { get; set; }
 
     [Navigate(NavigateType.OneToOne, nameof(DefinitionId))]
     public Definition Definition { get; set; }
@@ -120,11 +139,11 @@ public class Workflow : CreationEntity
     [Navigate(NavigateType.OneToMany, nameof(WorkflowSupplement.WorkflowId))]
     public List<WorkflowSupplement> Supplements { get; set; }
 
-    /// <summary>
-    /// 接办信息
-    /// </summary>
-    [Navigate(NavigateType.OneToMany, nameof(WorkflowAssign.WorkflowId))]
-    public List<WorkflowAssign> Assigns { get; set; }
+    ///// <summary>
+    ///// 接办信息
+    ///// </summary>
+    //[Navigate(NavigateType.OneToMany, nameof(WorkflowAssign.WorkflowId))]
+    //public List<WorkflowAssign> Assigns { get; set; }
 
     /// <summary>
     /// 会签
@@ -164,12 +183,28 @@ public class Workflow : CreationEntity
     /// </summary>
     public void FLow(string currentStepName, DateTime currentStepTime, string? currentCountersignCode = null)
     {
-        CurrentStepName = currentStepName;
-        CurrentStepTime = currentStepTime;
+        ActualHandleStepName = currentStepName;
+        ActualHandleStepTime = currentStepTime;
         if (!string.IsNullOrEmpty(currentCountersignCode))
             TopCountersignStepCode = currentCountersignCode;
     }
 
+    public void SetTopCountersignStepCode(string currentCountersignCode) =>
+        TopCountersignStepCode = currentCountersignCode;
+
+    /// <summary>
+    /// 更新实际办理节点、部门数据
+    /// </summary>
+    public void ActualHandle(
+        string actualHandleStepName, DateTime actualHandleStepTime,
+        string actualHandleOrgName, string actualHandleOrgCode)
+    {
+        ActualHandleStepName = actualHandleStepName;
+        ActualHandleStepTime = actualHandleStepTime;
+        ActualHandleOrgName = actualHandleOrgName;
+        ActualHandleOrgCode = actualHandleOrgCode;
+    }
+
     /// <summary>
     /// 检查会签是否结束
     /// </summary>
@@ -186,22 +221,20 @@ public class Workflow : CreationEntity
     public void EndCountersign() => TopCountersignStepCode = null;
 
     /// <summary>
-    /// 更新workflow中当前停留节点,时间和会签开始节点code
+    /// 更新workflow中实际办理节点数据(节点,时间,部门)
     /// </summary>
-    /// <param name="isStartCountersign"></param>
-    /// <param name="stepBox"></param>
-    public void SetWorkflowCurrentStepInfo(bool isStartCountersign, WorkflowStep stepBox)
+    public void SetWorkflowActualHandleInfo(WorkflowStep currentStepBox, WorkflowStep nextStepBox, string handlerOrgName, string handlerOrgCode)
     {
         //1.不在会签中,未发起会签(普通处理) 2.不在会签中,发起会签(保存会签节点),3.会签中,不更新
         if (IsInCountersign()) return;
-        if (isStartCountersign)
-        {
-            FLow(stepBox.Name, stepBox.CreationTime, stepBox.Code);
-        }
-        else
-        {
-            FLow(stepBox.Name, stepBox.CreationTime);
-        }
+
+        if (nextStepBox.StepType is EStepType.CountersignEnd or EStepType.End) return;
+
+        ActualHandle(currentStepBox.Name, currentStepBox.CreationTime, handlerOrgName, handlerOrgCode);
+
+
+        //if (isStartCountersign)
+        //    SetTopCountersignStepCode(stepBox.Code);
     }
 
     /// <summary>
@@ -339,24 +372,24 @@ public class Workflow : CreationEntity
         }
     }
 
-    public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
-    {
-        handlers = handlers.Select(d => d.ToLower());
-        switch (type)
-        {
-            case EFlowAssignType.Org:
-                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgCodes(true)).ToList();
-                AssignOrgCodes.AddRange(orgCodes);
-                AssignOrgCodes = AssignOrgCodes.Distinct().ToList();
-                break;
-            case EFlowAssignType.User:
-                AssignUserIds.AddRange(handlers);
-                AssignUserIds = AssignUserIds.Distinct().ToList();
-                break;
-            default:
-                throw new ArgumentOutOfRangeException(nameof(type), type, null);
-        }
-    }
+    //public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
+    //{
+    //    handlers = handlers.Select(d => d.ToLower());
+    //    switch (type)
+    //    {
+    //        case EFlowAssignType.Org:
+    //            var orgCodes = handlers.SelectMany(d => d.GetHigherOrgCodes(true)).ToList();
+    //            AssignOrgCodes.AddRange(orgCodes);
+    //            AssignOrgCodes = AssignOrgCodes.Distinct().ToList();
+    //            break;
+    //        case EFlowAssignType.User:
+    //            AssignUserIds.AddRange(handlers);
+    //            AssignUserIds = AssignUserIds.Distinct().ToList();
+    //            break;
+    //        default:
+    //            throw new ArgumentOutOfRangeException(nameof(type), type, null);
+    //    }
+    //}
 
     #endregion
 }

+ 228 - 84
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -1,21 +1,15 @@
 using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WfModules;
-using Hotline.Orders;
-using Hotline.SeedData;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
-using Hotline.Users;
 using MapsterMapper;
 using MediatR;
 using Microsoft.Extensions.Logging;
-using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
-using XF.Domain.Entities;
 using XF.Domain.Exceptions;
-using XF.Utility.SequentialId;
 
 namespace Hotline.FlowEngine.Workflows
 {
@@ -70,7 +64,7 @@ namespace Hotline.FlowEngine.Workflows
                 DefinitionId = definition.Id,
                 Status = EWorkflowStatus.Runnable,
                 TimeLimit = GetTimeLimit(definition.Code),
-                ExpiredTime = GenerateExpiredTime(definition.Code),
+                ExpiredTime = CalculateExpiredTime(definition.Code),
                 StepBoxes = new(),
                 Traces = new(),
                 Definition = definition,
@@ -95,50 +89,60 @@ namespace Hotline.FlowEngine.Workflows
         public async Task StartAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
             bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken)
         {
-            //var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+            ////var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
 
-            //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选
-            if (nextStepBoxDefine.HandlerType is not EHandlerType.Role && !dto.NextHandlers.Any())
-                throw UserFriendlyException.SameMessage("未指派办理人");
+            ////1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选
+            //if (nextStepBoxDefine.HandlerType is not EHandlerType.Role && !dto.NextHandlers.Any())
+            //    throw UserFriendlyException.SameMessage("未指派办理人");
 
-            //开始节点
-            var (startStepBox, startStep) = await CreateStartStepAsync(workflow, dto, cancellationToken);
+            ////开始节点
+            //var (startStepBox, startStep) = await CreateStartStepAsync(workflow, dto, cancellationToken);
 
-            //var isStartCountersign = startStep.ShouldStartCountersign(dto.NextHandlers.Count);
-            //检查是否支持会签
-            //if (isStartCountersign && startStep.CountersignMode == ECountersignMode.UnSupport)
-            //    throw new UserFriendlyException($"当前节点不支持发起会签, stepId: {startStep.Id}", "当前节点不支持发起会签");
+            ////var isStartCountersign = startStep.ShouldStartCountersign(dto.NextHandlers.Count);
+            ////检查是否支持会签
+            ////if (isStartCountersign && startStep.CountersignMode == ECountersignMode.UnSupport)
+            ////    throw new UserFriendlyException($"当前节点不支持发起会签, stepId: {startStep.Id}", "当前节点不支持发起会签");
 
-            if (isStartCountersign)
-            {
-                //创建会签数据
-                var countersign = await StartCountersignAsync(workflow.Id, startStep, startStepBox.CountersignEndStepCode,
-                    dto.NextHandlers.Count, startStep.CountersignId, cancellationToken);
-                startStep.StartCountersignId = countersign.Id;
-                await _workflowStepRepository.UpdateAsync(startStep, cancellationToken);
-            }
+            //if (isStartCountersign)
+            //{
+            //    //创建会签数据
+            //    var countersign = await StartCountersignAsync(workflow.Id, startStep, startStepBox.CountersignEndStepCode,
+            //        dto.NextHandlers.Count, startStep.CountersignId, cancellationToken);
+            //    startStep.StartCountersignId = countersign.Id;
+            //    await _workflowStepRepository.UpdateAsync(startStep, cancellationToken);
+            //}
 
-            ////开始节点trace
-            //await AcceptTraceAsync(workflow, startStepBox, startStep, cancellationToken);
-            await NextTraceAsync(workflow, dto, startStep, cancellationToken);
+            //////开始节点trace
+            ////await AcceptTraceAsync(workflow, startStepBox, startStep, cancellationToken);
+            //await NextTraceAsync(workflow, dto, startStep, cancellationToken);
 
-            //第二节点(创建即为 已指派/待接办 状态)
-            var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned,
-                startStepBox, startStep, EWorkflowTraceStatus.Normal, cancellationToken);
+            ////第二节点(创建即为 已指派/待接办 状态)
+            //var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned,
+            //    startStepBox, startStep, EWorkflowTraceStatus.Normal, cancellationToken);
 
-            //更新当前节点名称、时间、会签节点code 等字段
-            workflow.SetWorkflowCurrentStepInfo(isStartCountersign, nextStepBox);
+            ////更新实际办理节点名称、时间
+            //workflow.SetWorkflowActualHandleInfo(startStepBox, nextStepBox, _sessionContext.OrgName, _sessionContext.RequiredOrgCode);
 
-            workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode,
-            flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects);
+            ////发起会签时记录顶层会签节点
+            //if (isStartCountersign)
+            //    workflow.SetTopCountersignStepCode(startStepBox.Code);
 
-            //更新指派信息
-            workflow.Assign(flowAssignMode.FlowAssignType, flowAssignMode.GetHandlers());
+            //workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode,
+            //flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects);
 
-            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+            //////更新指派信息
+            ////workflow.Assign(flowAssignMode.FlowAssignType, flowAssignMode.GetHandlers());
+
+            //await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
-            //publish
-            await _mediator.Publish(new StartWorkflowNotify(workflow, dto, isStartCountersign, flowAssignMode), cancellationToken);
+            ////publish
+            //await _mediator.Publish(new StartWorkflowNotify(workflow, dto, isStartCountersign, flowAssignMode), cancellationToken);
+        }
+
+        public async Task StartAsync(Workflow workflow, CancellationToken cancellationToken)
+        {
+            //开始节点
+            await CreateStartStepAsync(workflow, cancellationToken);
         }
 
         public async Task<Workflow> GetWorkflowAsync(string workflowId,
@@ -152,8 +156,8 @@ namespace Hotline.FlowEngine.Workflows
                 query = query.Includes(d => d.Definition);
             if (withSupplements)
                 query = query.Includes(d => d.Supplements, d => d.Creator);
-            if (withAssigns)
-                query = query.Includes(d => d.Assigns);
+            //if (withAssigns)
+            //    query = query.Includes(d => d.Assigns);
             if (withCountersigns)
                 query = query.Includes(d => d.Countersigns);
 
@@ -183,6 +187,18 @@ namespace Hotline.FlowEngine.Workflows
             return workflow;
         }
 
+        /// <summary>
+        /// 查询工作流包含当前用户办理权限(是否可办理)
+        /// </summary>
+        public async Task<(Workflow, bool)> GetWorkflowHandlePermissionAsync(string workflowId, string userId, string orgCode, bool withDefine = false,
+            bool withSteps = false, bool withTraces = false, bool withSupplements = false, bool withAssigns = false,
+            bool withCountersigns = false, CancellationToken cancellationToken = default)
+        {
+            var workflow = await GetWorkflowAsync(workflowId, withDefine, withSteps, withTraces, withSupplements, withAssigns, withCountersigns, cancellationToken);
+            var canHandle = workflow.CanHandle(userId, orgCode);
+            return (workflow, canHandle);
+        }
+
         /// <summary>
         /// 受理(接办)
         /// </summary>
@@ -240,7 +256,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 办理(流转至下一节点)
         /// </summary>
         public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
-            bool isOutFromCallCenter, bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken)
+            bool isStartCountersign, FlowAssignMode flowAssignMode, CancellationToken cancellationToken)
         {
             ValidatePermission(workflow);
             CheckWhetherRunnable(workflow.Status);
@@ -251,12 +267,8 @@ namespace Hotline.FlowEngine.Workflows
             if (currentStep.Status is EWorkflowStepStatus.Completed or EWorkflowStepStatus.Created)
                 throw UserFriendlyException.SameMessage("当前节点状态无法办理");
 
-            //var isStartCountersign = currentStep.ShouldStartCountersign(dto.NextHandlers.Count);
-            //检查是否支持发起会签
-            //if (isStartCountersign && currentStep.CountersignMode == ECountersignMode.UnSupport)
-            //    throw new UserFriendlyException($"当前节点不支持发起会签, stepId: {currentStep.Id}", "当前节点不支持发起会签");
-            if (isStartCountersign && nextStepBoxDefine.StepType is EStepType.CountersignEnd)
-                throw new UserFriendlyException($"汇总节点不支持办理会签, stepId: {currentStep.Id}", "汇总节点不支持办理会签");
+            //if (isStartCountersign && nextStepBoxDefine.StepType is EStepType.CountersignEnd)
+            //    throw new UserFriendlyException($"汇总节点不支持办理会签, stepId: {currentStep.Id}", "汇总节点不支持办理会签");
 
             if (currentStep.Status is EWorkflowStepStatus.Assigned)
                 await AcceptAsync(workflow,
@@ -355,10 +367,11 @@ namespace Hotline.FlowEngine.Workflows
             }
 
             //是否从中心流转出去,重新计算expiredTime 
-            if (isOutFromCallCenter)
+            var isFromCenterToOrg = CheckIfFlowFromCenterToOrg(currentStepBox, nextStepBoxDefine);
+            if (isFromCenterToOrg)
             {
                 workflow.ProcessType = EProcessType.Jiaoban;
-                workflow.ExpiredTime = GenerateExpiredTime(workflow.Definition.Code);
+                workflow.ExpiredTime = CalculateExpiredTime(workflow.Definition.Code);
                 workflow.AssignTime = DateTime.Now;
             }
 
@@ -416,11 +429,15 @@ namespace Hotline.FlowEngine.Workflows
                 await _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
             }
 
-            //更新workflow当前节点名称、时间、会签节点code 等字段
-            workflow.SetWorkflowCurrentStepInfo(isStartCountersign, nextStepBox);
+            //更新实际办理节点名称、时间
+            workflow.SetWorkflowActualHandleInfo(currentStepBox, nextStepBox, _sessionContext.OrgName, _sessionContext.RequiredOrgCode);
+
+            //发起会签时记录顶层会签节点
+            if (isStartCountersign)
+                workflow.SetTopCountersignStepCode(currentStepBox.Code);
 
-            //更新指派信息
-            workflow.Assign(flowAssignMode.FlowAssignType, flowAssignMode.GetHandlers());
+            ////更新指派信息
+            //workflow.Assign(flowAssignMode.FlowAssignType, flowAssignMode.GetHandlers());
 
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
@@ -432,7 +449,7 @@ namespace Hotline.FlowEngine.Workflows
 
             #endregion
 
-            await _mediator.Publish(new NextStepNotify(workflow, dto, trace, isStartCountersign, isCountersignOver, flowAssignMode), cancellationToken);
+            await _mediator.Publish(new NextStepNotify(workflow, dto, trace, isFromCenterToOrg, isStartCountersign, isCountersignOver, flowAssignMode), cancellationToken);
         }
 
         /// <summary>
@@ -457,22 +474,27 @@ namespace Hotline.FlowEngine.Workflows
             if (prevStep == null)
                 throw UserFriendlyException.SameMessage("未查询到前一节点");
 
+            //update trace
+            await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken);
+
             //检查并重置上级stepbox状态为待接办
             await ResetStepBoxStatusAsync(prevStepBox, cancellationToken);
 
-            //复制一个节点为待接办
-            var newPrevStep = await CreateByAsync(prevStep, cancellationToken);
+            //复制一个节点为待接办
+            var newPrevStep = await CreateByAsync(workflow, prevStep, cancellationToken);
 
             //remove workflow.steps
-            await _workflowStepRepository.RemoveRangeAsync(new List<WorkflowStep> { prevStep, currentStep },
-                cancellationToken);
+            await _workflowStepRepository.RemoveRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep, prevStep }, cancellationToken);
 
             //更新流程可办理对象
             workflow.UpdatePreviousHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgCode, newPrevStep);
-            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
-            //update trace
-            await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken);
+            //orgToCenter会触发重新计算期满时间,1.无需审核按当前时间进行计算 2.需审核按审核通过时间计算
+            var isOrgToCenter = CheckIfFlowFromOrgToCenter(currentStepBox, prevStepBox);
+            if (isOrgToCenter)
+                workflow.ExpiredTime = CalculateExpiredTime("");
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
             await _mediator.Publish(new PreviousNotify(workflow, dto), cancellationToken);
         }
@@ -485,10 +507,6 @@ namespace Hotline.FlowEngine.Workflows
             //ValidatePermission(workflow);
             CheckWhetherRunnable(workflow.Status);
 
-            //var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
-            //if (currentStepBox.StepType is EStepType.Start)
-            //    throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
-
             //update uncompleted traces
             await RecallTraceAsync(workflow.Id, dto, cancellationToken);
 
@@ -499,6 +517,12 @@ namespace Hotline.FlowEngine.Workflows
             await RecallAsync(workflow, dto, targetStepDefine, targetStepBox, isStartCountersign, EWorkflowTraceStatus.Recall, cancellationToken);
 
             workflow.ResetHandlers(flowAssignMode.FlowAssignType, flowAssignMode.HandlerObjects);
+
+            //calc workflow expired time
+            var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStepBox);
+            if (isOrgToCenter)
+                workflow.ExpiredTime = CalculateExpiredTime("");
+
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
             await _mediator.Publish(new RecallNotify(workflow, dto), cancellationToken);
@@ -512,12 +536,6 @@ namespace Hotline.FlowEngine.Workflows
         {
             CheckWhetherRunnable(workflow.Status);
 
-            //var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
-            //if (currentStepBox.StepType is EStepType.Start)
-            //    throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
-            //if (currentStepBox.StepType is EStepType.End)
-            //    throw UserFriendlyException.SameMessage("当前流程已流转到结束节点");
-
             //update uncompleted traces
             await JumpTraceAsync(workflow.Id, dto, cancellationToken);
 
@@ -536,6 +554,11 @@ namespace Hotline.FlowEngine.Workflows
 
                 await ResetWorkflowCurrentStepInfo(workflow, dto, targetStepBox, isStartCountersign, cancellationToken);
 
+                //calc workflow expired time
+                var isCenterToOrg = CheckIfFlowFromCenterToOrg(workflow, targetStepBox);
+                if (isCenterToOrg)
+                    workflow.ExpiredTime = CalculateExpiredTime("");
+
                 #region 补充中间节点处理方案(暂不需要)
 
                 //var completeStepCodes = workflow.StepBoxes.Select(d => d.Code);
@@ -626,10 +649,70 @@ namespace Hotline.FlowEngine.Workflows
 
         #region private
 
+        /// <summary>
+        /// 检查是否从中心流转至部门
+        /// </summary>
+        private bool CheckIfFlowFromCenterToOrg(WorkflowStep sourceStepBox, StepDefine targetStepBoxDefine)
+        {
+            var isFromCenter = sourceStepBox.IsCenter();
+            if (!isFromCenter) return false;
+
+            var isToOrg = targetStepBoxDefine.IsOrg();
+            return isFromCenter && isToOrg;
+        }
+
+        /// <summary>
+        /// 检查是否从中心流转至部门
+        /// </summary>
+        private bool CheckIfFlowFromCenterToOrg(Workflow workflow, WorkflowStep targetStepBox)
+        {
+            var isToOrg = targetStepBox.IsOrg();
+            if (!isToOrg) return false;
+
+            var isFromCenter = workflow.StepBoxes.All(d => d.BusinessProperty is not EBusinessProperty.Department);
+            return isFromCenter && isToOrg;
+        }
+
+        /// <summary>
+        /// 检查是否从部门流转至中心
+        /// </summary>
+        private bool CheckIfFlowFromOrgToCenter(WorkflowStep sourceStepBox, StepDefine targetStepBoxDefine)
+        {
+            var isFromOrg = sourceStepBox.IsOrg();
+            if (!isFromOrg) return false;
+
+            var isToCenter = targetStepBoxDefine.IsCenter();
+            return isFromOrg && isToCenter;
+        }
+
+        /// <summary>
+        /// 检查是否从部门流转至中心
+        /// </summary>
+        private bool CheckIfFlowFromOrgToCenter(WorkflowStep sourceStepBox, WorkflowStep targetStepBox)
+        {
+            var isFromOrg = sourceStepBox.IsOrg();
+            if (!isFromOrg) return false;
+
+            var isToCenter = targetStepBox.IsCenter();
+            return isFromOrg && isToCenter;
+        }
+
+        /// <summary>
+        /// 检查是否从部门流转至中心
+        /// </summary>
+        private bool CheckIfFlowFromOrgToCenter(Workflow workflow, WorkflowStep targetStepBox)
+        {
+            var isToCenter = targetStepBox.IsCenter();
+            if (!isToCenter) return false;
+
+            var isFromOrg = workflow.StepBoxes.Any(d => d.BusinessProperty is EBusinessProperty.Department);
+            return isFromOrg && isToCenter;
+        }
+
         /// <summary>
         /// 复制一个节点为待接办
         /// </summary>
-        private async Task<WorkflowStep> CreateByAsync(WorkflowStep step, CancellationToken cancellationToken)
+        private async Task<WorkflowStep> CreateByAsync(Workflow workflow, WorkflowStep step, CancellationToken cancellationToken)
         {
             step.Reset();
             var newStep = _mapper.Map<WorkflowStep>(step);
@@ -642,6 +725,9 @@ namespace Hotline.FlowEngine.Workflows
             newStep.CountersignId = step.CountersignId;
             newStep.IsStartedCountersignComplete = step.IsStartedCountersignComplete;
             await _workflowStepRepository.AddAsync(newStep, cancellationToken);
+
+            await CreateTraceAsync(workflow, newStep, EWorkflowTraceStatus.Back, cancellationToken);
+
             return newStep;
         }
 
@@ -880,7 +966,8 @@ namespace Hotline.FlowEngine.Workflows
         {
             //更新当前节点名称、时间、会签节点code
             workflow.CloseCountersignStatus();
-            workflow.SetWorkflowCurrentStepInfo(isStartCountersign, stepBox);
+            //todo 
+            //workflow.SetWorkflowActualHandleInfo(isStartCountersign, stepBox);
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
         }
 
@@ -921,6 +1008,29 @@ namespace Hotline.FlowEngine.Workflows
             return (stepBox, step);
         }
 
+        private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateStartStepAsync(Workflow workflow, CancellationToken cancellationToken)
+        {
+            if (workflow.StepBoxes.Any())
+                throw UserFriendlyException.SameMessage("无法重复创建开始节点");
+            var startStepDefinition = workflow.Definition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
+            if (startStepDefinition == null)
+                throw new UserFriendlyException($"模板未配置开始节点, defineCode: {workflow.Definition.Code}", "模板未配置开始节点");
+
+            var startStepBox = CreateStepBox(workflow.Id, startStepDefinition, string.Empty);
+            await _workflowStepRepository.AddAsync(startStepBox, cancellationToken);
+
+            //start节点的办理人分类默认为用户,即为当前发起流程的操作员
+            var handler = new IdName { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
+            //开始节点的下一个节点(工单业务:话务员节点)
+            var firstStepCode = workflow.Definition.Steps.First(d => d.StepType == EStepType.Start).NextSteps.First().Code;
+            var firstStep = workflow.Definition.Steps.First(d => d.Code == firstStepCode);
+            var step = await CreateStartSubStepAsync(handler, firstStep, startStepBox, cancellationToken);
+
+            //开始节点trace
+            await CreateTraceAsync(workflow, step, cancellationToken: cancellationToken);
+            return (startStepBox, step);
+        }
+
         private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateEndStepAsync(
             Workflow workflow,
             StepDefine endStepDefine,
@@ -985,6 +1095,7 @@ namespace Hotline.FlowEngine.Workflows
             return stepBox;
         }
 
+        //todo obsolete
         private async Task<WorkflowStep> CreateStartSubStepAsync(
             IdName handler,
             BasicWorkflowDto dto,
@@ -1008,6 +1119,29 @@ namespace Hotline.FlowEngine.Workflows
             return subStep;
         }
 
+        private async Task<WorkflowStep> CreateStartSubStepAsync(
+            IdName handler,
+            StepDefine firstStepDefine,
+            WorkflowStep stepBox,
+            CancellationToken cancellationToken)
+        {
+            //开始节点既不发起会签,也不处于会签中
+            var subStep = CreateSubStep(stepBox, new List<IdName> { handler }, firstStepDefine.Code, null,
+                null, null, EWorkflowStepStatus.Completed, EStepCountersignStatus.None);
+            subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgCode, _sessionContext.OrgName);
+
+            //step办理状态
+            subStep.Complete(
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgCode, _sessionContext.OrgName,
+                firstStepDefine.Code);
+
+            stepBox.Steps.Add(subStep);
+            await _workflowStepRepository.AddAsync(subStep, cancellationToken);
+            return subStep;
+        }
+
         private async Task<WorkflowStep> CreateEndSubStepAsync(
             IdName handler,
             WorkflowStep currentStepBox,
@@ -1186,24 +1320,34 @@ namespace Hotline.FlowEngine.Workflows
         /// 依据配置生成过期时间
         /// </summary>
         /// <returns></returns>
-        private DateTime GenerateExpiredTime(string defineCode)
+        private DateTime CalculateExpiredTime(string defineCode, DateTime? time = null)
         {
-            //GetConfig(string defineCode).Time
-            return DateTime.Now.AddDays(7); //todo 依据配置生成, Think about 工作日
+            time ??= DateTime.Now;
+            var config = GetConfig(defineCode);
+            return time.Value.AddDays(config.Days);
         }
 
         private string GetTimeLimit(string defineCode)
         {
-            //return GetConfig(string defineCode).Description;
-            return "7个工作日";
+            return GetConfig(defineCode).Description;
         }
 
-        //private ConfigInCludeDescriptionAndTime GetConfig(string defineCode)
-        //{
-        //    throw new NotImplementedException();
-        //}
+        private ConfigIncludeDescriptionAndTime GetConfig(string defineCode)
+        {
+            return new ConfigIncludeDescriptionAndTime
+            {
+                Days = 7,
+                Description = "7个工作日"//todo 依据配置生成, Think about 工作日
+            };
+        }
 
         #endregion
 
     }
+
+    public class ConfigIncludeDescriptionAndTime
+    {
+        public int Days { get; set; }
+        public string Description { get; set; }
+    }
 }

+ 5 - 5
src/Hotline/FlowEngine/Workflows/WorkflowStep.cs

@@ -10,8 +10,8 @@ public class WorkflowStep : StepBasicEntity
     public List<NextStep> NextSteps { get; set; }
 
     /// <summary>
-    /// 被指派办理对象(依据不同指派方式可能为:depCode或userId),该字段subStep才会存在,stepBox不存在
-    /// 改为list,兼容多个办理对象可以办理同一个节点的场景
+    /// 被指派办理对象(依据不同指派方式可能为:orgCode或userId),该字段subStep才会存在,stepBox不存在
+    /// 采用list类型,兼容多个办理对象可以办理同一个节点的场景
     /// </summary>
     [SugarColumn(ColumnDataType = "json", IsJson = true)]
     public List<IdName> Handlers { get; set; } = new();
@@ -66,13 +66,13 @@ public class WorkflowStep : StepBasicEntity
     public EStepCountersignStatus StepCountersignStatus { get; set; }
 
     /// <summary>
-    /// 发起会签节点code,冗余(不支持发起会签节点无此字段
+    /// 发起会签节点code(冗余
     /// </summary>
     [SugarColumn(IsNullable = true)]
     public string? CountersignStartStepCode { get; set; }
 
     /// <summary>
-    /// 会签汇总节点code,冗余(不支持发起会签节点无此字段
+    /// 会签汇总节点code(冗余
     /// </summary>
     [SugarColumn(IsNullable = true)]
     public string? CountersignEndStepCode { get; set; }
@@ -124,7 +124,7 @@ public class WorkflowStep : StepBasicEntity
         CompleteTime = DateTime.Now;
         Status = EWorkflowStepStatus.Completed;
 
-        if (StepType is not EStepType.End)
+        if (StepType is not EStepType.End or EStepType.Start)
             NextSteps.First(d => d.Code == nextStepCode).Selected = true;
     }
 

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

@@ -33,7 +33,7 @@ namespace Hotline.Orders
         /// <summary>
         /// 工单办理流程流转(每个节点办理都会触发)
         /// </summary>
-        Task<OrderDto> ManageFlowNextAsync(FlowAssignMode assignMode, bool isCountersignStart, bool isCountersignEnd,
+        Task<OrderDto> ManageFlowNextAsync(FlowAssignMode assignMode, bool isFromCenterToOrg, bool isCountersignStart, bool isCountersignEnd,
             string? orderId, DateTime? currentStepTime, string? currentStepName, DateTime expiredTime, EProcessType processType, CancellationToken cancellationToken);
 
         /// <summary>

+ 10 - 3
src/Hotline/Orders/OrderDomainService.cs

@@ -1,5 +1,6 @@
 using DotNetCore.CAP;
 using Hotline.FlowEngine;
+using Hotline.FlowEngine.Workflows;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Mq;
@@ -104,8 +105,9 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     /// 工单办理(每个节点都会触发)
     /// </summary>
     public async Task<OrderDto> ManageFlowNextAsync(FlowAssignMode assignMode,
-        bool isCountersignStart, bool isCountersignEnd,
-        string? orderId, DateTime? currentStepTime, string? currentStepName, DateTime expiredTime, EProcessType processType,
+        bool isFromCenterToOrg, bool isCountersignStart, bool isCountersignEnd,
+        string? orderId, DateTime? currentStepTime, string? currentStepName, 
+        DateTime expiredTime, EProcessType processType,
         CancellationToken cancellationToken)
     {
         var order = await GetOrderAsync(orderId, cancellationToken);
@@ -120,7 +122,12 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
 
         await _orderRepository.UpdateAsync(order, cancellationToken);
 
-        return _mapper.Map<OrderDto>(order);
+        var dto = _mapper.Map<OrderDto>(order);
+
+        if (isFromCenterToOrg)
+            await _capPublisher.PublishAsync(EventNames.HotlineOrderCenterToOrg, dto, cancellationToken: cancellationToken);
+
+        return dto;
         //await _capPublisher.PublishAsync(EventNames.HotlineOrderFlow, _mapper.Map<OrderDto>(order),
         //    cancellationToken: cancellationToken);
     }