Bladeren bron

合并冲突处理

TANG JIANG 2 jaren geleden
bovenliggende
commit
15f9e59165
24 gewijzigde bestanden met toevoegingen van 354 en 127 verwijderingen
  1. 14 3
      src/Hotline.Api/Controllers/OrderController.cs
  2. 1 11
      src/Hotline.Api/Controllers/SysController.cs
  3. 8 21
      src/Hotline.Api/Controllers/TestController.cs
  4. 16 0
      src/Hotline.Api/Controllers/UserController.cs
  5. 42 4
      src/Hotline.Api/Controllers/WorkflowController.cs
  6. 4 1
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  7. 17 0
      src/Hotline.Application/Handlers/FlowEngine/EndWorkflowHandler.cs
  8. 15 0
      src/Hotline.Application/Handlers/FlowEngine/NextStepHandler.cs
  9. 4 1
      src/Hotline.Application/Mappers/MapperConfigs.cs
  10. 12 11
      src/Hotline.Repository.SqlSugar/DataPermissions/DataPermissionFilterBuilder.cs
  11. 5 0
      src/Hotline.Share/Dtos/FlowEngine/StepBasicDto.cs
  12. 40 0
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  13. 6 1
      src/Hotline.Share/Enums/FlowEngine/ECountersignMode.cs
  14. 8 1
      src/Hotline.Share/Enums/FlowEngine/EHandlerType.cs
  15. 5 0
      src/Hotline/FlowEngine/Definitions/StepBasic.cs
  16. 25 9
      src/Hotline/FlowEngine/Definitions/StepDefine.cs
  17. 6 4
      src/Hotline/FlowEngine/Notifies/WorkflowNotify.cs
  18. 1 1
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  19. 11 0
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  20. 24 15
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  21. 23 1
      src/Hotline/Orders/Order.cs
  22. 10 26
      src/Hotline/Settings/WorkflowModule.cs
  23. 44 14
      src/XF.Domain.Repository/Entity.cs
  24. 13 3
      src/XF.Domain/Entities/IDataPermission.cs

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

@@ -1,7 +1,9 @@
 using Hotline.Orders;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Requests;
+using MapsterMapper;
 using Microsoft.AspNetCore.Mvc;
 
 namespace Hotline.Api.Controllers;
@@ -9,19 +11,28 @@ namespace Hotline.Api.Controllers;
 public class OrderController : BaseController
 {
     private readonly IOrderRepository _orderRepository;
+    private readonly IMapper _mapper;
 
-    public OrderController(IOrderRepository orderRepository)
+    public OrderController(
+        IOrderRepository orderRepository,
+        IMapper mapper)
     {
         _orderRepository = orderRepository;
+        _mapper = mapper;
     }
 
+    /// <summary>
+    /// 工单列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
     [HttpGet]
-    public async Task<PagedDto<Order>> Query(PagedKeywordRequest dto)
+    public async Task<PagedDto<OrderDto>> Query(PagedKeywordRequest dto)
     {
         var (total, items) = await _orderRepository.Queryable()
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Title.Contains(dto.Keyword) || x.No.Contains(dto.Keyword))
             .ToPagedListAsync(dto.PageIndex, dto.PageSize);
 
-        return new PagedDto<Order>(total, items);
+        return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
 }

+ 1 - 11
src/Hotline.Api/Controllers/SysController.cs

@@ -276,17 +276,7 @@ namespace Hotline.Api.Controllers
         [HttpGet("dictdata-type")]
         public async Task<List<SysDicData>> GetSysDicData([FromQuery] GetSysDicDataDto dto)
         {
-            //var (total, items) = await _sysDicDataRepository.QueryPagedAsync(
-            //    x=> true,
-            //    x=>x.OrderByDescending(d=>d.CreationTime),
-            //    dto.PageIndex,
-            //    dto.PageSize,
-            //    false,
-            //    whereIfs: (true, x => x.DicTypeId == dto.typeid)
-            //    );
             return await _sysDicDataRepository.Queryable().Where(x => x.DicTypeId == dto.typeid).WhereIF(!string.IsNullOrEmpty(dto.datavalue),x=>x.DicDataValue.Contains(dto.datavalue)).ToTreeAsync(x=>x.children,x=>x.ParentId,"");
-            //return await _sysDicDataRepository.Queryable().Where(x => x.DicTypeId == typeid).ToListAsync();
-            //return new PagedDto<SysDicData> { Total = total, Items = items };
         }
 
         /// <summary>
@@ -299,7 +289,7 @@ namespace Hotline.Api.Controllers
         {
             return await _sysDicDataRepository.Queryable().Where(x => x.DicTypeCode == code).ToListAsync();
         }
-
+         
         /// <summary>
         /// 获取字典对象
         /// </summary>

+ 8 - 21
src/Hotline.Api/Controllers/TestController.cs

