Browse Source

新增跨级派单业务

xfe 6 tháng trước cách đây
mục cha
commit
d9cd63afe5

+ 176 - 16
src/Hotline.Api/Controllers/OrderController.cs

@@ -58,6 +58,7 @@ using MiniExcelLibs;
 using SqlSugar;
 using StackExchange.Redis;
 using System.Text;
+using MongoDB.Driver.Linq;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Entities;
@@ -65,6 +66,8 @@ using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
 using Newtonsoft.Json;
+using XF.Domain.Extensions;
+using WorkflowStep = Hotline.FlowEngine.Workflows.WorkflowStep;
 
 namespace Hotline.Api.Controllers;
 
@@ -3519,18 +3522,98 @@ public class OrderController : BaseController
 
         try
         {
-            // 平均派单
-            var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-            if (dto.Workflow.BusinessType == EBusinessType.Send && averageSendOrder)
-            {
-                var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                dto.Workflow.NextHandlers = new List<FlowStepHandler> { handler };
-            }
+            // // 平均派单
+            // var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+            // if (dto.Workflow.BusinessType == EBusinessType.Send && averageSendOrder)
+            // {
+            //     var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+            //     dto.Workflow.NextHandlers = new List<FlowStepHandler> { handler };
+            // }
+            //
+            // var startDto = _mapper.Map<StartWorkflowDto>(dto.Workflow);
+            // startDto.DefinitionModuleCode = WorkflowModuleConsts.OrderHandle;
+            // startDto.Title = order.Title;
+            // await _workflowApplication.StartWorkflowAsync(startDto, order.Id, order.ExpiredTime, HttpContext.RequestAborted);
 
             var startDto = _mapper.Map<StartWorkflowDto>(dto.Workflow);
             startDto.DefinitionModuleCode = WorkflowModuleConsts.OrderHandle;
             startDto.Title = order.Title;
-            await _workflowApplication.StartWorkflowAsync(startDto, order.Id, order.ExpiredTime, HttpContext.RequestAborted);
+            var startStep = await _workflowDomainService.StartAsync(startDto, order.Id, order.ExpiredTime, HttpContext.RequestAborted);
+            
+            switch (dto.Data.OrderAssignMode)
+            {
+                case EOrderAssignMode.AdjoinLevel:
+                    var nextDto = _mapper.Map<NextWorkflowDto>(dto.Workflow);
+                    // 平均派单
+                    var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+                    if (dto.Workflow.BusinessType == EBusinessType.Send && averageSendOrder)
+                    {
+                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                        nextDto.NextHandlers = new List<FlowStepHandler> { handler };
+                    }
+                    await _workflowDomainService.NextAsync(nextDto,order.ExpiredTime,HttpContext.RequestAborted);
+                    break;
+                case EOrderAssignMode.CrossLevel:
+                    if (!dto.Data.SecondaryHandlers.Any())
+                        throw new UserFriendlyException("跨级指派参数异常");
+                    var orgIds = dto.Data.SecondaryHandlers
+                        .Where(d=>!string.IsNullOrEmpty(d.OrgId))
+                        .SelectMany(d => d.OrgId!.GetHigherOrgIds(true))
+                        .ToList();
+                    orgIds.Add(dto.Workflow.NextHandlers.First().OrgId);
+                    var orgs = await _organizeRepository.Queryable()
+                        .Where(d=> orgIds.Contains(d.Id))
+                        .ToListAsync(HttpContext.RequestAborted);
+                    var maxLevel = orgs.MaxBy(d => d.Level).Level;
+
+                    var unhandleSteps = new List<WorkflowStep>
+                    {
+                        startStep
+                    };
+                    
+                    for (int i = 1; i < maxLevel; i++)
+                    {
+                        var tempSteps = new List<WorkflowStep>();
+                        foreach (var unhandleStep in unhandleSteps)
+                        {
+                            var handleOrgs = orgs.Where(d => d.Level == i).ToList();
+                            if (i != 1)
+                                handleOrgs = handleOrgs.Where(d => d.Id.StartsWith(unhandleStep.HandlerOrgId)).ToList();
+                            var isStartCountersign = handleOrgs.Count > 1;
+                            var handlers = handleOrgs.Select(d => new FlowStepHandler
+                            {
+                                OrgId = d.Id,
+                                OrgName = d.Name,
+                                Key = d.Id,
+                                Value = d.Name
+                            }).ToList();
+                            var nextflowDto = new NextWorkflowDto
+                            {
+                                WorkflowId = unhandleStep.WorkflowId,
+                                StepId = unhandleStep.Id,
+                                NextStepCode = unhandleStep.Code,
+                                NextStepName = unhandleStep.Name,
+                                FlowDirection = EFlowDirection.CenterToOrg,
+                                HandlerType = unhandleStep.HandlerType,
+                                StepType = unhandleStep.StepType,
+                                NextHandlers = handlers,
+                                IsStartCountersign = isStartCountersign,
+                                BusinessType = unhandleStep.BusinessType,
+                                Opinion = "跨级派单,自动办理"
+                            };
+
+                            var nextSteps = await _workflowDomainService.NextAsync(nextflowDto, order.ExpiredTime, HttpContext.RequestAborted);
+                            tempSteps.AddRange(nextSteps);
+                        }
+                        unhandleSteps = tempSteps;
+                    }
+                    
+                    break;
+                case EOrderAssignMode.MainAndSecondary:
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
         }
         catch (Exception e)
         {
@@ -3564,7 +3647,7 @@ public class OrderController : BaseController
         //1.是否是判断节点  2.是否存在历史派单节点  3.存在获取上个派单节点  4.不存在走平均派单
         if (dto.WorkflowDto.BusinessType == EBusinessType.Send)
         {
-            var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowDto.WorkflowId, withTraces: true,
+            var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowDto.WorkflowId, withSteps:true, withTraces: true,
                 cancellationToken: HttpContext.RequestAborted);
             var sendOrderTraces = workflow.Traces.Where(x => x.BusinessType == EBusinessType.Send);
             if (sendOrderTraces.Any())
@@ -3583,13 +3666,90 @@ public class OrderController : BaseController
             }
             else
             {
-                // 平均派单
-                var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-                if (averageSendOrder)
-                {
-                    var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-                    dto.WorkflowDto.NextHandlers = new List<FlowStepHandler> { handler };
-                }
+                // // 平均派单
+                // var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+                // if (averageSendOrder)
+                // {
+                //     var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                //     dto.WorkflowDto.NextHandlers = new List<FlowStepHandler> { handler };
+                // }
+
+                var startStep = workflow.Steps.First(d => d.Id == dto.WorkflowDto.StepId);
+                
+                switch (dto.Data.OrderAssignMode)
+            {
+                case EOrderAssignMode.AdjoinLevel:
+                    var nextDto = _mapper.Map<NextWorkflowDto>(dto.WorkflowDto);
+                    // 平均派单
+                    var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+                    if (dto.WorkflowDto.BusinessType == EBusinessType.Send && averageSendOrder)
+                    {
+                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                        nextDto.NextHandlers = new List<FlowStepHandler> { handler };
+                    }
+                    await _workflowDomainService.NextAsync(nextDto,order.ExpiredTime,HttpContext.RequestAborted);
+                    break;
+                case EOrderAssignMode.CrossLevel:
+                    if (!dto.Data.SecondaryHandlers.Any())
+                        throw new UserFriendlyException("跨级指派参数异常");
+                    var orgIds = dto.Data.SecondaryHandlers
+                        .Where(d=>!string.IsNullOrEmpty(d.OrgId))
+                        .SelectMany(d => d.OrgId!.GetHigherOrgIds(true))
+                        .ToList();
+                    orgIds.Add(dto.WorkflowDto.NextHandlers.First().OrgId);
+                    var orgs = await _organizeRepository.Queryable()
+                        .Where(d=> orgIds.Contains(d.Id))
+                        .ToListAsync(HttpContext.RequestAborted);
+                    var maxLevel = orgs.MaxBy(d => d.Level).Level;
+
+                    var unhandleSteps = new List<WorkflowStep>
+                    {
+                        startStep
+                    };
+                    
+                    for (int i = 1; i < maxLevel; i++)
+                    {
+                        var tempSteps = new List<WorkflowStep>();
+                        foreach (var unhandleStep in unhandleSteps)
+                        {
+                            var handleOrgs = orgs.Where(d => d.Level == i).ToList();
+                            if (i != 1)
+                                handleOrgs = handleOrgs.Where(d => d.Id.StartsWith(unhandleStep.HandlerOrgId)).ToList();
+                            var isStartCountersign = handleOrgs.Count > 1;
+                            var handlers = handleOrgs.Select(d => new FlowStepHandler
+                            {
+                                OrgId = d.Id,
+                                OrgName = d.Name,
+                                Key = d.Id,
+                                Value = d.Name
+                            }).ToList();
+                            var nextflowDto = new NextWorkflowDto
+                            {
+                                WorkflowId = unhandleStep.WorkflowId,
+                                StepId = unhandleStep.Id,
+                                NextStepCode = unhandleStep.Code,
+                                NextStepName = unhandleStep.Name,
+                                FlowDirection = EFlowDirection.CenterToOrg,
+                                HandlerType = unhandleStep.HandlerType,
+                                StepType = unhandleStep.StepType,
+                                NextHandlers = handlers,
+                                IsStartCountersign = isStartCountersign,
+                                BusinessType = unhandleStep.BusinessType,
+                                Opinion = "跨级派单,自动办理"
+                            };
+
+                            var nextSteps = await _workflowDomainService.NextAsync(nextflowDto, order.ExpiredTime, HttpContext.RequestAborted);
+                            tempSteps.AddRange(nextSteps);
+                        }
+                        unhandleSteps = tempSteps;
+                    }
+                    
+                    break;
+                case EOrderAssignMode.MainAndSecondary:
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
             }
         }
 

+ 9 - 9
src/Hotline.Application.Tests/Application/OrderApplicationTest.cs

@@ -76,16 +76,16 @@ public class OrderApplicationTest
             .ExecuteCommandAsync();
         var time = DateTime.Now;
         var dicSystem = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.TranspondCity).First();
