Xfedge 1 год назад
Родитель
Сommit
e7b0fc7389
55 измененных файлов с 2714 добавлено и 510 удалено
  1. 1 1
      src/Hotline.Api/Controllers/KnowledgeApplyController.cs
  2. 3 3
      src/Hotline.Api/Controllers/KnowledgeController.cs
  3. 3 3
      src/Hotline.Api/Controllers/OrderController.cs
  4. 8 8
      src/Hotline.Api/Controllers/OrgController.cs
  5. 1 1
      src/Hotline.Api/Controllers/PushMessageController.cs
  6. 15 15
      src/Hotline.Api/Controllers/WorkflowController.cs
  7. 1 1
      src/Hotline.Application.Contracts/Validators/FlowEngine/StartWorkflowDtoValidator.cs
  8. 2 2
      src/Hotline.Application.Contracts/Validators/Orgs/AddOrgDtoValidator.cs
  9. 6 12
      src/Hotline.Application/FlowEngine/IWorkflowApplication.cs
  10. 88 62
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  11. 417 0
      src/Hotline.Application/FlowEngine/WorkflowApplication1.cs
  12. 2 2
      src/Hotline.Application/Handlers/FlowEngine/EndWorkflowHandler.cs
  13. 1 1
      src/Hotline.Application/Identity/IdentityAppService.cs
  14. 3 3
      src/Hotline.Application/Knowledge/KnowApplication.cs
  15. 5 5
      src/Hotline.Application/Mappers/KnowledgeMapperConfigs.cs
  16. 1 1
      src/Hotline.Application/Mappers/MapperConfigs.cs
  17. 4 4
      src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs
  18. 7 7
      src/Hotline.Share/Dtos/FlowEngine/BasicWorkflowDto.cs
  19. 9 8
      src/Hotline.Share/Dtos/FlowEngine/DefinedStepDto.cs
  20. 24 16
      src/Hotline.Share/Dtos/FlowEngine/Definition/StepDefineBasic.cs
  21. 3 3
      src/Hotline.Share/Dtos/FlowEngine/NextStepOptionDto.cs
  22. 2 2
      src/Hotline.Share/Dtos/FlowEngine/StepBasicDto.cs
  23. 3 3
      src/Hotline.Share/Dtos/FlowEngine/WorkflowStepDto.cs
  24. 0 18
      src/Hotline.Share/Dtos/IdName.cs
  25. 20 0
      src/Hotline.Share/Dtos/Kv.cs
  26. 1 1
      src/Hotline.Share/Dtos/Order/OrderComplainDto.cs
  27. 7 7
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  28. 3 3
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  29. 2 2
      src/Hotline.Share/Dtos/Org/OrgDto.cs
  30. 1 1
      src/Hotline.Share/Enums/FlowEngine/EBusinessType.cs
  31. 2 8
      src/Hotline.Share/Enums/FlowEngine/EHandlerType.cs
  32. 2 2
      src/Hotline.Share/Enums/FlowEngine/EInstanceMode.cs
  33. 1 1
      src/Hotline.Share/Enums/FlowEngine/ERepeatPolicy.cs
  34. 1 1
      src/Hotline.Share/Enums/FlowEngine/EStepType.cs
  35. 2 2
      src/Hotline/FlowEngine/Definitions/StepDefine.cs
  36. 1 1
      src/Hotline/FlowEngine/FlowAssignInfo.cs
  37. 1 1
      src/Hotline/FlowEngine/Workflows/EStepCountersignStatus.cs
  38. 1 1
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  39. 103 91
      src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs
  40. 8 5
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  41. 5 4
      src/Hotline/FlowEngine/Workflows/WorkflowCountersign.cs
  42. 52 52
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  43. 1508 0
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService1.cs
  44. 65 66
      src/Hotline/FlowEngine/Workflows/WorkflowStep.cs
  45. 200 0
      src/Hotline/FlowEngine/Workflows/WorkflowStep1.cs
  46. 10 10
      src/Hotline/FlowEngine/Workflows/WorkflowTrace.cs
  47. 1 1
      src/Hotline/Orders/OrderComplain.cs
  48. 1 1
      src/Hotline/Orders/OrderVisit.cs
  49. 3 3
      src/Hotline/Orders/OrderVisitDetail.cs
  50. 6 6
      src/Hotline/SeedData/Codes/ICode.cs
  51. 14 11
      src/Hotline/SeedData/OrgSeedData.cs
  52. 6 0
      src/Hotline/Settings/ISystemDomainService.cs
  53. 26 11
      src/Hotline/Settings/SystemDomainService.cs
  54. 30 37
      src/Hotline/Settings/SystemOrganize.cs
  55. 22 0
      src/XF.Domain/Extensions/OrgExtensions.cs

+ 1 - 1
src/Hotline.Api/Controllers/KnowledgeApplyController.cs

@@ -196,7 +196,7 @@ namespace Hotline.Api.Controllers
 
             var organize = await _systemOrganizeRepository.GetAsync(handle.CreatorOrgId, HttpContext.RequestAborted);
             if (organize != null)
-                dataKnowledgeApplyDto.CreationBMName = organize.OrgName;
+                dataKnowledgeApplyDto.CreationBMName = organize.Name;
 
             //审批中,更新审批查询最新的审批通过数据
             if (handle.Status == EKnowledgeApplyStatus.Handling && handle.KnowledgeApplyType == EApplyType.Update)

+ 3 - 3
src/Hotline.Api/Controllers/KnowledgeController.cs

@@ -218,7 +218,7 @@ namespace Hotline.Api.Controllers
                {
                    index = SqlFunc.RowNumber($"{o.Version} desc ", $"{o.Code}"),
                    DepartmentId = sys.Id,
-                   Department = sys.OrgName,
+                   Department = sys.Name,
                    o.Id,
                    o.Title,
                    o.Status,
@@ -401,7 +401,7 @@ namespace Hotline.Api.Controllers
                .WhereIF(pagedDto.StartApplyTime.HasValue, d => d.CreationTime >= pagedDto.StartApplyTime)
                .WhereIF(pagedDto.EndApplyTime.HasValue, d => d.CreationTime <= pagedDto.EndApplyTime)
                .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), d => d.Knowledge.User.Name.Contains(pagedDto.Keyword!)
-               || d.Knowledge.SystemOrganize.OrgName.Contains(pagedDto.Keyword!)
+               || d.Knowledge.SystemOrganize.Name.Contains(pagedDto.Keyword!)
                || d.Knowledge.Title.Contains(pagedDto.Keyword!))
 
                .OrderByDescending(p => p.CreationTime)
@@ -448,7 +448,7 @@ namespace Hotline.Api.Controllers
                 .WhereIF(pagedDto.EKnowledgeApplyType.HasValue, d => d.WorkflowModuleStatus == pagedDto.EKnowledgeApplyType)
                 .WhereIF(pagedDto.EKnowledgeWorkFlowStatus.HasValue, d => d.WorkFlowApplyStatus == pagedDto.EKnowledgeWorkFlowStatus)
                 .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), d => d.Knowledge.User.Name.Contains(pagedDto.Keyword!)
-                || d.Knowledge.SystemOrganize.OrgName.Contains(pagedDto.Keyword!)
+                || d.Knowledge.SystemOrganize.Name.Contains(pagedDto.Keyword!)
                 || d.Knowledge.Title.Contains(pagedDto.Keyword!))
 
                 .OrderByDescending(p => p.CreationTime)

+ 3 - 3
src/Hotline.Api/Controllers/OrderController.cs

@@ -293,8 +293,8 @@ public class OrderController : BaseController
             var details = visit.OrderVisitDetails.Where(x => x.VisitTarget == EVisitTarget.Org).ToList();
             if (details!=null && details.Count>0)
             {
-                pubentity.IdNames = new List<IdName>();
-                details.ForEach(f => pubentity.IdNames.Add(new IdName() { Id = f.VisitOrgCode, Name = f.VisitOrgName }));
+                pubentity.IdNames = new List<Kv>();
+                details.ForEach(f => pubentity.IdNames.Add(new Kv() { Id = f.VisitOrgCode, Name = f.VisitOrgName }));
             }
         }
         return pubentity;
@@ -1283,7 +1283,7 @@ public class OrderController : BaseController
             ECommercePlatformOptions = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.ECommercePlatform),
             PatentTypeOptions = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.PatentType),
             AffairTargetOptions = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.AffairTarget),
-            ComplainTypeOptions = EnumExts.GetDescriptions<EComplainType>().Select(d => new IdName(d.Key.ToString(), d.Value)),
+            ComplainTypeOptions = EnumExts.GetDescriptions<EComplainType>().Select(d => new Kv(d.Key.ToString(), d.Value)),
             SalesChannelOptions = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.SalesChannel),
         };
         return rsp;

+ 8 - 8
src/Hotline.Api/Controllers/OrgController.cs

@@ -57,9 +57,9 @@ namespace Hotline.Api.Controllers
         {
             var org = _mapper.Map<SystemOrganize>(dto);
             //验证名称是否重复
-            if (await _systemDomainService.IsNameRepeat(dto.OrgName, HttpContext.RequestAborted) > 0)
+            if (await _systemDomainService.IsNameRepeat(dto.Name, HttpContext.RequestAborted) > 0)
                 throw UserFriendlyException.SameMessage("组织名称重复");
-            if (await _systemDomainService.IsShrotNameRepeat(dto.OrgShortName, HttpContext.RequestAborted) > 0)
+            if (await _systemDomainService.IsShrotNameRepeat(dto.ShortName, HttpContext.RequestAborted) > 0)
                 throw UserFriendlyException.SameMessage("组织简称重复");
 
             //处理编码
@@ -79,13 +79,13 @@ namespace Hotline.Api.Controllers
         public async Task Add([FromBody] AddOrgDto dto)
         {
             var exists = await _systemOrganizeRepository.AnyAsync(
-                d => d.OrgName == dto.OrgName || d.OrgShortName == dto.OrgShortName, HttpContext.RequestAborted);
+                d => d.Name == dto.Name || d.ShortName == dto.ShortName, HttpContext.RequestAborted);
             if (exists)
                 throw UserFriendlyException.SameMessage("名称或简称已存在");
 
             var org = _mapper.Map<SystemOrganize>(dto);
 
-            org.Id= await _systemDomainService.GenerateNewOrgCodeAsync(dto.ParentId, HttpContext.RequestAborted);
+            org.Id = await _systemDomainService.GenerateNewOrgCodeAsync(dto.ParentId, HttpContext.RequestAborted);
             org.InitOrgLevel();
             await _systemOrganizeRepository.AddAsync(org, HttpContext.RequestAborted);
         }
@@ -106,9 +106,9 @@ namespace Hotline.Api.Controllers
             if (org.Id == dto.ParentId)
                 throw UserFriendlyException.SameMessage("父级不能为自己");
             //验证名称是否重复
-            if (await _systemDomainService.IsNameRepeat(dto.OrgName, HttpContext.RequestAborted) > 1)
+            if (await _systemDomainService.IsNameRepeat(dto.Name, HttpContext.RequestAborted) > 1)
                 throw UserFriendlyException.SameMessage("组织名称重复");
-            if (await _systemDomainService.IsShrotNameRepeat(dto.OrgShortName, HttpContext.RequestAborted) > 1)
+            if (await _systemDomainService.IsShrotNameRepeat(dto.ShortName, HttpContext.RequestAborted) > 1)
                 throw UserFriendlyException.SameMessage("组织简称重复");
 
             _mapper.Map(dto, org);
@@ -132,7 +132,7 @@ namespace Hotline.Api.Controllers
 
             var exists = await _systemOrganizeRepository.AnyAsync(
                 d => d.Id != dto.Id &&
-                    (d.OrgName == dto.OrgName || d.OrgShortName == dto.OrgShortName), HttpContext.RequestAborted);
+                     (d.Name == dto.Name || d.ShortName == dto.ShortName), HttpContext.RequestAborted);
             if (exists)
                 throw UserFriendlyException.SameMessage("名称或简称已存在");
 
@@ -193,4 +193,4 @@ namespace Hotline.Api.Controllers
                 Areas = await _areaDomainService.GetAreaTree()
             };
     }
-}
+}

+ 1 - 1
src/Hotline.Api/Controllers/PushMessageController.cs

@@ -105,7 +105,7 @@ namespace Hotline.Api.Controllers
                     .Includes(it => it.Order)
                     .WhereIF(pagedDto.PushBusiness.HasValue, d => d.PushBusiness == pagedDto.PushBusiness)
                     .WhereIF(pagedDto.Status.HasValue, d => d.Status == pagedDto.Status)
-                    .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), d => d.Name.Contains(pagedDto.Keyword!) || d.Content.Contains(pagedDto.Keyword!) || d.TelNumber.Contains(pagedDto.Keyword!) || d.User.Name.Contains(pagedDto.Keyword!) || d.SystemOrganize.OrgName.Contains(pagedDto.Keyword!))
+                    .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), d => d.Name.Contains(pagedDto.Keyword!) || d.Content.Contains(pagedDto.Keyword!) || d.TelNumber.Contains(pagedDto.Keyword!) || d.User.Name.Contains(pagedDto.Keyword!) || d.SystemOrganize.Name.Contains(pagedDto.Keyword!))
                     .WhereIF(pagedDto.StartTime.HasValue, d => d.SendTime >= pagedDto.StartTime)
                     .WhereIF(pagedDto.EndTime.HasValue, d => d.SendTime <= pagedDto.EndTime)
                     .OrderByDescending(it => it.CreationTime)

+ 15 - 15
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -436,10 +436,10 @@ public class WorkflowController : BaseController
                 return orgs1.ToList();
             case EHandlerType.OrgType:
                 return EnumExts.GetDescriptions<EOrgType>().Select(d => new KeyValuePair<string, string>(d.Key.ToString(), d.Value)).ToList();
-            case EHandlerType.AssignOrg:
+            case EHandlerType.AssignedOrg:
                 var orgs = await _organizeRepository.GetOrgJson();
-                return orgs.Select(d => new KeyValuePair<string, string>(d.Id, d.OrgName)).ToList();
-            case EHandlerType.AssignUser:
+                return orgs.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
+            case EHandlerType.AssignedUser:
             default:
                 throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, null);
         }
@@ -455,7 +455,7 @@ public class WorkflowController : BaseController
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, cancellationToken: HttpContext.RequestAborted);
         workflow.Traces = await _workflowTraceRepository.Queryable()
-                .Where(d => d.WorkflowId == workflow.Id && !string.IsNullOrEmpty(d.AcceptUserId))
+                .Where(d => d.WorkflowId == workflow.Id && !string.IsNullOrEmpty(d.AcceptorId))
                 .OrderBy(d => d.CreationTime)
                 .ToTreeAsync(d => d.Traces, d => d.ParentId, null);
 
@@ -468,34 +468,34 @@ public class WorkflowController : BaseController
     /// <param name="workflowId"></param>
     /// <returns></returns>
     [HttpGet("{workflowId}/urge")]
-    public async Task<IReadOnlyList<IdName>> GetUrgeOrgs(string workflowId)
+    public async Task<IReadOnlyList<Kv>> GetUrgeOrgs(string workflowId)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, true, true, cancellationToken: HttpContext.RequestAborted);
         var stepBoxes = workflow.StepBoxes.Where(d => d.StepType != EStepType.Start && d.StepType != EStepType.End).ToList();
         var steps = stepBoxes.SelectMany(d => d.Steps);
 
-        var orgs = new List<IdName>();
+        var orgs = new List<Kv>();
         foreach (var step in steps)
         {
-            var orgCode = step.OrgCode ?? step.AcceptOrgCode;
-            var orgName = step.OrgName ?? step.AcceptOrgName;
+            var orgCode = step.HandlerOrgId ?? step.AcceptorOrgId;
+            var orgName = step.HandlerOrgName ?? step.AcceptorOrgName;
             if (string.IsNullOrEmpty(orgCode))
             {
                 switch (step.HandlerType)
                 {
                     case EHandlerType.Role:
-                    case EHandlerType.AssignUser:
+                    case EHandlerType.AssignedUser:
                         //todo
                         var users = await _userRepository.Queryable()
                             .Includes(d => d.Organization)
-                            .Where(d => step.Handlers.Select(x => x.Id).Contains(d.Id))
+                            .Where(d => step.AssignedHandlers.Select(x => x.Id).Contains(d.Id))
                             .ToListAsync(HttpContext.RequestAborted);
-                        orgs.AddRange(users.Select(d => new IdName(d.OrgId, d.Organization.OrgName)));
+                        orgs.AddRange(users.Select(d => new Kv(d.OrgId, d.Organization.Name)));
                         break;
                     case EHandlerType.OrgLevel:
                     case EHandlerType.OrgType:
-                    case EHandlerType.AssignOrg:
-                        orgs.AddRange(step.Handlers);
+                    case EHandlerType.AssignedOrg:
+                        orgs.AddRange(step.AssignedHandlers);
                         break;
                     default:
                         throw new ArgumentOutOfRangeException();
@@ -503,7 +503,7 @@ public class WorkflowController : BaseController
             }
             else
             {
-                orgs.Add(new IdName(orgCode, orgName));
+                orgs.Add(new Kv(orgCode, orgName));
             }
         }
         return orgs.DistinctBy(d => d.Id).ToList();
@@ -517,7 +517,7 @@ public class WorkflowController : BaseController
             ModuleOptions = WorkflowModuleConsts.AllModules.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)),
             HandlerTypeOptions = EnumExts.GetDescriptions<EHandlerType>(),
             CountersignMode = EnumExts.GetDescriptions<ECountersignMode>().Where(d => d.Key != 1),
-            BusinessPropertyOptions = EnumExts.GetDescriptions<EBusinessProperty>(),
+            BusinessPropertyOptions = EnumExts.GetDescriptions<EBusinessType>(),
             StepPropertiesOptions = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.WorkflowStepComponent, HttpContext.RequestAborted),
             PathPolicyOptions = EnumExts.GetDescriptions<EPathPolicy>(),
         };

+ 1 - 1
src/Hotline.Application.Contracts/Validators/FlowEngine/StartWorkflowDtoValidator.cs

@@ -21,7 +21,7 @@ namespace Hotline.Application.Contracts.Validators.FlowEngine
             //RuleFor(d => d.DefinitionModuleCode).NotEmpty();
             //RuleFor(d => d.Title).NotEmpty();
             RuleFor(d => d.NextStepCode).NotEmpty();
-            RuleFor(d => d.AcceptSms).NotNull();
+            RuleFor(d => d.IsSms).NotNull();
             RuleFor(d => d.IsStartCountersign).NotNull();
             RuleFor(d => d.Opinion)
                 .Cascade(CascadeMode.Stop)

+ 2 - 2
src/Hotline.Application.Contracts/Validators/Orgs/AddOrgDtoValidator.cs

@@ -7,8 +7,8 @@ public class AddOrgDtoValidator : AbstractValidator<AddOrgDto>
 {
     public AddOrgDtoValidator()
     {
-        RuleFor(d => d.OrgName).NotEmpty();
-        RuleFor(d => d.OrgShortName).NotEmpty();
+        RuleFor(d => d.Name).NotEmpty();
+        RuleFor(d => d.ShortName).NotEmpty();
         RuleFor(d => d.AreaCode).NotEmpty();
         RuleFor(d => d.AreaName).NotEmpty();
     }

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

@@ -1,22 +1,19 @@
-using Hotline.FlowEngine;
-using Hotline.FlowEngine.Definitions;
-using Hotline.FlowEngine.Workflows;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine;
-using Hotline.Share.Enums.FlowEngine;
-using Microsoft.AspNetCore.Mvc;
+using Hotline.Share.Dtos.FlowEngine.Definition;
 
 namespace Hotline.Application.FlowEngine
 {
     public interface IWorkflowApplication
     {
+        /// <summary>
+        /// 开始流程
+        /// </summary>
         Task StartWorkflowAsync(StartWorkflowDto dto, string externalId, CancellationToken cancellationToken);
 
         /// <summary>
         /// 流转至下一节点(节点办理)
         /// </summary>
-        /// <param name="dto"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken);
 
         /// <summary>
@@ -42,9 +39,6 @@ namespace Hotline.Application.FlowEngine
         /// <summary>
         /// 查询节点配置可选参数
         /// </summary>
-        /// <param name="stepDefine"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        Task<NextStepOptionDto> GetNextStepOptionsAsync(StepDefine stepDefine, CancellationToken cancellationToken);
+        Task<IReadOnlyList<Kv>> GetNextStepOptionsAsync(StepDefineBasic stepDefine, CancellationToken cancellationToken);
     }
 }

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

@@ -13,6 +13,7 @@ using Hotline.Settings;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.FlowEngine.Definition;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Identity;
 using Hotline.Users;
@@ -79,7 +80,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         var validator = new StartWorkflowDtoValidator();
         var validResult = validator.Validate(dto);
         if (!validResult.IsValid)
-            throw new UserFriendlyException($"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
+            throw new UserFriendlyException(
+                $"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
 
         var wfModule = await GetWorkflowModuleAsync(dto.DefinitionModuleCode, cancellationToken);
         var definition = wfModule.Definition;
@@ -90,7 +92,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
         var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(definition, dto.NextStepCode);
         if (dto.IsStartCountersign &&
-            (nextStepBoxDefine.StepType is EStepType.Summary || nextStepBoxDefine.CountersignMode is not ECountersignMode.Support))
+            (nextStepBoxDefine.StepType is EStepType.Summary ||
+             nextStepBoxDefine.CountersignMode is not ECountersignMode.Support))
             throw UserFriendlyException.SameMessage("下一节点不支持办理会签");
 
         //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选
@@ -100,7 +103,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         var workflow = await _workflowDomainService.CreateWorkflowAsync(wfModule, dto.Title,
             _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, externalId, cancellationToken);
 
-        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, dto.IsStartCountersign, dto.NextHandlers,
+            cancellationToken);
 
         await _workflowDomainService.StartAsync(workflow, dto, nextStepBoxDefine, flowAssignMode, cancellationToken);
 
@@ -113,11 +117,13 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, withCountersigns: true, cancellationToken: cancellationToken);
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, withCountersigns: true,
+            cancellationToken: cancellationToken);
         //await NextAsync(workflow, dto, dto.ExpiredTime, dto.IsStartCountersign, cancellationToken);
 
         //未超期工单,节点超期时间不能小于当前时间,不能大于流程整体超期时间
-        if (workflow.ExpiredTime > DateTime.Now && (dto.ExpiredTime <= DateTime.Now || dto.ExpiredTime > workflow.ExpiredTime))
+        if (workflow.ExpiredTime > DateTime.Now &&
+            (dto.ExpiredTime <= DateTime.Now || dto.ExpiredTime > workflow.ExpiredTime))
             throw UserFriendlyException.SameMessage("节点期满时间无效");
 
         var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
@@ -127,12 +133,15 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException("未指定节点处理者");
 
         if (dto.IsStartCountersign &&
-            (nextStepBoxDefine.StepType is EStepType.Summary || nextStepBoxDefine.CountersignMode is not ECountersignMode.Support))
+            (nextStepBoxDefine.StepType is EStepType.Summary ||
+             nextStepBoxDefine.CountersignMode is not ECountersignMode.Support))
             throw UserFriendlyException.SameMessage("下一节点不支持办理会签");
 
-        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, dto.IsStartCountersign, dto.NextHandlers,
+            cancellationToken);
 
-        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, flowAssignMode, dto.ExpiredTime, cancellationToken);
+        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, flowAssignMode, dto.ExpiredTime,
+            cancellationToken);
 
         ////更新接办部门(详情页面展示)
         //await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
@@ -143,13 +152,16 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task RecallAsync(RecallDto dto, CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+        var workflow =
+            await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true,
+                cancellationToken: cancellationToken);
 
         await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
 
         var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
         //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
-        var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers,
+            cancellationToken);
         await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, flowAssignMode, cancellationToken);
     }
 
@@ -158,14 +170,18 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task JumpAsync(RecallDto dto, CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+        var workflow =
+            await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true,
+                cancellationToken: cancellationToken);
 
         await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
 
         var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
         //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