@@ -3,12 +3,15 @@ using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Devices;
 using Hotline.CallCenter.Ivrs;
 using Hotline.FlowEngine.Definitions;
+using Hotline.FlowEngine.Notifies;
+using Hotline.FlowEngine.Workflows;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Realtimes;
 using Hotline.Repository.SqlSugar;
 using Hotline.Share.Dtos.Realtime;
 using Hotline.Users;
+using MediatR;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
@@ -39,6 +42,7 @@ public class TestController : BaseController
     private readonly IIvrDomainService _ivrDomainService;
     private readonly ISugarUnitOfWork<HotlineDbContext> _uow;
     private readonly IRoleRepository _roleRepository;
+    private readonly IMediator _mediator;
 
     //private readonly ITypedCache<List<User>> _cache;
     //private readonly ICacheManager<User> _cache;
@@ -70,7 +74,8 @@ public class TestController : BaseController
         IBlacklistDomainService blacklistDomainService,
         IIvrDomainService ivrDomainService,
         ISugarUnitOfWork<HotlineDbContext> uow,
-        IRoleRepository roleRepository
+        IRoleRepository roleRepository,
+        IMediator mediator
     )
     {
         _logger = logger;
@@ -84,6 +89,7 @@ public class TestController : BaseController
         _ivrDomainService = ivrDomainService;
         _uow = uow;
         _roleRepository = roleRepository;
+        _mediator = mediator;
     }
 
     [AllowAnonymous]
@@ -152,27 +158,8 @@ public class TestController : BaseController
 
     [AllowAnonymous]
     [HttpGet("wfdefine")]
-    public async Task<IReadOnlyList<Definition>> GetWorkflowDefine()
+    public async Task GetWorkflowDefine()
     {
-        throw new NotImplementedException();
-    }
-
-    [ApiExplorerSettings(IgnoreApi = true)]
-    [AllowAnonymous]
-    [HttpGet("cdb")]
-    public Task CreateTableMultiple()
-    {
-        var db = _uow.Db;
-        db.DbMaintenance.CreateDatabase();
-
-        var types = typeof(User).Assembly.GetTypes()
-            .Where(d => d.GetInterfaces().Any(x => x == typeof(IEntity)))
-            .Distinct()
-            .ToArray();
-
-        db.CodeFirst.InitTables(types);//根据types创建表
-
-        return Task.CompletedTask;
     }
 
     [AllowAnonymous]

+ 16 - 0
src/Hotline.Api/Controllers/UserController.cs

@@ -319,6 +319,22 @@ public class UserController : BaseController
         return _mapper.Map<IReadOnlyList<UserDto>>(users);
     }
 
+    /// <summary>
+    /// 根据姓名模糊查询用户
+    /// </summary>
+    /// <param name="name"></param>
+    /// <returns></returns>
+    [HttpGet("withorg")]
+    public async Task<IReadOnlyList<UserDto>> Query([FromQuery] string name)
+    {
+        var users = await _userRepository.Queryable()
+            .Includes(d => d.Organization)
+            .Where(d => d.Name.Contains(name))
+            .OrderByDescending(d => d.Name)
+            .ToListAsync();
+        return _mapper.Map<IReadOnlyList<UserDto>>(users);
+    }
+
     [HttpGet("base-data")]
     public object BaseData()
     {

+ 42 - 4
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -9,10 +9,12 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Requests;
 using Hotline.Users;
 using MapsterMapper;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Exceptions;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Api.Controllers;
 
@@ -198,7 +200,7 @@ public class WorkflowController : BaseController
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    [HttpGet("paged")]
+    [HttpGet]
     public async Task<PagedDto<WorkflowDto>> QueryPaged([FromQuery] QueryWorkflowPagedDto dto)
     {
         var (total, items) = await _workflowRepository.Queryable()
@@ -242,8 +244,11 @@ public class WorkflowController : BaseController
                     break;
                 case EHandlerType.Role:
                     var roles = await _roleRepository.Queryable().Includes(d => d.Accounts, d => d.User)
-                        .Where(d => nextStepDefine.HandlerClassifies.Contains(d.Id)).ToListAsync();
-                    options.NextSteps = roles.SelectMany(d => d.Accounts).Select(d => d.User).Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
+                        .Where(d => nextStepDefine.HandlerClassifies.Contains(d.Name)).ToListAsync();
+                    var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
+                    if (nextStepDefine.OnlySelfOrg ?? false)
+                        users1 = users1.Where(d => d.OrgCode == _sessionContext.RequiredOrgCode);
+                    options.NextSteps = users1.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
                     break;
                 case EHandlerType.OrgLevel:
                     //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
@@ -288,7 +293,7 @@ public class WorkflowController : BaseController
     /// </summary>
     /// <param name="workflowId"></param>
     /// <returns></returns>
-    [HttpPut("terminate/{workflowId}")]
+    [HttpPost("terminate/{workflowId}")]
     public async Task Terminate(string workflowId)
     {
         await _workflowDomainService.TerminateAsync(workflowId, HttpContext.RequestAborted);
@@ -318,4 +323,37 @@ public class WorkflowController : BaseController
         return _mapper.Map<IReadOnlyList<StepDefineDto>>(workflow.StepBoxes);
     }
 
+    [HttpGet("handlerclassify")]
+    public async Task<List<KeyValuePair<string, string>>> GetHanlderClassifies(EHandlerType handlerType)
+    {
+        switch (handlerType)
+        {
+            case EHandlerType.Role:
+                var roles = await _roleRepository.QueryAsync();
+                return roles.Select(d => new KeyValuePair<string, string>(d.Name, d.DisplayName)).ToList();
+            case EHandlerType.OrgLevel:
+                //todo 一级部门、二级部门...
+                return new();
+            case EHandlerType.OrgType:
+                //todo
+                return new();
+            case EHandlerType.AssignOrg:
+                var orgs = await _organizeRepository.GetOrgJson();
+                return orgs.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName)).ToList();
+            case EHandlerType.AssignUser:
+            default:
+                throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, null);
+        }
+    }
+
+    [HttpGet("base-data")]
+    public dynamic BaseData()
+    {
+        return new
+        {
+            ModuleOptions = WorkflowModule.Modules.ToList(),
+            GenderOptions = EnumExts.GetDescriptions<EHandlerType>(),
+            CountersignMode = EnumExts.GetDescriptions<ECountersignMode>().Where(d => d.Key != 1)
+        };
+    }
 }

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

