Explorar o código

Merge branch 'master' of http://git.fwt.com/Hotline/hotline

TANG JIANG %!s(int64=2) %!d(string=hai) anos
pai
achega
cc96bfa7a1

+ 1 - 0
src/Hotline.Api/Controllers/OrderController.cs

@@ -91,6 +91,7 @@ public class OrderController : BaseController
             .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart)
             .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.ExpiredTime <= dto.ExpiredTimeEnd)
             .WhereIF(dto.Statuses.Any(), d => dto.Statuses.Contains(d.Status))
+            .OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));

+ 4 - 0
src/Hotline.Api/Controllers/RoleController.cs

@@ -27,6 +27,7 @@ public class RoleController : BaseController
     private readonly ITableAccessLevelRepository _tableAccessLevelRepository;
     private readonly IMapper _mapper;
     private readonly ITableAccessLevelCacheManager _tableAccessLevelCacheManager;
+    private readonly IRolePermissionsCacheManager _rolePermissionsCacheManager;
     private readonly IOptions<IdentityConfiguration> _identityConfigurationAccessor;
     private readonly ISystemDataTableRepository _systemDataTableRepository;
 
@@ -36,6 +37,7 @@ public class RoleController : BaseController
         ISystemDataAuthorityRepository systemDataAuthorityRepository,
         ITableAccessLevelRepository tableAccessLevelRepository,
         ITableAccessLevelCacheManager tableAccessLevelCacheManager,
+        IRolePermissionsCacheManager rolePermissionsCacheManager,
         IMapper mapper,
         IOptions<IdentityConfiguration> identityConfigurationAccessor,
         ISystemDataTableRepository systemDataTableRepository)
@@ -45,6 +47,7 @@ public class RoleController : BaseController
         _systemDataAuthorityRepository = systemDataAuthorityRepository;
         _tableAccessLevelRepository = tableAccessLevelRepository;
         _tableAccessLevelCacheManager = tableAccessLevelCacheManager;
+        _rolePermissionsCacheManager = rolePermissionsCacheManager;
         _mapper = mapper;
         _identityConfigurationAccessor = identityConfigurationAccessor;
         _systemDataTableRepository = systemDataTableRepository;
@@ -150,6 +153,7 @@ public class RoleController : BaseController
             _mapper.Map(dto, model);
             await _systemAuthorityRepository.UpdateAsync(model);
         }
+        _rolePermissionsCacheManager.RemovePermissions(dto.RoleCode);
     }
 
     /// <summary>

+ 12 - 5
src/Hotline.Api/Permissions/IPermissionManager.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Caches;
 using XF.Domain.Dependency;
 
 namespace Hotline.Permissions
@@ -15,19 +16,25 @@ namespace Hotline.Permissions
 
     public class PermissionManager : IPermissionManager, ISingletonDependency
     {
-        private readonly ISystemAuthorityRepository _systemauthorityRepository;
+        private readonly IServiceScopeFactory _serviceScopeFactory;
 
-        public PermissionManager(IServiceScopeFactory factory)
+        public PermissionManager(IServiceScopeFactory serviceScopeFactory)
         {
-            _systemauthorityRepository = factory.CreateScope().ServiceProvider.GetRequiredService<ISystemAuthorityRepository>();
+            _serviceScopeFactory = serviceScopeFactory;
+
+            //_systemauthorityRepository = factory.CreateScope().ServiceProvider.GetRequiredService<ISystemAuthorityRepository>();
         }
 
 
         public IReadOnlyList<string> RolesToPermissions(List<string> roles)
         {
-            var list = _systemauthorityRepository.GetPermission(roles);
+            using var scope = _serviceScopeFactory.CreateScope();
+            var cacheManager = scope.ServiceProvider.GetService<IRolePermissionsCacheManager>();
+            return roles.SelectMany(d => cacheManager.GetPermissions(d)).ToList();
+
+            //var list = _systemauthorityRepository.GetPermission(roles);
 
-            return list.Select(x => Enum.Parse(typeof(EPermission), x).ToString()).ToList();
+            //return list.Select(x => Enum.Parse(typeof(EPermission), x).ToString()).ToList();
         }
     }
 }

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