-        var dto = new NextWorkflowDto
+        var dto = new NextWorkflowDto<OrderHandleFlowDto>
         {
-            RealCommunicationAddress = realCommunicationAddress,
-            WorkflowId = workflowId,
-            RealHandlerName = realHandlerName,
-            RealHandlerPhone = realHandlerPhone,
-            RealCommunicationMode = ERealCommunicationMode.Locale,
-            RealCommunicationTime = time,
-            RealIsContacted = true,
-            RealContactLocale = true,
+            // RealCommunicationAddress = realCommunicationAddress,
+            // WorkflowId = workflowId,
+            // RealHandlerName = realHandlerName,
+            // RealHandlerPhone = realHandlerPhone,
+            // RealCommunicationMode = ERealCommunicationMode.Locale,
+            // RealCommunicationTime = time,
+            // RealIsContacted = true,
+            // RealContactLocale = true,
             // IsOther = true,
             // OtherRemark = "备注",
             // TranspondCityId = dicSystem.Id,

+ 18 - 10
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -178,8 +178,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         var counterSignType = _workflowDomainService.GetCounterSignType(dto.IsStartCountersign);
 
         //办理开始节点
-        await _workflowDomainService.HandleStepAsync(startStep, workflow, dto, flowAssignInfo.FlowAssignType,
-            counterSignType, expiredTime, cancellationToken);
+        await _workflowDomainService.HandleStepAsync(startStep, workflow, dto, counterSignType, expiredTime, cancellationToken);
 
         if (dto.Files.Any())
             startStep.FileJson =
@@ -493,8 +492,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
         foreach (var step in unhandleSteps)
         {
-            await _workflowDomainService.HandleStepAsync(step, workflow, dto, null,
-                null, null, cancellationToken);
+            await _workflowDomainService.HandleStepAsync(step, workflow, dto, null, null, cancellationToken);
 
             var trace = unhandleTraces.First(d => d.StepId == step.Id);
             _mapper.Map(dto, trace);
@@ -606,8 +604,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             //动态生成下一步
             var nextStepOption = await GetDynamicStepAsync(currentStep.InstancePolicy.Value, currentStep.StepType,
                 currentStep.BusinessType, settingHandle?.SettingValue[0], settingLead?.SettingValue[0], cancellationToken);
-            dto.Steps = new List<NextStepOption> { nextStepOption
-};
+            dto.Steps = new List<NextStepOption>
+            {
+                nextStepOption
+            };
             return dto;
         }
 
@@ -1293,13 +1293,15 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     isLead = _sessionContext.Roles.Any(x => x == leadRoleCode);
                     if (!isLead)
                     {
-                        isSkip = await _userRepository.Queryable().AnyAsync(x => x.OrgId == _sessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
+                        isSkip = await _userRepository.Queryable()
+                            .AnyAsync(x => x.OrgId == _sessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
                         if (isSkip)
                         {
                             roleId = leadRoleCode;
                             roleName = leadRoleName;
                         }
                     }
+
                     if (isLead || !isSkip)
                     {
                         //上级部门Id
@@ -1307,6 +1309,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                         roleId = handleRoleCode;
                         roleName = handleRoleName;
                     }
+
                     items = await _organizeRepository.Queryable()
                         .Where(d => d.Id == upperOrgId)
                         .Select(d => new FlowStepHandler
@@ -1334,13 +1337,15 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 isLead = _sessionContext.Roles.Any(x => x == leadRoleCode);
                 if (!isLead)
                 {
-                    isSkip = await _userRepository.Queryable().AnyAsync(x => x.OrgId == _sessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
+                    isSkip = await _userRepository.Queryable()
+                        .AnyAsync(x => x.OrgId == _sessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
                     if (isSkip)
                     {
                         roleId = leadRoleCode;
                         roleName = leadRoleName;
                     }
                 }
+
                 if (isLead || !isSkip)
                 {
                     //上级部门Id
@@ -1348,6 +1353,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     roleId = handleRoleCode;
                     roleName = handleRoleName;
                 }
+
                 items = await _organizeRepository.Queryable()
                     .Where(d => d.Id == upperOrgId)
                     .Select(d => new FlowStepHandler
@@ -1384,7 +1390,9 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 businessType = _sessionContext.OrgIsCenter ? EBusinessType.Send : EBusinessType.Department;
                 orgLevel = _sessionContext.OrgIsCenter ? 0 : 1;
                 //上级部门Id
-                upperOrgId = _sessionContext.OrgIsCenter ? _sessionContext.RequiredOrgId.Substring(0, 3) : _sessionContext.RequiredOrgId.Substring(0, 6);
+                upperOrgId = _sessionContext.OrgIsCenter
+                    ? _sessionContext.RequiredOrgId.Substring(0, 3)
+                    : _sessionContext.RequiredOrgId.Substring(0, 6);
                 items = await _organizeRepository.Queryable()
                     .Where(d => d.Id == upperOrgId)
                     .Select(d => new FlowStepHandler
@@ -1463,7 +1471,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// 会签 动态策略
     /// </summary>
     /// <returns></returns>
-
     private async Task<NextStepOption> GetDynamicStepAsync(
         EDynamicPolicyCountersign policy, EStepType stepType,
         EBusinessType currentBusinessType, CancellationToken cancellationToken)
@@ -1598,6 +1605,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             Items = items
         };
     }
+
     /// <summary>
     /// 查询下一节点办理对象类型(user or org)及实际办理对象
     /// </summary>

+ 37 - 0
src/Hotline.Application/Mappers/OrderMapperConfigs.cs

@@ -172,5 +172,42 @@ public class OrderMapperConfigs : IRegister
         config.ForType<OrderScreen, OrderScreenListDto>()
             .IgnoreIf((s, d) => s.VisitDetail == null, d => d.VisitDetail)
             ;
+        
+        config.ForType<OrderHandleFlowDto, Workflow>()
+            .Map(d => d.RealHandlerPhone, s => s.RealHandlerPhone)
+            .Map(d => d.RealHandlerName, s => s.RealHandlerName)
+            .Map(d => d.RealCommunicationMode, s => s.RealCommunicationMode)
+            .Map(d => d.RealCommunicationTime, s => s.RealCommunicationTime)
+            .Map(d => d.RealCommunicationAddress, s => s.RealCommunicationAddress)
+            .Map(d => d.RealIsContacted, s => s.RealIsContacted)
+            .Map(d => d.RealContactLocale, s => s.RealContactLocale)
+            .IgnoreNonMapped(true);
+
+        config.ForType<OrderHandleFlowDto, Order>()
+            .Map(src => src.RealCommunicationAddress, dest => dest.RealCommunicationAddress)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.RealCommunicationAddress), dest => dest.RealCommunicationAddress)
+            .Map(src => src.RealHandlerPhone , dest => dest.RealHandlerPhone)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.RealHandlerPhone), dest => dest.RealHandlerPhone)
+            .Map(src => src.RealHandlerName, dest => dest.RealHandlerName)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.RealHandlerName), dest => dest.RealHandlerName)
+            .Map(src => src.RealCommunicationMode, dest => dest.RealCommunicationMode)
+            .IgnoreIf((src, dest) => src.RealCommunicationMode == null, dest => dest.RealCommunicationMode)
+            .Map(src => src.RealCommunicationTime, dest => dest.RealCommunicationTime)
+            .IgnoreIf((src, dest) => src.RealCommunicationTime == null, dest => dest.RealCommunicationTime)
+            .Map(src => src.RealIsContacted, dest => dest.RealIsContacted)
+            .IgnoreIf((src, dest) => src.RealIsContacted == null, dest => dest.RealIsContacted)
+            .Map(src => src.RealContactLocale, dest => dest.RealContactLocale)
+            .IgnoreIf((src, dest) => src.RealContactLocale == null, dest => dest.RealContactLocale)
+            .Map(src => src.IsOther, dest => dest.IsOther)
+            .IgnoreIf((src, dest) => src.IsOther == null, dest => dest.IsOther)
+            .Map(src => src.OtherRemark, dest => dest.OtherRemark)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.OtherRemark), dest => dest.OtherRemark)
+            .Map(src => src.TranspondCityId, dest => dest.TranspondCityId)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.TranspondCityId), dest => dest.TranspondCityId)
+            .Map(src => src.TranspondCityName, dest => dest.TranspondCityName)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.TranspondCityName), dest => dest.TranspondCityName)
+            .Map(src => src.TranspondCityValue, dest => dest.TranspondCityValue)
+            .IgnoreIf((src, dest) => string.IsNullOrEmpty(src.TranspondCityValue), dest => dest.TranspondCityValue)
+            .IgnoreNonMapped(true);
     }
 }