@@ -59,7 +59,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
         var isOutOfCallCenter =
             await CheckIfFlowOutOfCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
-        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isOutOfCallCenter, cancellationToken);
+        var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.Handlers.Count);
+        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isOutOfCallCenter, isStartCountersign, cancellationToken);
     }
 
     #region private
@@ -101,5 +102,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// <returns></returns>
     private bool IsOrgFromCallCenter(string orgCode) => orgCode.StartsWith(OrgSeedData.Code);
 
+
+
     #endregion
 }

+ 17 - 0
src/Hotline.Application/Handlers/FlowEngine/EndWorkflowHandler.cs

@@ -0,0 +1,17 @@
+using Hotline.FlowEngine.Notifies;
+using MediatR;
+
+namespace Hotline.Application.Handlers.FlowEngine;
+
+public class EndWorkflowHandler : INotificationHandler<EndWorkflowNotify>
+{
+    /// <summary>Handles a notification</summary>
+    /// <param name="notification">The notification</param>
+    /// <param name="cancellationToken">Cancellation token</param>
+    public async Task Handle(EndWorkflowNotify notification, CancellationToken cancellationToken)
+    {
+        //todo 知识审批收到此消息表示审批通过
+
+        throw new NotImplementedException();
+    }
+}

+ 15 - 0
src/Hotline.Application/Handlers/FlowEngine/NextStepHandler.cs

@@ -2,6 +2,7 @@
 using Hotline.KnowledgeBase;
 using Hotline.Orders;
 using Hotline.Settings;
+using Hotline.Share.Enums.Order;
 using MediatR;
 using XF.Domain.Exceptions;
 
@@ -34,6 +35,20 @@ public class NextStepHandler : INotificationHandler<NextStepNotify>
                 order.Assign(notification.FlowAssignType, data.Handlers);
 
                 //todo business logic
+
+                if (order.Status is EOrderStatus.Filed) return;
+                //1.如果order未处于会签中,则判断是否发起会签(isstartCountersign) 2.如果处于会签中,则判断会签是否结束(isCountersignEnd)
+                if (order.Status is EOrderStatus.Countersigning && notification.IsCountersignEnd)
+                {
+                    order.Status = EOrderStatus.WaitForSign;
+                }
+                else if (order.Status is not EOrderStatus.Countersigning && notification.IsCountersignStart)
+                {
+                    order.Status = EOrderStatus.Countersigning;
+                }
+
+                order.CurrentStepTime = workflow.CurrentStepTime;
+                order.CurrentStepName = workflow.CurrentStepName;
                 break;
 
             //case WorkflowModuleConsts.KnowledgePublish:

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

@@ -27,7 +27,10 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.UserName, x => x.Account.UserName)
                 .Map(d => d.OrgName, x => x.Organization.OrgName)
                 .Map(d => d.Roles, x => string.Join(',', x.Account.Roles.Select(d => d.DisplayName)))
-                .Map(d => d.State, x => x.IsDeleted ? "已删除" : "正常");
+                .Map(d => d.State, x => x.IsDeleted ? "已删除" : "正常")
+                .IgnoreIf((s, d) => s.Account == null, d => d.UserName)
+                .IgnoreIf((s, d) => s.Account == null, d => d.Roles)
+                ;
 
             config.NewConfig<Role, RoleDto>()
                 .Map(d => d.AccountIds, x => x.Accounts.Select(d => d.Id))