@@ -54,10 +54,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
     public async Task<string> StartWorkflowAsync(StartWorkflowDto dto, CancellationToken cancellationToken = default)
     {
-        //var validator = new StartWorkflowDtoValidator();
-        //var validResult = validator.Validate(dto);
-        //if (!validResult.IsValid)
-        //    throw new UserFriendlyException(string.Join(',', validResult.Errors), "非法参数");
         if (string.IsNullOrEmpty(dto.DefinitionCode) && string.IsNullOrEmpty(dto.DefinitionModuleCode))
             throw new UserFriendlyException("非法参数");
 
@@ -93,12 +89,16 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, withCountersigns: true, cancellationToken: cancellationToken);
         var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+
+        //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签)
+        if (nextStepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
+            throw new UserFriendlyException("未指定节点处理者");
+
         //下一节点为结束节点时,无办理人等参数,只有办理意见即可
         var isOutOfCallCenter = nextStepBoxDefine.StepType is not EStepType.End
                                 && await CheckIfFlowOutOfCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
-        var isStartCountersign = nextStepBoxDefine.StepType is not EStepType.End
-                                 && nextStepBoxDefine.IsStartCountersign(dto.NextHandlers.Count);
 
+        var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.NextHandlers.Count);
         if (isStartCountersign && nextStepBoxDefine.StepType is EStepType.CountersignEnd)
             throw UserFriendlyException.SameMessage("汇总节点不允许发起会签");
 

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

@@ -50,7 +50,7 @@ namespace Hotline.Application.Mappers
                 .Ignore(d => d.IsMain)
                 .Ignore(d => d.Status)
                 .Ignore(d => d.ParentId)
-                .Ignore(d => d.HandlerId)
+                .Ignore(d => d.HandlerIds)
                 .Ignore(d => d.Steps)
                 .Ignore(d => d.StartCountersignId)
                 .Ignore(d => d.CountersignId)

+ 45 - 0
src/Hotline.CacheManager/RolePermissionsCacheManager.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Caches;
+using Hotline.Settings;
+using XF.Domain.Cache;
+using XF.Domain.Dependency;
+
+namespace Hotline.CacheManager
+{
+    public class RolePermissionsCacheManager : IRolePermissionsCacheManager, IScopeDependency
+    {
+        private const string RolePermissionCacheKey = "RolePermissionCache_";
+        private readonly ITypedCache<IReadOnlyList<string>> _cache;
+        private readonly ISystemAuthorityRepository _systemAuthorityRepository;
+
+        public RolePermissionsCacheManager(ITypedCache<IReadOnlyList<string>> cache, ISystemAuthorityRepository systemAuthorityRepository)
+        {
+            _cache = cache;
+            _systemAuthorityRepository = systemAuthorityRepository;
+        }
+
+        public IReadOnlyList<string> GetPermissions(string role)
+        {
+            return _cache.GetOrAdd(GetKey(role), d =>
+                {
+                    var systemAuth = _systemAuthorityRepository.GetAsync(d => d.RoleCode == role).GetAwaiter()
+                        .GetResult();
+                    return systemAuth.GetAllPermissions();
+                }
+            );
+        }
+
+        public void RemovePermissions(string role)
+        {
+            _cache.Remove(GetKey(role));
+        }
+
+
+        private string GetKey(string role) =>
+            $"{RolePermissionCacheKey}{role}";
+    }
+}

+ 26 - 0
src/Hotline.Share/Dtos/Roles/AddAccessLevelDto.cs