+ 0 - 37
src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs

@@ -71,43 +71,6 @@ public class WorkflowMapperConfigs : IRegister
             //.Ignore(d => d.Traces)
             ;
 
-        config.ForType<NextWorkflowDto, Workflow>()
-            .Map(d => d.RealHandlerPhone, s => s.RealHandlerPhone)
-            .Map(d => d.RealHandlerName, s => s.RealHandlerName)
-            .Map(d => d.RealCommunicationMode, s => s.RealCommunicationMode)
-            .Map(d => d.RealCommunicationTime, s => s.RealCommunicationTime)
-            .Map(d => d.RealCommunicationAddress, s => s.RealCommunicationAddress)
-            .Map(d => d.RealIsContacted, s => s.RealIsContacted)
-            .Map(d => d.RealContactLocale, s => s.RealContactLocale)
-            .IgnoreNonMapped(true);
-
-        config.ForType<NextWorkflowDto, Order>()
-            .Map(src => src.RealCommunicationAddress, dest => dest.RealCommunicationAddress)
-            .IgnoreIf((src, dest) => src.RealCommunicationAddress.IsNullOrEmpty(), dest => dest.RealCommunicationAddress)
-            .Map(src => src.RealHandlerPhone , dest => dest.RealHandlerPhone)
-            .IgnoreIf((src, dest) => src.RealHandlerPhone.IsNullOrEmpty(), dest => dest.RealHandlerPhone)
-            .Map(src => src.RealHandlerName, dest => dest.RealHandlerName)
-            .IgnoreIf((src, dest) => src.RealHandlerName.IsNullOrEmpty(), dest => dest.RealHandlerName)
-            .Map(src => src.RealCommunicationMode, dest => dest.RealCommunicationMode)
-            .IgnoreIf((src, dest) => src.RealCommunicationMode == null, dest => dest.RealCommunicationMode)
-            .Map(src => src.RealCommunicationTime, dest => dest.RealCommunicationTime)
-            .IgnoreIf((src, dest) => src.RealCommunicationTime == null, dest => dest.RealCommunicationTime)
-            .Map(src => src.RealIsContacted, dest => dest.RealIsContacted)
-            .IgnoreIf((src, dest) => src.RealIsContacted == null, dest => dest.RealIsContacted)
-            .Map(src => src.RealContactLocale, dest => dest.RealContactLocale)
-            .IgnoreIf((src, dest) => src.RealContactLocale == null, dest => dest.RealContactLocale)
-            .Map(src => src.IsOther, dest => dest.IsOther)
-            .IgnoreIf((src, dest) => src.IsOther == null, dest => dest.IsOther)
-            .Map(src => src.OtherRemark, dest => dest.OtherRemark)
-            .IgnoreIf((src, dest) => src.OtherRemark.IsNullOrEmpty(), dest => dest.OtherRemark)
-            .Map(src => src.TranspondCityId, dest => dest.TranspondCityId)
-            .IgnoreIf((src, dest) => src.TranspondCityId.IsNullOrEmpty(), dest => dest.TranspondCityId)
-            .Map(src => src.TranspondCityName, dest => dest.TranspondCityName)
-            .IgnoreIf((src, dest) => src.TranspondCityName.IsNullOrEmpty(), dest => dest.TranspondCityName)
-            .Map(src => src.TranspondCityValue, dest => dest.TranspondCityValue)
-            .IgnoreIf((src, dest) => src.TranspondCityValue.IsNullOrEmpty(), dest => dest.TranspondCityValue)
-            .IgnoreNonMapped(true);
-
         config.ForType<Workflow, WorkflowStep>()
             .Map(d => d.WorkflowId, s => s.Id)
             .Map(d => d.ExternalId, s => s.ExternalId)

+ 1 - 1
src/Hotline.Application/Orders/OrderApplication.cs

@@ -786,7 +786,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             _capPublisher.Publish(EventNames.HotlineLeaderSMS, new PublishLeaderSMSDto(order.Id, dic.DicDataName, dic.DicDataValue));
         }
 
-        _mapper.Map(dto, order);
+        _mapper.Map(dto.Data, order);
         await _orderRepository.UpdateAsync(order, cancellationToken);
 
         if (_appOptions.Value.IsZiGong && dto.Data.Transpond.HasValue && dto.Data.Transpond.Value)

+ 42 - 42
src/Hotline.Share/Dtos/FlowEngine/NextWorkflowDto.cs

@@ -21,48 +21,48 @@ public class NextWorkflowDto : BasicWorkflowDto
     ///// </summary>
     //public DateTime StepExpiredTime { get; set; }
 