+ 12 - 11
src/Hotline.Repository.SqlSugar/DataPermissions/DataPermissionFilterBuilder.cs

@@ -40,35 +40,36 @@ public class DataPermissionFilterBuilder : IDataPermissionFilterBuilder, IScopeD
     public Expression<Func<TEntity, bool>> BuildIncludeFlowData<TEntity>() where TEntity : class, IEntity<string>, IDataPermission, IWorkflow, new()
     {
         var userId = _sessionContext.RequiredUserId;
+        var roles = _sessionContext.Roles;
         var scheme = DataPermissionManager.GetQueryFilter<TEntity>(_sessionContext);
-        var (_,depCode, _, _) = DataPermissionManager.GetDataPermissionOptions();
+        var (_, depCode, _, _) = DataPermissionManager.GetDataPermissionOptions();
 
         switch (scheme.QueryFilter)
         {
             case EAuthorityType.Create:
-                return d => d.CreatorId == userId || FlowDataFiltering(d, userId, depCode);
+                return d => d.CreatorId == userId || FlowDataFiltering(d, userId, depCode, roles);
             case EAuthorityType.Org:
-                return d => d.CreatorOrgCode == scheme.OrgCode || FlowDataFiltering(d, userId, depCode);
+                return d => d.CreatorOrgCode == scheme.OrgCode || FlowDataFiltering(d, userId, depCode, roles);
             case EAuthorityType.OrgAndBelow:
-                return d => d.CreatorOrgCode.StartsWith(scheme.OrgCode) || FlowDataFiltering(d, userId, depCode);
+                return d => d.CreatorOrgCode.StartsWith(scheme.OrgCode) || FlowDataFiltering(d, userId, depCode, roles);
             case EAuthorityType.All:
                 return d => true;
             default:
-                return d => FlowDataFiltering(d, userId, depCode);
+                return d => FlowDataFiltering(d, userId, depCode, roles);
         }
     }
 
-    private static bool FlowDataFiltering<TEntity>(TEntity entity, string userId, string depCode) where TEntity : class, IEntity<string>, IDataPermission, IWorkflow, new()
+    private static bool FlowDataFiltering<TEntity>(TEntity entity, string userId, string orgCode, string[] roles) where TEntity : class, IEntity<string>, IDataPermission, IWorkflow, new()
     {
         if (entity.AssignUserIds.Contains(userId)) return true;
-        foreach (var assignDepCode in entity.AssignOrgCodes)
+        foreach (var assignOrgCode in entity.AssignOrgCodes)
         {
-            if (assignDepCode == depCode) return true;
-            var baseDep = assignDepCode.Substring(0, 3);
-            if (depCode.StartsWith(baseDep) && depCode.Length < assignDepCode.Length)
+            if (assignOrgCode == orgCode) return true;
+            var baseOrg = assignOrgCode.Substring(0, 3);
+            if (orgCode.StartsWith(baseOrg) && orgCode.Length < assignOrgCode.Length)
                 return true;
         }
 
-        return false;
+        return entity.AssignRoles.Intersect(roles).Any();
     }
 }

+ 5 - 0
src/Hotline.Share/Dtos/FlowEngine/StepBasicDto.cs

@@ -18,6 +18,11 @@ namespace Hotline.Share.Dtos.FlowEngine
         /// </summary>
         public EHandlerType HandlerType { get; set; }
 
+        /// <summary>
+        /// 只允许该角色下本部门人办理,否则该角色下所有人均可办理(办理者类型为角色时此字段有值)
+        /// </summary>
+        public bool? OnlySelfOrg { get; set; }
+
         /// <summary>
         /// 办理者分类(或是直接保存办理者)
         /// <example>

+ 40 - 0
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.Order
+{
+    public record QueryOrderDto : PagedKeywordRequest
+    {
+
+        /// <summary>
+        /// 诉求内容
+        /// </summary>
+        public string? Content { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public EAcceptType? AcceptType { get; set; }
+
+        /// <summary>
+        /// 来源渠道
+        /// </summary>
+        public EChannel? Channel { get; set; }
+
+        /// <summary>
+        /// 转接号码(转接来源)
+        /// </summary>
+        public string? TransferPhone { get; set; }
+
+        /// <summary>
+        /// 热点分类
+        /// </summary>
+        public string HotspotId { get; set; }
+        
+    }
+}

+ 6 - 1
src/Hotline.Share/Enums/FlowEngine/ECountersignMode.cs

@@ -1,4 +1,6 @@
-namespace Hotline.Share.Enums.FlowEngine;
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.FlowEngine;
 
 /// <summary>
 /// 会签模式
@@ -8,15 +10,18 @@ public enum ECountersignMode
     /// <summary>
     /// 不支持会签
     /// </summary>
+    [Description("不支持会签")]
     UnSupport = 0,
 
     /// <summary>
     /// 立即汇总(在发起会签的当前节点进行汇总才会继续下一节点,无需另行配置汇总节点)
     /// </summary>
+    [Description("立即汇总")]
     GatherImmediately = 1,
 
     /// <summary>
     /// 稍后汇总,延迟汇总(不会在会签发起节点进行汇总,汇总节点需单独配置)
     /// </summary>
+    [Description("支持会签")]
     GatherLater = 2
 }

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