@@ -0,0 +1,26 @@
+using Hotline.Share.Enums.Settings;
+
+namespace Hotline.Share.Dtos.Roles;
+
+public record AddAccessLevelDto
+{
+    /// <summary>
+    /// 角色ID
+    /// </summary>
+    public string RoleId { get; set; }
+
+    /// <summary>
+    /// 角色Code
+    /// </summary>
+    public string RoleCode { get; set; }
+
+    /// <summary>
+    /// 访问等级
+    /// </summary>
+    public ETableAccessLevel AccessLevel { get; set; }
+
+    /// <summary>
+    /// 表名
+    /// </summary>
+    public string TableName { get; set; }
+}

+ 1 - 37
src/Hotline.Share/Dtos/Roles/RoleAuthorityDto.cs

@@ -1,6 +1,4 @@
-using Hotline.Share.Enums.Settings;
-
-namespace Hotline.Share.Dtos.Roles
+namespace Hotline.Share.Dtos.Roles
 {
     public record RoleAuthorityDto
     {
@@ -13,38 +11,4 @@ namespace Hotline.Share.Dtos.Roles
         public List<string> SystemButtonArr { get; set; }
 
     }
-
-    public record AddAccessLevelDto
-    {
-        /// <summary>
-        /// 角色ID
-        /// </summary>
-        public string RoleId { get; set; }
-
-        /// <summary>
-        /// 角色Code
-        /// </summary>
-        public string RoleCode { get; set; }
-
-        /// <summary>
-        /// 访问等级
-        /// </summary>
-        public ETableAccessLevel AccessLevel { get; set; }
-
-        /// <summary>
-        /// 表名
-        /// </summary>
-        public string TableName { get; set; }
-    }
-
-    public record UpdateDataAuthorityDto
-    {
-        public string Id { get; set; }
-
-        /// <summary>
-        /// 访问等级
-        /// </summary>
-        public ETableAccessLevel AccessLevel { get; set; }
-    }
-
 }

+ 13 - 0
src/Hotline.Share/Dtos/Roles/UpdateDataAuthorityDto.cs

@@ -0,0 +1,13 @@
+using Hotline.Share.Enums.Settings;
+
+namespace Hotline.Share.Dtos.Roles;
+
+public record UpdateDataAuthorityDto
+{
+    public string Id { get; set; }
+
+    /// <summary>
+    /// 访问等级
+    /// </summary>
+    public ETableAccessLevel AccessLevel { get; set; }
+}

+ 15 - 0
src/Hotline/Caches/IRolePermissionsCacheManager.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Caches
+{
+    public interface IRolePermissionsCacheManager
+    {
+        IReadOnlyList<string> GetPermissions(string role);
+
+        void RemovePermissions(string role);
+    }
+}

+ 4 - 1
src/Hotline/FlowEngine/Definitions/StepDefine.cs

@@ -10,7 +10,7 @@ namespace Hotline.FlowEngine.Definitions;
 public class StepDefine : StepBasic
 {
     public List<NextStepDefine> NextSteps { get; set; }
-    
+
     /// <summary>
     /// 是否发起会签
     /// </summary>
@@ -18,8 +18,11 @@ public class StepDefine : StepBasic
     /// <returns></returns>
     public bool IsStartCountersign(int handlerCount)
     {
+        if (StepType is EStepType.End) return false;
+
         //需求:按角色指派默认不发起会签
         if (HandlerType is EHandlerType.Role) return false;
+
         return handlerCount > 1;
     }
 }

+ 66 - 56
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -99,7 +99,7 @@ namespace Hotline.FlowEngine.Workflows
                 throw UserFriendlyException.SameMessage("未指派办理人");
 
             //开始节点