-    #region 手动填入办理人信息
-
-    /// <summary>
-    /// 真实办理人姓名(手动填写)
-    /// </summary>
-    public string? RealHandlerName { get; set; }
-
-    /// <summary>
-    /// 真实办理人电话(手动填写)
-    /// </summary>
-    public string? RealHandlerPhone { get; set; }
-
-    /// <summary>
-    /// 沟通方式(手动填写)
-    /// </summary>
-    public ERealCommunicationMode? RealCommunicationMode { get; set; }
-
-    /// <summary>
-    /// 沟通时间(手动填写)
-    /// </summary>
-    public DateTime? RealCommunicationTime { get; set; }
-
-    /// <summary>
-    /// 沟通地点(手动填写)
-    /// </summary>
-    public string? RealCommunicationAddress { get; set; }
-
-    /// <summary>
-    /// 已与市民沟通
-    /// 已与市民电话联系,确认办理结果
-    /// </summary>
-    [Description("已与市民电话联系,确认办理结果")]
-    public bool? RealIsContacted { get; set; }
-
-    /// <summary>
-    /// 已与市民现场沟通
-    /// 已赴现场处置,将处理结果告知市民
-    /// </summary>
-    [Description("已赴现场处置,将处理结果告知市民")]
-    public bool? RealContactLocale { get; set; }
-
-    #endregion
+    // #region 手动填入办理人信息
+    //
+    // /// <summary>
+    // /// 真实办理人姓名(手动填写)
+    // /// </summary>
+    // public string? RealHandlerName { get; set; }
+    //
+    // /// <summary>
+    // /// 真实办理人电话(手动填写)
+    // /// </summary>
+    // public string? RealHandlerPhone { get; set; }
+    //
+    // /// <summary>
+    // /// 沟通方式(手动填写)
+    // /// </summary>
+    // public ERealCommunicationMode? RealCommunicationMode { get; set; }
+    //
+    // /// <summary>
+    // /// 沟通时间(手动填写)
+    // /// </summary>
+    // public DateTime? RealCommunicationTime { get; set; }
+    //
+    // /// <summary>
+    // /// 沟通地点(手动填写)
+    // /// </summary>
+    // public string? RealCommunicationAddress { get; set; }
+    //
+    // /// <summary>
+    // /// 已与市民沟通
+    // /// 已与市民电话联系,确认办理结果
+    // /// </summary>
+    // [Description("已与市民电话联系,确认办理结果")]
+    // public bool? RealIsContacted { get; set; }
+    //
+    // /// <summary>
+    // /// 已与市民现场沟通
+    // /// 已赴现场处置,将处理结果告知市民
+    // /// </summary>
+    // [Description("已赴现场处置,将处理结果告知市民")]
+    // public bool? RealContactLocale { get; set; }
+    //
+    // #endregion
 }
 
 public class NextWorkflowDto<TData>

+ 44 - 0
src/Hotline.Share/Dtos/Order/OrderStartFlowDto.cs

@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.FlowEngine;
 
 namespace Hotline.Share.Dtos.Order
 {
@@ -25,6 +26,49 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public List<FlowStepHandler> CopyToHandlers { get; set; }
         
+        #region 手动填入办理人信息
+
+        /// <summary>
+        /// 真实办理人姓名(手动填写)
+        /// </summary>
+        public string? RealHandlerName { get; set; }
+
+        /// <summary>
+        /// 真实办理人电话(手动填写)
+        /// </summary>
+        public string? RealHandlerPhone { get; set; }
+
+        /// <summary>
+        /// 沟通方式(手动填写)
+        /// </summary>
+        public ERealCommunicationMode? RealCommunicationMode { get; set; }
+
+        /// <summary>
+        /// 沟通时间(手动填写)
+        /// </summary>
+        public DateTime? RealCommunicationTime { get; set; }
+
+        /// <summary>
+        /// 沟通地点(手动填写)
+        /// </summary>
+        public string? RealCommunicationAddress { get; set; }
+
+        /// <summary>
+        /// 已与市民沟通
+        /// 已与市民电话联系,确认办理结果
+        /// </summary>
+        [Description("已与市民电话联系,确认办理结果")]
+        public bool? RealIsContacted { get; set; }
+
+        /// <summary>
+        /// 已与市民现场沟通
+        /// 已赴现场处置,将处理结果告知市民
+        /// </summary>
+        [Description("已赴现场处置,将处理结果告知市民")]
+        public bool? RealContactLocale { get; set; }
+
+        #endregion
+        
         #region task_298
 
         /// <summary>

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

@@ -1,6 +1,10 @@
-using Hotline.Share.Dtos;
+using Hotline.FlowEngine.Definitions;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
 using XF.Domain.Entities;
+using XF.Domain.Exceptions;
 
 namespace Hotline.FlowEngine;
 
@@ -11,6 +15,11 @@ public class FlowAssignInfo
     /// </summary>
     public EFlowAssignType FlowAssignType { get; set; }
 
+    /// <summary>
+    /// 办理对象
+    /// </summary>
+    public FlowStepHandler Handler { get; set; }
+
     /// <summary>
     /// 办理对象(UserIds/OrgCodes)
     /// </summary>
@@ -75,5 +84,18 @@ public class FlowAssignInfo
             EHandlerType.AssignedOrg => EFlowAssignType.Org,
             _ => throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, null)
         };
+}
 
+public class StepAssignInfo
+{
+    /// <summary>
+    /// 流程指派类型
+    /// </summary>
+    public EFlowAssignType FlowAssignType { get; set; }
+
+    /// <summary>
+    /// 办理对象
+    /// </summary>
+    public FlowStepHandler Handler { get; set; }
+    
 }

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

@@ -26,6 +26,17 @@ namespace Hotline.FlowEngine.Workflows
             bool isNextDynamic, FlowAssignInfo flowAssignInfo, ECounterSignType? counterSignType, DateTime? expiredTime,
             CancellationToken cancellationToken);
 
+        /// <summary>
+        /// new
+        /// </summary>
+        Task<WorkflowStep> StartAsync(StartWorkflowDto dto, string externalId, DateTime? expiredTime, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// new
+        /// </summary>
+        Task<List<WorkflowStep>> NextAsync(NextWorkflowDto dto,
+            DateTime? expiredTime = null, CancellationToken cancellationToken = default);
+
         /// <summary>
         /// 查询工作流
         /// </summary>
@@ -203,7 +214,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 办理节点
         /// </summary>
         Task HandleStepAsync(WorkflowStep step, Workflow workflow, BasicWorkflowDto dto,
-            EFlowAssignType? flowAssignType, ECounterSignType? counterSignType, DateTime? expiredTime,
+            ECounterSignType? counterSignType, DateTime? expiredTime,
             CancellationToken cancellationToken);
 
         /// <summary>

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

@@ -794,7 +794,7 @@ public partial class Workflow
         switch (type)
         {
             case EFlowAssignType.Org:
-                var orgCodes = handler.GetHigherOrgCodes(true).ToList();
+                var orgCodes = handler.GetHigherOrgIds(true).ToList();
                 FlowedOrgIds.AddRange(orgCodes);
                 FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
                 break;
@@ -817,7 +817,7 @@ public partial class Workflow
         switch (type)
         {
             case EFlowAssignType.Org:
-                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgCodes(true)).ToList();
+                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
                 FlowedOrgIds.AddRange(orgCodes);
                 FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
                 break;

+ 540 - 43
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -36,6 +36,7 @@ namespace Hotline.FlowEngine.Workflows
         private readonly IFileRepository _fileRepository;
         private readonly IRepository<User> _userRepository;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IWfModuleCacheManager _wfModuleCacheManager;
 
         public WorkflowDomainService(
             IWorkflowRepository workflowRepository,
@@ -44,6 +45,7 @@ namespace Hotline.FlowEngine.Workflows
             IRepository<WorkflowSupplement> workflowSupplementRepository,
             IRepository<WorkflowCountersign> workflowCountersignRepository,
             ISystemSettingCacheManager systemSettingCacheManager,
+            IWfModuleCacheManager wfModuleCacheManager,
             ISessionContextProvider sessionContextProvider,
             IMapper mapper,
             Publisher publisher,
@@ -61,6 +63,7 @@ namespace Hotline.FlowEngine.Workflows
             _logger = logger;
             _fileRepository = fileRepository;
             _systemSettingCacheManager = systemSettingCacheManager;
+            _wfModuleCacheManager = wfModuleCacheManager;
         }
 
         public async Task<Workflow> CreateWorkflowAsync(WorkflowModule wfModule, string title, string userId,
@@ -116,7 +119,7 @@ namespace Hotline.FlowEngine.Workflows
 
             //firststeps
             var firstSteps = await CreateNextStepsAsync(workflow, startStep, dto, firstStepDefine,
-                isNextDynamic, flowAssignInfo, expiredTime, dto.IsStartCountersign, cancellationToken);
+                isNextDynamic, flowAssignInfo.FlowAssignType, expiredTime, dto.IsStartCountersign, cancellationToken);
 
             await _workflowStepRepository.UpdateAsync(startStep, cancellationToken);
 
@@ -140,6 +143,354 @@ namespace Hotline.FlowEngine.Workflows
                 PublishStrategy.ParallelWhenAll, cancellationToken);
         }
 