@@ -1,29 +1,36 @@
-namespace Hotline.Share.Enums.FlowEngine;
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.FlowEngine;
 
 public enum EHandlerType
 {
     /// <summary>
     /// 角色
     /// </summary>
+    [Description("角色")]
     Role = 0,
 
     /// <summary>
     /// 部门级别
     /// </summary>
+    [Description("部门级别")]
     OrgLevel = 1,
 
     /// <summary>
     /// 部门类型
     /// </summary>
+    [Description("部门类型")]
     OrgType = 2,
 
     /// <summary>
     /// 指定用户
     /// </summary>
+    [Description("指定用户")]
     AssignUser = 3,
 
     /// <summary>
     /// 指定部门
     /// </summary>
+    [Description("指定部门")]
     AssignOrg = 4,
 }

+ 5 - 0
src/Hotline/FlowEngine/Definitions/StepBasic.cs

@@ -22,6 +22,11 @@ public class StepBasic
     /// </summary>
     public EHandlerType HandlerType { get; set; }
 
+    /// <summary>
+    /// 只允许该角色下本部门人办理,否则该角色下所有人均可办理(办理者类型为角色时此字段有值)
+    /// </summary>
+    public bool? OnlySelfOrg { get; set; }
+
     /// <summary>
     /// 办理者分类(或是直接保存办理者)
     /// <example>

+ 25 - 9
src/Hotline/FlowEngine/Definitions/StepDefine.cs

@@ -9,17 +9,33 @@ namespace Hotline.FlowEngine.Definitions;
 public class StepDefine : StepBasic
 {
     public List<NextStepDefine> NextSteps { get; set; }
+    
+    /// <summary>
+    /// 是否发起会签
+    /// </summary>
+    /// <param name="handlerCount">下一节点办理人数量</param>
+    /// <returns></returns>
+    public bool IsStartCountersign(int handlerCount)
+    {
+        //需求:按角色指派默认不发起会签
+        if (HandlerType is EHandlerType.Role) return false;
+        return handlerCount > 1;
+    }
 
-    public EFlowAssignType GFlowAssignType()
+    public (EFlowAssignType assignType, List<string> handlers) GetFlowAssignMode(List<string> handlers)
     {
-        return HandlerType switch
+        switch (HandlerType)
         {
-            EHandlerType.Role => EFlowAssignType.User,
-            EHandlerType.OrgLevel => EFlowAssignType.Org,
-            EHandlerType.OrgType => EFlowAssignType.Org,
-            EHandlerType.AssignUser => EFlowAssignType.User,
-            EHandlerType.AssignOrg => EFlowAssignType.Org,
-            _ => throw new ArgumentOutOfRangeException()
-        };
+            case EHandlerType.Role:
+                return handlers.Any() ? (EFlowAssignType.User, handlers) : (EFlowAssignType.Role, HandlerClassifies);
+            case EHandlerType.OrgLevel:
+            case EHandlerType.OrgType:
+            case EHandlerType.AssignOrg:
+                return (EFlowAssignType.Org, handlers);
+            case EHandlerType.AssignUser:
+                return (EFlowAssignType.User, handlers);
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
     }
 }

+ 6 - 4
src/Hotline/FlowEngine/Notifies/WorkflowNotify.cs

@@ -5,14 +5,16 @@ using XF.Domain.Entities;
 
 namespace Hotline.FlowEngine.Notifies;
 
-public record WorkflowNotify(Workflow Workflow, EFlowAssignType FlowAssignType) : INotification;
+public record WorkflowNotify(Workflow Workflow, EFlowAssignType FlowAssignType, List<string> Handlers) : INotification;
 
-public record StartWorkflowNotify(Workflow Workflow, EFlowAssignType FlowAssignType, BasicWorkflowDto Dto) : WorkflowNotify(Workflow, FlowAssignType);
+public record StartWorkflowNotify(Workflow Workflow, EFlowAssignType FlowAssignType, List<string> Handlers, BasicWorkflowDto Dto) : WorkflowNotify(Workflow, FlowAssignType, Handlers);
 
-public record NextStepNotify(Workflow Workflow, EFlowAssignType FlowAssignType, BasicWorkflowDto Dto) : WorkflowNotify(Workflow, FlowAssignType);
+public record NextStepNotify(Workflow Workflow, EFlowAssignType FlowAssignType, List<string> Handlers, BasicWorkflowDto Dto, bool IsCountersignStart, bool IsCountersignEnd) : WorkflowNotify(Workflow, FlowAssignType, Handlers);
 
 public record AcceptWorkflowNotify(Workflow Workflow) : INotification;
 
 public record CountersignEndAssigned(Workflow Workflow) : INotification;
 