-        var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
-        await _workflowDomainService.JumpAsync(workflow, dto, targetStepDefine, dto.IsStartCountersign, flowAssignMode, cancellationToken);
+        var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers,
+            cancellationToken);
+        await _workflowDomainService.JumpAsync(workflow, dto, targetStepDefine, dto.IsStartCountersign, flowAssignMode,
+            cancellationToken);
     }
 
     /// <summary>
@@ -173,9 +189,12 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task RedoAsync(RecallDto dto, CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+        var workflow =
+            await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true,
+                cancellationToken: cancellationToken);
         var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
-        var flowAssignInfo = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        var flowAssignInfo = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers,
+            cancellationToken);
         await _workflowDomainService.RedoAsync(workflow, dto, targetStepDefine, flowAssignInfo, cancellationToken);
     }
 
@@ -190,22 +209,17 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
         var startStep = definition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
         if (startStep == null)
-            throw new UserFriendlyException("未正确配置开始节点");
-        //var firstStepDefines = definition.FindStepDefines(startStep.NextSteps.Select(d => d.Code));
-        var firstStepDefine = definition.FindStepDefine(startStep.NextSteps.First().Code);
-        if (firstStepDefine == null)
-            throw new UserFriendlyException("未正确配置首个办理节点");
+            throw new UserFriendlyException("未正确配置发起人节点");
 
-        var secondStepDefines = definition.FindStepDefines(firstStepDefine.NextSteps.Select(d => d.Code));
-
-        if (firstStepDefine.PathPolicy is not EPathPolicy.None && firstStepDefine.NextSteps.Count > 1)
-            _workflowDomainService.NextStepDefineFilter(firstStepDefine.PathPolicy, secondStepDefines);
+        var firstStepDefines = definition.FindStepDefines(startStep.NextSteps.Select(d => d.Code));
+        if (!firstStepDefines.Any())
+            throw new UserFriendlyException("未正确配置首个办理节点");
 
         return new DefinedStepDto
         {
             Id = definition.Id,
-            Steps = _mapper.Map<IReadOnlyList<StepBasicDto>>(secondStepDefines),//secondStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList()
-            Components = firstStepDefine.Components
+            Steps = firstStepDefines
+            // Components = firstStepDefine.Components
         };
     }
 
@@ -216,63 +230,70 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// <param name="cancellationToken"></param>
     /// <returns></returns>
     /// <exception cref="ArgumentOutOfRangeException"></exception>
-    public async Task<NextStepOptionDto> GetNextStepOptionsAsync(StepDefine stepDefine, CancellationToken cancellationToken)
+    public async Task<IReadOnlyList<Kv>> GetNextStepOptionsAsync(StepDefineBasic stepDefine,
+        CancellationToken cancellationToken)
     {
-        if (stepDefine.StepType == EStepType.End) return new();
+        if (stepDefine.StepType == EStepType.End)
+            throw new UserFriendlyException("结束节点无待选项");
 
-        var handlers = new List<IdName>();
+        var handlers = new List<Kv>();
         switch (stepDefine.HandlerType)
         {
-            case EHandlerType.AssignUser:
-                var users = await _userRepository.QueryAsync(d =>
-                    !d.IsDeleted &&
-                    stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Id));
-                handlers = users.Select(d => new IdName(d.Id, d.Name)).ToList();
-                break;
-            case EHandlerType.AssignOrg:
-                var orgs = await _organizeRepository.QueryAsync(d =>
-                    d.IsEnable &&
-                    stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Id));
-                handlers = orgs.Select(d => new IdName(d.Id, d.OrgName))
-                    .ToList();
+            // case EHandlerType.AssignedUser:
+            //     var users = await _userRepository.Queryable()
+            //         .Where(d => stepDefine.HandlerTypeItems.Select(x => x.Key).Contains(d.Id))
+            //         .ToListAsync(cancellationToken);
+            //     handlers = users.Select(d => new Kv(d.Id, d.Name)).ToList();
+            //     break;
+            // case EHandlerType.AssignedOrg:
+            //     var orgs = await _organizeRepository.QueryAsync(d =>
+            //         d.IsEnable &&
+            //         stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Id));
+            //     handlers = orgs.Select(d => new Kv(d.Id, d.OrgName))
+            //         .ToList();
+            //     break;
+            case EHandlerType.AssignedUser:
+            case EHandlerType.AssignedOrg:
+                handlers = stepDefine.HandlerTypeItems;
                 break;
             case EHandlerType.Role:
                 var roles = await _roleRepository.Queryable()
-                    .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(), x => x.User)
-                    .Where(d => stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Name))
+                    .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(),
+                        x => x.User)
+                    .Where(d => stepDefine.HandlerTypeItems.Select(x => x.Key).Contains(d.Name))
                     .ToListAsync(cancellationToken);
                 var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
-                handlers = users1.Select(d => new IdName(d.Id, d.Name)).ToList();
+                handlers = users1.Select(d => new Kv(d.Id, d.Name)).ToList();
                 break;
             case EHandlerType.OrgLevel:
                 //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
                 var levels = stepDefine.HandlerTypeItems.Select(d => d.Key).Select(d => int.Parse(d));
-                var levelOneOrg = _sessionContext.RequiredOrgId.GetUpperOrgCode();
+                var levelOneOrg = _sessionContext.RequiredOrgId.GetHigherOrgCode();
                 //var orgs1 = await _organizeRepository.QueryAsync(d =>
                 //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg) &&
                 //    levels.Contains(d.OrgLevel));
 
                 var orgs1 = await _organizeRepository.Queryable()
-                    .Where(d => d.IsEnable && levels.Contains(d.OrgLevel))
+                    .Where(d => d.IsEnable && levels.Contains(d.Level))
                     .WhereIF(!levelOneOrg.IsCenter(), d => d.Id.StartsWith(levelOneOrg))
                     .ToListAsync(cancellationToken);
 
-                handlers = orgs1.Select(d => new IdName(d.Id, d.OrgName)).ToList();
+                handlers = orgs1.Select(d => new Kv(d.Id, d.Name)).ToList();
                 break;
             case EHandlerType.OrgType:
                 var types = stepDefine.HandlerTypeItems.Select(d => d.Key)
                     .Select(d => Enum.Parse<EOrgType>(d));
-                var levelOneOrg1 = _sessionContext.RequiredOrgId.GetUpperOrgCode();
+                var levelOneOrg1 = _sessionContext.RequiredOrgId.GetHigherOrgCode();
                 //var org2 = await _organizeRepository.QueryAsync(d =>
                 //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg1) &&
                 //    types.Contains(d.OrgType));
 
                 var orgs2 = await _organizeRepository.Queryable()
-                .Where(d => d.IsEnable && types.Contains(d.OrgType))
+                    .Where(d => d.IsEnable && types.Contains(d.OrgType))
                     .WhereIF(!levelOneOrg1.IsCenter(), d => d.Id.StartsWith(levelOneOrg1))
                     .ToListAsync(cancellationToken);
 
-                handlers = orgs2.Select(d => new IdName(d.Id, d.OrgName)).ToList();
+                handlers = orgs2.Select(d => new Kv(d.Id, d.Name)).ToList();
                 break;
             default:
                 throw new ArgumentOutOfRangeException();
@@ -282,8 +303,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
         if (stepDefine.Components.Contains(SysDicTypeConsts.OrderRedoReason))
         {
-            var orderRedoReasons = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.OrderRedoReason, cancellationToken);
-            dto.OrderRedoReasonOptions = orderRedoReasons.Select(d => new IdName(d.DicDataValue, d.DicDataName)).ToList();
+            var orderRedoReasons =
+                await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.OrderRedoReason,
+                    cancellationToken);
+            dto.OrderRedoReasonOptions = orderRedoReasons.Select(d => new Kv(d.DicDataValue, d.DicDataName)).ToList();
         }
 
         return dto;
@@ -292,7 +315,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// <summary>
     /// 查询指派办理人的处理方式及实际办理人
     /// </summary>
-    public async Task<FlowAssignInfo> GetFlowAssignModeAsync(StepDefine stepDefine, bool isStartCountersign, List<IdName> handlers, CancellationToken cancellationToken)
+    public async Task<FlowAssignInfo> GetFlowAssignModeAsync(StepDefine stepDefine, bool isStartCountersign,
+        List<Kv> handlers, CancellationToken cancellationToken)
     {
         if (stepDefine.StepType is EStepType.Start or EStepType.End) return new();
         switch (stepDefine.HandlerType)
@@ -304,17 +328,18 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                         .Includes(d => d.Accounts, x => x.User)
                         .Where(d => stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Name))
                         .ToListAsync(cancellationToken);
-                    handlers = roles.SelectMany(d => d.Accounts).Distinct().Select(d => new IdName(d.Id, d.User.Name)).ToList();
+                    handlers = roles.SelectMany(d => d.Accounts).Distinct().Select(d => new Kv(d.Id, d.User.Name))
+                        .ToList();
                 }
 
                 return FlowAssignInfo.Create(EFlowAssignType.User, handlers, isStartCountersign);
 
             case EHandlerType.OrgLevel:
             case EHandlerType.OrgType:
-            case EHandlerType.AssignOrg:
+            case EHandlerType.AssignedOrg:
                 return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign);
 
-            case EHandlerType.AssignUser:
+            case EHandlerType.AssignedUser:
                 return FlowAssignInfo.Create(EFlowAssignType.User, handlers, isStartCountersign);
 
             default:
@@ -344,7 +369,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// <summary>
     /// 更新接办部门
     /// </summary>
-    private async Task AddOrUpdateAssignAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, CancellationToken cancellationToken)
+    private async Task AddOrUpdateAssignAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
+        CancellationToken cancellationToken)
     {
         if (nextStepBoxDefine.StepType is EStepType.Normal)
         {
@@ -363,7 +389,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                             .Includes(d => d.Organization)
                             .Where(d => dto.NextHandlers.Select(x => x.Id).Contains(d.Id))
                             .ToListAsync(cancellationToken);
-                        assigns = users1.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Organization.OrgName))
+                        assigns = users1.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Organization.Name))
                             .ToList();
                     }
                     else
@@ -383,7 +409,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                             .Where(d => dto.NextHandlers.Select(d => d.Id).Contains(d.Name))
                             .ToListAsync(cancellationToken);
                         assigns = accounts.Select(d => d.User.Organization).Select(d =>
-                            WorkflowAssign.Create(workflow.Id, d.Id, d.OrgName)).ToList();
+                            WorkflowAssign.Create(workflow.Id, d.Id, d.Name)).ToList();
                         //}
                     }
 
@@ -391,17 +417,17 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
                 case EHandlerType.OrgLevel:
                 case EHandlerType.OrgType:
-                case EHandlerType.AssignOrg:
+                case EHandlerType.AssignedOrg:
                     assigns = dto.NextHandlers.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Name)).ToList();
                     break;
 
-                case EHandlerType.AssignUser:
+                case EHandlerType.AssignedUser:
                     //指定人所属部门
                     var users = await _userRepository.Queryable()
                         .Includes(d => d.Organization)
                         .Where(d => dto.NextHandlers.Select(x => x.Id).Contains(d.Id))
                         .ToListAsync(cancellationToken);
-                    assigns = users.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Organization.OrgName))
+                    assigns = users.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Organization.Name))
                         .ToList();
                     break;
                 default:

+ 417 - 0
src/Hotline.Application/FlowEngine/WorkflowApplication1.cs

@@ -0,0 +1,417 @@
+using Hotline.Application.Contracts.Validators.FlowEngine;
+using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
+using Hotline.FlowEngine;
+using Hotline.FlowEngine.Definitions;
+using Hotline.FlowEngine.WorkflowModules;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
+using Hotline.Orders;
+using Hotline.SeedData;
+using Hotline.Settings;
+using Hotline.Settings.TimeLimits;
+using Hotline.Share.Dtos;
+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;
+using XF.Domain.Entities;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.FlowEngine;
+
+public class WorkflowApplication1 : IWorkflowApplication, IScopeDependency
+{
+    private readonly IDefinitionDomainService _definitionDomainService;
+    private readonly IWorkflowDomainService _workflowDomainService;
+    private readonly IOrderDomainService _orderDomainService;
+    private readonly IWorkflowRepository _workflowRepository;
+    private readonly IRepository<WorkflowAssign> _workflowAssignRepository;
+    private readonly IRepository<User> _userRepository;
+    private readonly IAccountRepository _accountRepository;
+    private readonly ISystemOrganizeRepository _organizeRepository;
+    private readonly IRepository<Role> _roleRepository;
+    private readonly IWfModuleCacheManager _wfModuleCacheManager;
+    private readonly ISystemDomainService _systemDomainService;
+    private readonly IUserDomainService _userDomainService;
+    private readonly IAccountDomainService _accountDomainService;
+    private readonly ISessionContext _sessionContext;
+    private readonly IMapper _mapper;
+
+    public WorkflowApplication(
+        IDefinitionDomainService definitionDomainService,
+        IWorkflowDomainService workflowDomainService,
+        IOrderDomainService orderDomainService,
+        IWorkflowRepository workflowRepository,
+        IRepository<WorkflowAssign> workflowAssignRepository,
+        IRepository<User> userRepository,
+        IAccountRepository accountRepository,
+        ISystemOrganizeRepository organizeRepository,
+        IRepository<Role> roleRepository,
+        IWfModuleCacheManager wfModuleCacheManager,
+        ISystemDomainService systemDomainService,
+        ISessionContext sessionContext,
+        IMapper mapper)
+    {
+        _definitionDomainService = definitionDomainService;
+        _workflowDomainService = workflowDomainService;
+        _orderDomainService = orderDomainService;
+        _workflowRepository = workflowRepository;
+        _workflowAssignRepository = workflowAssignRepository;
+        _userRepository = userRepository;
+        _accountRepository = accountRepository;
+        _organizeRepository = organizeRepository;
+        _roleRepository = roleRepository;
+        _wfModuleCacheManager = wfModuleCacheManager;
+        _systemDomainService = systemDomainService;
+        _sessionContext = sessionContext;
+        _mapper = mapper;
+    }
+
+    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))}");
+
+        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 nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(definition, dto.NextStepCode);
+        if (dto.IsStartCountersign &&
+            (nextStepBoxDefine.StepType is EStepType.Summary || nextStepBoxDefine.CountersignMode is not ECountersignMode.Support))
+            throw UserFriendlyException.SameMessage("下一节点不支持办理会签");
+
+        //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选
+        if (nextStepBoxDefine.HandlerType is not EHandlerType.Role && !dto.NextHandlers.Any())
+            throw UserFriendlyException.SameMessage("未指派办理人");
+
+        var workflow = await _workflowDomainService.CreateWorkflowAsync(wfModule, dto.Title,
+            _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, externalId, cancellationToken);
+
+        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+
+        await _workflowDomainService.StartAsync(workflow, dto, nextStepBoxDefine, flowAssignMode, cancellationToken);
+
+        ////更新接办部门(详情页面展示)
+        //await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
+    }
+
+    /// <summary>
+    /// 流转至下一节点(节点办理)
+    /// </summary>
+    public async Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken)
+    {
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, withCountersigns: true, cancellationToken: cancellationToken);
+        //await NextAsync(workflow, dto, dto.ExpiredTime, dto.IsStartCountersign, cancellationToken);
+
+        //未超期工单,节点超期时间不能小于当前时间,不能大于流程整体超期时间
+        if (workflow.ExpiredTime > DateTime.Now && (dto.ExpiredTime <= DateTime.Now || dto.ExpiredTime > workflow.ExpiredTime))
+            throw UserFriendlyException.SameMessage("节点期满时间无效");
+
+        var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+
+        //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签)
+        if (nextStepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
+            throw new UserFriendlyException("未指定节点处理者");
+
+        if (dto.IsStartCountersign &&
+            (nextStepBoxDefine.StepType is EStepType.Summary || nextStepBoxDefine.CountersignMode is not ECountersignMode.Support))
+            throw UserFriendlyException.SameMessage("下一节点不支持办理会签");
+
+        var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+
+        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, flowAssignMode, dto.ExpiredTime, cancellationToken);
+
+        ////更新接办部门(详情页面展示)
+        //await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
+    }
+
+    /// <summary>
+    /// 撤回至任意节点
+    /// </summary>
+    public async Task RecallAsync(RecallDto dto, CancellationToken cancellationToken)
+    {
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+
+        await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
+
+        var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+        //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
+        var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, flowAssignMode, cancellationToken);
+    }
+
+    /// <summary>
+    /// 跳转至任意节点
+    /// </summary>
+    public async Task JumpAsync(RecallDto dto, CancellationToken cancellationToken)
+    {
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+
+        await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
+
+        var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+        //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
+        var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        await _workflowDomainService.JumpAsync(workflow, dto, targetStepDefine, dto.IsStartCountersign, flowAssignMode, cancellationToken);
+    }
+
+    /// <summary>
+    /// 重办
+    /// </summary>
+    public async Task RedoAsync(RecallDto dto, CancellationToken cancellationToken)
+    {
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
+        var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.WorkflowDefinition, dto.NextStepCode);
+        var flowAssignInfo = await GetFlowAssignModeAsync(targetStepDefine, dto.IsStartCountersign, dto.NextHandlers, cancellationToken);
+        await _workflowDomainService.RedoAsync(workflow, dto, targetStepDefine, flowAssignInfo, cancellationToken);
+    }
+
+    public async Task<DefinedStepDto> GetStartOptionsAsync(string moduleCode, CancellationToken cancellationToken)
+    {
+        var wfModule = await GetWorkflowModuleAsync(moduleCode, cancellationToken);
+        var definition = wfModule.Definition;
+        if (definition == null)
+            throw new UserFriendlyException("无效模板编码");
+        if (definition.Status is not EDefinitionStatus.Enable)
+            throw new UserFriendlyException("该模板不可用");
+
+        var startStep = definition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
+        if (startStep == null)
+            throw new UserFriendlyException("未正确配置开始节点");
+        //var firstStepDefines = definition.FindStepDefines(startStep.NextSteps.Select(d => d.Code));
+        var firstStepDefine = definition.FindStepDefine(startStep.NextSteps.First().Code);
+        if (firstStepDefine == null)
+            throw new UserFriendlyException("未正确配置首个办理节点");
+
+        var secondStepDefines = definition.FindStepDefines(firstStepDefine.NextSteps.Select(d => d.Code));
+
+        if (firstStepDefine.PathPolicy is not EPathPolicy.None && firstStepDefine.NextSteps.Count > 1)
+            _workflowDomainService.NextStepDefineFilter(firstStepDefine.PathPolicy, secondStepDefines);
+
+        return new DefinedStepDto
+        {
+            Id = definition.Id,
+            Steps = _mapper.Map<IReadOnlyList<StepBasicDto>>(secondStepDefines),//secondStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList()
+            Components = firstStepDefine.Components
+        };
+    }
+
+    /// <summary>
+    /// 根据节点配置查询待选参数
+    /// </summary>
+    /// <param name="stepDefine"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    /// <exception cref="ArgumentOutOfRangeException"></exception>
+    public async Task<NextStepOptionDto> GetNextStepOptionsAsync(StepDefine stepDefine, CancellationToken cancellationToken)
+    {
+        if (stepDefine.StepType == EStepType.End) return new();
+
+        var handlers = new List<Kv>();
+        switch (stepDefine.HandlerType)
+        {
+            case EHandlerType.AssignedUser:
+                var users = await _userRepository.QueryAsync(d =>
+                    !d.IsDeleted &&
+                    stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Id));
+                handlers = users.Select(d => new Kv(d.Id, d.Name)).ToList();
+                break;
+            case EHandlerType.AssignedOrg:
+                var orgs = await _organizeRepository.QueryAsync(d =>
+                    d.IsEnable &&
+                    stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Id));
+                handlers = orgs.Select(d => new Kv(d.Id, d.Name))
+                    .ToList();
+                break;
+            case EHandlerType.Role:
+                var roles = await _roleRepository.Queryable()
+                    .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(), x => x.User)
+                    .Where(d => stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Name))
+                    .ToListAsync(cancellationToken);
+                var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
+                handlers = users1.Select(d => new Kv(d.Id, d.Name)).ToList();
+                break;
+            case EHandlerType.OrgLevel:
+                //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
+                var levels = stepDefine.HandlerTypeItems.Select(d => d.Key).Select(d => int.Parse(d));
+                var levelOneOrg = _sessionContext.RequiredOrgId.GetHigherOrgCode();
+                //var orgs1 = await _organizeRepository.QueryAsync(d =>
+                //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg) &&
+                //    levels.Contains(d.OrgLevel));
+
+                var orgs1 = await _organizeRepository.Queryable()
+                    .Where(d => d.IsEnable && levels.Contains(d.Level))
+                    .WhereIF(!levelOneOrg.IsCenter(), d => d.Id.StartsWith(levelOneOrg))
+                    .ToListAsync(cancellationToken);
+
+                handlers = orgs1.Select(d => new Kv(d.Id, d.Name)).ToList();
+                break;
+            case EHandlerType.OrgType:
+                var types = stepDefine.HandlerTypeItems.Select(d => d.Key)
+                    .Select(d => Enum.Parse<EOrgType>(d));
+                var levelOneOrg1 = _sessionContext.RequiredOrgId.GetHigherOrgCode();
+                //var org2 = await _organizeRepository.QueryAsync(d =>
+                //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg1) &&
+                //    types.Contains(d.OrgType));
+
+                var orgs2 = await _organizeRepository.Queryable()
+                .Where(d => d.IsEnable && types.Contains(d.OrgType))
+                    .WhereIF(!levelOneOrg1.IsCenter(), d => d.Id.StartsWith(levelOneOrg1))
+                    .ToListAsync(cancellationToken);
+
+                handlers = orgs2.Select(d => new Kv(d.Id, d.Name)).ToList();
+                break;
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
+
+        var dto = new NextStepOptionDto { Handlers = handlers };
+
+        if (stepDefine.Components.Contains(SysDicTypeConsts.OrderRedoReason))
+        {
+            var orderRedoReasons = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.OrderRedoReason, cancellationToken);
+            dto.OrderRedoReasonOptions = orderRedoReasons.Select(d => new Kv(d.DicDataValue, d.DicDataName)).ToList();
+        }
+
+        return dto;
+    }
+
+    /// <summary>
+    /// 查询指派办理人的处理方式及实际办理人
+    /// </summary>
+    public async Task<FlowAssignInfo> GetFlowAssignModeAsync(StepDefine stepDefine, bool isStartCountersign, List<Kv> handlers, CancellationToken cancellationToken)
+    {
+        if (stepDefine.StepType is EStepType.Start or EStepType.End) return new();
+        switch (stepDefine.HandlerType)
+        {
+            case EHandlerType.Role:
+                if (!handlers.Any())
+                {
+                    var roles = await _roleRepository.Queryable()
+                        .Includes(d => d.Accounts, x => x.User)
+                        .Where(d => stepDefine.HandlerTypeItems.Select(d => d.Key).Contains(d.Name))
+                        .ToListAsync(cancellationToken);
+                    handlers = roles.SelectMany(d => d.Accounts).Distinct().Select(d => new Kv(d.Id, d.User.Name)).ToList();
+                }
+
+                return FlowAssignInfo.Create(EFlowAssignType.User, handlers, isStartCountersign);
+
+            case EHandlerType.OrgLevel:
+            case EHandlerType.OrgType:
+            case EHandlerType.AssignedOrg:
+                return FlowAssignInfo.Create(EFlowAssignType.Org, handlers, isStartCountersign);
+
+            case EHandlerType.AssignedUser:
+                return FlowAssignInfo.Create(EFlowAssignType.User, handlers, isStartCountersign);
+
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
+    }
+
+
+    #region private
+
+    /// <summary>
+    /// 查询流程业务模块
+    /// </summary>
+    /// <param name="code"></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;
+    }
+
+    /// <summary>
+    /// 更新接办部门
+    /// </summary>
+    private async Task AddOrUpdateAssignAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, CancellationToken cancellationToken)
+    {
+        if (nextStepBoxDefine.StepType is EStepType.Normal)
+        {
+            await _workflowAssignRepository.RemoveAsync(d =>
+                    d.WorkflowId == workflow.Id && d.OrgCode == _sessionContext.RequiredOrgId,
+                cancellationToken: cancellationToken);
+
+            var assigns = new List<WorkflowAssign>();
+            switch (nextStepBoxDefine.HandlerType)
+            {
+                case EHandlerType.Role:
+                    if (dto.NextHandlers.Any())
+                    {
+                        //选了handler,handler为userId 
+                        var users1 = await _userRepository.Queryable()
+                            .Includes(d => d.Organization)
+                            .Where(d => dto.NextHandlers.Select(x => x.Id).Contains(d.Id))
+                            .ToListAsync(cancellationToken);
+                        assigns = users1.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Organization.Name))
+                            .ToList();
+                    }
+                    else
+                    {
+                        ////如果模板配置为本部门办理,则为当前办理人orgCode,非本部门办理:属于该角色的所有用户所属部门
+                        //if (nextStepBoxDefine.OnlySelfOrg.HasValue && nextStepBoxDefine.OnlySelfOrg.Value)
+                        //{
+                        //    assigns = new List<WorkflowAssign>
+                        //    {
+                        //        WorkflowAssign.Create(workflow.Id, _sessionContext.RequiredOrgCode, _sessionContext.OrgName)
+                        //    };
+                        //}
+                        //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(cancellationToken);
+                        assigns = accounts.Select(d => d.User.Organization).Select(d =>
+                            WorkflowAssign.Create(workflow.Id, d.Id, d.Name)).ToList();
+                        //}
+                    }
+
+                    break;
+
+                case EHandlerType.OrgLevel:
+                case EHandlerType.OrgType:
+                case EHandlerType.AssignedOrg:
+                    assigns = dto.NextHandlers.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Name)).ToList();
+                    break;
+
+                case EHandlerType.AssignedUser:
+                    //指定人所属部门
+                    var users = await _userRepository.Queryable()
+                        .Includes(d => d.Organization)
+                        .Where(d => dto.NextHandlers.Select(x => x.Id).Contains(d.Id))
+                        .ToListAsync(cancellationToken);
+                    assigns = users.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Organization.Name))
+                        .ToList();
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+
+            if (assigns.Any())
+                await _workflowAssignRepository.AddRangeAsync(assigns, cancellationToken);
+        }
+    }
+
+    #endregion
+}

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