-            var (startStepBox, startStep) = await CreateStartStepAsync(workflow, dto, cancellationToken);
+            var (startStepBox, startStep) = await CreateStartStepAsync(workflow, dto, isStartCountersign, cancellationToken);
 
             if (isStartCountersign)
             {
@@ -114,7 +114,7 @@ namespace Hotline.FlowEngine.Workflows
             await NextTraceAsync(workflow, dto, startStep, cancellationToken);
 
             //第二节点(创建即为 已指派/待接办 状态)
-            var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned,
+            var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned,
                 startStepBox, startStep, cancellationToken);
 
             //更新当前节点名称、时间、会签节点code 等字段
@@ -186,12 +186,12 @@ namespace Hotline.FlowEngine.Workflows
             if (currentStep.HandlerType is EHandlerType.AssignUser or EHandlerType.Role)
             {
                 //userId
-                if (currentStep.HandlerId != userId) return;
+                if (!currentStep.HandlerIds.Contains(userId)) return;
             }
             else
             {
                 //orgId
-                if (currentStep.HandlerId != orgCode) return;
+                if (!currentStep.HandlerIds.Contains(orgCode)) return;
             }
             if (currentStep.StepType is EStepType.End)
                 throw new UserFriendlyException("当前流程已流转到最终步骤");
@@ -347,7 +347,7 @@ namespace Hotline.FlowEngine.Workflows
             }
 
             //创建下一节点(会签汇总节点不重复创建)
-            var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Created,
+            var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Created,
                 currentStepBox, currentStep, cancellationToken);
 
             //下一节点为汇总节点时,检查下一节点是否可办理
@@ -455,7 +455,8 @@ namespace Hotline.FlowEngine.Workflows
             {
                 //向后跳转
                 var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
-                var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned, currentStepBox, currentStep, cancellationToken);
+                var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.NextHandlers.Count);
+                var nextStepBox = await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned, currentStepBox, currentStep, cancellationToken);
 
                 await ResetWorkflowCurrentStepInfo(workflow, dto, nextStepBox, cancellationToken);
 
@@ -571,23 +572,19 @@ namespace Hotline.FlowEngine.Workflows
         /// <returns></returns>
         private async Task SetNextCountersignEndAssignedAsync(WorkflowStep nextStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
         {
-            WorkflowStep? nextStep;
-            if (currentStep.StepCountersignStatus is EStepCountersignStatus.InCountersign)
-            {
-                nextStep = nextStepBox.Steps.FirstOrDefault(d => d.CountersignId == currentStep.CountersignId);
-            }
-            else
-            {
-                nextStep = nextStepBox.Steps.FirstOrDefault(d => d.ParentId == currentStep.Id);
-            }
+            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();
 
-            if (nextStep == null)
-                throw new UserFriendlyException(
-                    $"未查询到下一节点, workflowId:{currentStep.WorkflowId}, currentStepId: {currentStep.Id}");
+            if (!nextSteps.Any())
+                throw new UserFriendlyException($"未查询到下一节点, currentStepId: {currentStep.Id}");
 
-            nextStep.SetAssigned();
+            foreach (var nextStep in nextSteps)
+            {
+                nextStep.SetAssigned();
+            }
 
-            await _workflowStepRepository.UpdateAsync(nextStep, cancellationToken);
+            await _workflowStepRepository.UpdateRangeAsync(nextSteps, cancellationToken);
         }
 
         /// <summary>
@@ -704,7 +701,8 @@ namespace Hotline.FlowEngine.Workflows
 
             //recreate targetStep
             var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
-            await CreateStepAsync(workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned, targetStepBox, targetStepBox.Steps.First(), cancellationToken);
+            var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.NextHandlers.Count);
+            await CreateStepAsync(isStartCountersign, workflow, nextStepBoxDefine, dto, EWorkflowStepStatus.Assigned, targetStepBox, targetStepBox.Steps.First(), cancellationToken);
 
             //flow manage
             if (workflow.IsInCountersign())
@@ -745,7 +743,7 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 创建开始节点(保存开始流程的办理意见,对应definition的start节点)
         /// </summary>