-public record PreviousStepNotify(PreviousWorkflowDto Data) : INotification;
+public record PreviousStepNotify(PreviousWorkflowDto Data) : INotification;
+
+public record EndWorkflowNotify(Workflow Workflow) : INotification;

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

@@ -25,7 +25,7 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 办理(流转至下一节点)
         /// </summary>
-        Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, CancellationToken cancellationToken);
+        Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, bool isStartCountersign, CancellationToken cancellationToken);
 
         /// <summary>
         /// 退回(返回前一节点)

+ 11 - 0
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -110,6 +110,17 @@ public class Workflow : CreationEntity
         }
     }
 
+    public bool CheckIfCountersignOver()
+    {
+        var countersignStepBox = StepBoxes.First(d => d.Code == CurrentCountersignStepCode);
+        var isCountersignOver = countersignStepBox.Steps.All(d =>
+            d.Status is EWorkflowStepStatus.Completed &&
+            (!d.HasStartCountersign || d.IsCountersignComplete.GetValueOrDefault()));
+        return isCountersignOver;
+    }
+
+    public void EndCountersign() => CurrentCountersignStepCode = null;
+
     /// <summary>
     /// 更新workflow中当前停留节点,时间和会签开始节点code
     /// </summary>

+ 24 - 15
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -74,18 +74,21 @@ namespace Hotline.FlowEngine.Workflows
         {
             var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
 
-            if (dto.Handlers.Count > 1)
-            {
-                //检查是否支持会签
-                if (nextStepBoxDefine.CountersignMode == ECountersignMode.UnSupport)
-                    throw new UserFriendlyException($"当前节点不支持会签, defineCode: {workflow.Definition.Code}", "当前节点不支持会签");
-            }
+            var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.Handlers.Count);
+            //检查是否支持会签
+            if (isStartCountersign && nextStepBoxDefine.CountersignMode == ECountersignMode.UnSupport)
+                throw new UserFriendlyException($"当前节点不支持会签, defineCode: {workflow.Definition.Code}", "当前节点不支持会签");
+
+            //todo 1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选 2.1 不选则检查节点配置是否只允许当前部门办理,T:指派到人,F:指派到角色
+            if (nextStepBoxDefine.HandlerType is not EHandlerType.Role && !dto.Handlers.Any())
+                throw UserFriendlyException.SameMessage("未指派办理人");
 
             //第二节点的previousId is string.Empty
             await CreateStepAsync(workflow, nextStepBoxDefine, dto, cancellationToken: cancellationToken);
 
+            var assignMode = nextStepBoxDefine.GetFlowAssignMode(dto.Handlers);
             //publish
-            await _mediator.Publish(new StartWorkflowNotify(workflow, nextStepBoxDefine.GFlowAssignType(), dto), cancellationToken);
+            _mediator.Publish(new StartWorkflowNotify(workflow, assignMode.assignType, assignMode.handlers, dto), cancellationToken);
         }
 
         public async Task<Workflow> GetWorkflowAsync(string workflowId,
@@ -159,7 +162,7 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 办理(流转至下一节点)
         /// </summary>
-        public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, CancellationToken cancellationToken)
+        public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, bool isStartCountersign, CancellationToken cancellationToken)
         {
             CheckWhetherRunnable(workflow.Status);
 
@@ -171,8 +174,6 @@ namespace Hotline.FlowEngine.Workflows
             if (currentStep.StepType is EStepType.End)
                 throw new UserFriendlyException("当前流程已流转到最终步骤");
 
-            //是否发起会签
-            var isStartCountersign = dto.Handlers.Count > 1;
             //检查是否支持会签办理
             if (isStartCountersign && nextStepBoxDefine.CountersignMode == ECountersignMode.UnSupport)
                 throw UserFriendlyException.SameMessage($"下一节点不支持会签办理, code: {currentStep.Code}");
@@ -209,7 +210,13 @@ namespace Hotline.FlowEngine.Workflows
             #region 处理流程
 
             //检查会签是否结束,并更新当前会签节点字段
-            workflow.CompleteCountersign();
+            var isCountersignOver = false;
+            if (workflow.IsInCountersign())
+            {
+                isCountersignOver = workflow.CheckIfCountersignOver();
+                if (isCountersignOver)
+                    workflow.EndCountersign();
+            }
 
             //检查是否流转到流程终点
             if (nextStepBoxDefine.StepType is EStepType.End)
@@ -217,7 +224,7 @@ namespace Hotline.FlowEngine.Workflows
                 workflow.Complete();
                 await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
-                //todo publish workflow end
+                _mediator.Publish(new EndWorkflowNotify(workflow), cancellationToken);
                 return;
             }
 