@@ -69,11 +69,11 @@ public class EndWorkflowHandler : INotificationHandler<EndWorkflowNotify>
                 break;
 
             case WorkflowModuleConsts.OrderHandle:
-                var levelOneOrg = workflow.ActualHandleOrgCode.GetUpperOrgCode();
+                var levelOneOrg = workflow.ActualHandleOrgCode.GetHigherOrgCode();
                 var org = await _orgRepository.GetAsync(d => d.Id == levelOneOrg, cancellationToken);
                 if (org is null)
                     throw new UserFriendlyException($"无效部门编码, levelOneOrg: {levelOneOrg}", "无效部门编码");
-                await _workflowDomainService.UpdateOrgLevelOneAsync(workflow, org.Id, org.OrgName, cancellationToken);
+                await _workflowDomainService.UpdateOrgLevelOneAsync(workflow, org.Id, org.Name, cancellationToken);
 
                 var order = await _orderDomainService.GetOrderAsync(workflow.ExternalId, cancellationToken);
                 order.CheckIfFiled();

+ 1 - 1
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -82,7 +82,7 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
             new(AppClaimTypes.UserPasswordChanged, account.PasswordChanged.ToString()),
             new(AppClaimTypes.DepartmentId, user.OrgId??string.Empty),
             new(AppClaimTypes.DepartmentCode, user.OrgId??string.Empty),
-            new(AppClaimTypes.DepartmentName, user.Organization?.OrgName??string.Empty),
+            new(AppClaimTypes.DepartmentName, user.Organization?.Name??string.Empty),
             new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode??string.Empty),
             new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName??string.Empty),
             new(AppClaimTypes.StaffNo, user.StaffNo),

+ 3 - 3
src/Hotline.Application/Knowledge/KnowApplication.cs

@@ -53,7 +53,7 @@ namespace Hotline.Application.Knowledge
                .LeftJoin<Hotspot>((o, cus, sys, kn, hst) => o.HotspotId == hst.Id)
                .Where((o, cus, sys, kn, hst) => o.IsDeleted == false && o.Status != EKnowledgeStatus.Revert && o.Status != EKnowledgeStatus.Drafts)
                //关键词查询标题、创建人、创建部门
-               .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), (o, cus, sys, kn, hst) => o.Title.Contains(pagedDto.Keyword!) || cus.Name.Contains(pagedDto.Keyword!) || sys.OrgName.Contains(pagedDto.Keyword!))
+               .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), (o, cus, sys, kn, hst) => o.Title.Contains(pagedDto.Keyword!) || cus.Name.Contains(pagedDto.Keyword!) || sys.Name.Contains(pagedDto.Keyword!))
                //分类
                .WhereIF(!string.IsNullOrEmpty(pagedDto.KnowledgeTypeId), (o, cus, sys, kn, hst) => o.KnowledgeTypeId == pagedDto.KnowledgeTypeId)
                //热点
@@ -76,7 +76,7 @@ namespace Hotline.Application.Knowledge
                {
                    index = SqlFunc.RowNumber($"{o.Version} desc ", $"{o.Code}"),
                    CreationName = cus.Name,
-                   CreationBMName = sys.OrgName,
+                   CreationBMName = sys.Name,
                    KnowledgeTypeName = kn.SpliceName,
                    HotspotName = hst.HotSpotFullName,
                    o.Id,
@@ -148,7 +148,7 @@ namespace Hotline.Application.Knowledge
                  .Includes(it => it.SystemOrganize)
                  .Where(p => p.DepartmentId == _sessionContext.RequiredOrgId)
                  .WhereIF(pagedDto.Status.HasValue, d => d.Status == pagedDto.Status)
-                 .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), d => d.User.Name.Contains(pagedDto.Keyword!) || d.SystemOrganize.OrgName.Contains(pagedDto.Keyword!))
+                 .WhereIF(!string.IsNullOrEmpty(pagedDto.Keyword), d => d.User.Name.Contains(pagedDto.Keyword!) || d.SystemOrganize.Name.Contains(pagedDto.Keyword!))
                  .WhereIF(pagedDto.ApplyType.HasValue, d => d.KnowledgeApplyType == pagedDto.ApplyType)
                  .WhereIF(!string.IsNullOrEmpty(pagedDto.IsOvertime) && pagedDto.IsOvertime == "0", d => d.IsOvertime == true)
                  .WhereIF(!string.IsNullOrEmpty(pagedDto.IsOvertime) && pagedDto.IsOvertime == "1", d => d.IsOvertime == false)

+ 5 - 5
src/Hotline.Application/Mappers/KnowledgeMapperConfigs.cs

@@ -11,7 +11,7 @@ public class KnowledgeMapperConfigs : IRegister
         #region Knowledge
         config.NewConfig<KnowledgeApply, KnowledgeApplyHandlePageDto>()
             .Map(d => d.CreationName, x => x.User.Name)
-            .Map(d => d.CreationOrgName, x => x.SystemOrganize.OrgName);
+            .Map(d => d.CreationOrgName, x => x.SystemOrganize.Name);
 
         config.NewConfig<Hotline.KnowledgeBase.Knowledge, KnowledgeDataDto>()
             .Map(d => d.CreationName, x => x.User.Name)
@@ -22,12 +22,12 @@ public class KnowledgeMapperConfigs : IRegister
         config.NewConfig<Hotline.KnowledgeBase.Knowledge, KnowledgeApprovalDataDto>()
             .Map(d => d.CreationName, x => x.User.Name)
             .Map(d => d.KnowledgeTypeName, x => x.KnowledgeType.SpliceName)
-            .Map(d => d.CreateBMName, x => x.SystemOrganize.OrgName);
+            .Map(d => d.CreateBMName, x => x.SystemOrganize.Name);
 
         config.NewConfig<KnowledgeWorkFlow, KnowledgeApprovalDataDto>()
             .Map(d => d.CreationName, x => x.User.Name)
             .Map(d => d.KnowledgeTypeName, x => x.Knowledge.KnowledgeType.SpliceName)
-            .Map(d => d.CreateBMName, x => x.SystemOrganize.OrgName)
+            .Map(d => d.CreateBMName, x => x.SystemOrganize.Name)
             .Map(d => d.Id, x => x.Knowledge.Id)
             .Map(d => d.Title, x => x.Knowledge.Title)
             .Map(d => d.PageView, x => x.Knowledge.PageView)
@@ -41,7 +41,7 @@ public class KnowledgeMapperConfigs : IRegister
             .Map(d => d.KnowledgeTypeName, x => x.Knowledge.KnowledgeType.SpliceName)
             .Map(d => d.HotspotName, x => x.Knowledge.HotspotType.HotSpotFullName)
             .Map(d => d.CreationName, x => x.User.Name)
-            .Map(d => d.CreationBMName, x => x.SystemOrganize.OrgName)
+            .Map(d => d.CreationBMName, x => x.SystemOrganize.Name)
             .Map(d => d.PageView, x => x.Knowledge.PageView)
             .Map(d => d.Status, x => x.Knowledge.Status)
             .Map(d => d.WorkFlowApplyStatus, x => x.WorkFlowApplyStatus);
@@ -50,7 +50,7 @@ public class KnowledgeMapperConfigs : IRegister
             .Map(d => d.CategoryCode, x => x.HotspotType.ProvinceCode)
             .Map(d => d.CategoryName, x => x.HotspotType.HotSpotName)
             .Map(d => d.PublishPerson, x => x.User.Name)
-            .Map(d => d.OuName, x => x.SystemOrganize.OrgName)
+            .Map(d => d.OuName, x => x.SystemOrganize.Name)
             .Map(d => d.KName, x => x.Title)
             .Map(d => d.KContent, x => x.Content)
             .Map(d => d.CreatDate, x => x.OnShelfTime)

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

@@ -19,7 +19,7 @@ namespace Hotline.Application.Mappers
 
             config.ForType<Message, MessageDataDto>()
                .Map(d => d.SendUser, x => x.User.Name)
-               .Map(d => d.SendOrganize, x => x.SystemOrganize.OrgName)
+               .Map(d => d.SendOrganize, x => x.SystemOrganize.Name)
                .Map(d => d.OrderId, x => x.Order.Id)
                .Map(d => d.OrderNo, x => x.Order.No);
 

+ 4 - 4
src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs

@@ -16,15 +16,15 @@ public class WorkflowMapperConfigs : IRegister
 
         config.ForType<WorkflowStep, WorkflowStep>()
             .Ignore(d => d.Id)
-            .Ignore(d => d.PreviousId)
+            .Ignore(d => d.PreviousStepId)
             .Ignore(d => d.IsMain)
             .Ignore(d => d.Status)
             .Ignore(d => d.ParentId)
-            .Ignore(d => d.Handlers)
+            .Ignore(d => d.AssignedHandlers)
             .Ignore(d => d.Steps)
             .Ignore(d => d.StartCountersignId)
             .Ignore(d => d.CountersignId)
-            .Ignore(d => d.IsStartedCountersignComplete)
+            .Ignore(d => d.IsStartedCountersignSummary)
             ;
 
         config.ForType<WorkflowStep, WorkflowTrace>()
@@ -58,7 +58,7 @@ public class WorkflowMapperConfigs : IRegister
             .Map(d => d.NextHandlers, s => s.NextHandlers)
             .Map(d => d.NextMainHandler, s => s.NextMainHandler)
             .Map(d => d.NextStepCode, s => s.NextStepCode)
-            .Map(d => d.AcceptSms, s => s.AcceptSms)
+            .Map(d => d.IsSms, s => s.IsSms)
             .Map(d => d.Opinion, s => s.Opinion)
             .Map(d => d.Additions, s => s.Additions)
             .IgnoreNonMapped(true)

+ 7 - 7
src/Hotline.Share/Dtos/FlowEngine/BasicWorkflowDto.cs

@@ -16,12 +16,12 @@ public class BasicWorkflowDto : EndWorkflowDto
     /// 部门等级/分类为:depCodes, 角色为:userIds
     /// </example>
     /// </summary>
-    public List<IdName> NextHandlers { get; set; } = new();
+    public List<Kv> NextHandlers { get; set; } = new();
 
     /// <summary>
     /// 是否短信通知
     /// </summary>
-    public bool AcceptSms { get; set; }
+    public bool IsSms { get; set; }
 
     /// <summary>
     /// 下一节点主办
@@ -31,10 +31,10 @@ public class BasicWorkflowDto : EndWorkflowDto
     /// <summary>
     /// 发起会签
     /// </summary>
-    public bool IsStartCountersign { get; set; } = false;
+    public bool IsStartCountersign { get; set; }
 
-    /// <summary>
-    /// 扩展信息
-    /// </summary>
-    public StepExtension Extension { get; set; } = new();
+    // /// <summary>
+    // /// 扩展信息
+    // /// </summary>
+    // public StepExtension Extension { get; set; } = new();
 }

+ 9 - 8
src/Hotline.Share/Dtos/FlowEngine/DefinedStepDto.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Share.Dtos.FlowEngine.Definition;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Settings;
 using XF.Utility.EnumExtensions;
@@ -16,16 +17,16 @@ namespace Hotline.Share.Dtos.FlowEngine
     {
         public string Id { get; set; }
 
-        public IReadOnlyList<StepBasicDto> Steps { get; set; }
+        public IReadOnlyList<StepDefineBasic> Steps { get; set; }
 
         public DateTime? ExpiredTime { get; set; }
 
-        public IReadOnlyList<string> Components { get; set; }
-
-        public IEnumerable<KeyValuePair<int, string>> CommunicationModeOptions =>
-            EnumExts.GetDescriptions<ERealCommunicationMode>();
-
-        public IEnumerable<KeyValuePair<int, string>> TimeTypeOptions =>
-            EnumExts.GetDescriptions<ETimeType>();
+        // public IReadOnlyList<string> Components { get; set; }
+        //
+        // public IEnumerable<KeyValuePair<int, string>> CommunicationModeOptions =>
+        //     EnumExts.GetDescriptions<ERealCommunicationMode>();
+        //
+        // public IEnumerable<KeyValuePair<int, string>> TimeTypeOptions =>
+        //     EnumExts.GetDescriptions<ETimeType>();
     }
 }

+ 24 - 16
src/Hotline.Share/Dtos/FlowEngine/Definition/StepDefineBasic.cs

@@ -25,9 +25,9 @@ namespace Hotline.Share.Dtos.FlowEngine.Definition
         public EStepType StepType { get; init; }
 
         /// <summary>
-        /// 业务属性
+        /// 节点业务类型
         /// </summary>
-        public EBusinessProperty BusinessProperty { get; set; }
+        public EBusinessType BusinessType { get; set; }
 
         /// <summary>
         /// 办理者类型
@@ -40,27 +40,33 @@ namespace Hotline.Share.Dtos.FlowEngine.Definition
         /// 根据类型可能为:roles, orgLevels, orgTypes, orgCodes, userIds
         /// </example>
         /// </summary>
-        public List<KeyValuePair<string, string>> HandlerTypeItems { get; set; } = new();
+        public List<Kv> HandlerTypeItems { get; set; } = new();
 
         /// <summary>
-        /// 是否支持发起会签
+        /// 是否有否决按钮
         /// </summary>
-        public bool CanStartCountersign { get; set; }
+        public bool CanReject { get; set; }
 
         /// <summary>
-        /// 会签策略
+        /// 执行模式(自动与否)
         /// </summary>
-        public ECountersignPolicy? CountersignPolicy { get; set; }
+        public EExecuteMode ExecuteMode { get; set; }
+        
+        #region 会签
 
         /// <summary>
-        /// 是否有否决按钮
+        /// 是否支持发起会签(即使支持发起,当下一节点为汇总或结束节点时亦不可发起)
         /// </summary>
-        public bool CanReject { get; set; }
+        public bool CanStartCountersign { get; set; }
 
         /// <summary>
-        /// 执行模式(自动与否)
+        /// 会签策略
         /// </summary>
-        public EExecuteMode ExecuteMode { get; set; }
+        public ECountersignPolicy? CountersignPolicy { get; set; }
+
+        #endregion
+        
+        #region 实例化方式
 
         /// <summary>
         /// 实例化模式
@@ -68,17 +74,19 @@ namespace Hotline.Share.Dtos.FlowEngine.Definition
         public EInstanceMode InstanceMode { get; set; }
 
         /// <summary>
-        /// 循环策略(loop才有)
+        /// 重复实例化策略(多次模式才有)
         /// </summary>
-        public ELoopPolicy? LoopPolicy { get; set; }
+        public ERepeatPolicy? RepeatPolicy { get; set; }
 
         /// <summary>
-        /// 到此标记loop终止(loop才有)
+        /// 到此标记终止重复实例化(多次模式才有)
         /// <remarks>
-        /// 按直属部门循环既保存orgId
+        /// 按直属部门重复既保存orgId
         /// </remarks>
         /// </summary>
-        public string? TerminalLoopTag { get; set; }
+        public string? TerminalMark { get; set; }
+
+        #endregion
 
         /// <summary>
         /// 标签

+ 3 - 3
src/Hotline.Share/Dtos/FlowEngine/NextStepOptionDto.cs

@@ -12,13 +12,13 @@ namespace Hotline.Share.Dtos.FlowEngine
     public class NextStepOptionDto
     {
         /// <summary>
-        /// 办理人/部门(待选项)
+        /// 办理对象:人/部门(待选项)
         /// </summary>
-        public IReadOnlyList<IdName> Handlers { get; set; }
+        public IReadOnlyList<Kv> Handlers { get; set; }
 
         /// <summary>
         /// 工单重办理由待选项,对应code:OrderRedoReason
         /// </summary>
-        public IReadOnlyList<IdName> OrderRedoReasonOptions { get; set; }
+        public IReadOnlyList<Kv> OrderRedoReasonOptions { get; set; }
     }
 }

+ 2 - 2
src/Hotline.Share/Dtos/FlowEngine/StepBasicDto.cs

@@ -21,7 +21,7 @@ namespace Hotline.Share.Dtos.FlowEngine
         /// <summary>
         /// 业务属性
         /// </summary>
-        public EBusinessProperty BusinessProperty { get; set; }
+        public EBusinessType BusinessType { get; set; }
 
         /// <summary>
         /// 办理者分类(或是直接保存办理者)
@@ -29,7 +29,7 @@ namespace Hotline.Share.Dtos.FlowEngine
         /// 根据类型可能为:roles, orgLevels, orgTypes, orgCodes, userIds
         /// </example>
         /// </summary>
-        public List<IdName> HandlerClassifies { get; set; } = new();
+        public List<Kv> HandlerClassifies { get; set; } = new();
 
         /// <summary>
         /// 会签模式

+ 3 - 3
src/Hotline.Share/Dtos/FlowEngine/WorkflowStepDto.cs

@@ -30,7 +30,7 @@ public class WorkflowStepDto
     /// 根据类型可能为:roles, orgLevels, orgTypes, orgCodes, userIds
     /// </example>
     /// </summary>
-    public List<IdName> HandlerClassifies { get; set; } = new();
+    public List<Kv> HandlerClassifies { get; set; } = new();
 
     /// <summary>
     /// 会签模式
@@ -43,7 +43,7 @@ public class WorkflowStepDto
     /// 被指派办理对象(依据不同指派方式可能为:orgCode或userId),该字段subStep才会存在,stepBox不存在
     /// 采用list类型,兼容多个办理对象可以办理同一个节点的场景
     /// </summary>
-    public List<IdName> Handlers { get; set; } = new();
+    public List<Kv> Handlers { get; set; } = new();
 
     #region 办理参数
 
@@ -53,7 +53,7 @@ public class WorkflowStepDto
     /// 部门等级/分类为:orgCodes, 角色为:userIds
     /// </example>
     /// </summary>
-    public List<IdName> NextHandlers { get; set; } = new();
+    public List<Kv> NextHandlers { get; set; } = new();
 
     /// <summary>
     /// 下一节点主办,(NextHandlers其中一个, 如果不是会签则只有一个)

+ 0 - 18
src/Hotline.Share/Dtos/IdName.cs

@@ -1,18 +0,0 @@
-namespace Hotline.Share.Dtos;
-
-public class IdName
-{
-    public IdName()
-    {
-
-    }
-
-    public IdName(string id, string name)
-    {
-        Id = id;
-        Name = name;
-    }
-
-    public string Id { get; set; }
-    public string Name { get; set; }
-}

+ 20 - 0
src/Hotline.Share/Dtos/Kv.cs

@@ -0,0 +1,20 @@
+namespace Hotline.Share.Dtos;
+
+/// <summary>
+/// key-value:string,string
+/// </summary>
+public struct Kv
+{
+    public Kv()
+    {
+    }
+
+    public Kv(string key, string value)
+    {
+        Key = key;
+        Value = value;
+    }
+
+    public string Key { get; set; }
+    public string Value { get; set; }
+}

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

@@ -33,7 +33,7 @@ public class OrderComplainDto : OrderExtensionDto
     /// <summary>
     /// 诉求类型,多选
     /// </summary>
-    public List<IdName> ComplainTypes { get; set; } = new();
+    public List<Kv> ComplainTypes { get; set; } = new();
 
     #endregion
 }

+ 7 - 7
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -564,7 +564,7 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 回访部门
         /// </summary>
-        public List<IdName> IdNames { get; set; }
+        public List<Kv> IdNames { get; set; }
 
     }
 
@@ -638,12 +638,12 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 实际办理部门名称
         /// </summary>
-        public IdName ActualHandleOrgName { get; set; }
+        public Kv ActualHandleOrgName { get; set; }
 
         /// <summary>
         /// 需回访部门
         /// </summary>
-        public List<IdName>? idNames { get; set; }
+        public List<Kv>? idNames { get; set; }
     }
 
 
@@ -677,7 +677,7 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 需回访部门
         /// </summary>
-        public List<IdName> IdNames { get; set; }
+        public List<Kv> IdNames { get; set; }
 
         #region 省工单使用
 
@@ -805,17 +805,17 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 部门办件结果
         /// </summary>
-        public IdName? OrgProcessingResults { get; set; }
+        public Kv? OrgProcessingResults { get; set; }
 
         /// <summary>
         /// 不满意原因
         /// </summary>
-        public List<IdName>? OrgNoSatisfiedReason { get; set; }
+        public List<Kv>? OrgNoSatisfiedReason { get; set; }
 
         /// <summary>
         /// 部门办件态度
         /// </summary>
-        public IdName? OrgHandledAttitude { get; set; }
+        public Kv? OrgHandledAttitude { get; set; }
 
         /// <summary>
         /// 回访内容

+ 3 - 3
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -183,17 +183,17 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 部门办件结果
         /// </summary>
-        public IdName? OrgProcessingResults { get; set; }
+        public Kv? OrgProcessingResults { get; set; }
 
         /// <summary>
         /// 不满意原因
         /// </summary>
-        public List<IdName>? OrgNoSatisfiedReason { get; set; }
+        public List<Kv>? OrgNoSatisfiedReason { get; set; }
 
         /// <summary>
         /// 部门办件态度
         /// </summary>
-        public IdName? OrgHandledAttitude { get; set; }
+        public Kv? OrgHandledAttitude { get; set; }
 
         /// <summary>
         /// 回访内容

+ 2 - 2
src/Hotline.Share/Dtos/Org/OrgDto.cs

@@ -8,12 +8,12 @@ namespace Hotline.Share.Dtos.Org
         /// <summary>
         /// 组织架构名称
         /// </summary>
-        public string OrgName { get; set; }
+        public string Name { get; set; }
 
         /// <summary>
         /// 组织架构简称
         /// </summary>
-        public string OrgShortName { get; set; }
+        public string ShortName { get; set; }
 
         /// <summary>
         /// 上级ID

+ 1 - 1
src/Hotline.Share/Enums/FlowEngine/EBusinessProperty.cs → src/Hotline.Share/Enums/FlowEngine/EBusinessType.cs

@@ -2,7 +2,7 @@
 
 namespace Hotline.Share.Enums.FlowEngine;
 