+        /// <summary>
+        /// new(开启流程并停留在开始节点,开始节点为待办节点,指派给当前操作人)
+        /// </summary>
+        public async Task<WorkflowStep> StartAsync(StartWorkflowDto dto,
+            string externalId, DateTime? expiredTime, CancellationToken cancellationToken = default)
+        {
+            //     var validator = new StartWorkflowDtoValidator();
+            // var validResult = await validator.ValidateAsync(dto, cancellationToken);
+            // if (!validResult.IsValid)
+            //     throw new UserFriendlyException(
+            //         $"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
+
+            var wfModule = await GetWorkflowModuleAsync(dto.DefinitionModuleCode, cancellationToken);
+            var definition = wfModule.Definition;
+            if (definition == null)
+                throw new UserFriendlyException("无效模板编码");
+            if (definition.Status is not EDefinitionStatus.Enable)
+                throw new UserFriendlyException("该模板不可用");
+
+            //如果发起会签需检查是否支持发起会签
+            var startStepDefine = definition.FindStartStepDefine();
+
+            //下一节点是否为动态节点
+            var isNextDynamic = startStepDefine.InstanceMode is EInstanceMode.Dynamic &&
+                                !DynamicShouldTerminal(startStepDefine, _sessionContext.OrgLevel);
+            var firstStepDefine = isNextDynamic
+                ? startStepDefine
+                : definition.FindStepDefine(dto.NextStepCode);
+            if (firstStepDefine is null)
+                throw new UserFriendlyException("未查询到下一步节点配置");
+
+            //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选
+            if (firstStepDefine.HandlerType is not EHandlerType.Role && !dto.NextHandlers.Any())
+                throw UserFriendlyException.SameMessage("未指派办理人");
+
+            if (dto.IsStartCountersign)
+            {
+                if (!startStepDefine.CanStartCountersign)
+                    throw new UserFriendlyException("当前节点不支持发起会签");
+                //if (startStepDefine.HandlerType is EHandlerType.Role)
+                //    throw new UserFriendlyException("当前节点不支持发起会签");
+                //即使当前节点支持发起会签,但下一节点为信息汇总节点、结束节点时也不可发起会签
+                if (firstStepDefine.StepType is EStepType.Summary or EStepType.End)
+                    throw new UserFriendlyException("下一节点不允许发起会签");
+                //下一节点是会签汇总节点也不允许发起会签
+                if (dto.BackToCountersignEnd)
+                    throw new UserFriendlyException("下一节点不允许发起会签");
+            }
+
+            var workflow = await CreateWorkflowAsync(wfModule, dto.Title,
+                _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
+                externalId, cancellationToken);
+
+            var startStep = CreateStartStep(workflow, startStepDefine, dto,
+                new FlowStepHandler
+                {
+                    Key = _sessionContext.RequiredUserId,
+                    Value = _sessionContext.UserName,
+                    UserId = _sessionContext.UserId,
+                    Username = _sessionContext.UserName,
+                    OrgId = _sessionContext.RequiredOrgId,
+                    OrgName = _sessionContext.OrgName,
+                    // RoleId = defineHandler.Key,
+                    // RoleName = defineHandler.Value,
+                }, expiredTime);
+
+            if (dto.Files.Any())
+                startStep.FileJson =
+                    await _fileRepository.AddFileAsync(dto.Files, workflow.ExternalId, startStep.Id, cancellationToken);
+
+            await _workflowStepRepository.AddAsync(startStep, cancellationToken);
+            workflow.Steps.Add(startStep);
+
+            //starttrace
+            var startTrace = _mapper.Map<WorkflowTrace>(startStep);
+            startTrace.StepId = startStep.Id;
+            startTrace.TraceType = EWorkflowTraceType.Normal;
+            await _workflowTraceRepository.AddAsync(startTrace, cancellationToken);
+            workflow.Traces.Add(startTrace);
+            startStep.WorkflowTrace = startTrace;
+
+            //更新受理人信息
+            workflow.UpdateAcceptor(
+                _sessionContext.RequiredUserId,
+                _sessionContext.UserName,
+                _sessionContext.StaffNo,
+                _sessionContext.RequiredOrgId,
+                _sessionContext.OrgName);
+
+            return startStep;
+        }
+
+        /// <summary>
+        /// new
+        /// </summary>
+        public async Task<List<WorkflowStep>> NextAsync(NextWorkflowDto dto,
+            DateTime? expiredTime = null, CancellationToken cancellationToken = default)
+        {
+            var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
+            withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
+            CheckWhetherRunnable(workflow.Status);
+
+        //var currentStep = _workflowDomainService.FindCurrentStepWaitForHandle(workflow,
+        //    current.RequiredUserId, current.RequiredOrgId, current.Roles);
+        var currentStep = workflow.Steps.FirstOrDefault(d => d.Id == dto.StepId);
+        if (currentStep == null)
+            throw new UserFriendlyException(
+                $"未找到对应节点, workflowId: {dto.WorkflowId}, stepId: {dto.StepId}", "未找到对应节点");
+        if (currentStep.Status is EWorkflowStepStatus.Handled)
+            throw new UserFriendlyException("该状态不支持继续办理");
+
+        var currentStepDefine = GetStepDefine(workflow.WorkflowDefinition, currentStep.Code);
+
+        //下一节点是否为动态节点
+        var isNextDynamic = currentStepDefine.InstanceMode is EInstanceMode.Dynamic &&
+                            !DynamicShouldTerminal(currentStepDefine, _sessionContext.OrgLevel);
+
+        StepDefine nextStepDefine;
+        if (isNextDynamic
+            || (currentStep.IsInCountersign() && !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+            || dto.IsStartCountersign)
+        {
+            //下一步配置为当前节点配置
+            nextStepDefine = currentStepDefine;
+        }
+        else
+        {
+            //下一步配置为下一步节点配置
+            nextStepDefine = GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+        }
+
+        //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签)
+        if (nextStepDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
+            throw new UserFriendlyException("未指定节点处理者");
+
+        if (dto.IsStartCountersign)
+        {
+            if (!currentStepDefine.CanStartCountersign)
+                throw new UserFriendlyException("当前节点不支持发起会签");
+            //if (currentStepDefine.HandlerType is EHandlerType.Role)
+            //    throw new UserFriendlyException("当前节点不支持发起会签");
+            //即使当前节点支持发起会签,但下一节点为信息汇总节点、结束节点时也不可发起会签
+            if (nextStepDefine.StepType is EStepType.Summary or EStepType.End)
+                throw new UserFriendlyException("下一节点不允许发起会签");
+            //下一节点是会签汇总节点也不允许发起会签
+            if (dto.BackToCountersignEnd)
+                throw new UserFriendlyException("下一节点不允许发起会签");
+        }
+
+        var flowAssignInfo =
+            await GetNextStepFlowAssignInfoAsync(workflow, currentStep, dto, nextStepDefine, isNextDynamic, cancellationToken);
+        
+
+         #region 办理当前节点
+
+            if (dto.Files != null && dto.Files.Any())
+                currentStep.FileJson = await _fileRepository.AddFileAsync(dto.Files, workflow.ExternalId,
+                    currentStep.Id, cancellationToken);
+
+            //(currentStep.IsInCountersign() && !dto.BackToCountersignEnd) || dto.IsStartCountersign;
+            var isStartCountersign = currentStep.CountersignPosition switch
+            {
+                ECountersignPosition.None => dto.IsStartCountersign,
+                ECountersignPosition.Multi => !dto.BackToCountersignEnd,
+                ECountersignPosition.Single => !dto.BackToCountersignEnd,
+                ECountersignPosition.End => dto.IsStartCountersign,
+                _ => throw new ArgumentOutOfRangeException()
+            };
+
+            var counterSignType = GetCounterSignType(dto.IsStartCountersign);
+
+            var updateSteps = new List<WorkflowStep> { currentStep };
+
+            //结束当前会签流程
+            if (currentStep.IsCountersignEndStep)
+            {
+                var countersignStartStep =
+                    workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
+                if (countersignStartStep is null)
+                    throw new UserFriendlyException(
+                        $"未查询到会签开始step, workflowId: {workflow.Id}, currentStepId: {currentStep.Id}",
+                        "未查询到会签开始节点");
+
+                if (countersignStartStep.IsStartCountersign)
+                {
+                    var currentCountersign =
+                        workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId);
+                    if (currentCountersign is null)
+                        throw new UserFriendlyException(
+                            $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}",
+                            "无效会签编号");
+
+                    //结束step会签信息
+                    countersignStartStep.CountersignEnd();
+                    updateSteps.Add(countersignStartStep);
+
+                    //结束会签
+                    currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType,
+                        _sessionContext.RequiredUserId, _sessionContext.UserName,
+                        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                    await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
+                }
+            }
+
+            await HandleStepAsync(currentStep, workflow, dto, counterSignType, expiredTime, cancellationToken);
+
+            //创建会签数据
+            if (isStartCountersign)
+            {
+                var exists = workflow.Countersigns.Any(d =>
+                    !d.IsCompleted() && d.StarterId == _sessionContext.RequiredUserId);
+                if (exists)
+                    throw new UserFriendlyException("该用户在当前流程存在未结束会签");
+                await StartCountersignAsync(_sessionContext, workflow, currentStep, dto, flowAssignInfo.FlowAssignType,
+                    counterSignType, expiredTime, cancellationToken);
+            }
+
+            currentStep.IsActualHandled = CheckIsActualHandle(workflow, currentStep, nextStepDefine, dto);
+
+            _mapper.Map(dto, workflow);
+            
+            //会签办理节点办理时更新会签members字段
+            if (currentStep.CountersignPosition is ECountersignPosition.Multi or ECountersignPosition.Single)
+            {
+                if (!string.IsNullOrEmpty(currentStep.CountersignId))
+                {
+                    //会签中正常办理节点,更新会签members办理状态
+                    var countersign =
+                        workflow.Countersigns.FirstOrDefault(d =>
+                            !d.IsCompleted() && d.Id == currentStep.CountersignId);
+                    if (countersign is not null)
+                    {
+                        //throw new UserFriendlyException(
+                        //    $"会签数据异常, workflowId: {currentStep.WorkflowId}, countersignId: {currentStep.CountersignId}",
+                        //    "会签数据异常");
+                        countersign.MemberHandled(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
+                        //update cs
+                        await _workflowCountersignRepository.UpdateNav(countersign)
+                            .Include(d => d.Members)
+                            .ExecuteCommandAsync();
+                    }
+                }
+            }
+
+            await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken);
+
+            //更新traces
+            var updateTraces = new List<WorkflowTrace>();
+            foreach (var updateStep in updateSteps)
+            {
+                var updateTrace = workflow.Traces.First(d => d.Id == updateStep.Id);
+                _mapper.Map(updateStep, updateTrace);
+                updateTraces.Add(updateTrace);
+            }
+
+            await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken);
+
+            //var trace = await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
+
+            #endregion
+
+            #region 处理流程
+
+            //检查会签是否结束,并更新当前会签节点字段
+            var isCountersignOver = false;
+            if (workflow.IsInCountersign && currentStep.IsCountersignEndStep)
+            {
+                isCountersignOver = workflow.CheckIfCountersignOver();
+                if (isCountersignOver)
+                    workflow.EndCountersign();
+            }
+
+            if (workflow.ActualHandleStepId == currentStep.Id)
+            {
+                //更新实际办理节点信息
+                workflow.UpdateActualStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+            }
+
+            if (workflow.CurrentStepId == currentStep.Id)
+            {
+                workflow.UpdateCurrentStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+            }
+
+            //检查是否流转到流程终点
+            if (nextStepDefine.StepType is EStepType.End)
+            {
+                var endTrace = await EndAsync(workflow, dto, nextStepDefine, currentStep, expiredTime, cancellationToken);
+                return new List<WorkflowStep>();
+            }
+
+            //创建下一/N个节点(会签汇总节点:会签未全部办理时不创建,最后一个会签办理节点创建会签汇总节点)
+            var nextSteps = await CreateNextStepsAsync(workflow, currentStep, dto,
+                nextStepDefine, isNextDynamic, flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign,
+                cancellationToken);
+
+            ////赋值当前节点的下级办理节点
+            //if (dto.IsStartCountersign
+            //   //|| (currentStep.IsInCountersign() &&
+            //   //    !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+            //   )
+            //{
+            //    currentStep.CreateCountersignSteps(nextSteps);
+            //    await _workflowStepRepository.UpdateAsync(currentStep, cancellationToken);
+            //}
+
+            // //更新办理对象(nextSteps无元素表示当前节点为会签办理节点且当前会签没有全部办理完成)
+            // workflow.UpdateHandlers(current.RequiredUserId, current.RequiredOrgId,
+            //     flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects, nextSteps.Any());
+
+            //todo 计算办理工作时长
+
+            //指派实际办理节点
+            UpdateActualStep(workflow, dto, nextStepDefine, nextSteps);
+
+            //更新实际办理节点
+            UpdateCurrentStep(workflow, dto, nextStepDefine, nextSteps);
+
+            //发起会签时记录顶层会签节点
+            if (dto.IsStartCountersign && !workflow.IsInCountersign)
+                workflow.StartCountersign(currentStep.Id, counterSignType);
+
+            //更新指派信息
+            //workflow.Assign(flowAssignInfo.FlowAssignType, flowAssignInfo.GetHandlerIds());
+
+            //更新会签实际办理对象信息
+            if (currentStep.IsActualHandled)
+                workflow.AddCsActualHandler(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            #endregion
+
+            #region 流转记录
+
+            //var trace = await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
+
+            #endregion
+
+            var currentTrace = workflow.Traces.First(d => d.Id == currentStep.Id);
+            await _publisher.PublishAsync(
+                new NextStepNotify(workflow, dto, flowAssignInfo, currentTrace, nextStepDefine,
+                    _sessionContext.RequiredOrgId, expiredTime.HasValue), PublishStrategy.ParallelWhenAll,
+                cancellationToken);
+            
+            return nextSteps;
+        }
+
         public async Task<Workflow> GetWorkflowAsync(string workflowId,
             bool withDefine = false, bool withSteps = false,
             bool withTraces = false, bool withTracesTree = false,
@@ -197,8 +548,7 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 查询工作流包含当前用户结束会签权限(是否可结束)
         /// </summary>
-        public async Task<(Workflow Workflow, string? CountersignId, bool CanHandle, bool CanPrevious, WorkflowTrace?
-                Trace)>
+        public async Task<(Workflow Workflow, string? CountersignId, bool CanHandle, bool CanPrevious, WorkflowTrace? Trace)>
             GetWorkflowHandlePermissionAsync(
                 string workflowId, string userId, string orgId, string[] roleIds,
                 CancellationToken cancellationToken = default)
@@ -341,8 +691,7 @@ namespace Hotline.FlowEngine.Workflows
                 }
             }
 