@@ -256,14 +263,14 @@ namespace Hotline.FlowEngine.Workflows
                     if (canHandle)
                     {
                         await UpdateNextCountersignEndAssignedAsync(nextStepBox, currentStep, cancellationToken);
-                        await _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
+                        _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
                     }
 
                 }
                 else
                 {
                     await UpdateNextCountersignEndAssignedAsync(nextStepBox, currentStep, cancellationToken);
-                    await _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
+                    _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
                 }
             }
 
@@ -280,7 +287,9 @@ namespace Hotline.FlowEngine.Workflows
 
             #endregion
 
-            await _mediator.Publish(new NextStepNotify(workflow, nextStepBoxDefine.GFlowAssignType(), dto), cancellationToken);
+            var assignMode = nextStepBoxDefine.GetFlowAssignMode(dto.Handlers);
+            _mediator.Publish(new NextStepNotify(workflow, assignMode.assignType, assignMode.handlers, dto,
+                isStartCountersign, isCountersignOver), cancellationToken);
         }
 
         /// <summary>

+ 23 - 1
src/Hotline/Orders/Order.cs

@@ -158,7 +158,6 @@ namespace Hotline.Orders
 
         #endregion
         
-
         /// <summary>
         /// 当前节点名称
         /// </summary>
@@ -183,6 +182,29 @@ namespace Hotline.Orders
         /// 过期时间
         /// </summary>
         public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 过期状态
+        /// </summary>
+        public EExpiredStatus ExpiredStatus { get; set; }
+    }
+
+    public enum EExpiredStatus
+    {
+        /// <summary>
+        /// 正常状态(未超期)
+        /// </summary>
+        Normal = 0,
+
+        /// <summary>
+        /// 即将超期
+        /// </summary>
+        GoingToExpired = 1,
+
+        /// <summary>
+        /// 已超期
+        /// </summary>
+        Expired = 2,
     }
 
     /// <summary>

+ 10 - 26
src/Hotline/Settings/WorkflowModule.cs

@@ -13,44 +13,28 @@ namespace Hotline.Settings;
 /// <summary>
 /// 工作流业务模块
 /// </summary>
-//[SugarIndex("unique_account_username", nameof(WorkflowModule.Code), OrderByType.Asc, true)]
-//public class WorkflowModule : CreationEntity
-//{
-//    public string Name { get; set; }
-
-//    public string Code { get; set; }
-
-//    public string DefinitionCode { get; set; }
-//}
-
 public class WorkflowModule
 {
-    public WorkflowModule()
-    {
-        Modules = new Dictionary<string, string>
+    /// <summary>
+    /// code: name
+    /// </summary>
+    public static Dictionary<string, string> Modules { get; } =
+        new Dictionary<string, string>
         {
-            { WorkflowModuleConsts.Order, "工单审批" },
-            { WorkflowModuleConsts.KnowledgePublish, "知识发布" },
-            { WorkflowModuleConsts.KnowledgeUpdate, "知识更新" },
-            { WorkflowModuleConsts.KnowledgeDelete, "知识删除" },
+            { WorkflowModuleConsts.Order, "工单办理" },
+            { WorkflowModuleConsts.KnowledgeAdd, "新增知识审批" },
         };
-    }
-
-    public Dictionary<string, string> Modules { get; init; }
 }
 
 public class WorkflowModuleConsts
 {
     /// <summary>
-    /// 工单审批
+    /// 工单办理
     /// </summary>
     public const string Order = "Order";
 
     /// <summary>
-    /// 知识发布流程
+    /// 新增知识审批
     /// </summary>
-    public const string KnowledgePublish = "KnowledgePublish";
-    public const string KnowledgeUpdate = "KnowledgeUpdate";
-    public const string KnowledgeDelete = "KnowledgeDelete";
-
+    public const string KnowledgeAdd = "KnowledgeAdd";
 }

+ 44 - 14
src/XF.Domain.Repository/Entity.cs

@@ -139,36 +139,51 @@ public abstract class WorkflowEntity : FullStateEntity, IWorkflow
     [SugarColumn(IsNullable = true)]
     public string? WorkflowId { get; set; }
 