-        private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateStartStepAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
+        private async Task<(WorkflowStep stepBox, WorkflowStep step)> CreateStartStepAsync(Workflow workflow, BasicWorkflowDto dto, bool isStartCountersign, CancellationToken cancellationToken)
         {
             if (workflow.StepBoxes.Any())
                 throw UserFriendlyException.SameMessage("无法反复开始流程");
@@ -759,14 +757,14 @@ namespace Hotline.FlowEngine.Workflows
 
             //start节点的办理人分类默认为用户,即为当前发起流程的操作员
             var handler = new IdName { Id = _sessionContext.RequiredUserId, Name = _sessionContext.UserName };
-            await CreateStartSubStepAsync(handler, dto, stepBox, null, cancellationToken);
+            await CreateStartSubStepAsync(handler, dto, stepBox, cancellationToken);
             return (stepBox, stepBox.Steps.First());
         }
 
         /// <summary>
         /// 创建节点(不含开始、结束节点)
         /// </summary>
-        private async Task<WorkflowStep> CreateStepAsync(Workflow workflow, StepDefine stepBoxDefine, BasicWorkflowDto dto, EWorkflowStepStatus status,
+        private async Task<WorkflowStep> CreateStepAsync(bool isPrevStartCountersign, Workflow workflow, StepDefine stepBoxDefine, BasicWorkflowDto dto, EWorkflowStepStatus status,
             WorkflowStep? prevStepBox = null, WorkflowStep? prevStep = null, CancellationToken cancellationToken = default)
         {
             if (stepBoxDefine.StepType is EStepType.Start or EStepType.End)
@@ -792,7 +790,7 @@ namespace Hotline.FlowEngine.Workflows
                     return stepBox;
             }
 
-            await CreateSubStepsAsync(stepBoxDefine, dto, stepBox, status, prevStep, cancellationToken);
+            await CreateSubStepsAsync(isPrevStartCountersign, stepBoxDefine, dto, stepBox, status, prevStep, cancellationToken);
 
             return stepBox;
         }
@@ -801,40 +799,45 @@ namespace Hotline.FlowEngine.Workflows
             IdName handler,
             BasicWorkflowDto dto,
             WorkflowStep stepBox,
-            string? startCountersignId = null,
-            CancellationToken cancellationToken = default)
+            CancellationToken cancellationToken)
         {
-            var nextCountersignStatus = string.IsNullOrEmpty(startCountersignId)
-                ? EStepCountersignStatus.None
-                : EStepCountersignStatus.InCountersign;
-            var subStep = CreateSubStep(stepBox, handler, dto.NextStepCode, dto.NextMainHandler, null, startCountersignId, null,
-                EWorkflowStepStatus.Completed, nextCountersignStatus);
+            //开始节点既不发起会签,也不处于会签中
+            var subStep = CreateSubStep(stepBox, new List<IdName> { handler }, dto.NextStepCode, dto.NextMainHandler,
+                null, null, EWorkflowStepStatus.Completed, EStepCountersignStatus.None);
+            subStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgCode, _sessionContext.OrgName);
+            //step办理状态
+            subStep.StepComplete(
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                _sessionContext.RequiredOrgCode, _sessionContext.OrgName,
+                dto.NextStepCode);
             _mapper.Map(dto, subStep);