-public enum EBusinessProperty
+public enum EBusinessType
 {
     /// <summary>
     /// 中心节点

+ 2 - 8
src/Hotline.Share/Enums/FlowEngine/EHandlerType.cs

@@ -26,17 +26,11 @@ public enum EHandlerType
     /// 指定用户
     /// </summary>
     [Description("指定用户")]
-    AssignUser = 3,
+    AssignedUser = 3,
 
     /// <summary>
     /// 指定部门
     /// </summary>
     [Description("指定部门")]
-    AssignOrg = 4,
-
-    ///// <summary>
-    ///// 自定义(用户)
-    ///// </summary>
-    //[Description("自定义")]
-    //Custom = 5,
+    AssignedOrg = 4,
 }

+ 2 - 2
src/Hotline.Share/Enums/FlowEngine/EInstanceMode.cs

@@ -10,6 +10,6 @@ public enum EInstanceMode
     [Description("单次")]
     Single = 0,
 
-    [Description("循环")]
-    Loop = 1
+    [Description("多次")]
+    Multiple = 1
 }

+ 1 - 1
src/Hotline.Share/Enums/FlowEngine/ELoopPolicy.cs → src/Hotline.Share/Enums/FlowEngine/ERepeatPolicy.cs

@@ -5,7 +5,7 @@ namespace Hotline.Share.Enums.FlowEngine;
 /// <summary>
 /// 实例化循环策略
 /// </summary>
-public enum ELoopPolicy
+public enum ERepeatPolicy
 {
     /// <summary>
     /// 直属上级部门(中心作为顶级部门)

+ 1 - 1
src/Hotline.Share/Enums/FlowEngine/EStepType.cs

@@ -8,7 +8,7 @@ public enum EStepType
     Normal = 0,
 
     /// <summary>
-    /// 开始节点
+    /// 开始节点(发起人)
     /// </summary>
     Start = 1,
 

+ 2 - 2
src/Hotline/FlowEngine/Definitions/StepDefine.cs

@@ -11,9 +11,9 @@ public class StepDefine : StepDefineBasic
 {
     #region method
 
-    public bool IsCenter() => BusinessProperty is EBusinessProperty.Center or EBusinessProperty.Send;
+    public bool IsCenter() => BusinessType is EBusinessType.Center or EBusinessType.Send;
 
-    public bool IsOrg() => BusinessProperty is EBusinessProperty.Department;
+    public bool IsOrg() => BusinessType is EBusinessType.Department;
 
     #endregion
 }

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

@@ -22,7 +22,7 @@ public class FlowAssignInfo
     /// <param name="flowAssignType"></param>
     /// <param name="handlers"></param>
     /// <returns></returns>
-    public static FlowAssignInfo Create(EFlowAssignType flowAssignType, List<IdName> handlers, bool multiGroups = false)
+    public static FlowAssignInfo Create(EFlowAssignType flowAssignType, List<Kv> handlers, bool multiGroups = false)
     {
         if (handlers == null || !handlers.Any())
             return new();

+ 1 - 1
src/Hotline/FlowEngine/Workflows/EStepCountersignStatus.cs

@@ -11,7 +11,7 @@ public enum EStepCountersignStatus
     None = 0,
 
     /// <summary>
-    /// 会签(上级节点直接开启会签)
+    /// 办理会签(上级节点直接开启会签)
     /// </summary>
     InCountersign = 1,
 

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

@@ -88,7 +88,7 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 查询待回访部门
         /// </summary>
-        Task<(IdName, IReadOnlyList<IdName>)> GetUnvisitOrgsAsync(string workflowId,
+        Task<(Kv, IReadOnlyList<Kv>)> GetUnvisitOrgsAsync(string workflowId,
             CancellationToken cancellationToken);
 
         /// <summary>

+ 103 - 91
src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs

@@ -11,6 +11,8 @@ public abstract class StepBasicEntity : CreationEntity
 {
     public string WorkflowId { get; set; }
 
+    #region Difinition
+
     public string Name { get; set; }
 
     /// <summary>
@@ -21,181 +23,191 @@ public abstract class StepBasicEntity : CreationEntity
     public EStepType StepType { get; init; }
 
     /// <summary>
-    /// 业务属性
+    /// 节点业务类型
     /// </summary>
-    [SugarColumn(DefaultValue = "0")]
-    public EBusinessProperty BusinessProperty { get; set; }
+    public EBusinessType BusinessType { get; set; }
 
     /// <summary>
     /// 办理人类型
     /// </summary>
     public EHandlerType HandlerType { get; set; }
-
+    
     /// <summary>
-    /// 审批者分类(或是直接保存处理者)
-    /// <example>
-    /// 根据类型可能为:roles, depLevels, depTypes, depCodes, userIds
-    /// </example>
+    /// 是否有否决按钮
     /// </summary>
-    [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<IdName> HandlerClassifies { get; set; } = new();
+    public bool CanReject { get; set; }
+    
+    #endregion
 
-    /// <summary>
-    /// 当前环节会签模式
-    /// </summary>
-    public ECountersignMode CountersignMode { get; set; }
+    // /// <summary>
+    // /// 审批者分类(或是直接保存处理者)
+    // /// <example>
+    // /// 根据类型可能为:roles, depLevels, depTypes, depCodes, userIds
+    // /// </example>
+    // /// </summary>
+    // [SugarColumn(ColumnDataType = "json", IsJson = true)]
+    // public List<IdName> HandlerClassifies { get; set; } = new();
+
+    // /// <summary>
+    // /// 当前环节会签模式
+    // /// </summary>
+    // public ECountersignMode CountersignMode { get; set; }
 
     /// <summary>
-    /// 被指派办理对象(依据不同指派方式可能为:orgCode或userId),该字段subStep才会存在,stepBox不存在
+    /// 该节点指派办理对象(依据不同指派方式可能为:orgId或userId),该字段subStep才会存在,stepBox不存在
     /// 采用list类型,兼容多个办理对象可以办理同一个节点的场景
     /// </summary>
     [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<IdName> Handlers { get; set; } = new();
-
-    #region 办理参数
+    public List<Kv> AssignedHandlers { get; set; } = new();
+
+    // /// <summary>
+    // /// 办理时间限制(如:24小时、7个工作日)
+    // /// </summary>
+    // public string? TimeLimit { get; set; }
+
+    // /// <summary>
+    // /// 节点属性配置
+    // /// </summary>
+    // [SugarColumn(ColumnDataType = "json", IsJson = true)]
+    // public List<string> Components { get; set; } = new();
+    //
+    // /// <summary>
+    // /// 扩展信息
+    // /// </summary>
+    // [SugarColumn(ColumnDataType = "json", IsJson = true)]
+    // public StepExtension Extension { get; set; } = new();
+    //
+    // /// <summary>
+    // /// 自动路径策略
+    // /// </summary>
+    // [SugarColumn(DefaultValue = "0")]
+    // public EPathPolicy PathPolicy { get; set; }
+    
+    #region 接办
 
     /// <summary>
-    /// (下一节点办理人)根据审批者类型不同,此字段为不同内容
-    /// <example>
-    /// 部门等级/分类为:orgCodes, 角色为:userIds
-    /// </example>
+    /// 接办人
     /// </summary>
-    [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<IdName> NextHandlers { get; set; } = new();
+    public string? AcceptorId { get; set; }
 
-    /// <summary>
-    /// 下一节点主办,(NextHandlers其中一个, 如果不是会签则只有一个)
-    /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? NextMainHandler { get; set; }
+    public string? AcceptorName { get; set; }
 
     /// <summary>
-    /// 下一节点code(stepBox无值)
-    /// </summary>
-    public string NextStepCode { get; set; }
-
-    /// <summary>
-    /// 是否短信通知
+    /// 接办人部门code
     /// </summary>
-    public bool AcceptSms { get; set; }
+    public string? AcceptorOrgId { get; set; }
 
+    public string? AcceptorOrgName { get; set; }
+    
     /// <summary>
-    /// 办理意见
+    /// 接办人部门行政区划代码
     /// </summary>
-    [SugarColumn(Length = 2000)]
-    public string Opinion { get; set; }
+    public string? AcceptorOrgAreaCode { get; set; }
 
     /// <summary>
-    /// 附件
+    /// 接办人部门行政区划名称
     /// </summary>
-    [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<string> Additions { get; set; } = new();
+    public string? AcceptorOrgAreaName { get; set; }
 
     /// <summary>
-    /// 当前节点到期时间(期满时间)
+    /// 接办时间
     /// </summary>
-    public DateTime ExpiredTime { get; set; }
+    public DateTime? AcceptTime { get; set; }
 
     #endregion
-
-    /// <summary>
-    /// 办理时间限制(如:24小时、7个工作日)
-    /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? TimeLimit { get; set; }
-
-    /// <summary>
-    /// 节点属性配置
-    /// </summary>
-    [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<string> Components { get; set; } = new();
-
-    /// <summary>
-    /// 扩展信息
-    /// </summary>
-    [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public StepExtension Extension { get; set; } = new();
-
-    /// <summary>
-    /// 自动路径策略
-    /// </summary>
-    [SugarColumn(DefaultValue = "0")]
-    public EPathPolicy PathPolicy { get; set; }
-
+    
     #region 办理
 
     /// <summary>
     /// 办理人
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? UserId { get; set; }
+    public string? HandlerId { get; set; }
 
     [SugarColumn(IsNullable = true)]
-    public string? UserName { get; set; }
+    public string? HandlerName { get; set; }
 
     /// <summary>
     /// 办理人部门code
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? OrgCode { get; set; }
+    public string? HandlerOrgId { get; set; }
 
     /// <summary>
     /// 办理人部门名称
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? OrgName { get; set; }
+    public string? HandlerOrgName { get; set; }
 
     /// <summary>
     /// 办理人部门行政区划代码
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? OrgAreaCode { get; set; }
+    public string? HandlerOrgAreaCode { get; set; }
 
     /// <summary>
     /// 办理人部门行政区划名称
     /// </summary>
     [SugarColumn(IsNullable = true)]
-    public string? OrgAreaName { get; set; }
+    public string? HandlerOrgAreaName { get; set; }
 
     /// <summary>
     /// 办理完成时间
     /// </summary>
-    public DateTime? CompleteTime { get; set; }
+    public DateTime? HandleTime { get; set; }
 
     #endregion
+    
+    #region 办理参数
 
-    #region 接办
+    /// <summary>
+    /// (下一节点办理人)根据审批者类型不同,此字段为不同内容
+    /// <example>
+    /// 部门等级/分类为:orgCodes, 角色为:userIds
+    /// </example>
+    /// </summary>
+    [SugarColumn(ColumnDataType = "json", IsJson = true)]
+    public List<Kv> NextHandlers { get; set; } = new();
 
     /// <summary>
-    /// 接办人
+    /// 下一节点主办,(NextHandlers其中一个, 如果不是会签则只有一个)
     /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? AcceptUserId { get; set; }
+    public string? NextMainHandler { get; set; }
 
-    [SugarColumn(IsNullable = true)]
-    public string? AcceptUserName { get; set; }
+    /// <summary>
+    /// 下一节点code(stepBox无值)
+    /// </summary>
+    public string NextStepCode { get; set; }
 
     /// <summary>
-    /// 接办人部门code
+    /// 是否短信通知
     /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? AcceptOrgCode { get; set; }
+    public bool IsSms { get; set; }
 
-    [SugarColumn(IsNullable = true)]
-    public string? AcceptOrgName { get; set; }
+    /// <summary>
+    /// 办理意见
+    /// </summary>
+    [SugarColumn(Length = 2000)]
+    public string Opinion { get; set; }
 
     /// <summary>
-    /// 接办时间
+    /// 附件
     /// </summary>
-    public DateTime? AcceptTime { get; set; }
+    [SugarColumn(ColumnDataType = "json", IsJson = true)]
+    public List<string> Additions { get; set; } = new();
+
+    /// <summary>
+    /// 当前节点到期时间(期满时间)
+    /// </summary>
+    public DateTime ExpiredTime { get; set; }
 
     #endregion
 
     #region method
 
-    public bool IsCenter() => BusinessProperty is EBusinessProperty.Center or EBusinessProperty.Send;
+    public bool IsCenter() => BusinessType is EBusinessType.Center or EBusinessType.Send;
 
-    public bool IsOrg() => BusinessProperty is EBusinessProperty.Department;
+    public bool IsOrg() => BusinessType is EBusinessType.Department;
 
     #endregion
 }

+ 8 - 5
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -36,15 +36,18 @@ public partial class Workflow : CreationEntity
     /// </summary>
     public DateTime ExpiredTime { get; set; }
 
+    #region 办理时限
+
     /// <summary>
     /// 办理时间限制(如:24小时、7个工作日)
     /// </summary>
     [SugarColumn(IsNullable = true)]
     public string? TimeLimitText { get; set; }
-
     public int? TimeLimit { get; set; }
 
     public ETimeType? TimeLimitUnit { get; set; } = ETimeType.WorkDay;
+    
+    #endregion
 
     /// <summary>
     /// end节点办理完成时间
@@ -539,7 +542,7 @@ public partial class Workflow
         RemoveCurrentHandleGroup(handlerId, handlerOrg);
 
         var groupId = Guid.NewGuid().ToString();
-        var handlerObjects = prevStep.Handlers.Select(d => new HandlerGroupItem
+        var handlerObjects = prevStep.AssignedHandlers.Select(d => new HandlerGroupItem
         {
             GroupId = groupId,
             Id = d.Id,
@@ -549,12 +552,12 @@ public partial class Workflow
         {
             case EHandlerType.OrgLevel:
             case EHandlerType.OrgType:
-            case EHandlerType.AssignOrg:
+            case EHandlerType.AssignedOrg:
                 HandlerOrgs.AddRange(handlerObjects);
                 HandlerOrgs = HandlerOrgs.Distinct().ToList();
                 break;
             case EHandlerType.Role:
-            case EHandlerType.AssignUser:
+            case EHandlerType.AssignedUser:
                 HandlerUsers.AddRange(handlerObjects);
                 HandlerUsers = HandlerUsers.Distinct().ToList();
                 break;
@@ -689,7 +692,7 @@ public partial class Workflow
 /// <summary>
 /// 办理对象分组(以办理step分组,多人办理一个step为一组)
 /// </summary>
-public class HandlerGroupItem : IdName
+public class HandlerGroupItem : Kv
 {
     public string GroupId { get; set; }
 }

+ 5 - 4
src/Hotline/FlowEngine/Workflows/WorkflowCountersign.cs

@@ -25,7 +25,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 业务属性
         /// </summary>
         [SugarColumn(DefaultValue = "0")]
-        public EBusinessProperty StartStepBusiProperty { get; set; }
+        public EBusinessType StartStepBusiType { get; set; }
 
         /// <summary>
         /// 会签汇总节点id
@@ -41,7 +41,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 业务属性
         /// </summary>
         [SugarColumn(DefaultValue = "0")]
-        public EBusinessProperty EndStepBusiProperty { get; set; }
+        public EBusinessType EndStepBusiType { get; set; }
 
         /// <summary>
         /// 会签结束时间
@@ -77,11 +77,12 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         /// <param name="endStepId"></param>
         /// <param name="endStepCode"></param>
-        public void Complete(string endStepId, string endStepCode, EBusinessProperty businessProperty)
+        /// <param name="businessType"></param>
+        public void Complete(string endStepId, string endStepCode, EBusinessType businessType)
         {
             EndStepId = endStepId;
             EndStepCode = endStepCode;
-            EndStepBusiProperty = businessProperty;
+            EndStepBusiType = businessType;
             CompleteTime = DateTime.Now;
         }
 

+ 52 - 52
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -99,7 +99,7 @@ namespace Hotline.FlowEngine.Workflows
             //1.创建开始节点trace 2.创建firstStep(开始节点的下一个节点),办理firstStep 3.创建sec节点
             //办理firstStep
             var firstStep = firstStepBox.Steps.First();
-            var counterSignType = GetCounterSignType(firstStep.BusinessProperty);
+            var counterSignType = GetCounterSignType(firstStep.BusinessType);
 
             await HandleStepAsync(workflow, dto, firstStepBox, firstStep, counterSignType, cancellationToken);
 
@@ -216,15 +216,15 @@ namespace Hotline.FlowEngine.Workflows
             var (currentStepBox, currentStep) = GetUnCompleteStepOrDefault(workflow.StepBoxes, orgCode, userId);
             if (currentStep is null) return;
             if (currentStep.Status is EWorkflowStepStatus.Handling) return;
-            if (currentStep.HandlerType is EHandlerType.AssignUser or EHandlerType.Role)
+            if (currentStep.HandlerType is EHandlerType.AssignedUser or EHandlerType.Role)
             {
                 //userId
-                if (currentStep.Handlers.All(d => d.Id != userId)) return;
+                if (currentStep.AssignedHandlers.All(d => d.Id != userId)) return;
             }
             else
             {
                 //orgId
-                if (currentStep.Handlers.All(d => d.Id != orgCode)) return;
+                if (currentStep.AssignedHandlers.All(d => d.Id != orgCode)) return;
             }
             if (currentStep.StepType is EStepType.End)
                 throw new UserFriendlyException("当前流程已流转到最终步骤");
@@ -270,7 +270,7 @@ namespace Hotline.FlowEngine.Workflows
 
             var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
 
-            var counterSignType = GetCounterSignType(currentStep.BusinessProperty);
+            var counterSignType = GetCounterSignType(currentStep.BusinessType);
 
             await HandleStepAsync(workflow, dto, currentStepBox, currentStep, counterSignType, cancellationToken);
 
@@ -306,7 +306,7 @@ namespace Hotline.FlowEngine.Workflows
                     updateSteps.Add(countersignStartStep);
 
                     //结束会签
-                    currentCountersign.Complete(currentStep.Id, currentStep.Code, currentStep.BusinessProperty);
+                    currentCountersign.Complete(currentStep.Id, currentStep.Code, currentStep.BusinessType);
                     await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
                 }
             }
@@ -357,7 +357,7 @@ namespace Hotline.FlowEngine.Workflows
                 //(当前办理节点所处同一会签内的所有step全都办理完成并且如果开启了会签的step,必须会签结束)
                 var unComplete = steps.Any(d =>
                     d.Status != EWorkflowStepStatus.Handled ||
-                    (d.HasStartedCountersign && !(d.IsStartedCountersignComplete ?? false)));
+                    (d.HasStartedCountersign && !(d.IsStartedCountersignSummary ?? false)));
                 nextStepCanHandle = !unComplete;
             }
 
@@ -428,10 +428,10 @@ namespace Hotline.FlowEngine.Workflows
                 throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
 
             //find prevStep, update handler
-            var prevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == currentStepBox.PreviousId);
+            var prevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == currentStepBox.PreviousStepId);
             if (prevStepBox == null)
                 throw UserFriendlyException.SameMessage("未查询到上级节点");
-            var prevStep = prevStepBox.Steps.FirstOrDefault(d => d.Id == currentStep.PreviousId);
+            var prevStep = prevStepBox.Steps.FirstOrDefault(d => d.Id == currentStep.PreviousStepId);
             if (prevStep == null)
                 throw UserFriendlyException.SameMessage("未查询到前一节点");
             if (prevStep.StepType is EStepType.Start)
@@ -666,17 +666,17 @@ namespace Hotline.FlowEngine.Workflows
         /// 查询待回访部门
         /// </summary>
         /// <returns></returns>
-        public async Task<(IdName, IReadOnlyList<IdName>)> GetUnvisitOrgsAsync(string workflowId, CancellationToken cancellationToken)
+        public async Task<(Kv, IReadOnlyList<Kv>)> GetUnvisitOrgsAsync(string workflowId, CancellationToken cancellationToken)
         {
             var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellationToken);
             if (workflow.CounterSignType is not ECounterSignType.Center)
-                return new(new IdName(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), new List<IdName>());
+                return new(new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), new List<Kv>());
             var steps = workflow.StepBoxes
-                .Where(d => d.StepType is EStepType.Normal && d.BusinessProperty is EBusinessProperty.Department)
+                .Where(d => d.StepType is EStepType.Normal && d.BusinessType is EBusinessType.Department)
                 .SelectMany(d => d.Steps)
                 .ToList();
-            var items = steps.Select(d => new IdName(d.OrgCode, d.OrgName)).ToList();
-            return (new IdName(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), items);
+            var items = steps.Select(d => new Kv(d.HandlerOrgId, d.HandlerOrgName)).ToList();
+            return (new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), items);
         }
 
         /// <summary>
@@ -763,16 +763,16 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 判断会签类型(中心会签或部门会签)
         /// </summary>
-        /// <param name="businessProperty"></param>
+        /// <param name="businessType"></param>
         /// <returns></returns>
         /// <exception cref="ArgumentOutOfRangeException"></exception>
-        private ECounterSignType GetCounterSignType(EBusinessProperty businessProperty) =>
-            businessProperty switch
+        private ECounterSignType GetCounterSignType(EBusinessType businessType) =>
+            businessType switch
             {
-                EBusinessProperty.Center => ECounterSignType.Center,
-                EBusinessProperty.Send => ECounterSignType.Center,
-                EBusinessProperty.Department => ECounterSignType.Department,
-                _ => throw new ArgumentOutOfRangeException(nameof(businessProperty), businessProperty, null)
+                EBusinessType.Center => ECounterSignType.Center,
+                EBusinessType.Send => ECounterSignType.Center,
+                EBusinessType.Department => ECounterSignType.Department,
+                _ => throw new ArgumentOutOfRangeException(nameof(businessType), businessType, null)
             };
 
         /// <summary>
@@ -801,7 +801,7 @@ namespace Hotline.FlowEngine.Workflows
             _mapper.Map(dto, currentStep);
 
             //step办理状态
-            currentStep.Complete(
+            currentStep.Handled(
                 _sessionContext.RequiredUserId, _sessionContext.UserName,
                 _sessionContext.RequiredOrgId, _sessionContext.OrgName,
                 _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
@@ -843,7 +843,7 @@ namespace Hotline.FlowEngine.Workflows
             var isToOrg = targetStepBox.IsOrg();
             if (!isToOrg) return false;
 
-            var isFromCenter = workflow.StepBoxes.All(d => d.BusinessProperty is not EBusinessProperty.Department);
+            var isFromCenter = workflow.StepBoxes.All(d => d.BusinessType is not EBusinessType.Department);
             return isFromCenter && isToOrg;
         }
 
@@ -879,7 +879,7 @@ namespace Hotline.FlowEngine.Workflows
             var isToCenter = targetStepBox.IsCenter();
             if (!isToCenter) return false;
 
-            var isFromOrg = workflow.StepBoxes.Any(d => d.BusinessProperty is EBusinessProperty.Department);
+            var isFromOrg = workflow.StepBoxes.Any(d => d.BusinessType is EBusinessType.Department);
             return isFromOrg && isToCenter;
         }
 
@@ -891,13 +891,13 @@ namespace Hotline.FlowEngine.Workflows
             step.Reset();
             var newStep = _mapper.Map<WorkflowStep>(step);
             newStep.Status = EWorkflowStepStatus.WaitForAccept;
-            newStep.PreviousId = step.PreviousId;
+            newStep.PreviousStepId = step.PreviousStepId;
             newStep.IsMain = step.IsMain;
             newStep.ParentId = step.ParentId;
-            newStep.Handlers = step.Handlers;
+            newStep.AssignedHandlers = step.AssignedHandlers;
             newStep.StartCountersignId = step.StartCountersignId;
             newStep.CountersignId = step.CountersignId;
-            newStep.IsStartedCountersignComplete = step.IsStartedCountersignComplete;
+            newStep.IsStartedCountersignSummary = step.IsStartedCountersignSummary;
             await _workflowStepRepository.AddAsync(newStep, cancellationToken);
 
             await CreateTraceAsync(workflow, newStep, EWorkflowTraceStatus.Previous, cancellationToken);
@@ -926,7 +926,7 @@ namespace Hotline.FlowEngine.Workflows
                 WorkflowId = workflowId,
                 StartStepId = startStep.Id,
                 StartStepCode = startStep.Code,
-                StartStepBusiProperty = startStep.BusinessProperty,
+                StartStepBusiType = startStep.BusinessType,
                 EndStepCode = endStepCode,
                 Members = count,
                 ParentId = parentId,
@@ -948,7 +948,7 @@ namespace Hotline.FlowEngine.Workflows
         {
             var nextSteps = currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign
                 ? nextStepBox.Steps.Where(d => d.CountersignId == currentStep.CountersignId).ToList()
-                : nextStepBox.Steps.Where(d => d.PreviousId == currentStep.Id).ToList();
+                : nextStepBox.Steps.Where(d => d.PreviousStepId == currentStep.Id).ToList();
 
             if (!nextSteps.Any())
                 throw new UserFriendlyException($"未查询到下一节点, currentStepId: {currentStep.Id}");
@@ -966,7 +966,7 @@ namespace Hotline.FlowEngine.Workflows
             //未办理的traces
             var uncompleteTraces =
                 await _workflowTraceRepository.QueryAsync(d =>
-                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
+                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.HandlerId));
             foreach (var trace in uncompleteTraces)
             {
                 trace.Jump(
@@ -985,7 +985,7 @@ namespace Hotline.FlowEngine.Workflows
             //未办理的traces
             var uncompleteTraces =
                 await _workflowTraceRepository.QueryAsync(d =>
-                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
+                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.HandlerId));
 
             if (uncompleteTraces.Any())
             {
@@ -1066,12 +1066,12 @@ namespace Hotline.FlowEngine.Workflows
             {
                 if (currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign)
                 {
-                    var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken);
+                    var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousStepId, cancellationToken);
                     trace.ParentId = prevTrace.Id;
                 }
                 else if (currentStep.StepCountersignStatus is EStepCountersignStatus.OuterCountersign)
                 {
-                    var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken);
+                    var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousStepId, cancellationToken);
                     trace.ParentId = prevTrace.ParentId;
                 }
             }
@@ -1097,9 +1097,9 @@ namespace Hotline.FlowEngine.Workflows
         private async Task<bool> RecallAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine targetStepDefine, WorkflowStep targetStepBox, EWorkflowTraceStatus traceStatus, CancellationToken cancellationToken)
         {
             //get targetStep's previous
-            var targetPrevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == targetStepBox.PreviousId);
+            var targetPrevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == targetStepBox.PreviousStepId);
             if (targetPrevStepBox == null)