-    [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
+    [SugarColumn(ColumnDataType = "varchar(1000)", IsJson = true)]
     public List<string> AssignOrgCodes { get; set; } = new();
 
     [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
     public List<string> AssignUserIds { get; set; } = new();
 
-    public void Assign(EFlowAssignType type, string id)
+    [SugarColumn(ColumnDataType = "varchar(600)", IsJson = true)]
+    public List<string> AssignRoles { get; set; } = new();
+
+    public void Assign(EFlowAssignType type, string handler)
     {
         switch (type)
         {
             case EFlowAssignType.Org:
-                AssignOrgCodes.Add(id);
+                if (!AssignOrgCodes.Exists(d => d == handler))
+                    AssignOrgCodes.Add(handler);
                 break;
             case EFlowAssignType.User:
-                AssignUserIds.Add(id);
+                if (!AssignUserIds.Exists(d => d == handler))
+                    AssignUserIds.Add(handler);
+                break;
+            case EFlowAssignType.Role:
+                if (!AssignRoles.Exists(d => d == handler))
+                    AssignRoles.Add(handler);
                 break;
             default:
                 throw new ArgumentOutOfRangeException(nameof(type), type, null);
         }
     }
 
-    public void Assign(EFlowAssignType type, IEnumerable<string> ids)
+    public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
     {
         switch (type)
         {
             case EFlowAssignType.Org:
-                AssignOrgCodes.AddRange(ids);
+                AssignOrgCodes.AddRange(handlers);
+                AssignOrgCodes = AssignOrgCodes.Distinct().ToList();
                 break;
             case EFlowAssignType.User:
-                AssignUserIds.AddRange(ids);
+                AssignUserIds.AddRange(handlers);
+                AssignUserIds = AssignUserIds.Distinct().ToList();
+                break;
+            case EFlowAssignType.Role:
+                AssignRoles.AddRange(handlers);
+                AssignRoles = AssignRoles.Distinct().ToList();
                 break;
             default:
                 throw new ArgumentOutOfRangeException(nameof(type), type, null);
@@ -217,36 +232,51 @@ public abstract class PositionWorkflowEntity : PositionEntity, IWorkflow
     [SugarColumn(IsNullable = true)]
     public string? WorkflowId { get; set; }
 
-    [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
+    [SugarColumn(ColumnDataType = "varchar(1000)", IsJson = true)]
     public List<string> AssignOrgCodes { get; set; } = new();
 
     [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
     public List<string> AssignUserIds { get; set; } = new();
 
-    public void Assign(EFlowAssignType type, string id)
+    [SugarColumn(ColumnDataType = "varchar(600)", IsJson = true)]
+    public List<string> AssignRoles { get; set; } = new();
+
+    public void Assign(EFlowAssignType type, string handler)
     {
         switch (type)
         {
             case EFlowAssignType.Org:
-                AssignOrgCodes.Add(id);
+                if (!AssignOrgCodes.Exists(d => d == handler))
+                    AssignOrgCodes.Add(handler);
                 break;
             case EFlowAssignType.User:
-                AssignUserIds.Add(id);
+                if (!AssignUserIds.Exists(d => d == handler))
+                    AssignUserIds.Add(handler);
+                break;
+            case EFlowAssignType.Role:
+                if (!AssignRoles.Exists(d => d == handler))
+                    AssignRoles.Add(handler);
                 break;
             default:
                 throw new ArgumentOutOfRangeException(nameof(type), type, null);
         }
     }
 
-    public void Assign(EFlowAssignType type, IEnumerable<string> ids)
+    public void Assign(EFlowAssignType type, IEnumerable<string> handlers)
     {
         switch (type)
         {
             case EFlowAssignType.Org:
-                AssignOrgCodes.AddRange(ids);
+                AssignOrgCodes.AddRange(handlers);
+                AssignOrgCodes = AssignOrgCodes.Distinct().ToList();
                 break;
             case EFlowAssignType.User:
-                AssignUserIds.AddRange(ids);
+                AssignUserIds.AddRange(handlers);
+                AssignUserIds = AssignUserIds.Distinct().ToList();
+                break;
+            case EFlowAssignType.Role:
+                AssignRoles.AddRange(handlers);
+                AssignRoles = AssignRoles.Distinct().ToList();
                 break;
             default:
                 throw new ArgumentOutOfRangeException(nameof(type), type, null);

+ 13 - 3
src/XF.Domain/Entities/IDataPermission.cs

@@ -11,7 +11,7 @@ namespace XF.Domain.Entities
 
         string AreaId { get; }
 
-        void CreateDataPermission(string orgId,string orgCode, string creatorId, string? areaId);
+        void CreateDataPermission(string orgId, string orgCode, string creatorId, string? areaId);
     }
 
     public interface IWorkflow
@@ -28,9 +28,14 @@ namespace XF.Domain.Entities
         /// </summary>
         List<string> AssignUserIds { get; set; }
 
-        void Assign(EFlowAssignType type, string id);
+        /// <summary>
+        /// 指派角色名称
+        /// </summary>
+        List<string> AssignRoles { get; set; }
+
+        void Assign(EFlowAssignType type, string handler);
 
-        void Assign(EFlowAssignType type, IEnumerable<string> ids);
+        void Assign(EFlowAssignType type, IEnumerable<string> handlers);
     }
 
     public enum EFlowAssignType
@@ -44,5 +49,10 @@ namespace XF.Domain.Entities
         /// 指派到用户
         /// </summary>
         User = 1,
+
+        /// <summary>
+        /// 指派到角色
+        /// </summary>
+        Role = 2,
     }
 }