-            await HandleStepAsync(currentStep, workflow, dto, flowAssignInfo.FlowAssignType,
-                counterSignType, expiredTime, cancellationToken);
+            await HandleStepAsync(currentStep, workflow, dto, counterSignType, expiredTime, cancellationToken);
 
             //创建会签数据
             if (isStartCountersign)
@@ -454,7 +803,7 @@ namespace Hotline.FlowEngine.Workflows
 
             //创建下一/N个节点(会签汇总节点:会签未全部办理时不创建,最后一个会签办理节点创建会签汇总节点)
             var nextSteps = await CreateNextStepsAsync(workflow, currentStep, dto,
-                nextStepDefine, isNextDynamic, flowAssignInfo, expiredTime, isStartCountersign,
+                nextStepDefine, isNextDynamic, flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign,
                 cancellationToken);
 
             ////赋值当前节点的下级办理节点
@@ -1519,7 +1868,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 办理节点
         /// </summary>
         public async Task HandleStepAsync(WorkflowStep step, Workflow workflow,
-            BasicWorkflowDto dto, EFlowAssignType? flowAssignType, ECounterSignType? counterSignType,
+            BasicWorkflowDto dto, ECounterSignType? counterSignType,
             DateTime? expiredTime, CancellationToken cancellationToken)
         {
             if (step.Status is EWorkflowStepStatus.Handled)
@@ -1643,7 +1992,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 创建下1/N个节点
         /// </summary>
         private async Task<List<WorkflowStep>> CreateNextStepsAsync(Workflow workflow, WorkflowStep currentStep,
-            BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic, FlowAssignInfo flowAssignInfo,
+            BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic, EFlowAssignType flowAssignType,
             DateTime? expiredTime, bool isStartCountersign,
             CancellationToken cancellationToken)
         {
@@ -1660,13 +2009,13 @@ namespace Hotline.FlowEngine.Workflows
                         {
                             //依据会签策略创建会签下一级节点
                             nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                                flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign, cancellationToken);
+                                flowAssignType, expiredTime, cancellationToken);
                         }
                         else
                         {
                             //创建普通节点(根据配置)
                             nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                                flowAssignInfo, EWorkflowTraceType.Normal, expiredTime, cancellationToken);