-                throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}, targetStepBoxPrevId: {targetPrevStepBox?.PreviousId}");
+                throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}, targetStepBoxPrevId: {targetPrevStepBox?.PreviousStepId}");
             //真实的前一节点并不存在(非正常流转造成的),所以取前一stepbox任意一个step替代
             var targetPrevStep = targetPrevStepBox.Steps.FirstOrDefault();
             if (targetPrevStep == null)
@@ -1134,7 +1134,7 @@ namespace Hotline.FlowEngine.Workflows
         private ICollection<WorkflowStep> GetStepsBehindTargetStepBox(List<WorkflowStep> stepBoxes, WorkflowStep targetStepBox)
         {
             var steps = GetStepsIncludeStepBox(targetStepBox);
-            var nextStepBoxes = stepBoxes.Where(d => d.PreviousId == targetStepBox.Id).ToList();
+            var nextStepBoxes = stepBoxes.Where(d => d.PreviousStepId == targetStepBox.Id).ToList();
             if (!nextStepBoxes.Any())
                 return steps;
             foreach (var nextStepBox in nextStepBoxes)
@@ -1178,7 +1178,7 @@ namespace Hotline.FlowEngine.Workflows
 
 
             //start节点的办理人分类默认为用户,即为当前发起流程的操作员
-            var handler = new IdName { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
+            var handler = new Kv { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
 
             //开始节点的下一个节点(工单业务:话务员节点)
             var firstStepCode = workflow.WorkflowDefinition.FindStartStepDefine().NextSteps.First().Code;
@@ -1208,7 +1208,7 @@ namespace Hotline.FlowEngine.Workflows
             var stepBox = CreateStepBox(workflow.Id, endStepDefine, prevStepBox.Id);
             await _workflowStepRepository.AddAsync(stepBox, cancellationToken);
 
-            var handler = new IdName { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
+            var handler = new Kv { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
             var step = await CreateEndSubStepAsync(handler, stepBox, prevStep, cancellationToken);
 
             //end trace
@@ -1262,21 +1262,21 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         private async Task<WorkflowStep> CreateStartSubStepAsync(
-            IdName handler,
+            Kv handler,
             string nextStepCode,
             WorkflowStep stepBox,
             BasicWorkflowDto dto,
             CancellationToken cancellationToken)
         {
             //开始节点既不发起会签,也不处于会签中
-            var subStep = CreateSubStep(stepBox, new List<IdName> { handler }, nextStepCode, null,
+            var subStep = CreateSubStep(stepBox, new List<Kv> { handler }, nextStepCode, null,
                 null, null, EWorkflowStepStatus.Handled, EStepCountersignStatus.None, DateTime.Today,
                 _mapper.Map<StepExtension>(dto.Extension));
             subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
                 _sessionContext.RequiredOrgId, _sessionContext.OrgName);
 
             //step办理状态
-            subStep.Complete(_sessionContext.RequiredUserId, _sessionContext.UserName,
+            subStep.Handled(_sessionContext.RequiredUserId, _sessionContext.UserName,
                 _sessionContext.RequiredOrgId, _sessionContext.OrgName,
                 _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
                 nextStepCode);
@@ -1288,16 +1288,16 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         private async Task<WorkflowStep> CreateEndSubStepAsync(
-            IdName handler,
+            Kv handler,
             WorkflowStep currentStepBox,
             WorkflowStep prevStep,
             CancellationToken cancellationToken)
         {
-            var subStep = CreateSubStep(currentStepBox, new List<IdName> { handler }, null, null, prevStep.Id,
+            var subStep = CreateSubStep(currentStepBox, new List<Kv> { handler }, null, null, prevStep.Id,
                 null, EWorkflowStepStatus.Handled, EStepCountersignStatus.None, DateTime.Today, new());
             subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId,
                 _sessionContext.OrgName);
-            subStep.Complete(_sessionContext.RequiredUserId, _sessionContext.UserName,
+            subStep.Handled(_sessionContext.RequiredUserId, _sessionContext.UserName,
                 _sessionContext.RequiredOrgId, _sessionContext.OrgName,
                 _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
                 string.Empty);
@@ -1328,7 +1328,7 @@ namespace Hotline.FlowEngine.Workflows
 
             List<WorkflowStep> subSteps;
             var stepExtension = _mapper.Map<StepExtension>(dto.Extension);
-            if (stepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.AssignOrg)
+            if (stepBoxDefine.HandlerType is EHandlerType.AssignedUser or EHandlerType.AssignedOrg)
             {
                 subSteps = CreateSubSteps(dto.IsStartCountersign, stepBox, stepBox.HandlerClassifies, dto.NextStepCode, dto.NextMainHandler,
                     prevStep?.Id, countersignId, stepStatus, countersignStatus, expiredTime, stepExtension);
@@ -1377,7 +1377,7 @@ namespace Hotline.FlowEngine.Workflows
             {
                 foreach (var step in stepBox.Steps)
                 {
-                    if (predicate(step.Status) && (step.Handlers.Any(d => d.Id == orgCode) || step.Handlers.Any(d => d.Id == userId)))
+                    if (predicate(step.Status) && (step.AssignedHandlers.Any(d => d.Id == orgCode) || step.AssignedHandlers.Any(d => d.Id == userId)))
                         return (stepBox, step);
                 }
             }
@@ -1389,7 +1389,7 @@ namespace Hotline.FlowEngine.Workflows
         {
             var stepBox = _mapper.Map<WorkflowStep>(stepDefine);
             stepBox.WorkflowId = workflowId;
-            stepBox.PreviousId = prevStepBoxId;
+            stepBox.PreviousStepId = prevStepBoxId;
             stepBox.NextStepCode = string.Empty;
             stepBox.Opinion = string.Empty;
             stepBox.CountersignStartStepCode = stepDefine.CountersignStartStepCode;
@@ -1400,7 +1400,7 @@ namespace Hotline.FlowEngine.Workflows
         private List<WorkflowStep> CreateSubSteps(
             bool isPrevStartCountersign,
             WorkflowStep stepBox,
-            List<IdName> handlers,
+            List<Kv> handlers,
             string nextStepCode,
             string? nextMainHandler,
             string? prevStepId,
@@ -1421,7 +1421,7 @@ namespace Hotline.FlowEngine.Workflows
             {
                 foreach (var handler in handlers)
                 {
-                    var step = CreateSubStep(stepBox, new List<IdName> { handler }, nextStepCode, nextMainHandler,
+                    var step = CreateSubStep(stepBox, new List<Kv> { handler }, nextStepCode, nextMainHandler,
                         prevStepId, countersignId, stepStatus, countersignStatus, expiredTime, extension);
 
                     steps.Add(step);
@@ -1440,7 +1440,7 @@ namespace Hotline.FlowEngine.Workflows
 
         private WorkflowStep CreateSubStep(
             WorkflowStep stepBox,
-            List<IdName> handlers,
+            List<Kv> handlers,
             string nextStepCode,
             string? nextMainHandler,
             string? prevStepId,
@@ -1457,10 +1457,10 @@ namespace Hotline.FlowEngine.Workflows
             var isMain = handlers.Count == 1 || (handlers.Count > 1 || handlerIds.First() == nextMainHandler);
 
             step.ParentId = stepBox.Id;
-            step.Handlers = handlers;
+            step.AssignedHandlers = handlers;
             step.NextStepCode = step.StepType is EStepType.End ? string.Empty : nextStepCode;
             step.IsMain = isMain;
-            step.PreviousId = prevStepId;
+            step.PreviousStepId = prevStepId;
             step.CountersignId = countersignId;
             step.Status = stepStatus;
             step.StepCountersignStatus = countersignStatus;

+ 1508 - 0
src/Hotline/FlowEngine/Workflows/WorkflowDomainService1.cs

@@ -0,0 +1,1508 @@
+using Hotline.FlowEngine.Definitions;
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.WorkflowModules;
+using Hotline.Settings;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.FlowEngine;
+using MapsterMapper;
+using MediatR;
+using Microsoft.Extensions.Logging;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows
+{
+    public class WorkflowDomainService1 : IWorkflowDomainService, IScopeDependency
+    {
+        private readonly IWorkflowRepository _workflowRepository;
+        private readonly IRepository<WorkflowStep> _workflowStepRepository;
+        private readonly IRepository<WorkflowTrace> _workflowTraceRepository;
+        private readonly IRepository<WorkflowAssign> _workflowAssignRepository;
+        private readonly IRepository<WorkflowSupplement> _workflowSupplementRepository;
+        private readonly IRepository<WorkflowCountersign> _workflowCountersignRepository;
+        private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
+        private readonly IMediator _mediator;
+        private readonly ILogger<WorkflowDomainService> _logger;
+
+        public WorkflowDomainService(
+            IWorkflowRepository workflowRepository,
+            IRepository<WorkflowStep> workflowStepRepository,
+            IRepository<WorkflowTrace> workflowTraceRepository,
+            IRepository<WorkflowAssign> workflowAssignRepository,
+            IRepository<WorkflowSupplement> workflowSupplementRepository,
+            IRepository<WorkflowCountersign> workflowCountersignRepository,
+            ISessionContext sessionContext,
+            IMapper mapper,
+            IMediator mediator,
+            ILogger<WorkflowDomainService> logger)
+        {
+            _workflowRepository = workflowRepository;
+            _workflowStepRepository = workflowStepRepository;
+            _workflowTraceRepository = workflowTraceRepository;
+            _workflowAssignRepository = workflowAssignRepository;
+            _workflowSupplementRepository = workflowSupplementRepository;
+            _workflowCountersignRepository = workflowCountersignRepository;
+
+            _sessionContext = sessionContext;
+            _mapper = mapper;
+            _mediator = mediator;
+            _logger = logger;
+        }
+
+        public async Task<Workflow> CreateWorkflowAsync(WorkflowModule wfModule, string title, string userId, string userCode,
+            string? externalId = null, CancellationToken cancellationToken = default)
+        {
+            var definition = wfModule.Definition;
+            if (definition is null)
+                throw new UserFriendlyException("无效流程模板");
+            var workflow = new Workflow
+            {
+                Title = title,
+                ModuleId = wfModule.Id,
+                ModuleName = wfModule.Name,
+                ModuleCode = wfModule.Code,
+                DefinitionId = definition.Id,
+                Status = EWorkflowStatus.Runnable,
+                TimeLimit = GetTimeLimit(definition.Code),//todo 过期时间
+                ExpiredTime = CalculateExpiredTime(definition.Code),//todo 过期时间
+                StepBoxes = new(),
+                Traces = new(),
+                WorkflowDefinition = definition,
+                CenterToOrgTime = DateTime.Now,
+                ExternalId = externalId ?? string.Empty,
+                FlowedOrgIds = new List<string> { userCode },
+                FlowedUserIds = new List<string> { userId },
+            };
+
+            await _workflowRepository.AddAsync(workflow, cancellationToken);
+
+            return workflow;
+        }
+
+        /// <summary>
+        /// 流程开始
+        /// </summary>
+        /// <param name="workflow"></param>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task StartAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
+             FlowAssignInfo flowAssignInfo, CancellationToken cancellationToken)
+        {
+            //创建开始节点
+            var (startStepBox, startStep, firstStepBox) = await CreateStartAndFirstStepAsync(workflow, dto, cancellationToken);
+
+            //1.创建开始节点trace 2.创建firstStep(开始节点的下一个节点),办理firstStep 3.创建sec节点
+            //办理firstStep
+            var firstStep = firstStepBox.Steps.First();
+            var counterSignType = GetCounterSignType(firstStep.BusinessType);
+
+            await HandleStepAsync(workflow, dto, firstStepBox, firstStep, counterSignType, cancellationToken);
+
+            await _workflowStepRepository.UpdateRangeAsync(new List<WorkflowStep> { firstStepBox, firstStep }, cancellationToken);
+
+            //firstStep trace
+            await NextTraceAsync(workflow, dto, firstStep, cancellationToken);
+
+            //secondStep
+            var secondStepDefine = workflow.WorkflowDefinition.FindStepDefine(dto.NextStepCode);
+            var secondStepBox = await CreateStepAsync(workflow, secondStepDefine, dto,
+                EWorkflowStepStatus.Created, firstStepBox, firstStep, EWorkflowTraceStatus.Normal,
+                workflow.ExpiredTime, cancellationToken);
+
+            //更新实际办理节点信息
+            workflow.UpdateWorkflowActualHandleInfo(firstStepBox, firstStep,
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                 _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+
+            //todo 计算办理工作时长
+
+            //更新当前办理节点信息
+            workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, firstStep, secondStepBox.Steps.First());
+
+            //发起会签时记录顶层会签节点(必须在update currentStep之后)
+            if (dto.IsStartCountersign && !workflow.IsInCountersign())
+                workflow.StartCountersign(firstStepBox.Code, counterSignType);
+
+            //更新受理人信息
+            workflow.UpdateAcceptor(
+                _sessionContext.RequiredUserId,
+                _sessionContext.UserName,
+                _sessionContext.StaffNo,
+                _sessionContext.RequiredOrgId,
+                _sessionContext.OrgName);
+
+            workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
+            flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
+
+            //更新指派信息
+            workflow.Assign(flowAssignInfo.FlowAssignType, flowAssignInfo.GetHandlers());
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            //publish
+            await _mediator.Publish(new StartWorkflowNotify(workflow, dto, flowAssignInfo), cancellationToken);
+
+        }
+
+        public async 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)
+        {
+            var query = _workflowRepository.Queryable().Where(d => d.Id == workflowId);
+            if (withDefine)
+                query = query.Includes(d => d.WorkflowDefinition);
+            if (withSupplements)
+                query = query.Includes(d => d.Supplements, d => d.Creator);
+            //if (withAssigns)
+            //    query = query.Includes(d => d.Assigns);
+            if (withCountersigns)
+                query = query.Includes(d => d.Countersigns);
+
+            var workflow = await query.FirstAsync();
+            if (workflow is null)
+                throw new UserFriendlyException("无效workflowId");
+
+            if (withSteps)
+            {
+                var steps = await _workflowStepRepository.Queryable()
+                    .Where(d => d.WorkflowId == workflow.Id)
+                    .OrderBy(d => d.CreationTime)
+                    .ToTreeAsync(d => d.Steps, d => d.ParentId, null);
+                workflow.StepBoxes = steps;
+            }
+
+            if (withTraces)
+            {
+                var traces = await _workflowTraceRepository.Queryable()
+                    .Where(d => d.WorkflowId == workflow.Id)
+                    .OrderBy(d => d.CreationTime)
+                    .ToTreeAsync(d => d.Traces, d => d.ParentId, null);
+                workflow.Traces = traces;
+            }
+
+
+            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>
+        public async Task AcceptAsync(Workflow workflow, string userId, string userName, string orgCode, string orgName, CancellationToken cancellationToken)
+        {
+            if (!workflow.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId)) return;
+            //工单完成以后查看的场景
+            if (workflow.Status != EWorkflowStatus.Runnable) return;
+
+            var (currentStepBox, currentStep) = GetUnCompleteStepOrDefault(workflow.StepBoxes, orgCode, userId);
+            if (currentStep is null) return;
+            if (currentStep.Status is EWorkflowStepStatus.Handling) return;
+            if (currentStep.HandlerType is EHandlerType.AssignedUser or EHandlerType.Role)
+            {
+                //userId
+                if (currentStep.AssignedHandlers.All(d => d.Id != userId)) return;
+            }
+            else
+            {
+                //orgId
+                if (currentStep.AssignedHandlers.All(d => d.Id != orgCode)) return;
+            }
+            if (currentStep.StepType is EStepType.End)
+                throw new UserFriendlyException("当前流程已流转到最终步骤");
+
+            var changedSteps = new List<WorkflowStep> { currentStep };
+            if (currentStepBox.Status is EWorkflowStepStatus.WaitForAccept)
+            {
+                currentStepBox.Status = EWorkflowStepStatus.Handling;
+                changedSteps.Add(currentStepBox);
+            }
+            currentStep.Accept(userId, userName, _sessionContext.RequiredOrgId, _sessionContext.OrgName);
+
+            //接办时非会签并且有多个接办部门时需更新接办部门
+            if (!workflow.IsInCountersign())
+            {
+                var assigns = await _workflowAssignRepository.QueryAsync(d => d.WorkflowId == workflow.Id);
+                if (assigns.Count > 1)
+                {
+                    await _workflowAssignRepository.RemoveRangeAsync(assigns, cancellationToken);
+
+                    var assign = WorkflowAssign.Create(workflow.Id, orgCode, orgName);
+                    await _workflowAssignRepository.AddAsync(assign, cancellationToken);
+                }
+            }
+
+            await _workflowStepRepository.UpdateRangeAsync(changedSteps, cancellationToken);
+
+            await AcceptTraceAsync(workflow, currentStep, cancellationToken);
+
+            await _mediator.Publish(new AcceptWorkflowNotify(workflow), cancellationToken);
+        }
+
+        /// <summary>
+        /// 办理(流转至下一节点)
+        /// </summary>
+        public async Task NextAsync(Workflow workflow, NextWorkflowDto dto, StepDefine nextStepBoxDefine,
+            FlowAssignInfo flowAssignInfo, DateTime expiredTime, CancellationToken cancellationToken)
+        {
+            ValidatePermission(workflow);
+            CheckWhetherRunnable(workflow.Status);
+
+            #region 办理当前节点
+
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+
+            var counterSignType = GetCounterSignType(currentStep.BusinessType);
+
+            await HandleStepAsync(workflow, dto, currentStepBox, currentStep, counterSignType, cancellationToken);
+
+            //update realhandle info
+            _mapper.Map(dto, workflow);
+
+            var updateSteps = new List<WorkflowStep> { currentStepBox, currentStep };
+            //结束当前会签流程
+            if (currentStep.StepType is EStepType.Summary && currentStep.IsInCountersign)
+            {
+                var currentCountersign = workflow.Countersigns.FirstOrDefault(d => d.Id == currentStep.CountersignId);
+                if (currentCountersign is null)
+                    throw new UserFriendlyException(
+                        $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}", "无效会签编号");
+
+                //1.根据当前节点配置查找结束节点对应开始节点 2.如该开始节点与当前会签开始节点吻合说明可以结束
+
+                //如果!=,说明未发起会签而是继承的外层会签
+                if (currentStepBox.CountersignStartStepCode == currentCountersign.StartStepCode)
+                {
+                    //结束step会签信息
+                    var countersignStartStepBox =
+                        workflow.StepBoxes.FirstOrDefault(d => d.Code == currentCountersign.StartStepCode);
+                    if (countersignStartStepBox is null)
+                        throw new UserFriendlyException(
+                            $"未查询到会签开始stepBox, workflowId: {workflow.Id}, startStepCode: {currentCountersign.StartStepCode}", "未查询到会签开始节点");
+                    var countersignStartStep =
+                        countersignStartStepBox.Steps.FirstOrDefault(d => d.HasStartedCountersign && d.Id == currentCountersign.StartStepId);
+                    if (countersignStartStep is null)
+                        throw new UserFriendlyException(
+                            $"未查询到会签开始step, workflowId: {workflow.Id}, startStepId: {currentCountersign.StartStepId}", "未查询到会签开始节点");
+                    countersignStartStep.CountersignComplete();
+                    updateSteps.Add(countersignStartStep);
+
+                    //结束会签
+                    currentCountersign.Complete(currentStep.Id, currentStep.Code, currentStep.BusinessType);
+                    await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
+                }
+            }
+
+            await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken);
+
+            await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
+
+            #endregion
+
+            #region 处理流程
+
+            //检查会签是否结束,并更新当前会签节点字段
+            var isCountersignOver = false;
+            if (workflow.IsInCountersign() && currentStep.StepType is EStepType.Summary)
+            {
+                isCountersignOver = workflow.CheckIfCountersignOver();
+                if (isCountersignOver)
+                    workflow.EndCountersign();
+            }
+
+            //检查是否流转到流程终点
+            if (nextStepBoxDefine.StepType is EStepType.End && !workflow.IsInCountersign())
+            {
+                var endTrace = await WorkflowEnd(workflow, dto, nextStepBoxDefine, currentStepBox, currentStep, cancellationToken);
+                return;
+            }
+
+            //是否从中心流转出去,重新计算expiredTime 
+            var isCenterToOrg = CheckIfFlowFromCenterToOrg(currentStepBox, nextStepBoxDefine);
+            if (isCenterToOrg)
+                workflow.CenterToOrg(CalculateExpiredTime(workflow.WorkflowDefinition.Code));//todo 过期时间
+
+            //创建下一节点(会签汇总节点不重复创建)
+            var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Created,
+                currentStepBox, currentStep, EWorkflowTraceStatus.Normal, expiredTime, cancellationToken);
+
+            //下一节点为汇总节点时,检查下一节点是否可办理
+            var nextStepCanHandle = true;
+            if (nextStepBox.StepType is EStepType.Summary && currentStep.IsInCountersign)
+            {
+                //同一会签Id,非汇总节点
+                var steps = await _workflowStepRepository.QueryAsync(d =>
+                    d.WorkflowId == workflow.Id
+                    && d.CountersignId == currentStep.CountersignId
+                    && d.StepType == EStepType.Normal
+                    && !string.IsNullOrEmpty(d.ParentId));
+                //(当前办理节点所处同一会签内的所有step全都办理完成并且如果开启了会签的step,必须会签结束)
+                var unComplete = steps.Any(d =>
+                    d.Status != EWorkflowStepStatus.Handled ||
+                    (d.HasStartedCountersign && !(d.IsStartedCountersignSummary ?? false)));
+                nextStepCanHandle = !unComplete;
+            }
+
+            //更新办理人
+            workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
+                flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
+
+            if (nextStepCanHandle)
+            {
+                //将下一节点处理为已指派/可接办
+                await SetNextCountersignEndAssignedAsync(nextStepBox, currentStep, cancellationToken);
+
+                await _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
+            }
+
+            //更新实际办理节点名称、时间
+            workflow.UpdateWorkflowActualHandleInfo(currentStepBox, currentStep,
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+
+            //todo 计算办理工作时长
+
+            //更新当前办理节点信息
+            workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, currentStep, nextStepBox.Steps.First());
+
+            //发起会签时记录顶层会签节点
+            if (dto.IsStartCountersign && !workflow.IsInCountersign())
+                workflow.StartCountersign(currentStepBox.Code, counterSignType);
+
+            //更新指派信息
+            workflow.Assign(flowAssignInfo.FlowAssignType, flowAssignInfo.GetHandlers());
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            #endregion
+
+            #region 流转记录
+
+            var trace = await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
+
+            #endregion
+
+            #region 处理额外参数(短信通知、办理时限、省延期)
+
+            //需统一处理的放在这里,与业务关联的放在业务中去处理,如:省延期
+            //todo
+
+            #endregion
+
+            await _mediator.Publish(new NextStepNotify(workflow, dto, trace,
+                isCenterToOrg, isCountersignOver,
+                _sessionContext.RequiredOrgId, flowAssignInfo), cancellationToken);
+        }
+
+        /// <summary>
+        /// 退回(返回前一节点)
+        /// </summary>
+        /// <returns></returns>
+        public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            ValidatePermission(workflow);
+            CheckWhetherRunnable(workflow.Status);
+            if (workflow.IsInCountersign())
+                throw UserFriendlyException.SameMessage("会签流程不支持退回");
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+            if (currentStepBox.StepType is EStepType.Start)
+                throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
+
+            //find prevStep, update handler
+            var prevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == currentStepBox.PreviousStepId);
+            if (prevStepBox == null)
+                throw UserFriendlyException.SameMessage("未查询到上级节点");
+            var prevStep = prevStepBox.Steps.FirstOrDefault(d => d.Id == currentStep.PreviousStepId);
+            if (prevStep == null)
+                throw UserFriendlyException.SameMessage("未查询到前一节点");
+            if (prevStep.StepType is EStepType.Start)
+                throw UserFriendlyException.SameMessage("当前流程已退回到第一节点");
+
+            //update trace
+            await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken);
+
+            //检查并重置上级stepbox状态为待接办
+            await ResetStepBoxStatusAsync(prevStepBox, cancellationToken);
+
+            //复制上一个节点为待接办
+            var newPrevStep = await CreatePrevStepAsync(workflow, prevStep, cancellationToken);
+
+            //remove workflow.steps
+            await _workflowStepRepository.RemoveRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep, prevStep }, cancellationToken);
+
+            //更新当前办理节点信息
+            workflow.UpdateWorkflowCurrentStepInfo(false, nextStep: newPrevStep);
+
+            //更新流程可办理对象
+            workflow.UpdatePreviousHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, newPrevStep);
+
+            //orgToCenter会触发重新计算期满时间,1.无需审核按当前时间进行计算 2.需审核按审核通过时间计算
+            var isOrgToCenter = CheckIfFlowFromOrgToCenter(currentStepBox, prevStepBox);
+            if (isOrgToCenter)
+                workflow.OrgToCenter(CalculateExpiredTime(""));//todo 过期时间
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            await _mediator.Publish(new PreviousNotify(workflow, dto, isOrgToCenter), cancellationToken);
+        }
+
+        /// <summary>
+        /// 撤回(返回到之前任意节点)
+        /// </summary>
+        public async Task RecallAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine, FlowAssignInfo flowAssignInfo, CancellationToken cancellationToken)
+        {
+            if (targetStepDefine.StepType is EStepType.Start or EStepType.End)
+                throw UserFriendlyException.SameMessage("开始/结束节点不支持撤回");
+
+            var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode);
+            if (targetStepBox is null)
+                throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
+
+            //update uncompleted traces
+            await RecallTraceAsync(workflow.Id, cancellationToken);
+
+            var isOrgToCenter = await RecallAsync(workflow, dto, targetStepDefine, targetStepBox, EWorkflowTraceStatus.Recall, cancellationToken);
+
+            workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            await _mediator.Publish(new RecallNotify(workflow, dto, isOrgToCenter), cancellationToken);
+        }
+
+        /// <summary>
+        /// 跳转(直接将流程跳转至任意节点)
+        /// </summary>
+        public async Task JumpAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine,
+            bool isStartCountersign, FlowAssignInfo flowAssignInfo, CancellationToken cancellationToken)
+        {
+            if (targetStepDefine.StepType is EStepType.Start or EStepType.End)
+                throw UserFriendlyException.SameMessage("开始/结束节点不支持跳转");
+
+            //update uncompleted traces
+            await JumpTraceAsync(workflow.Id, dto, cancellationToken);
+
+            bool isOrgToCenter = false, isCenterToOrg = false;
+            var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode);
+            if (targetStepBox == null)
+            {
+                //向后跳转
+
+                //此场景并非按配置流转,默认最靠后的节点做为targetStep的prevStep
+                var lastStepBox = workflow.StepBoxes.MaxBy(d => d.CreationTime);
+                if (lastStepBox is null || lastStepBox.StepType is EStepType.End)
+                    throw new UserFriendlyException($"流程流转数据异常,未结束流程出现endStep, flowId: {workflow.Id}", "流程流转数据异常");
+
+                targetStepBox = await CreateStepAsync(workflow, targetStepDefine, dto,
+                    EWorkflowStepStatus.WaitForAccept, lastStepBox, lastStepBox.Steps.First(),
+                    EWorkflowTraceStatus.Jump, workflow.ExpiredTime, cancellationToken);
+
+                workflow.EndCountersign();
+                workflow.ResetOption();
+
+                //更新当前办理节点信息
+                workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, nextStep: targetStepBox.Steps.First());
+
+                //calc workflow expired time
+                isCenterToOrg = CheckIfFlowFromCenterToOrg(workflow, targetStepBox);
+                if (isCenterToOrg)
+                    workflow.ExpiredTime = CalculateExpiredTime("");
+
+                #region 补充中间节点处理方案(暂不需要)
+
+                //var completeStepCodes = workflow.StepBoxes.Select(d => d.Code);
+                //var uncompleteStepDefines = workflow.Definition.Steps.Where(d => !completeStepCodes.Contains(d.Code));
+                //创建当前节点与目标节点中间节点
+                //var jumpDto = new BasicWorkflowDto
+                //{
+                //    Opinion = "跳转补充"
+                //};
+
+                //foreach (var stepDefine in uncompleteStepDefines)
+                //{
+                //    var previousStepId = lastStepBox.Steps.Count > 1 ? lastStepBox.Id : lastStepBox.Steps.First().Id;
+                //    if (dto.TargetStepCode == stepDefine.Code)
+                //    {
+                //        await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
+                //        break;
+                //    }
+
+                //    //jump业务下,如果当前节点为会签节点,第一个补充节点的subStep.PreviousId无法确定从哪个子节点跳转过来,统一处理为当前节点的stepBox.Id
+                //    lastStepBox = await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
+                //} 
+
+                #endregion
+            }
+            else
+            {
+                //返回之前节点
+                isOrgToCenter = await RecallAsync(workflow, dto, targetStepDefine, targetStepBox, EWorkflowTraceStatus.Jump, cancellationToken);
+            }
+
+            workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            await _mediator.Publish(new JumpNotify(workflow, dto, flowAssignInfo, isCenterToOrg, isOrgToCenter), cancellationToken);
+        }
+
+        /// <summary>
+        /// 重办
+        /// </summary>
+        public async Task RedoAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine,
+            FlowAssignInfo flowAssignInfo, CancellationToken cancellationToken)
+        {
+            if (targetStepDefine.StepType is EStepType.Start or EStepType.End)
+                throw UserFriendlyException.SameMessage("开始/结束节点不支持重办");
+
+            var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode);
+            if (targetStepBox is null)
+                throw UserFriendlyException.SameMessage("未找到该节点配置");
+
+            var isOrgToCenter = await RecallAsync(workflow, dto, targetStepDefine, targetStepBox, EWorkflowTraceStatus.Redo, cancellationToken);
+
+            workflow.Redo();
+            workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
+
+            //todo calc expiredTime
+            //dto.Extension.TimeLimitCount
+
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            await _mediator.Publish(new RedoNotify(workflow, dto, isOrgToCenter), cancellationToken);
+        }
+
+        /// <summary>
+        /// 否决(审批流程不通过)
+        /// </summary>
+        /// <returns></returns>
+        public async Task RejectAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+
+            var endStepDefine = workflow.WorkflowDefinition.FindEndStepDefine();
+
+            var endTrace = await WorkflowEnd(workflow, dto, endStepDefine, currentStepBox, currentStep, cancellationToken);
+
+            await _mediator.Publish(new RejectNotify(workflow, dto), cancellationToken);
+        }
+
+        /// <summary>
+        /// 补充
+        /// </summary>
+        /// <returns></returns>
+        public async Task SupplementAsync(Workflow workflow, EndWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            CheckWhetherRunnable(workflow.Status);
+            //todo 检查当前办理人是否为该流程中的办理人
+
+            var supplement = _mapper.Map<WorkflowSupplement>(dto);
+            await _workflowSupplementRepository.AddAsync(supplement, cancellationToken);
+        }
+
+        /// <summary>
+        /// 终止流程
+        /// </summary>
+        public async Task TerminateAsync(TerminateDto dto, CancellationToken cancellationToken)
+        {
+            var workflow = await _workflowRepository.GetAsync(dto.WorkflowId, cancellationToken);
+            if (workflow == null)
+                throw UserFriendlyException.SameMessage("无效的流程编号");
+            //workflow.Terminate(dto.Opinion);
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            await _mediator.Publish(new TerminalWorkflowNotify(workflow), cancellationToken);
+        }
+
+        /// <summary>
+        /// 根据stepCode查询流程配置中对应的节点
+        /// </summary>
+        public StepDefine GetStepBoxDefine(WorkflowDefinition workflowDefinition, string stepCode)
+        {
+            if (workflowDefinition == null) throw new ArgumentNullException(nameof(workflowDefinition));
+            if (string.IsNullOrEmpty(stepCode)) throw new ArgumentNullException(nameof(stepCode));
+            var stepDefine = workflowDefinition.FindStepDefine(stepCode);
+            if (stepDefine == null)
+                throw new UserFriendlyException($"未找到流程中对应的节点,DefineCode: {workflowDefinition.Code}, stepCode: {stepCode}",
+                    "未查询到对应节点");
+            return stepDefine;
+        }
+
+        /// <summary>
+        /// 查询当前待办节点的下一级节点配置(办理参数)
+        /// </summary>
+        public IReadOnlyList<StepDefine> GetNextStepDefines(Workflow workflow)
+        {
+            var (currentStepBox, _) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+            return workflow.WorkflowDefinition.FindStepDefines(currentStepBox.NextSteps.Select(d => d.Code));
+        }
+
+        /// <summary>
+        /// 查找当前办理节点
+        /// </summary>
+        public (WorkflowStep StepBox, WorkflowStep Step) FindCurrentStep(Workflow workflow) =>
+            GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+
+        /// <summary>
+        /// 查询待回访部门
+        /// </summary>
+        /// <returns></returns>
+        public async Task<(Kv, IReadOnlyList<Kv>)> GetUnvisitOrgsAsync(string workflowId, CancellationToken cancellationToken)
+        {
+            var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellationToken);
+            if (workflow.CounterSignType is not ECounterSignType.Center)
+                return new(new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), new List<Kv>());
+            var steps = workflow.StepBoxes
+                .Where(d => d.StepType is EStepType.Normal && d.BusinessType is EBusinessType.Department)
+                .SelectMany(d => d.Steps)
+                .ToList();
+            var items = steps.Select(d => new Kv(d.HandlerOrgId, d.HandlerOrgName)).ToList();
+            return (new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), items);
+        }
+
+        /// <summary>
+        /// 更新一级部门信息
+        /// </summary>
+        public async Task UpdateOrgLevelOneAsync(Workflow workflow, string orgCode, string orgName, CancellationToken cancellationToken)
+        {
+            workflow.UpdateOrgLevelOne(orgCode, orgName);
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+        }
+
+        /// <summary>
+        /// 依据配置过滤下一节点
+        /// </summary>
+        public List<StepDefine> NextStepDefineFilter(EPathPolicy pathPolicy, List<StepDefine> nextStepDefines)
+        {
+            switch (pathPolicy)
+            {
+                case EPathPolicy.DirectUpper:
+                    break;
+                case EPathPolicy.DirectUpperCenterIsTop:
+                    var currentOrgLevel = _sessionContext.RequiredOrgId.CalcOrgLevel();
+                    if (currentOrgLevel == 1)
+                    {
+                        nextStepDefines = nextStepDefines.Where(d => d.IsCenter()).ToList();
+                    }
+                    else
+                    {
+                        var upperLevel = (--currentOrgLevel).ToString();
+                        nextStepDefines = nextStepDefines
+                            .Where(d => d.HandlerType is EHandlerType.OrgLevel &&
+                                        d.HandlerTypeItems.Any(x => x.Key == upperLevel))
+                            .ToList();
+                    }
+
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+
+            return nextStepDefines;
+        }
+
+        /// <summary>
+        /// 撤销流程
+        /// </summary>
+        public async Task CancelAsync(CancelDto dto, CancellationToken cancellationToken)
+        {
+            var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true, cancellationToken: cancellationToken);
+
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+
+            var endStepDefine = workflow.WorkflowDefinition.FindEndStepDefine();
+
+            var basicDto = _mapper.Map<BasicWorkflowDto>(dto);
+            var endTrace = await WorkflowEnd(workflow, basicDto, endStepDefine, currentStepBox, currentStep, cancellationToken);
+
+            await _mediator.Publish(new CancelWorkflowNotify(workflow), cancellationToken);
+        }
+
+        #region private method
+
+        /// <summary>
+        /// 流程结束
+        /// </summary>
+        private async Task<WorkflowTrace> WorkflowEnd(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine,
+            WorkflowStep currentStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
+        {
+            //create endStep
+            var (_, endStep) = await CreateEndStepAsync(workflow, nextStepBoxDefine, currentStepBox, currentStep, cancellationToken);
+
+            //update endTrace
+            var endTrace = await NextTraceAsync(workflow, dto, endStep, cancellationToken);
+
+            workflow.Complete();
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+            await _mediator.Publish(new EndWorkflowNotify(workflow, endTrace), cancellationToken);
+
+            return endTrace;
+        }
+
+        /// <summary>
+        /// 判断会签类型(中心会签或部门会签)
+        /// </summary>
+        /// <param name="businessType"></param>
+        /// <returns></returns>
+        /// <exception cref="ArgumentOutOfRangeException"></exception>
+        private ECounterSignType GetCounterSignType(EBusinessType businessType) =>
+            businessType switch
+            {
+                EBusinessType.Center => ECounterSignType.Center,
+                EBusinessType.Send => ECounterSignType.Center,
+                EBusinessType.Department => ECounterSignType.Department,
+                _ => throw new ArgumentOutOfRangeException(nameof(businessType), businessType, null)
+            };
+
+        /// <summary>
+        /// 办理节点
+        /// </summary>
+        private async Task HandleStepAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep currentStepBox, WorkflowStep currentStep,
+            ECounterSignType counterSignType, CancellationToken cancellationToken)
+        {
+            if (currentStep.Status is EWorkflowStepStatus.Handled or EWorkflowStepStatus.Created)
+                throw UserFriendlyException.SameMessage("当前节点状态无法办理");
+
+            if (currentStep.Status is EWorkflowStepStatus.WaitForAccept)
+                await AcceptAsync(workflow,
+                    _sessionContext.RequiredUserId,
+                    _sessionContext.UserName,
+                    _sessionContext.RequiredOrgId,
+                    _sessionContext.OrgName,
+                    cancellationToken);
+            if (currentStep.StepType is EStepType.End)
+                throw new UserFriendlyException("当前流程已流转到最终步骤");
+
+            //创建会签数据
+            if (dto.IsStartCountersign)
+                await StartCountersignAsync(workflow, dto, currentStepBox, currentStep, counterSignType, cancellationToken);
+
+            _mapper.Map(dto, currentStep);
+
+            //step办理状态
+            currentStep.Handled(
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
+                dto.NextStepCode);
+
+            //stepBox办理状态
+            currentStepBox.CheckStepBoxStatusAndUpdate();
+        }
+
+        /// <summary>
+        /// 开始会签(创建会签数据,更新currentStep会签数据)
+        /// </summary>
+        private async Task StartCountersignAsync(Workflow workflow, BasicWorkflowDto dto,
+            WorkflowStep currentStepBox, WorkflowStep currentStep, ECounterSignType counterSignType,
+            CancellationToken cancellationToken)
+        {
+            var countersign = await CreateCountersignAsync(workflow.Id, currentStep, currentStepBox.CountersignEndStepCode,
+                dto.NextHandlers.Count, counterSignType, currentStep.CountersignId, cancellationToken);
+            currentStep.StartCountersign(countersign.Id);
+        }
+
+        /// <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.BusinessType is not EBusinessType.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.BusinessType is EBusinessType.Department);
+            return isFromOrg && isToCenter;
+        }
+
+        /// <summary>
+        /// 复制一个节点为待接办
+        /// </summary>
+        private async Task<WorkflowStep> CreatePrevStepAsync(Workflow workflow, WorkflowStep step, CancellationToken cancellationToken)
+        {
+            step.Reset();
+            var newStep = _mapper.Map<WorkflowStep>(step);
+            newStep.Status = EWorkflowStepStatus.WaitForAccept;
+            newStep.PreviousStepId = step.PreviousStepId;
+            newStep.IsMain = step.IsMain;
+            newStep.ParentId = step.ParentId;
+            newStep.AssignedHandlers = step.AssignedHandlers;
+            newStep.StartCountersignId = step.StartCountersignId;
+            newStep.CountersignId = step.CountersignId;
+            newStep.IsStartedCountersignSummary = step.IsStartedCountersignSummary;
+            await _workflowStepRepository.AddAsync(newStep, cancellationToken);
+
+            await CreateTraceAsync(workflow, newStep, EWorkflowTraceStatus.Previous, cancellationToken);
+
+            return newStep;
+        }
+
+        /// <summary>
+        /// 检查并重置目标stepbox状态为待接办
+        /// </summary>
+        private async Task ResetStepBoxStatusAsync(WorkflowStep stepBox, CancellationToken cancellationToken)
+        {
+            if (stepBox.Status is EWorkflowStepStatus.Handled)
+            {
+                stepBox.Status = EWorkflowStepStatus.WaitForAccept;
+                await _workflowStepRepository.UpdateAsync(stepBox, cancellationToken);
+            }
+        }
+
+        private async Task<WorkflowCountersign> CreateCountersignAsync(
+            string workflowId, WorkflowStep startStep, string endStepCode, int count,
+            ECounterSignType counterSignType, string? parentId = null, CancellationToken cancellationToken = default)
+        {
+            var countersign = new WorkflowCountersign
+            {
+                WorkflowId = workflowId,
+                StartStepId = startStep.Id,
+                StartStepCode = startStep.Code,
+                StartStepBusiType = startStep.BusinessType,
+                EndStepCode = endStepCode,
+                Members = count,
+                ParentId = parentId,
+                CounterSignType = counterSignType,
+            };
+            await _workflowCountersignRepository.AddAsync(countersign, cancellationToken);
+            return countersign;
+        }
+
+
+        /// <summary>
+        /// 更新下级汇总节点可办理状态
+        /// </summary>
+        /// <param name="nextStepBox"></param>
+        /// <param name="currentStep"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        private async Task SetNextCountersignEndAssignedAsync(WorkflowStep nextStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
+        {
+            var nextSteps = currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign
+                ? nextStepBox.Steps.Where(d => d.CountersignId == currentStep.CountersignId).ToList()
+                : nextStepBox.Steps.Where(d => d.PreviousStepId == currentStep.Id).ToList();
+
+            if (!nextSteps.Any())
+                throw new UserFriendlyException($"未查询到下一节点, currentStepId: {currentStep.Id}");
+
+            foreach (var nextStep in nextSteps)
+            {
+                nextStep.SetAssigned();
+            }
+
+            await _workflowStepRepository.UpdateRangeAsync(nextSteps, cancellationToken);
+        }
+
+        private async Task JumpTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
+        {
+            //未办理的traces
+            var uncompleteTraces =
+                await _workflowTraceRepository.QueryAsync(d =>
+                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.HandlerId));
+            foreach (var trace in uncompleteTraces)
+            {
+                trace.Jump(
+                    _sessionContext.RequiredUserId,
+                    _sessionContext.UserName,
+                    _sessionContext.RequiredOrgId,
+                    _sessionContext.OrgName,
+                    dto.Opinion);
+            }
+
+            await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
+        }
+
+        private async Task RecallTraceAsync(string workflowId, CancellationToken cancellationToken)
+        {
+            //未办理的traces
+            var uncompleteTraces =
+                await _workflowTraceRepository.QueryAsync(d =>
+                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.HandlerId));
+
+            if (uncompleteTraces.Any())
+            {
+                foreach (var trace in uncompleteTraces)
+                {
+                    trace.Complete(
+                        _sessionContext.RequiredUserId,
+                        _sessionContext.UserName,
+                        _sessionContext.RequiredOrgId,
+                        _sessionContext.OrgName);
+                }
+
+                await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
+            }
+        }
+
+        private async Task PreviousTraceAsync(string workflowId, PreviousWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
+        {
+            var trace = await GetWorkflowTraceAsync(workflowId, step.Id, cancellationToken);
+            _mapper.Map(dto, trace);
+            trace.Complete(
+                _sessionContext.RequiredUserId,
+                _sessionContext.UserName,
+                _sessionContext.RequiredOrgId,
+                _sessionContext.OrgName);
+            await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
+        }
+
+        //private async Task EndTraceAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
+        //{
+        //    var trace = _mapper.Map<WorkflowTrace>(step);
+        //    trace.Status = EWorkflowTraceStatus.Normal;
+        //    trace.ExpiredTime = workflow.ExpiredTime;
+        //    trace.TimeLimit = workflow.TimeLimit;
+        //    await _workflowTraceRepository.AddAsync(trace, cancellationToken);
+        //}
+
+        private async Task<WorkflowTrace> NextTraceAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
+        {
+            var trace = await GetWorkflowTraceAsync(workflow.Id, step.Id, cancellationToken);
+            _mapper.Map(dto, trace);
+            _mapper.Map(step, trace);
+            await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
+
+            return trace;
+        }
+
+        private async Task AcceptTraceAsync(Workflow workflow, WorkflowStep step, CancellationToken cancellationToken)
+        {
+            var trace = await GetWorkflowTraceAsync(workflow.Id, step.Id, cancellationToken);
+            _mapper.Map(step, trace);
+            await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
+        }
+
+        private async Task CreateTraceAsync(Workflow workflow, WorkflowStep currentStep,
+            EWorkflowTraceStatus traceStatus = EWorkflowTraceStatus.Normal, CancellationToken cancellationToken = default)
+        {
+            var trace = _mapper.Map<WorkflowTrace>(currentStep);
+            trace.Status = traceStatus;
+
+            //1.如果是汇总节点,trace.parentId=会签开始节点对应的trace.parentId(即与会签开始节点trace同级)
+            //2.普通节点:2.1: in 判断上级节点是否发起会签,有则赋值parentId为上级trace.Id, 2.2: outer 与上级节点trace保持同级,取值上级节点对应trace.parentId
+
+            if (currentStep.StepType is EStepType.Summary)
+            {
+                if (currentStep.IsInCountersign)
+                {
+                    var countersign =
+                        await _workflowCountersignRepository.GetAsync(currentStep.CountersignId!, cancellationToken);
+                    if (countersign == null)
+                        throw new UserFriendlyException(
+                            $"汇总节点处于会签中,未查询到对应会签,countersignId: {currentStep.CountersignId}");
+                    var startTrace = await GetWorkflowTraceAsync(workflow.Id, countersign.StartStepId, cancellationToken);
+                    trace.ParentId = startTrace.ParentId;
+                }
+            }
+            else if (currentStep.StepType is EStepType.Normal)
+            {
+                if (currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign)
+                {
+                    var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousStepId, cancellationToken);
+                    trace.ParentId = prevTrace.Id;
+                }
+                else if (currentStep.StepCountersignStatus is EStepCountersignStatus.OuterCountersign)
+                {
+                    var prevTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousStepId, cancellationToken);
+                    trace.ParentId = prevTrace.ParentId;
+                }
+            }
+            ////处于会签中的节点,其对应的trace.parentId赋值上级trace.Id
+            //if (currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign)
+            //{
+            //    var parentTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken);
+            //    trace.ParentId = parentTrace.Id;
+            //}
+
+            await _workflowTraceRepository.AddAsync(trace, cancellationToken);
+        }
+
+        private async Task<WorkflowTrace> GetWorkflowTraceAsync(string workflowId, string stepId, CancellationToken cancellationToken)
+        {
+            var parentTrace = await _workflowTraceRepository.GetAsync(d =>
+                d.WorkflowId == workflowId && d.StepId == stepId, cancellationToken);
+            if (parentTrace == null)
+                throw new UserFriendlyException($"未找到对应trace, workflowId: {workflowId}, stepId: {stepId}");
+            return parentTrace;
+        }
+
+        private async Task<bool> RecallAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine targetStepDefine, WorkflowStep targetStepBox, EWorkflowTraceStatus traceStatus, CancellationToken cancellationToken)
+        {
+            //get targetStep's previous
+            var targetPrevStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Id == targetStepBox.PreviousStepId);
+            if (targetPrevStepBox == null)
+                throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}, targetStepBoxPrevId: {targetPrevStepBox?.PreviousStepId}");
+            //真实的前一节点并不存在(非正常流转造成的),所以取前一stepbox任意一个step替代
+            var targetPrevStep = targetPrevStepBox.Steps.FirstOrDefault();
+            if (targetPrevStep == null)
+                throw new UserFriendlyException($"{nameof(RecallAsync)}, 未找到目标节点的前一节点, flowId: {workflow.Id}");
+
+            //查询所有目标节点之后的节点,然后删掉(包括目标节点)
+            var removeSteps = GetStepsBehindTargetStepBox(workflow.StepBoxes, targetStepBox);
+            if (removeSteps.Any())
+            {
+                await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
+                workflow.StepBoxes.RemoveAll(d => removeSteps.Contains(d));
+            }
+
+            workflow.EndCountersign();
+            workflow.ResetOption();
+
+            //recreate targetStep
+            var targetStepBoxNew = await CreateStepAsync(workflow, targetStepDefine, dto, EWorkflowStepStatus.WaitForAccept,
+                 targetPrevStepBox, targetPrevStep, traceStatus, workflow.ExpiredTime, cancellationToken);
+
+            //更新当前办理节点信息
+            workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, nextStep: targetStepBoxNew.Steps.First());
+
+            //calc workflow expired time
+            var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStepBox);
+            if (isOrgToCenter)
+                workflow.ExpiredTime = CalculateExpiredTime("");
+
+            return isOrgToCenter;
+        }
+
+        private ICollection<WorkflowStep> GetStepsBehindTargetStepBox(List<WorkflowStep> stepBoxes, WorkflowStep targetStepBox)
+        {
+            var steps = GetStepsIncludeStepBox(targetStepBox);
+            var nextStepBoxes = stepBoxes.Where(d => d.PreviousStepId == targetStepBox.Id).ToList();
+            if (!nextStepBoxes.Any())
+                return steps;
+            foreach (var nextStepBox in nextStepBoxes)
+            {
+                steps.AddRange(GetStepsBehindTargetStepBox(stepBoxes, nextStepBox));
+            }
+            return steps;
+        }
+
+        private List<WorkflowStep> GetStepsIncludeStepBox(WorkflowStep stepBox)
+        {
+            var steps = new List<WorkflowStep> { stepBox };
+            steps.AddRange(stepBox.Steps);
+            return steps;
+        }
+
+        private static void CheckWhetherRunnable(EWorkflowStatus status)
+        {
+            if (status != EWorkflowStatus.Runnable)
+                throw UserFriendlyException.SameMessage("当前流程状态不可继续流转");
+        }
+
+        private void ValidatePermission(Workflow workflow)
+        {
+            if (!workflow.CanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId))
+                throw UserFriendlyException.SameMessage("无办理权限");
+        }
+
+        private async Task<(WorkflowStep startStepBox, WorkflowStep startStep, WorkflowStep firstStepBox)> CreateStartAndFirstStepAsync(
+            Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            if (workflow.StepBoxes.Any())
+                throw UserFriendlyException.SameMessage("无法重复创建开始节点");
+
+            var startStepDefinition = workflow.WorkflowDefinition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
+            if (startStepDefinition == null)
+                throw new UserFriendlyException($"模板未配置开始节点, defineCode: {workflow.WorkflowDefinition.Code}", "模板未配置开始节点");
+
+            var startStepBox = CreateStepBox(workflow.Id, startStepDefinition, string.Empty);
+            await _workflowStepRepository.AddAsync(startStepBox, cancellationToken);
+
+
+            //start节点的办理人分类默认为用户,即为当前发起流程的操作员
+            var handler = new Kv { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
+
+            //开始节点的下一个节点(工单业务:话务员节点)
+            var firstStepCode = workflow.WorkflowDefinition.FindStartStepDefine().NextSteps.First().Code;
+            var startStep = await CreateStartSubStepAsync(handler, firstStepCode, startStepBox, dto, cancellationToken);
+
+            //开始节点trace
+            await CreateTraceAsync(workflow, startStep, cancellationToken: cancellationToken);
+
+            //创建firstStep
+            var firsStepDefine = workflow.WorkflowDefinition.FindStepDefine(firstStepCode);
+            var firstStepBox = await CreateStepAsync(workflow, firsStepDefine, dto, EWorkflowStepStatus.Handling,
+                startStepBox, startStep, EWorkflowTraceStatus.Normal, workflow.ExpiredTime, cancellationToken);
+
+            return (startStepBox, startStep, firstStepBox);
+        }
+
+        private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateEndStepAsync(
+            Workflow workflow,
+            StepDefine endStepDefine,
+            WorkflowStep prevStepBox,
+            WorkflowStep prevStep,
+            CancellationToken cancellationToken)
+        {
+            if (workflow.StepBoxes.Any(d => d.StepType == EStepType.End))
+                throw UserFriendlyException.SameMessage("无法重复创建结束节点");
+
+            var stepBox = CreateStepBox(workflow.Id, endStepDefine, prevStepBox.Id);
+            await _workflowStepRepository.AddAsync(stepBox, cancellationToken);
+
+            var handler = new Kv { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
+            var step = await CreateEndSubStepAsync(handler, stepBox, prevStep, cancellationToken);
+
+            //end trace
+            await CreateTraceAsync(workflow, step, cancellationToken: cancellationToken);
+
+            return (stepBox, step);
+        }
+
+        /// <summary>
+        /// 创建节点(不含开始、结束节点)
+        /// </summary>
+        private async Task<WorkflowStep> CreateStepAsync(
+        Workflow workflow,
+        StepDefine stepBoxDefine,
+        BasicWorkflowDto dto,
+        EWorkflowStepStatus status,
+        WorkflowStep prevStepBox,
+        WorkflowStep prevStep,
+        EWorkflowTraceStatus traceStatus,
+        DateTime expiredTime,
+        CancellationToken cancellationToken)
+        {
+            if (stepBoxDefine.StepType is EStepType.Start or EStepType.End)
+                throw new UserFriendlyException("该方法不支持创建开始或结束节点");
+            var stepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == stepBoxDefine.Code);
+            if (stepBox == null)
+            {
+                stepBox = CreateStepBox(workflow.Id, stepBoxDefine, prevStepBox.Id);
+                await _workflowStepRepository.AddAsync(stepBox, cancellationToken);
+                workflow.StepBoxes.Add(stepBox);
+            }
+            else if (stepBox.Status != EWorkflowStepStatus.Created)
+            {
+                stepBox.Status = EWorkflowStepStatus.Created;
+                await _workflowStepRepository.UpdateAsync(stepBox, cancellationToken);
+            }
+
+            //下一节点为汇总节点时,同一会签只需要创建一次汇总节点
+            if (stepBoxDefine.StepType is EStepType.Summary && prevStep.StepCountersignStatus == EStepCountersignStatus.InCountersign)
+            {
+                var step = stepBox.Steps.FirstOrDefault(d =>
+                    d.IsInCountersign && d.CountersignId == prevStep.CountersignId);
+                if (step != null)
+                    return stepBox;
+            }
+
+            await CreateSubStepsAsync(workflow, stepBoxDefine, dto, stepBox, status, prevStep, traceStatus, expiredTime, cancellationToken);
+
+
+            return stepBox;
+        }
+
+        private async Task<WorkflowStep> CreateStartSubStepAsync(
+            Kv handler,
+            string nextStepCode,
+            WorkflowStep stepBox,
+            BasicWorkflowDto dto,
+            CancellationToken cancellationToken)
+        {
+            //开始节点既不发起会签,也不处于会签中
+            var subStep = CreateSubStep(stepBox, new List<Kv> { handler }, nextStepCode, null,
+                null, null, EWorkflowStepStatus.Handled, EStepCountersignStatus.None, DateTime.Today,
+                _mapper.Map<StepExtension>(dto.Extension));
+            subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgId, _sessionContext.OrgName);
+
+            //step办理状态
+            subStep.Handled(_sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
+                nextStepCode);
+
+            subStep.Opinion = "流程开启";
+            stepBox.Steps.Add(subStep);
+            await _workflowStepRepository.AddAsync(subStep, cancellationToken);
+            return subStep;
+        }
+
+        private async Task<WorkflowStep> CreateEndSubStepAsync(
+            Kv handler,
+            WorkflowStep currentStepBox,
+            WorkflowStep prevStep,
+            CancellationToken cancellationToken)
+        {
+            var subStep = CreateSubStep(currentStepBox, new List<Kv> { handler }, null, null, prevStep.Id,
+                null, EWorkflowStepStatus.Handled, EStepCountersignStatus.None, DateTime.Today, new());
+            subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName, _sessionContext.RequiredOrgId,
+                _sessionContext.OrgName);
+            subStep.Handled(_sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgId, _sessionContext.OrgName,
+                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
+                string.Empty);
+
+            currentStepBox.Steps.Add(subStep);
+            await _workflowStepRepository.AddAsync(subStep, cancellationToken);
+            return subStep;
+        }
+
+        private async Task CreateSubStepsAsync(
+            Workflow workflow,
+            StepDefine stepBoxDefine,
+            BasicWorkflowDto dto,
+            WorkflowStep stepBox,
+            EWorkflowStepStatus stepStatus,
+            WorkflowStep prevStep,
+            EWorkflowTraceStatus traceStatus,
+            DateTime expiredTime,
+            CancellationToken cancellationToken = default)
+        {
+            var countersignStatus = stepBoxDefine.StepType is EStepType.Summary
+                ? prevStep.IsInCountersign
+                    ? EStepCountersignStatus.InCountersign
+                    : EStepCountersignStatus.None
+                : prevStep.GetNextStepCountersignStatus();
+
+            var countersignId = dto.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
+
+            List<WorkflowStep> subSteps;
+            var stepExtension = _mapper.Map<StepExtension>(dto.Extension);
+            if (stepBoxDefine.HandlerType is EHandlerType.AssignedUser or EHandlerType.AssignedOrg)
+            {
+                subSteps = CreateSubSteps(dto.IsStartCountersign, stepBox, stepBox.HandlerClassifies, dto.NextStepCode, dto.NextMainHandler,
+                    prevStep?.Id, countersignId, stepStatus, countersignStatus, expiredTime, stepExtension);
+            }
+            else
+            {
+                if (stepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
+                    throw new UserFriendlyException("未指定节点处理者");
+                subSteps = CreateSubSteps(dto.IsStartCountersign, stepBox, dto.NextHandlers, dto.NextStepCode, dto.NextMainHandler,
+                    prevStep?.Id, countersignId, stepStatus, countersignStatus, expiredTime, stepExtension);
+            }
+            stepBox.Steps.AddRange(subSteps);
+            await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
+
+            //create traces
+            foreach (var step in subSteps)
+            {
+                await CreateTraceAsync(workflow, step, traceStatus, cancellationToken);
+            }
+        }
+
+        /// <summary>
+        /// 查询未完成节点
+        /// </summary>
+        /// <param name="stepBoxes"></param>
+        /// <param name="orgCode"></param>
+        /// <param name="userId"></param>
+        /// <returns></returns>
+        private (WorkflowStep, WorkflowStep) GetUnCompleteStep(List<WorkflowStep> stepBoxes, string orgCode, string userId)
+        {
+            var (stepBox, step) = GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Handled);
+            if (step == null)
+                throw new UserFriendlyException(
+                    $"未找到对应节点, workflowId: {stepBoxes.FirstOrDefault()?.WorkflowId} orgCode:{orgCode}, userId: {userId}",
+                    "未找到对应节点");
+            return (stepBox, step);
+        }
+
+        private (WorkflowStep, WorkflowStep) GetUnCompleteStepOrDefault(List<WorkflowStep> stepBoxes, string orgCode, string userId) =>
+            GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Handled);
+
+        private (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string orgCode, string userId, Func<EWorkflowStepStatus, bool> predicate)
+        {
+            if (!stepBoxes.Any()) throw new UserFriendlyException("该流程中暂无节点");
+            foreach (var stepBox in stepBoxes)
+            {
+                foreach (var step in stepBox.Steps)
+                {
+                    if (predicate(step.Status) && (step.AssignedHandlers.Any(d => d.Id == orgCode) || step.AssignedHandlers.Any(d => d.Id == userId)))
+                        return (stepBox, step);
+                }
+            }
+
+            return new();
+        }
+
+        private WorkflowStep CreateStepBox(string workflowId, StepDefine stepDefine, string prevStepBoxId)
+        {
+            var stepBox = _mapper.Map<WorkflowStep>(stepDefine);
+            stepBox.WorkflowId = workflowId;
+            stepBox.PreviousStepId = prevStepBoxId;
+            stepBox.NextStepCode = string.Empty;
+            stepBox.Opinion = string.Empty;
+            stepBox.CountersignStartStepCode = stepDefine.CountersignStartStepCode;
+            stepBox.CountersignEndStepCode = stepDefine.CountersignEndStepCode;
+            return stepBox;
+        }
+
+        private List<WorkflowStep> CreateSubSteps(
+            bool isPrevStartCountersign,
+            WorkflowStep stepBox,
+            List<Kv> handlers,
+            string nextStepCode,
+            string? nextMainHandler,
+            string? prevStepId,
+            string? countersignId,
+            EWorkflowStepStatus stepStatus,
+            EStepCountersignStatus countersignStatus,
+            DateTime expiredTime,
+            StepExtension extension)
+        {
+            if (countersignStatus is EStepCountersignStatus.None && !string.IsNullOrEmpty(countersignId))
+                throw UserFriendlyException.SameMessage("非法参数");
+            if (countersignStatus is not EStepCountersignStatus.None && string.IsNullOrEmpty(countersignId))
+                throw UserFriendlyException.SameMessage("非法参数");
+
+            //依据是否发起会签创建step,发起会签表示一个handler创建一个step,未发起会签表示多人处理同一个节点,只创建一个step
+            var steps = new List<WorkflowStep>();
+            if (isPrevStartCountersign)
+            {
+                foreach (var handler in handlers)
+                {
+                    var step = CreateSubStep(stepBox, new List<Kv> { handler }, nextStepCode, nextMainHandler,
+                        prevStepId, countersignId, stepStatus, countersignStatus, expiredTime, extension);
+
+                    steps.Add(step);
+                }
+            }
+            else
+            {
+                var step = CreateSubStep(stepBox, handlers, nextStepCode, nextMainHandler,
+                    prevStepId, countersignId, stepStatus, countersignStatus, expiredTime, extension);
+
+                steps.Add(step);
+            }
+
+            return steps;
+        }
+
+        private WorkflowStep CreateSubStep(
+            WorkflowStep stepBox,
+            List<Kv> handlers,
+            string nextStepCode,
+            string? nextMainHandler,
+            string? prevStepId,
+            string? countersignId,
+            EWorkflowStepStatus stepStatus,
+            EStepCountersignStatus countersignStatus,
+            DateTime expiredTime,
+            StepExtension extension)
+        {
+            if (!handlers.Any())
+                throw new UserFriendlyException("非法参数");
+            var step = _mapper.Map<WorkflowStep>(stepBox);
+            var handlerIds = handlers.Select(d => d.Id).ToList();
+            var isMain = handlers.Count == 1 || (handlers.Count > 1 || handlerIds.First() == nextMainHandler);
+
+            step.ParentId = stepBox.Id;
+            step.AssignedHandlers = handlers;
+            step.NextStepCode = step.StepType is EStepType.End ? string.Empty : nextStepCode;
+            step.IsMain = isMain;
+            step.PreviousStepId = prevStepId;
+            step.CountersignId = countersignId;
+            step.Status = stepStatus;
+            step.StepCountersignStatus = countersignStatus;
+            step.ExpiredTime = expiredTime;
+            step.TimeLimit = GetTimeLimit("");//todo 过期时间
+            step.Extension = extension;
+
+            return step;
+        }
+
+        /// <summary>
+        /// 依据配置生成过期时间
+        /// </summary>
+        /// <returns></returns>
+        private DateTime CalculateExpiredTime(string defineCode, DateTime? time = null)
+        {
+            time ??= DateTime.Now;
+            var config = GetConfig(defineCode);
+            return time.Value.AddDays(config.Days);
+        }
+
+        private string GetTimeLimit(string defineCode)
+        {
+            return GetConfig(defineCode).Description;
+        }
+
+        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; }
+    }
+}