+
             stepBox.Steps.Add(subStep);
             await _workflowStepRepository.AddAsync(subStep, cancellationToken);
         }
 
         private async Task CreateSubStepsAsync(
+            bool isPrevStartCountersign,
             StepDefine stepBoxDefine,
             BasicWorkflowDto dto,
             WorkflowStep stepBox,
             EWorkflowStepStatus stepStatus,
-            WorkflowStep? prevStep = null,
+            WorkflowStep prevStep,
             CancellationToken cancellationToken = default)
         {
-            //开始节点无会签
-            var countersignStatus = prevStep?.GetNextStepCountersignStatus() ?? EStepCountersignStatus.None;
+            var countersignStatus = prevStep.GetNextStepCountersignStatus();
             List<WorkflowStep> subSteps;
             if (stepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.AssignOrg)
             {
-                subSteps = CreateSubSteps(stepBox, stepBox.HandlerClassifies, dto.NextStepCode, dto.NextMainHandler,
+                subSteps = CreateSubSteps(isPrevStartCountersign, stepBox, stepBox.HandlerClassifies, dto.NextStepCode, dto.NextMainHandler,
                     prevStep?.Id, prevStep?.StartCountersignId, stepStatus, countersignStatus);
             }
             else
             {
                 if (stepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
                     throw new UserFriendlyException("未指定节点处理者");
-                subSteps = CreateSubSteps(stepBox, dto.NextHandlers, dto.NextStepCode, dto.NextMainHandler,
+                subSteps = CreateSubSteps(isPrevStartCountersign, stepBox, dto.NextHandlers, dto.NextStepCode, dto.NextMainHandler,
                     prevStep?.Id, prevStep?.StartCountersignId, stepStatus, countersignStatus);
             }
             stepBox.Steps.AddRange(subSteps);
@@ -880,7 +883,7 @@ namespace Hotline.FlowEngine.Workflows
             {
                 foreach (var step in stepBox.Steps)
                 {
-                    if (predicate(step.Status) && (step.HandlerId == orgCode || step.HandlerId == userId))
+                    if (predicate(step.Status) && (step.HandlerIds.Contains(orgCode) || step.HandlerIds.Contains(userId)))
                         return (stepBox, step);
                 }
             }
@@ -899,6 +902,7 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         private List<WorkflowStep> CreateSubSteps(
+            bool isPrevStartContersign,
             WorkflowStep stepBox,
             List<IdName> handlers,
             string nextStepCode,
@@ -913,20 +917,23 @@ namespace Hotline.FlowEngine.Workflows
             if (countersignStatus is not EStepCountersignStatus.None && string.IsNullOrEmpty(countersignId))
                 throw UserFriendlyException.SameMessage("非法参数");
 
+            //依据是否发起会签创建step,发起会签表示一个handler创建一个step,未发起会签表示多人处理同一个节点,只创建一个step
             var steps = new List<WorkflowStep>();
-            foreach (var handler in handlers)
-            {
-                //var step = _mapper.Map<WorkflowStep>(stepBox);
-                //step.ParentId = stepBox.Id;
-                //step.HandlerId = handler.Id;
-                //step.IsMain = handler.Id == nextMainHandler;
-                //step.PreviousId = prevStepId;
-                //step.StartCountersignId = startCountersignId;
-                //step.CountersignId = countersignId;
-                //step.Status = stepStatus;
-
-                var step = CreateSubStep(stepBox, handler, nextMainHandler, nextStepCode,
-                    prevStepId, null, countersignId, stepStatus, countersignStatus);
+            if (isPrevStartContersign)
+            {
+                foreach (var handler in handlers)
+                {
+                    var step = CreateSubStep(stepBox, new List<IdName> { handler }, nextMainHandler, nextStepCode,
+                        prevStepId, countersignId, stepStatus, countersignStatus);
+
+                    steps.Add(step);
+                }
+            }
+            else
+            {
+                var step = CreateSubStep(stepBox, handlers, nextMainHandler, nextStepCode,
+                    prevStepId, countersignId, stepStatus, countersignStatus);
+
                 steps.Add(step);
             }
 
@@ -935,22 +942,25 @@ namespace Hotline.FlowEngine.Workflows
 
         private WorkflowStep CreateSubStep(
             WorkflowStep stepBox,
-            IdName handler,
+            List<IdName> handlers,
             string nextStepCode,
             string? nextMainHandler,
             string? prevStepId,
-            string? startCountersignId,
             string? countersignId,
             EWorkflowStepStatus stepStatus,
             EStepCountersignStatus countersignStatus)
         {
+            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 || handlerIds.First() == nextMainHandler;
+
             step.ParentId = stepBox.Id;
-            step.HandlerId = handler.Id;
+            step.HandlerIds = handlerIds;
             step.NextStepCode = nextStepCode;
-            step.IsMain = handler.Id == nextMainHandler;
+            step.IsMain = isMain;
             step.PreviousId = prevStepId;
-            step.StartCountersignId = startCountersignId;
             step.CountersignId = countersignId;
             step.Status = stepStatus;
             step.StepCountersignStatus = countersignStatus;

+ 4 - 3
src/Hotline/FlowEngine/Workflows/WorkflowStep.cs

@@ -10,9 +10,10 @@ public class WorkflowStep : StepBasicEntity
 
     /// <summary>
     /// 被指派办理对象(依据不同指派方式可能为:depCode或userId),该字段subStep才会存在,stepBox不存在
+    /// 改为list,兼容多个办理对象可以办理同一个节点的场景
     /// </summary>
-    [SugarColumn(IsNullable = true)]
-    public string? HandlerId { get; set; }
+    [SugarColumn(ColumnDataType = "varchar(1000)", IsJson = true)]
+    public List<string> HandlerIds { get; set; } = new();
 
     /// <summary>
     /// 前一级节点Id(stepBox此字段为上级stepBoxId,step为上级stepId),汇总节点无此字段(可能有多个上级来源)
@@ -37,7 +38,7 @@ public class WorkflowStep : StepBasicEntity
     public List<WorkflowStep> Steps { get; set; } = new();
 
     #region 会签
-    
+
     /// <summary>
     /// 发起会签生成会签Id(不发起会签节点无此字段)
     /// </summary>

+ 0 - 12
src/Hotline/IDeviceEventHandler.cs

@@ -1,12 +0,0 @@
-using Hotline.CallCenter.Devices;
-
-namespace Hotline
-{
-    /// <summary>
-    /// 处理设备事件
-    /// </summary>
-    public interface IDeviceEventHandler
-    {
-        Task HandleAsync(Stream eventStream, DeviceConfigs deviceConfigs, CancellationToken cancellationToken);
-    }
-}

+ 0 - 0
src/Hotline.Api/Permissions/EPermission.cs → src/Hotline/Permissions/EPermission.cs


+ 10 - 7
src/Hotline/Settings/SystemAuthority.cs

@@ -1,12 +1,13 @@
 using SqlSugar;
 using System.ComponentModel;
+using Hotline.Permissions;
 using XF.Domain.Entities;
 using XF.Domain.Repository;
 
 namespace Hotline.Settings
 {
     [Description("权限分配")]
-    public class SystemAuthority: CreationEntity
+    public class SystemAuthority : CreationEntity
     {
         /// <summary>
         /// 角色ID
@@ -21,19 +22,21 @@ namespace Hotline.Settings
         /// <summary>
         /// 菜单ID
         /// </summary>
-        [SugarColumn(ColumnDataType="varchar(3000)",IsJson = true)]
+        [SugarColumn(ColumnDataType = "varchar(3000)", IsJson = true)]
         public List<string> SystemMenuArr { get; set; }
 
         /// <summary>
         /// 功能点
         /// </summary>
-        [SugarColumn(ColumnDataType = "varchar(4000)",IsJson = true)]
+        [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
         public List<string> SystemButtonArr { get; set; }
 
-        /// <summary>
-        /// 创建时间
-        /// </summary>
-        public DateTime CreationTime { get; set; }
+        public List<string> GetAllPermissions()
+        {
+            var permissions = new List<string>(SystemMenuArr);
+            permissions.AddRange(SystemButtonArr);
+            return permissions.Select(d=>Enum.Parse<EPermission>(d).ToString()).Distinct().ToList();
+        }
     }
 
     public class SystemMenuAuth