+                                flowAssignType, EWorkflowTraceType.Normal, expiredTime, cancellationToken);
                         }
                     }
                     else
@@ -1685,7 +2034,7 @@ namespace Hotline.FlowEngine.Workflows
                         {
                             //依据会签策略创建会签下一级节点
                             nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                                flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign, cancellationToken);
+                                flowAssignType, expiredTime, cancellationToken);
                         }
                     }
                 }
@@ -1701,7 +2050,7 @@ namespace Hotline.FlowEngine.Workflows
                     {
                         //依据会签策略创建会签下一级节点
                         nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                            flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign, cancellationToken);
+                            flowAssignType, expiredTime, cancellationToken);
                     }
                 }
             }
@@ -1709,18 +2058,18 @@ namespace Hotline.FlowEngine.Workflows
             {
                 //依据会签策略创建会签下一级节点
                 nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                    flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign, cancellationToken);
+                    flowAssignType, expiredTime, cancellationToken);
             }
             else if (isNextDynamic)
             {
                 //创建动态下一级节点
-                nextSteps = await CreateDynamicStepsAsync(workflow, nextStepDefine, currentStep, dto, flowAssignInfo,
+                nextSteps = await CreateDynamicStepsAsync(workflow, nextStepDefine, currentStep, dto, flowAssignType,
                     expiredTime, cancellationToken);
             }
             else
             {
                 //创建普通节点(根据配置)
-                nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, flowAssignInfo,
+                nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, flowAssignType,
                     EWorkflowTraceType.Normal, expiredTime, cancellationToken);
             }
 
@@ -1745,7 +2094,7 @@ namespace Hotline.FlowEngine.Workflows
             StepDefine nextStepDefine,
             WorkflowStep prevStep,
             BasicWorkflowDto dto,
-            FlowAssignInfo flowAssignInfo,
+            EFlowAssignType flowAssignType,
             DateTime? expiredTime,
             CancellationToken cancellationToken)
         {
@@ -1766,7 +2115,7 @@ namespace Hotline.FlowEngine.Workflows
             };
 
             return await CreateStepsAsync(workflow, nextStepDefine, prevStep, dto,
-                flowAssignInfo.FlowAssignType, dto.NextHandlers, null, EWorkflowStepStatus.WaitForAccept,
+                flowAssignType, dto.NextHandlers, null, EWorkflowStepStatus.WaitForAccept,
                 ECountersignPosition.None, false, EWorkflowTraceType.Normal, handlerType, expiredTime,
                 cancellationToken: cancellationToken);
         }
@@ -1778,28 +2127,14 @@ namespace Hotline.FlowEngine.Workflows
             BasicWorkflowDto dto,
             EFlowAssignType flowAssignType,
             DateTime? expiredTime,
-            bool isStartCountersign,
             CancellationToken cancellationToken = default
         )
         {
             //var countersignId = dto.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
             var countersignId = prevStep.StartCountersignId;
 
-            var handlerType = stepDefine.CountersignPolicy switch
-            {
-                EDynamicPolicyCountersign.OrgUpCenterTop => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgUp => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgUpHandleCenterTop => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgUpHandle => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgUpLeadCenterTop => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgUpLead => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.ArriveCenter => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.ArriveOneOrg => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgDownCenterTop => EHandlerType.OrgLevel,
-                EDynamicPolicyCountersign.OrgDown => EHandlerType.OrgLevel,
-                null => throw new ArgumentOutOfRangeException(),
-                _ => throw new ArgumentOutOfRangeException()
-            };
+            //当前策略均为orglevel
+            var handlerType = EHandlerType.OrgLevel;
 
             var nextStepCountersignPosition = dto.NextHandlers.Count > 1
                 ? ECountersignPosition.Multi
@@ -2416,7 +2751,7 @@ namespace Hotline.FlowEngine.Workflows
             StepDefine stepDefine,
             WorkflowStep prevStep,
             BasicWorkflowDto dto,
-            FlowAssignInfo flowAssignInfo,
+            EFlowAssignType flowAssignType,
             EWorkflowTraceType traceType,
             DateTime? expiredTime,
             CancellationToken cancellationToken)
@@ -2437,8 +2772,8 @@ namespace Hotline.FlowEngine.Workflows
                 handlers = dto.NextHandlers;
             }
 
-            return await CreateStepsAsync(workflow, stepDefine, prevStep, dto, /*dto.IsStartCountersign,*/
-                flowAssignInfo.FlowAssignType, handlers, null,
+            return await CreateStepsAsync(workflow, stepDefine, prevStep, dto,
+                flowAssignType, handlers, null,
                 EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
                 true, traceType, null, expiredTime, cancellationToken);
         }
@@ -2468,18 +2803,11 @@ namespace Hotline.FlowEngine.Workflows
                 var step = CreateStep(workflow, stepDefine, prevStep, flowAssignType,
                     handler, dto.NextStepCode, countersignId, stepStatus, csPosition, expiredTime,
                     dto.NextStepName, isOrigin, isMain, handlerType, dto.BusinessType);
-
-                //var stepHandler = stepHandlers.First(d => d.GetHandler().Key == handler.Key);
-                //step.StepHandlers = new List<WorkflowStepHandler> { stepHandler };
-
                 steps.Add(step);
             }
 
             await _workflowStepRepository.AddRangeAsync(steps, cancellationToken);
             workflow.Steps.AddRange(steps);
-            //await _workflowStepRepository.AddNav(steps)
-            //    .Include(d => d.StepHandlers)
-            //    .ExecuteCommandAsync();
 
             //create traces todo add range traces
             foreach (var step in steps)
@@ -2848,6 +3176,175 @@ namespace Hotline.FlowEngine.Workflows
             }
         }
 