+ 65 - 66
src/Hotline/FlowEngine/Workflows/WorkflowStep.cs

@@ -7,13 +7,13 @@ namespace Hotline.FlowEngine.Workflows;
 public class WorkflowStep : StepBasicEntity
 {
     [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<StepSimple> NextSteps { get; set; }
+    public List<StepSimple> NextSteps { get; set; } = new();
 
     /// <summary>
     /// 前一级节点Id(stepBox此字段为上级stepBoxId,step为上级stepId),汇总节点无此字段(因可能有多个上级来源)
     /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? PreviousId { get; set; }
+    public string? PreviousStepId { get; set; }
+    public string? PreviousStepCode { get; set; }
 
     /// <summary>
     /// 主办
@@ -25,103 +25,102 @@ public class WorkflowStep : StepBasicEntity
     /// </summary>
     public EWorkflowStepStatus Status { get; set; } = EWorkflowStepStatus.WaitForAccept;
 
+
+    #region 会签
+
     /// <summary>
-    /// stepBox此字段无效,记录stepBox与其下subSteps关系
+    /// 发起会签节点Id
     /// </summary>
     public string? ParentId { get; set; }
 
+    /// <summary>
+    /// 会签办理节点
+    /// </summary>
     [SugarColumn(IsIgnore = true)]
     public List<WorkflowStep> Steps { get; set; } = new();
 
-    #region 会签
-
     /// <summary>
     /// 发起会签生成会签Id(不发起会签节点无此字段)
     /// </summary>
-    [SugarColumn(IsNullable = true)]
     public string? StartCountersignId { get; set; }
 
     /// <summary>
-    /// 会签id
+    /// 会签id(或外层会签的id)
     /// </summary>
-    [SugarColumn(IsNullable = true)]
     public string? CountersignId { get; set; }
 
     /// <summary>
-    /// 当前节点发起的会签是否已结束,冗余(未发起会签的节点此字段为null)
+    /// 当前节点发起的会签是否已汇总,冗余(未发起会签的节点此字段为null)
     /// <remarks>
     /// 发起的会签结束以后更新,如果有子会签,则所有子会签都结束以后才会更新此字段
-    /// </remarks>
+    /// </remarks>¡
     /// </summary>
-    public bool? IsStartedCountersignComplete { get; set; }
+    public bool? IsStartedCountersignSummary { get; set; }
 
     /// <summary>
-    /// 节点会签状态
+    /// 节点当前会签状态(区别直接办理会签和会签内非会签节点)
     /// </summary>
-    public EStepCountersignStatus StepCountersignStatus { get; set; }
+    public EStepCountersignStatus StepCountersignStatus { get; set; } //todo 无必要
 
-    /// <summary>
-    /// 发起会签节点code(冗余)
-    /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? CountersignStartStepCode { get; set; }
 
-    /// <summary>
-    /// 会签汇总节点code(冗余)
-    /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? CountersignEndStepCode { get; set; }
+    // /// <summary>
+    // /// 发起会签节点code(冗余)
+    // /// </summary>
+    // [SugarColumn(IsNullable = true)]
+    // public string? CountersignStartStepCode { get; set; }
+    //
+    // /// <summary>
+    // /// 会签汇总节点code(冗余)
+    // /// </summary>
+    // [SugarColumn(IsNullable = true)]
+    // public string? CountersignEndStepCode { get; set; }
+
+    #endregion
+
+    #region Method
 
     /// <summary>
     /// 是否处于会签流程中
     /// </summary>
-    [SugarColumn(IsIgnore = true)]
-    public bool IsInCountersign => !string.IsNullOrEmpty(CountersignId);
+    public bool IsInCountersign() => !string.IsNullOrEmpty(CountersignId);
 
     /// <summary>
     /// 是否发起过会签
     /// </summary>
-    [SugarColumn(IsIgnore = true)]
-    public bool HasStartedCountersign => !string.IsNullOrEmpty(StartCountersignId);
-
-    #endregion
-
-    #region Method
+    public bool HasStartedCountersign() => !string.IsNullOrEmpty(StartCountersignId);
 
     /// <summary>
     /// 接办
     /// </summary>
-    /// <param name="userId"></param>
-    /// <param name="userName"></param>
-    public void Accept(string userId, string? userName, string orgCode, string? orgName)
+    public void Accept(string userId, string? userName, string orgId, string? orgName, string? orgAreaCode,
+        string? orgAreaName)
     {
-        AcceptUserId = userId;
-        AcceptUserName = userName;
+        AcceptorId = userId;
+        AcceptorName = userName;
+        AcceptorOrgId = orgId;
+        AcceptorOrgName = orgName;
+        AcceptorOrgAreaCode = orgAreaCode;
+        AcceptorOrgAreaName = orgAreaName;
         AcceptTime = DateTime.Now;
-        AcceptOrgCode = orgCode;
-        AcceptOrgName = orgName;
         Status = EWorkflowStepStatus.Handling;
     }
 
     /// <summary>
     /// 节点办理完成
     /// </summary>
-    /// <param name="userId"></param>
-    /// <param name="userName"></param>
-    /// <param name="orgCode"></param>
-    /// <param name="orgName"></param>
-    public void Complete(string userId, string? userName, string orgCode, string? orgName, string? areaCode, string? areaName, string nextStepCode)
+    public void Handled(string userId, string? userName, string orgId, string? orgName, string? orgAreaCode,
+        string? orgAreaName, string nextStepCode)
     {
-        UserId = userId;
-        UserName = userName;
-        OrgCode = orgCode;
-        OrgName = orgName;
-        OrgAreaCode = areaCode;
-        OrgAreaName = areaName;
-        CompleteTime = DateTime.Now;
+        HandlerId = userId;
+        HandlerName = userName;
+        HandlerOrgId = orgId;
+        HandlerOrgName = orgName;
+        HandlerOrgAreaCode = orgAreaCode;
+        HandlerOrgAreaName = orgAreaName;
+        HandleTime = DateTime.Now;
         Status = EWorkflowStepStatus.Handled;
 
-        if (StepType is not EStepType.End or EStepType.Start)
+        if (StepType is not EStepType.End)
             NextSteps.First(d => d.Code == nextStepCode).Selected = true;
     }
 
@@ -133,14 +132,14 @@ public class WorkflowStep : StepBasicEntity
         if (Status is not EWorkflowStepStatus.Handled && Steps.All(d => d.Status is EWorkflowStepStatus.Handled))
         {
             Status = EWorkflowStepStatus.Handled;
-            CompleteTime = Steps.Max(d => d.CompleteTime);
+            HandleTime = Steps.Max(d => d.HandleTime);
         }
     }
 
     /// <summary>
     /// 会签结束
     /// </summary>
-    public void CountersignComplete() => IsStartedCountersignComplete = true;
+    public void CountersignComplete() => IsStartedCountersignSummary = true;
 
     /// <summary>
     /// 开启会签
@@ -148,7 +147,7 @@ public class WorkflowStep : StepBasicEntity
     public void StartCountersign(string startCountersignId)
     {
         StartCountersignId = startCountersignId;
-        IsStartedCountersignComplete = false;
+        IsStartedCountersignSummary = false;
     }
 
     /// <summary>
@@ -166,6 +165,7 @@ public class WorkflowStep : StepBasicEntity
         {
             return EStepCountersignStatus.InCountersign;
         }
+
         return IsInCountersign ? EStepCountersignStatus.OuterCountersign : EStepCountersignStatus.None;
     }
 
@@ -178,23 +178,22 @@ public class WorkflowStep : StepBasicEntity
         NextHandlers = new();
         NextMainHandler = null;
         NextStepCode = string.Empty;
-        AcceptSms = default;
+        IsSms = default;
         Opinion = string.Empty;
         Additions = new();
 
-        UserId = null;
-        UserName = null;
-        OrgCode = null;
-        OrgName = null;
-        CompleteTime = null;
+        HandlerId = null;
+        HandlerName = null;
+        HandlerOrgId = null;
+        HandlerOrgName = null;
+        HandleTime = null;
 
-        AcceptUserId = null;
-        AcceptUserName = null;
-        AcceptOrgCode = null;
-        AcceptOrgName = null;
+        AcceptorId = null;
+        AcceptorName = null;
+        AcceptorOrgId = null;
+        AcceptorOrgName = null;
         AcceptTime = null;
     }
 
     #endregion
-
 }

+ 200 - 0
src/Hotline/FlowEngine/Workflows/WorkflowStep1.cs

@@ -0,0 +1,200 @@
+using Hotline.Share.Dtos;
+using Hotline.Share.Enums.FlowEngine;
+using SqlSugar;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public class WorkflowStep1 : StepBasicEntity
+{
+    [SugarColumn(ColumnDataType = "json", IsJson = true)]
+    public List<StepSimple> NextSteps { get; set; }
+
+    /// <summary>
+    /// 前一级节点Id(stepBox此字段为上级stepBoxId,step为上级stepId),汇总节点无此字段(因可能有多个上级来源)
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? PreviousId { get; set; }
+
+    /// <summary>
+    /// 主办
+    /// </summary>
+    public bool IsMain { get; set; }
+
+    /// <summary>
+    /// 节点办理状态
+    /// </summary>
+    public EWorkflowStepStatus Status { get; set; } = EWorkflowStepStatus.WaitForAccept;
+
+    /// <summary>
+    /// stepBox此字段无效,记录stepBox与其下subSteps关系
+    /// </summary>
+    public string? ParentId { get; set; }
+
+    [SugarColumn(IsIgnore = true)]
+    public List<WorkflowStep> Steps { get; set; } = new();
+
+    #region 会签
+
+    /// <summary>
+    /// 发起会签生成会签Id(不发起会签节点无此字段)
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? StartCountersignId { get; set; }
+
+    /// <summary>
+    /// 会签id
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? CountersignId { get; set; }
+
+    /// <summary>
+    /// 当前节点发起的会签是否已结束,冗余(未发起会签的节点此字段为null)
+    /// <remarks>
+    /// 发起的会签结束以后更新,如果有子会签,则所有子会签都结束以后才会更新此字段
+    /// </remarks>¡
+    /// </summary>
+    public bool? IsStartedCountersignComplete { get; set; }
+
+    /// <summary>
+    /// 节点会签状态
+    /// </summary>
+    public EStepCountersignStatus StepCountersignStatus { get; set; }
+
+    /// <summary>
+    /// 发起会签节点code(冗余)
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? CountersignStartStepCode { get; set; }
+
+    /// <summary>
+    /// 会签汇总节点code(冗余)
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? CountersignEndStepCode { get; set; }
+
+    /// <summary>
+    /// 是否处于会签流程中
+    /// </summary>
+    [SugarColumn(IsIgnore = true)]
+    public bool IsInCountersign => !string.IsNullOrEmpty(CountersignId);
+
+    /// <summary>
+    /// 是否发起过会签
+    /// </summary>
+    [SugarColumn(IsIgnore = true)]
+    public bool HasStartedCountersign => !string.IsNullOrEmpty(StartCountersignId);
+
+    #endregion
+
+    #region Method
+
+    /// <summary>
+    /// 接办
+    /// </summary>
+    /// <param name="userId"></param>
+    /// <param name="userName"></param>
+    public void Accept(string userId, string? userName, string orgCode, string? orgName)
+    {
+        AcceptorId = userId;
+        AcceptorName = userName;
+        AcceptTime = DateTime.Now;
+        AcceptorOrgId = orgCode;
+        AcceptorOrgName = orgName;
+        Status = EWorkflowStepStatus.Handling;
+    }
+
+    /// <summary>
+    /// 节点办理完成
+    /// </summary>
+    /// <param name="userId"></param>
+    /// <param name="userName"></param>
+    /// <param name="orgCode"></param>
+    /// <param name="orgName"></param>
+    public void Complete(string userId, string? userName, string orgCode, string? orgName, string? areaCode, string? areaName, string nextStepCode)
+    {
+        HandlerId = userId;
+        HandlerName = userName;
+        HandlerOrgId = orgCode;
+        HandlerOrgName = orgName;
+        HandlerOrgAreaCode = areaCode;
+        HandlerOrgAreaName = areaName;
+        HandleTime = DateTime.Now;
+        Status = EWorkflowStepStatus.Handled;
+
+        if (StepType is not EStepType.End or EStepType.Start)
+            NextSteps.First(d => d.Code == nextStepCode).Selected = true;
+    }
+
+    /// <summary>
+    /// 检查stepBox状态,如果子节点全都办理,则stepBox办理完成
+    /// </summary>
+    public void CheckStepBoxStatusAndUpdate()
+    {
+        if (Status is not EWorkflowStepStatus.Handled && Steps.All(d => d.Status is EWorkflowStepStatus.Handled))
+        {
+            Status = EWorkflowStepStatus.Handled;
+            HandleTime = Steps.Max(d => d.HandleTime);
+        }
+    }
+
+    /// <summary>
+    /// 会签结束
+    /// </summary>
+    public void CountersignComplete() => IsStartedCountersignComplete = true;
+
+    /// <summary>
+    /// 开启会签
+    /// </summary>
+    public void StartCountersign(string startCountersignId)
+    {
+        StartCountersignId = startCountersignId;
+        IsStartedCountersignComplete = false;
+    }
+
+    /// <summary>
+    /// step设置为可接办状态
+    /// </summary>
+    public void SetAssigned() => Status = EWorkflowStepStatus.WaitForAccept;
+
+    /// <summary>
+    /// 依据当前节点获取下一节点会签状态
+    /// </summary>
+    /// <returns></returns>
+    public EStepCountersignStatus GetNextStepCountersignStatus()
+    {
+        if (HasStartedCountersign)
+        {
+            return EStepCountersignStatus.InCountersign;
+        }
+        return IsInCountersign ? EStepCountersignStatus.OuterCountersign : EStepCountersignStatus.None;
+    }
+
+    /// <summary>
+    /// 重置节点,只清除办理痕迹(退回场景,将上一级节点重置等待重新办理)
+    /// </summary>
+    public void Reset()
+    {
+        SetAssigned();
+        NextHandlers = new();
+        NextMainHandler = null;
+        NextStepCode = string.Empty;
+        IsSms = default;
+        Opinion = string.Empty;
+        Additions = new();
+
+        HandlerId = null;
+        HandlerName = null;
+        HandlerOrgId = null;
+        HandlerOrgName = null;
+        HandleTime = null;
+
+        AcceptorId = null;
+        AcceptorName = null;
+        AcceptorOrgId = null;
+        AcceptorOrgName = null;
+        AcceptTime = null;
+    }
+
+    #endregion
+
+}

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