+        //new
+
+        /// <summary>
+        /// 查询流程业务模块
+        /// </summary>
+        /// <param name="code"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        /// <exception cref="UserFriendlyException"></exception>
+        private async Task<WorkflowModule> GetWorkflowModuleAsync(string code, CancellationToken cancellationToken)
+        {
+            var wfModule = await _wfModuleCacheManager.GetWorkflowModuleAsync(code, cancellationToken);
+            if (wfModule == null)
+                throw UserFriendlyException.SameMessage("无效流程模块编码");
+            if (wfModule.Definition is null)
+                throw new UserFriendlyException($"{code} 未配置流程模板", "未配置流程模板");
+            return wfModule;
+        }
+        //
+        // private StepAssignInfo GetStepAssignInfo(TargetStepAssignPolicyInfo targetStepAssignPolicyInfo, bool anyHandlers,
+        //     StepDefine? stepDefine = null, WorkflowStep? targetStep = null, FlowStepHandler? handler = null)
+        // {
+        //     switch (targetStepAssignPolicyInfo.TargetStepAssignPolicy)
+        //     {
+        //         case ETargetStepAssignPolicy.Config:
+        //             if (stepDefine is null) throw new UserFriendlyException($"{nameof(stepDefine)} is null");
+        //             return new StepAssignInfo
+        //             {
+        //                 FlowAssignType = GetNextStepFlowAssignTypeByDefine(stepDefine, anyHandlers),
+        //                 //todo 
+        //                 // Handler = 
+        //             };
+        //         case ETargetStepAssignPolicy.TargetStep:
+        //             if (targetStep is null) throw new UserFriendlyException($"{nameof(targetStep)} is null");
+        //             if (targetStep.FlowAssignType is null) throw new UserFriendlyException($"targetStep.FlowAssignType is null");
+        //             return new StepAssignInfo
+        //             {
+        //                 FlowAssignType = targetStep.FlowAssignType.Value,
+        //                 Handler = targetStep.GetWorkflowStepHandler()
+        //             };
+        //         case ETargetStepAssignPolicy.AssignHandler:
+        //             if(handler is null) throw new UserFriendlyException($"{nameof(handler)} is null");
+        //             return new()
+        //             {
+        //                 FlowAssignType = EFlowAssignType.User,
+        //                 Handler = handler
+        //             };
+        //         default:
+        //             throw new ArgumentOutOfRangeException();
+        //     }
+        // }
+        //
+        // /// <summary>
+        // /// 按流程模板配置获取下一节点指派类型
+        // /// </summary>
+        // private EFlowAssignType GetNextStepFlowAssignTypeByDefine(StepDefine nextStepDefine, bool anyHandlers)
+        // {
+        //     switch (nextStepDefine.HandlerType)
+        //     {
+        //         case EHandlerType.Role:
+        //             return !anyHandlers ? EFlowAssignType.Role : EFlowAssignType.User;
+        //
+        //         case EHandlerType.OrgLevel:
+        //         case EHandlerType.OrgType:
+        //         case EHandlerType.AssignedOrg:
+        //             return EFlowAssignType.Org;
+        //         
+        //         case EHandlerType.AssignedUser:
+        //             return EFlowAssignType.User;
+        //         
+        //         default:
+        //             throw new ArgumentOutOfRangeException();
+        //     }
+        // }
+
+        /// <summary>
+    /// 查询下一节点办理对象类型(user or org)及实际办理对象
+    /// </summary>
+    private async Task<FlowAssignInfo> GetNextStepFlowAssignInfoAsync(Workflow workflow, WorkflowStep currentStep,
+        BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic, CancellationToken cancellationToken)
+    {
+        if (nextStepDefine.StepType is EStepType.End) return new();
+
+        var isStartCountersign = dto.IsStartCountersign;
+        var handlers = dto.NextHandlers.Select(d => new Kv(d.Key, d.Value)).ToList();
+
+        if (isStartCountersign)
+        {
+            var assignType = FlowAssignInfo.GetAssignType(dto.HandlerType, dto.NextHandlers.Any());
+            //按会签策略判断,目前所有策略为org
+            return FlowAssignInfo.Create(assignType, handlers, isStartCountersign);
+        }
+
+        //if (currentStep.IsInCountersign() && !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+        //    return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign);
+
+        if (currentStep.IsInCountersign())
+        {
+            if (currentStep.IsCountersignEndStep)
+            {
+                //汇总节点(非顶级)
+                if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+                {
+                    if (dto.BackToCountersignEnd)
+                    {
+                        var csStartStep = GetCsLoopStartStep(workflow, currentStep);
+                        var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == csStartStep.PrevStepId);
+                        if (prevStep is null)
+                            throw new UserFriendlyException("未查询到目标节点的前一节点");
+                        return FlowAssignInfo.Create(prevStep.FlowAssignType.Value, prevStep.Handlers, isStartCountersign);
+                    }
+                }
+            }
+            else
+            {
+                if (dto.BackToCountersignEnd)
+                {
+                    var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.PrevStepId);
+                    if (prevStep is null)
+                        throw new UserFriendlyException($"未查询到当前节点的上级节点");
+                    return FlowAssignInfo.Create(prevStep.FlowAssignType.Value, prevStep.Handlers, isStartCountersign);
+                }
+                else
+                {
+                    var assignType = FlowAssignInfo.GetAssignType(dto.HandlerType, dto.NextHandlers.Any());
+                    //按会签策略判断,目前所有策略为org
+                    return FlowAssignInfo.Create(assignType, handlers, isStartCountersign);
+                }
+            }
+        }
+
+        if (isNextDynamic)
+        {
+            switch (currentStep.InstancePolicy)
+            {
+                case EDynamicPolicy.OrgUpCenterTop:
+                case EDynamicPolicy.OrgUp:
+                case EDynamicPolicy.OrgDownCenterTop:
+                case EDynamicPolicy.OrgDown:
+                case EDynamicPolicy.ArriveCenter:
+                case EDynamicPolicy.ArriveOneOrg:
+                    return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign);
+                case EDynamicPolicy.OrgUpHandleCenterTop:
+                case EDynamicPolicy.OrgUpHandle:
+                case EDynamicPolicy.OrgUpLeadCenterTop:
+                case EDynamicPolicy.OrgUpLead:
+                    return FlowAssignInfo.Create(EFlowAssignType.OrgAndRole, handlers, isStartCountersign);
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+        }
+
+        return await GetNextStepFlowAssignInfoByDefineAsync(nextStepDefine, dto.HandlerType, isStartCountersign, handlers,
+            cancellationToken);
+    }
+        
+        private WorkflowStep GetCsLoopStartStep(Workflow workflow, WorkflowStep currentStep)
+        {
+            var startCountersignStep =
+                workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
+            if (startCountersignStep is null)
+                throw new UserFriendlyException(
+                    $"未查询到会签开始节点,workflowId: {workflow.Id}, startStepId: {currentStep.CountersignStartStepId}",
+                    "未查询到会签开始节点,数据异常");
+            if (!startCountersignStep.IsCountersignEndStep)
+                return startCountersignStep;
+            return GetCsLoopStartStep(workflow, startCountersignStep);
+        }
+        
         #endregion
     }
 }

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

@@ -175,7 +175,7 @@ public abstract class WorkflowEntity : FullStateEntity, IWorkflow
         switch (type)
         {
             case EFlowAssignType.Org:
-                var orgCodes = handler.GetHigherOrgCodes(true).ToList();
+                var orgCodes = handler.GetHigherOrgIds(true).ToList();
                 FlowedOrgIds.AddRange(orgCodes);
                 FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
                 break;
@@ -195,7 +195,7 @@ public abstract class WorkflowEntity : FullStateEntity, IWorkflow
         switch (type)
         {
             case EFlowAssignType.Org:
-                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgCodes(true)).ToList();
+                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
                 FlowedOrgIds.AddRange(orgCodes);
                 FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
                 break;
@@ -347,7 +347,7 @@ public abstract class PositionWorkflowEntity : PositionEntity, IWorkflow
         switch (type)
         {
             case EFlowAssignType.Org:
-                var orgCodes = handler.GetHigherOrgCodes(true).ToList();
+                var orgCodes = handler.GetHigherOrgIds(true).ToList();
                 FlowedOrgIds.AddRange(orgCodes);
                 FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
                 break;
@@ -367,7 +367,7 @@ public abstract class PositionWorkflowEntity : PositionEntity, IWorkflow
         switch (type)
         {
             case EFlowAssignType.Org:
-                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgCodes(true)).ToList();
+                var orgCodes = handlers.SelectMany(d => d.GetHigherOrgIds(true)).ToList();
                 FlowedOrgIds.AddRange(orgCodes);
                 FlowedOrgIds = FlowedOrgIds.Distinct().ToList();
                 break;

+ 1 - 1
src/XF.Domain/Extensions/OrgExtensions.cs

@@ -10,7 +10,7 @@ namespace XF.Domain.Extensions
 {
     public static class OrgExtensions
     {
-        public static List<string> GetHigherOrgCodes(this string orgCode, bool withSelf = false)
+        public static List<string> GetHigherOrgIds(this string orgCode, bool withSelf = false)
         {
             if (string.IsNullOrEmpty(orgCode))
                 return new();