@@ -36,13 +36,13 @@ public class WorkflowTrace : StepBasicEntity
 
     public void Jump(string userId, string? userName, string orgCode, string? orgName, string opinion)
     {
-        if (string.IsNullOrEmpty(AcceptUserId))
+        if (string.IsNullOrEmpty(AcceptorId))
         {
             //未接办
-            AcceptUserId = userId;
-            AcceptUserName = userName;
-            AcceptOrgCode = orgCode;
-            AcceptOrgName = orgName;
+            AcceptorId = userId;
+            AcceptorName = userName;
+            AcceptorOrgId = orgCode;
+            AcceptorOrgName = orgName;
             AcceptTime = DateTime.Now;
         }
         Complete(userId, userName, orgCode, orgName);
@@ -51,11 +51,11 @@ public class WorkflowTrace : StepBasicEntity
 
     public void Complete(string userId, string? userName, string orgCode, string? orgName)
     {
-        OrgCode = orgCode;
-        OrgName = orgName;
-        UserId = userId;
-        UserName = userName;
-        CompleteTime = DateTime.Now;
+        HandlerOrgId = orgCode;
+        HandlerOrgName = orgName;
+        HandlerId = userId;
+        HandlerName = userName;
+        HandleTime = DateTime.Now;
     }
 
     #endregion

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

@@ -43,7 +43,7 @@ public class OrderComplain : OrderExtensionEntity
     /// 诉求内容,多选
     /// </summary>
     [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<IdName> ComplainTypes { get; set; } = new();
+    public List<Kv> ComplainTypes { get; set; } = new();
 
     #endregion
 }

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

@@ -85,6 +85,6 @@ public class OrderVisit : CreationEntity
     /// 当前评价结果
     /// </summary>
     [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-    public IdName? NowEvaluate { get; set; }
+    public Kv? NowEvaluate { get; set; }
 
 }

+ 3 - 3
src/Hotline/Orders/OrderVisitDetail.cs

@@ -32,19 +32,19 @@ namespace Hotline.Orders
         /// 部门办件结果
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-        public IdName? OrgProcessingResults { get; set; }
+        public Kv? OrgProcessingResults { get; set; }
 
         /// <summary>
         /// 不满意原因
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-        public List<IdName>? OrgNoSatisfiedReason { get; set; }
+        public List<Kv>? OrgNoSatisfiedReason { get; set; }
 
         /// <summary>
         /// 部门办件态度
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
-        public IdName? OrgHandledAttitude { get; set; }
+        public Kv? OrgHandledAttitude { get; set; }
 
         /// <summary>
         /// 回访内容

+ 6 - 6
src/Hotline/SeedData/Codes/ICode.cs

@@ -9,30 +9,30 @@ namespace Hotline.SeedData.Codes
     /// </summary>
     public interface ICode
     {
-        IReadOnlyList<IdName> GetAll<TThis>();
+        IReadOnlyList<Kv> GetAll<TThis>();
     }
 
     public interface ICode<TTarget> : ICode where TTarget : class
     {
-        IReadOnlyList<TTarget> GetAll(Func<IdName, TTarget> construct);
+        IReadOnlyList<TTarget> GetAll(Func<Kv, TTarget> construct);
     }
 
     public class ConstsHelper
     {
-        public static IEnumerable<IdName> GetAll<TSource>(TSource source)
+        public static IEnumerable<Kv> GetAll<TSource>(TSource source)
         {
             var fields = typeof(TSource).GetFields();
             foreach (var fieldInfo in fields)
             {
                 var value = fieldInfo.GetValue(source);
-                if (value is IdName idname)
+                if (value is Kv idname)
                 {
                     yield return idname;
                 }
             }
         }
 
-        public static IEnumerable<TDest> GetAll<TSource, TDest>(TSource source, Func<IdName, TDest> construct)
+        public static IEnumerable<TDest> GetAll<TSource, TDest>(TSource source, Func<Kv, TDest> construct)
         {
             var idnames = GetAll(source);
             foreach (var idname in idnames)
@@ -45,7 +45,7 @@ namespace Hotline.SeedData.Codes
     public class TestDemo : IConstTable<WorkflowModule>
     {
         public static readonly KeyValuePair<string, string> OrderManage = new("OrderManage", "工单办理");
-        public static readonly IdName OrderManage1 = new IdName("OrderManage1", "工单办理1");
+        public static readonly Kv OrderManage1 = new Kv("OrderManage1", "工单办理1");
         public IEnumerable<WorkflowModule> GetData<TSource>(TSource source) =>
             ConstsHelper.GetAll(new TestDemo(), d => new WorkflowModule(d.Id, d.Name));
     }

+ 14 - 11
src/Hotline/SeedData/OrgSeedData.cs

@@ -6,19 +6,22 @@ namespace Hotline.SeedData
 {
     public class OrgSeedData : ISeedData<SystemOrganize>
     {
-        //public static readonly string CenterId = "08dac6ae-3096-4156-828e-b34e9c1fb5da";
+        public static readonly string CenterId = "08dac6ae-3096-4156-828e-b34e9c1fb5da";
         public static readonly string CenterCode = "001";
 
         public IEnumerable<SystemOrganize> HasData() =>
-        new[]
-        {
-            new SystemOrganize
+            new[]
             {
-                Id = CenterCode,
-                OrgName = "12345政务服务便民热线",
-                OrgLevel = 0,
-                IsEnable = true,
-            }
-        };
+                new SystemOrganize
+                {
+                    Id = CenterId,
+                    Code = CenterCode,
+                    Name = "12345政务服务便民热线",
+                    ShortName = "12345中心",
+                    Level = 1,
+                    IsCenter = true,
+                    IsEnable = true,
+                }
+            };
     }
-}
+}

+ 6 - 0
src/Hotline/Settings/ISystemDomainService.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Share.Dtos.Org;
 
 namespace Hotline.Settings
 {
@@ -40,5 +41,10 @@ namespace Hotline.Settings
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         Task<int> IsShrotNameRepeat(string orgShrotName, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 新增部门
+        /// </summary>
+        Task AddAsync(AddOrgDto dto, CancellationToken cancellationToken);
     }
 }

+ 26 - 11
src/Hotline/Settings/SystemDomainService.cs

@@ -1,5 +1,8 @@
-using XF.Domain.Dependency;
+using Hotline.SeedData;
+using Hotline.Share.Dtos.Org;
+using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
+using XF.Domain.Extensions;
 using XF.Domain.Repository;
 
 namespace Hotline.Settings;
@@ -17,29 +20,33 @@ public class SystemDomainService : ISystemDomainService, IScopeDependency
         _sysDicDataRepository = sysDicDataRepository;
     }
 
-    public async Task<IEnumerable<KeyValuePair<int, string>>> QueryOrgLevelOptionsAsync(CancellationToken cancellationToken)
+    public async Task<IEnumerable<KeyValuePair<int, string>>> QueryOrgLevelOptionsAsync(
+        CancellationToken cancellationToken)
     {
         var max = await _organizeRepository.Queryable()
-            .MaxAsync(d => d.OrgLevel);
+            .MaxAsync(d => d.Level);
 
         var rsp = new List<KeyValuePair<int, string>>();
         for (int i = 1; i <= max; i++)
         {
-            rsp.Add(new KeyValuePair<int, string>(i, $"{i}级部门"));
+            rsp.Add(new KeyValuePair<int, string>(i, $"{i.ToChinese()}级部门"));
         }
 
         return rsp;
     }
 
-    public async Task<IEnumerable<KeyValuePair<string, string>>> QueryOrgLevelStringOptionsAsync(CancellationToken cancellationToken)
+    public async Task<IEnumerable<KeyValuePair<string, string>>> QueryOrgLevelStringOptionsAsync(
+        CancellationToken cancellationToken)
     {
         var rsp = await QueryOrgLevelOptionsAsync(cancellationToken);
         return rsp.Select(d => new KeyValuePair<string, string>(d.Key.ToString(), d.Value));
     }
 
-    public async Task<IReadOnlyList<SystemDicData>> GetSysDicDataByCodeAsync(string code, CancellationToken cancellationToken = default)
+    public async Task<IReadOnlyList<SystemDicData>> GetSysDicDataByCodeAsync(string code,
+        CancellationToken cancellationToken = default)
     {
-        return await _sysDicDataRepository.Queryable().Where(x => x.DicTypeCode == code).ToTreeAsync(x => x.Children, x => x.ParentId, "");
+        return await _sysDicDataRepository.Queryable().Where(x => x.DicTypeCode == code)
+            .ToTreeAsync(x => x.Children, x => x.ParentId, "");
     }
 
     /// <summary>
@@ -54,8 +61,8 @@ public class SystemDomainService : ISystemDomainService, IScopeDependency
         {
             //一级部门
             var maxCode = await _organizeRepository.Queryable()
-                 .Where(d => d.OrgLevel == 1)
-                 .MaxAsync(d => d.Id, cancellationToken);
+                .Where(d => d.Level == 1)
+                .MaxAsync(d => d.Code, cancellationToken);
 
             if (!int.TryParse(maxCode.GetLastOrgCode(), out var max))
                 throw new UserFriendlyException("无效部门编码");
@@ -88,7 +95,7 @@ public class SystemDomainService : ISystemDomainService, IScopeDependency
     /// <returns></returns>
     public async Task<int> IsNameRepeat(string orgname, CancellationToken cancellationToken)
     {
-        return await _organizeRepository.CountAsync(x => x.OrgName == orgname, cancellationToken);
+        return await _organizeRepository.CountAsync(x => x.Name == orgname, cancellationToken);
     }
 
     /// <summary>
@@ -99,6 +106,14 @@ public class SystemDomainService : ISystemDomainService, IScopeDependency
     /// <returns></returns>
     public async Task<int> IsShrotNameRepeat(string orgShrotName, CancellationToken cancellationToken)
     {
-        return await _organizeRepository.CountAsync(x => x.OrgShortName == orgShrotName, cancellationToken);
+        return await _organizeRepository.CountAsync(x => x.ShortName == orgShrotName, cancellationToken);
+    }
+
+    /// <summary>
+    /// 新增部门
+    /// </summary>
+    public Task AddAsync(AddOrgDto dto, CancellationToken cancellationToken)
+    {
+        
     }
 }

+ 30 - 37
src/Hotline/Settings/SystemOrganize.cs

@@ -7,65 +7,57 @@ using XF.Utility.EnumExtensions;
 
 namespace Hotline.Settings;
 
-/// <summary>
-/// Id:(001001001) 解析:001,001,001)
-/// </summary>
-
-//[SugarIndex("unique_org_code", nameof(SystemOrganize.OrgCode), OrderByType.Desc, true)]
+[SugarIndex("unique_org_code", nameof(SystemOrganize.Code), OrderByType.Desc, true)]
 [Description("组织架构")]
 public class SystemOrganize : CreationSoftDeleteEntity
 {
+    /// <summary>
+    /// 编码
+    /// <remarks>
+    /// (001001001) 解析:001,001,001
+    /// 部门编码结构中,中心为顶级
+    /// </remarks>
+    /// </summary>
+    public string Code { get; set; }
+    
     /// <summary>
     /// 组织架构名称
     /// </summary>
-    public string OrgName { get; set; }
+    public string Name { get; set; }
 
     /// <summary>
     /// 组织架构简称
     /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string OrgShortName { get; set; }
-
-    ///// <summary>
-    ///// 组织架构Code(001001001) 解析:001,001,001)
-    ///// </summary>
-    //public string OrgCode { get; set; }
+    public string ShortName { get; set; }
 
     /// <summary>
     /// 区域Code(行政区域代码)
     /// </summary>
-    [SugarColumn(IsNullable = true)]
     public string AreaCode { get; set; }
 
     /// <summary>
     /// 区域名称(行政区域名称)
     /// </summary>
-    [SugarColumn(IsNullable = true)]
     public string AreaName { get; set; }
 
     /// <summary>
     /// 部门级别
     /// </summary>
-    public int OrgLevel { get; set; }
+    public int Level { get; set; }
 
     /// <summary>
     /// 部门类型
     /// </summary>
     public EOrgType OrgType { get; set; }
 
-    [SugarColumn(IsIgnore = true)]
-    public string OrgTypeText => OrgType.GetDescription();
-
     /// <summary>
     /// 上级ID
     /// </summary>
-    [SugarColumn(IsNullable = true)]
     public string? ParentId { get; set; }
 
     /// <summary>
     /// 上级名称
     /// </summary>
-    [SugarColumn(IsNullable = true)]
     public string? ParentName { get; set; }
 
     /// <summary>
@@ -73,32 +65,32 @@ public class SystemOrganize : CreationSoftDeleteEntity
     /// </summary>
     public bool IsEnable { get; set; }
 
+    /// <summary>
+    /// 是否为中心
+    /// </summary>
+    public bool IsCenter { get; set; }
+
     [SugarColumn(IsIgnore = true)]
     public List<SystemOrganize> Children { get; set; }
 
+    public void InitOrgLevel() => Level = Id.CalcOrgLevel();
 
-    public void InitOrgLevel() => OrgLevel = Id.CalcOrgLevel();
+    public string OrgTypeText() => OrgType.GetDescription();
 }
 
 public static class OrgExtensions
 {
     public static int CalcOrgLevel(this string orgCode)
     {
+        //中心算做一级部门
+        if (orgCode == OrgSeedData.CenterCode) return 1;
         if (orgCode.Length % 3 != 0)
             throw new UserFriendlyException("非法部门Code");
-        //return (orgCode.Length / 3) - 1;
-        return orgCode.Length / 3;
+        return (orgCode.Length / 3) - 1;
     }
 
-    public static bool CheckIfOrgLevelIs(this string orgCode, int orgLevel)
-    {
-        ////中心算做一级部门
-        //if (orgLevel == 1 && orgCode == OrgSeedData.CenterCode)
-        //    return true;
-
-        var level = orgCode.CalcOrgLevel();
-        return level == orgLevel;
-    }
+    public static bool CheckIfOrgLevelIs(this string orgCode, int orgLevel) => 
+        orgLevel == CalcOrgLevel(orgCode);
 
     /// <summary>
     /// 根据orgCode获取该部门所属对应级别的上级部门code
@@ -106,14 +98,15 @@ public static class OrgExtensions
     /// <param name="orgCode"></param>
     /// <param name="orgLevel">默认一级部门</param>
     /// <returns></returns>
-    public static string GetUpperOrgCode(this string orgCode, int orgLevel = 1)
+    public static string GetHigherOrgCode(this string orgCode, int orgLevel = 1)
     {
         if (string.IsNullOrEmpty(orgCode))
             throw UserFriendlyException.SameMessage("无效部门编码");
         ////中心算做一级部门
         //if (orgLevel == 1 && orgCode == OrgSeedData.CenterCode) return orgCode;
 
-        return orgCode.Substring(0, 3 * orgLevel);
+        // return orgCode.Substring(0, 3 * orgLevel);
+        return orgCode.Substring(0, 3 * (orgLevel + 1));
     }
 
     /// <summary>
@@ -137,7 +130,7 @@ public static class OrgExtensions
     {
         if (string.IsNullOrEmpty(orgCode))
             throw UserFriendlyException.SameMessage("无效部门编码");
-        var upperOrg = orgCode.GetUpperOrgCode();
+        var upperOrg = orgCode.GetHigherOrgCode();
         return IsCenter(upperOrg);
     }
-}
+}

+ 22 - 0
src/XF.Domain/Extensions/OrgExtensions.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
@@ -28,5 +29,26 @@ namespace XF.Domain.Extensions
             }
             return rsp;
         }
+
+        /// <summary>
+        /// 部门等级转为中文(支持1~10,如有需要自行扩展)
+        /// </summary>
+        /// <param name="orgLevel"></param>
+        /// <returns></returns>
+        public static string ToChinese(this int orgLevel) =>
+            orgLevel switch
+            {
+                1 => "一",
+                2 => "二",
+                3 => "三",
+                4 => "四",
+                5 => "五",
+                6 => "六",
+                7 => "七",
+                8 => "八",
+                9 => "九",
+                10 => "十",
+                _ => throw new ArgumentOutOfRangeException(nameof(orgLevel), orgLevel, "该部门等级暂不支持中文表示")
+            };
     }
 }