xf 2 жил өмнө
parent
commit
03b4b7a576
70 өөрчлөгдсөн 2121 нэмэгдсэн , 515 устгасан
  1. 21 2
      src/Hotline.Api/Context/DefaultSessionContext.cs
  2. 25 0
      src/Hotline.Api/Controllers/OrderController.cs
  3. 4 1
      src/Hotline.Api/Controllers/UserController.cs
  4. 12 9
      src/Hotline.Api/Controllers/WorkflowController.cs
  5. 7 7
      src/Hotline.Api/StartupExtensions.cs
  6. 0 12
      src/Hotline.Api/appsettings.Development.json
  7. 1 1
      src/Hotline.Api/appsettings.json
  8. 8 1
      src/Hotline.Application/FlowEngine/IWorkflowApplication.cs
  9. 68 12
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  10. 14 11
      src/Hotline.Application/Mappers/MapperConfigs.cs
  11. 33 0
      src/Hotline.Application/Orders/IOrderApplication.cs
  12. 55 0
      src/Hotline.CacheManager/CallCacheManager.cs
  13. 3 2
      src/Hotline.CacheManager/IvrCacheManager.cs
  14. 3 2
      src/Hotline.CacheManager/SystemSettingCacheManager.cs
  15. 3 2
      src/Hotline.CacheManager/TelCacheManager.cs
  16. 3 2
      src/Hotline.CacheManager/UserCacheManager.cs
  17. 11 0
      src/Hotline.Repository.SqlSugar/BaseRepository.cs
  18. 7 0
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  19. 13 0
      src/Hotline.Repository.SqlSugar/FlowEngine/WorkflowStepRepository.cs
  20. 13 0
      src/Hotline.Repository.SqlSugar/FlowEngine/WorkflowSupplementRepository.cs
  21. 13 0
      src/Hotline.Repository.SqlSugar/FlowEngine/WorkflowTraceRepository.cs
  22. 11 6
      src/Hotline.Share/Dtos/FlowEngine/BasicWorkflowDto.cs
  23. 24 2
      src/Hotline.Share/Dtos/FlowEngine/DefinitionDto.cs
  24. 14 0
      src/Hotline.Share/Dtos/FlowEngine/EndWorkflowDto.cs
  25. 9 0
      src/Hotline.Share/Dtos/FlowEngine/NextWorkflowDto.cs
  26. 0 15
      src/Hotline.Share/Dtos/FlowEngine/PreviousDto.cs
  27. 6 0
      src/Hotline.Share/Dtos/FlowEngine/PreviousWorkflowDto.cs
  28. 1 1
      src/Hotline.Share/Dtos/FlowEngine/QueryWorkflowDto.cs
  29. 12 0
      src/Hotline.Share/Dtos/FlowEngine/RecallDto.cs
  30. 1 19
      src/Hotline.Share/Dtos/FlowEngine/StartWorkflowDto.cs
  31. 4 6
      src/Hotline.Share/Dtos/FlowEngine/StepBasicDto.cs
  32. 0 48
      src/Hotline.Share/Dtos/FlowEngine/StepDto.cs
  33. 2 2
      src/Hotline.Share/Dtos/FlowEngine/WorkflowDto.cs
  34. 6 1
      src/Hotline.Share/Enums/FlowEngine/EDefinitionStatus.cs
  35. 24 0
      src/Hotline.Share/Enums/FlowEngine/EWorkflowStepStatus.cs
  36. 44 0
      src/Hotline.Share/Enums/FlowEngine/EWorkflowTraceStatus.cs
  37. 1 1
      src/Hotline.Share/Enums/Order/EFromIdentity.cs
  38. 48 0
      src/Hotline.Share/Enums/Order/EOrderStatus.cs
  39. 31 0
      src/Hotline.Share/Enums/Order/EPushType.cs
  40. 1 1
      src/Hotline/FlowEngine/Definitions/Definition.cs
  41. 22 16
      src/Hotline/FlowEngine/Definitions/DefinitionDomainService.cs
  42. 11 6
      src/Hotline/FlowEngine/Definitions/IDefinitionDomainService.cs
  43. 5 3
      src/Hotline/FlowEngine/Definitions/NextStepDefine.cs
  44. 3 5
      src/Hotline/FlowEngine/Definitions/StepBasic.cs
  45. 1 3
      src/Hotline/FlowEngine/Definitions/StepDefine.cs
  46. 2 2
      src/Hotline/FlowEngine/Notifies/NextStepNotify.cs
  47. 28 0
      src/Hotline/FlowEngine/WorkflowBusiness.cs
  48. 44 14
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  49. 8 0
      src/Hotline/FlowEngine/Workflows/IWorkflowStepRepository.cs
  50. 8 0
      src/Hotline/FlowEngine/Workflows/IWorkflowSupplementRepository.cs
  51. 8 0
      src/Hotline/FlowEngine/Workflows/IWorkflowTraceRepository.cs
  52. 39 0
      src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs
  53. 0 87
      src/Hotline/FlowEngine/Workflows/StepBox.cs
  54. 71 27
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  55. 501 111
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  56. 147 0
      src/Hotline/FlowEngine/Workflows/WorkflowStep.cs
  57. 24 0
      src/Hotline/FlowEngine/Workflows/WorkflowSupplement.cs
  58. 98 10
      src/Hotline/FlowEngine/Workflows/WorkflowTrace.cs
  59. 57 0
      src/Hotline/Orders/Citizen.cs
  60. 1 11
      src/Hotline/Orders/IOrderDomainService.cs
  61. 70 12
      src/Hotline/Orders/Order.cs
  62. 310 0
      src/Hotline/Orders/OrderComplain.cs
  63. 53 0
      src/Hotline/Orders/OrderDomainService.cs
  64. 0 9
      src/Hotline/Orders/OrderTemporary.cs
  65. 0 28
      src/Hotline/Position.cs
  66. 10 3
      src/XF.Domain.Repository/IRepositorySqlSugar.cs
  67. 3 1
      src/XF.Domain.Repository/IRepositoryWorkflow.cs
  68. 7 0
      src/XF.Domain/Authentications/ISessionContext.cs
  69. 1 1
      src/XF.Domain/Cache/ITypedCache.cs
  70. 33 0
      src/XF.Domain/Extensions/DataMaskExtensions.cs

+ 21 - 2
src/Hotline.Api/Context/DefaultSessionContext.cs

@@ -1,15 +1,17 @@
 using System.Security.Authentication;
 using System.Security.Claims;
+using Hotline.Users;
 using IdentityModel;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
 using XF.Utility.AppIdentityModel;
 
 namespace Hotline.Api.Token
 {
     public class DefaultSessionContext : ISessionContext, IScopeDependency
     {
-        public DefaultSessionContext(IHttpContextAccessor httpContextAccessor)
+        public DefaultSessionContext(IHttpContextAccessor httpContextAccessor, IUserRepository userRepository)
         {
             var httpContext = httpContextAccessor.HttpContext;
             if (httpContext is null)
@@ -22,6 +24,17 @@ namespace Hotline.Api.Token
             Phone = user.FindFirstValue(JwtClaimTypes.PhoneNumber);
             //Roles = user.Claims.Where(d => d.Type == JwtClaimTypes.Role).Select(d => d.Value).ToArray();
             Roles = user.Claims.Where(d => d.Type == ClaimTypes.Role).Select(d => d.Value).ToArray();
+
+            var dbUser = userRepository.Queryable()
+                .IncludeLeftJoin(d => d.Organization)
+                .FirstAsync(d => d.Id == RequiredUserId)
+                .GetAwaiter().GetResult();
+            if (dbUser == null)
+                throw new UserFriendlyException($"无效用户编号:{RequiredUserId}", "未查询到用户");
+            UserName ??= dbUser.Name;
+            OrgId = dbUser.OrgId;
+            OrgCode = dbUser.OrgCode;
+            OrgName = dbUser.Organization.OrgName;
         }
 
         /// <summary>
@@ -33,7 +46,7 @@ namespace Hotline.Api.Token
         /// Id of current user or throw Exception for guest
         /// </summary>
         /// <exception cref="AuthenticationException"></exception>
-        public string RequiredUserId => UserId ?? throw new ArgumentException();
+        public string RequiredUserId => UserId ?? throw new ArgumentNullException();
 
         public string? UserName { get; }
 
@@ -43,5 +56,11 @@ namespace Hotline.Api.Token
         /// Roles
         /// </summary>
         public string[] Roles { get; }
+
+        public string? OrgId { get; set; }
+        public string? OrgCode { get; set; }
+        public string? OrgName { get; set; }
+        public string RequiredOrgId => OrgId ?? throw new ArgumentNullException();
+        public string RequiredOrgCode => OrgCode ?? throw new ArgumentNullException();
     }
 }

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

@@ -0,0 +1,25 @@
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Dtos;
+using Hotline.Share.Requests;
+
+namespace Hotline.Api.Controllers;
+
+public class OrderController : BaseController
+{
+    private readonly IOrderRepository _orderRepository;
+
+    public OrderController(IOrderRepository orderRepository)
+    {
+        _orderRepository = orderRepository;
+    }
+
+    public async Task<PagedDto<Order>> 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);
+    }
+}

+ 4 - 1
src/Hotline.Api/Controllers/UserController.cs

@@ -105,7 +105,7 @@ public class UserController : BaseController
              .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
                  d => d.Name.Contains(dto.Keyword!) || d.PhoneNo.Contains(dto.Keyword!))
              .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => d.OrgCode == dto.OrgCode)
-             .WhereIF(!string.IsNullOrEmpty(dto.Role), d => d.Account.Roles.Any(x=>x.Id == dto.Role))
+             .WhereIF(!string.IsNullOrEmpty(dto.Role), d => d.Account.Roles.Any(x => x.Id == dto.Role))
              .OrderBy(d => d.Account.Status)
              .OrderBy(d => d.Organization.OrgCode)
              .OrderByDescending(d => d.CreationTime)
@@ -311,7 +311,10 @@ public class UserController : BaseController
     public async Task<IReadOnlyList<UserDto>> Query([FromQuery] IReadOnlyList<string> ids)
     {
         var users = await _userRepository.Queryable()
+            .Includes(d => d.Account, x => x.Roles)
+            .Includes(d => d.Organization)
             .Where(d => ids.Contains(d.Id))
+            .OrderByDescending(d => d.CreationTime)
             .ToListAsync();
         return _mapper.Map<IReadOnlyList<UserDto>>(users);
     }

+ 12 - 9
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -61,8 +61,7 @@ public class WorkflowController : BaseController
     [HttpPost("definition")]
     public async Task<string> AddDefinition([FromBody] AddDefinitionDto dto)
     {
-        var definition = _mapper.Map<Definition>(dto);
-        return await _definitionRepository.AddAsync(definition, HttpContext.RequestAborted);
+        return await _definitionDomainService.AddAsync(dto, HttpContext.RequestAborted);
     }
 
     /// <summary>
@@ -83,12 +82,17 @@ public class WorkflowController : BaseController
         }
         else
         {
-            var newDefinition = _mapper.Map<Definition>(definition);
-            _mapper.Map(dto, newDefinition);
+            var newDefinition = _mapper.Map<Definition>(dto);
             await _definitionRepository.AddAsync(newDefinition, HttpContext.RequestAborted);
         }
     }
 
+    /// <summary>
+    /// 删除草稿
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    /// <exception cref="UserFriendlyException"></exception>
     [HttpDelete("definition/{id}")]
     public async Task RemoveDefinition(string id)
     {
@@ -100,7 +104,7 @@ public class WorkflowController : BaseController
     }
 
     /// <summary>
-    /// 发布(将草稿发布为正式模板
+    /// 发布(列表操作
     /// </summary>
     /// <returns></returns>
     [HttpPost("definition/{id}/publish")]
@@ -110,14 +114,13 @@ public class WorkflowController : BaseController
     }
 
     /// <summary>
-    /// 发布(将草稿发布为正式模板
+    /// 发布(保存并发布
     /// </summary>
     /// <returns></returns>
     [HttpPost("definition/publish")]
-    public async Task Publish([FromBody] UpdateDefinitionDto dto)
+    public async Task Publish([FromBody] AddDefinitionDto dto)
     {
-        var definition = _mapper.Map<Definition>(dto);
-        await _definitionDomainService.PublishAsync(definition, HttpContext.RequestAborted);
+        await _definitionDomainService.PublishAsync(dto, HttpContext.RequestAborted);
     }
 
     /// <summary>

+ 7 - 7
src/Hotline.Api/StartupExtensions.cs

@@ -55,7 +55,7 @@ internal static class StartupExtensions
             .AddApplication()
             .AddScoped<IPasswordHasher<Account>, PasswordHasher<Account>>()
             ;
-        
+
         //Authentication
         services.RegisterAuthentication(configuration);
 
@@ -64,12 +64,12 @@ internal static class StartupExtensions
             options.Filters.Add<UnifyResponseFilter>();
             options.Filters.Add<UserFriendlyExceptionFilter>();
         })
-//            .AddDapr(d =>
-//            {
-//#if DEBUG
-//                d.UseHttpEndpoint("http://192.168.100.223:50112");
-//#endif
-//            })
+            //            .AddDapr(d =>
+            //            {
+            //#if DEBUG
+            //                d.UseHttpEndpoint("http://192.168.100.223:50112");
+            //#endif
+            //            })
             ;
 
         // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

+ 0 - 12
src/Hotline.Api/appsettings.Development.json

@@ -65,18 +65,6 @@
     "WorkCategory": "08da9b9f-a35d-4ade-8ea7-55e8abbcdefd",
     "RestCategory": "08daa5f5-ac7a-4ced-8295-1c78baa15f9e"
   },
-  "Exceptionless": {
-    "InUse": false,
-    "ServerUrl": "http://log.fw.com",
-    "ApiKey": "lOrLM87GirnokXWilYixwJ0zxUzhlC2sc7v3ggp4"
-  },
-  "IdentityConfigs": {
-    "IdentityUrl": "http://identity.fengwo.com",
-    "IdentityApiUrl": "http://open.identity.fengwo.com",
-    "ClientId": "hotline_server",
-    "ClientSecret": "ce2fae0e-f0f6-46d6-bd79-1f1a31dff494",
-    "ClientScope": "identity_admin_api"
-  },
   "IdentityConfiguration": {
     "Password": {
       "RequiredLength": 8,

+ 1 - 1
src/Hotline.Api/appsettings.json

@@ -48,7 +48,7 @@
           "collectionName": "logs",
           "cappedMaxSizeMb": "1024",
           "cappedMaxDocuments": "50000",
-          "rollingInterval": "Month",
+          "rollingInterval": "Day",
           "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"
         }
       }

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

@@ -5,6 +5,13 @@ namespace Hotline.Application.FlowEngine
     public interface IWorkflowApplication
     {
         Task<string> StartWorkflowAsync(StartWorkflowDto dto, CancellationToken cancellationToken = default);
-        //Task<string> StartWorkflowAsync<TData>(string definitionName, TData? data, CancellationToken cancellationToken = default) where TData : class, new();
+        
+        /// <summary>
+        /// 流转至下一节点(节点办理)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken);
     }
 }

+ 68 - 12
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -1,7 +1,11 @@
 using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.Workflows;
+using Hotline.SeedData;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
+using Hotline.Users;
+using SqlSugar;
+using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 
@@ -12,15 +16,21 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     private readonly IDefinitionDomainService _definitionDomainService;
     private readonly IWorkflowDomainService _workflowDomainService;
     private readonly IWorkflowRepository _workflowRepository;
+    private readonly IUserRepository _userRepository;
+    private readonly ISessionContext _sessionContext;
 
     public WorkflowApplication(
         IDefinitionDomainService definitionDomainService,
         IWorkflowDomainService workflowDomainService,
-        IWorkflowRepository workflowRepository)
+        IWorkflowRepository workflowRepository,
+        IUserRepository userRepository,
+        ISessionContext sessionContext)
     {
         _definitionDomainService = definitionDomainService;
         _workflowDomainService = workflowDomainService;
         _workflowRepository = workflowRepository;
+        _userRepository = userRepository;
+        _sessionContext = sessionContext;
     }
 
     public async Task<string> StartWorkflowAsync(StartWorkflowDto dto, CancellationToken cancellationToken = default)
@@ -30,20 +40,66 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException("无效模板名称");
         if (definition.Status != EDefinitionStatus.Enable)
             throw new UserFriendlyException("该模板不可用");
-        var workflow = _workflowDomainService.CreateWorkflow(definition, dto);
-        _workflowDomainService.AddStartTrace(workflow, dto);
 
-        //todo 交给 back process 处理
-        //await _workflowDomainService.RunNormalStepAsync(workflow, cancellationToken);
+        var workflow = await _workflowDomainService.CreateWorkflowAsync(definition, cancellationToken);
+        await _workflowDomainService.StartAsync(workflow, dto, cancellationToken);
 
-        var workflowId = await _workflowRepository.AddAsync(workflow, cancellationToken);
-        //todo publish notify
+        return workflow.Id;
+    }
+
+    /// <summary>
+    /// 流转至下一节点(节点办理)
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken)
+    {
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, cancellationToken);
+        var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+        var isOutOfCallCenter =
+            await CheckIfFlowOutOfCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
+        await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isOutOfCallCenter, cancellationToken);
+    }
+
+    #region private
+
+    private async Task<bool> CheckIfFlowOutOfCallCenterAsync(StepDefine nextStepBoxDefine, string mainHandler, CancellationToken CancellationToken)
+    {
+        //todo current is center & next is not center return true
+        var isFromCallCenter = IsOrgFromCallCenter(_sessionContext.RequiredOrgCode);
+        if (!isFromCallCenter) return false;
+
+        var isOutCallCenter = await CheckNextStepHandlerIsOutCallCenterAsync(nextStepBoxDefine, mainHandler, CancellationToken);
+        return isFromCallCenter && isOutCallCenter;
+    }
+
+    private async Task<bool> CheckNextStepHandlerIsOutCallCenterAsync(StepDefine nextStepBoxDefine, string mainHandler, CancellationToken cancellationToken)
+    {
+        if (nextStepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.Role)
+        {
+            //mainHandler is userId
+            //todo 临时处理方案(暂时耦合user)
+            var handler = await _userRepository.GetAsync(mainHandler, cancellationToken);
+            if (handler == null)
+                throw new UserFriendlyException($"mainHandler未找到对应User, handler: {mainHandler}");
+            if (string.IsNullOrEmpty(handler.OrgCode))
+                throw UserFriendlyException.SameMessage($"该用户未配置所属部门, userId: {mainHandler}");
+            return !IsOrgFromCallCenter(handler.OrgCode);
+        }
+        else
+        {
+            //mainHandler is orgCode
+            return !IsOrgFromCallCenter(mainHandler);
+        }
 
-        return workflowId;
     }
 
-    //public async Task<string> StartWorkflowAsync<TData>(string definitionName, TData? data, CancellationToken cancellationToken = default) where TData : class, new()
-    //{
-    //    throw new NotImplementedException();
-    //}
+    /// <summary>
+    /// 是否为呼叫中心部门
+    /// </summary>
+    /// <returns></returns>
+    private bool IsOrgFromCallCenter(string orgCode) => orgCode.StartsWith(OrgSeedData.Code);
+
+    #endregion
 }

+ 14 - 11
src/Hotline.Application/Mappers/MapperConfigs.cs

@@ -1,6 +1,8 @@
 using Hotline.CallCenter.BlackLists;
+using Hotline.FlowEngine.Definitions;
 using Hotline.Identity.Roles;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Roles;
 using Hotline.Share.Dtos.Users;
 using Hotline.Users;
@@ -20,22 +22,23 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.Name, x => x.Name ?? x.UserName);
 
             config.NewConfig<User, UserDto>()
-                .Ignore(d => d.UserName)
-                .Ignore(d => d.OrgName)
-                .Ignore(d => d.Roles)
-                .AfterMapping((s, d) =>
-                {
-                    d.UserName = s.Account?.UserName ?? string.Empty;
-                    d.OrgName = s.Organization?.OrgName ?? string.Empty;
-                    d.Roles = s.Account != null && s.Account.Roles.Any()
-                        ? string.Join(',', s.Account.Roles.Select(d => d.DisplayName))
-                        : string.Empty;
-                })
+                .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 ? "已删除" : "正常");
 
             config.NewConfig<Role, RoleDto>()
                 .Map(d => d.AccountIds, x => x.Accounts.Select(d => d.Id))
                 .Map(d => d.State, x => x.IsDeleted ? "已删除" : "正常");
+
+            #region workflow
+
+            config.NewConfig<UpdateDefinitionDto, Definition>()
+                .Ignore(d => d.Id);
+
+
+
+            #endregion
         }
     }
 }

+ 33 - 0
src/Hotline.Application/Orders/IOrderApplication.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Orders
+{
+    public interface IOrderApplication
+    {
+        Task<Order> GetAsync(string id);
+    }
+
+    public class OrderApplication : IOrderApplication, IScopeDependency
+    {
+        private readonly IWorkflowDomainService _workflowDomainService;
+
+        public OrderApplication(IWorkflowDomainService workflowDomainService)
+        {
+            _workflowDomainService = workflowDomainService;
+        }
+
+        public async Task<Order> GetAsync(string id)
+        {
+            //var order = 
+
+            throw new NotImplementedException();
+        }
+    }
+}

+ 55 - 0
src/Hotline.CacheManager/CallCacheManager.cs

@@ -0,0 +1,55 @@
+using Hotline.Caches;
+using Hotline.CallCenter.Calls;
+using XF.Domain.Cache;
+using XF.Domain.Dependency;
+
+namespace Hotline.CacheManager
+{
+    public class CallCacheManager:ICallCacheManager, IScopeDependency
+    {
+        private readonly ITypedCache<IReadOnlyList<Call>> _cacheCall;
+        private const string CallKey = "CallQueue";
+
+        public CallCacheManager(ITypedCache<IReadOnlyList<Call>> cacheCall)
+        {
+            _cacheCall = cacheCall;
+        }
+
+        /// <summary>
+        /// 获取队列
+        /// </summary>
+        /// <returns></returns>
+        public IReadOnlyList<Call> GetCallQueueList()
+        {
+            return _cacheCall.Get(CallKey)??new List<Call>();
+        }
+
+        /// <summary>
+        /// 新增或修改队列
+        /// </summary>
+        /// <param name="list"></param>
+        public void AddOrUpdateCallCache(List<Call> list)
+        {
+            _cacheCall.AddOrUpdate(CallKey, list);
+        }
+
+        /// <summary>
+        /// 删除队列
+        /// </summary>
+        /// <param name="id"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        public void RemoveCallCache(string id)
+        {
+            var list = _cacheCall.Get(CallKey)?.ToList();
+            if (list!=null)
+            {
+                var model = list.First(x=>x.Id == id);
+                if (model!=null)
+                {
+                    list.Remove(model);
+                    _cacheCall.AddOrUpdate(CallKey,list);
+                }
+            }
+        }
+    }
+}

+ 3 - 2
src/Hotline/Caches/IvrCacheManager.cs → src/Hotline.CacheManager/IvrCacheManager.cs

@@ -1,8 +1,9 @@
-using Hotline.CallCenter.Ivrs;
+using Hotline.Caches;
+using Hotline.CallCenter.Ivrs;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
 
-namespace Hotline.Caches;
+namespace Hotline.CacheManager;
 
 public class IvrCacheManager : IIvrCacheManager, IScopeDependency
 {

+ 3 - 2
src/Hotline/Caches/SystemSettingCacheManager.cs → src/Hotline.CacheManager/SystemSettingCacheManager.cs

@@ -1,9 +1,10 @@
-using Hotline.Settings;
+using Hotline.Caches;
+using Hotline.Settings;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 
-namespace Hotline.Caches
+namespace Hotline.CacheManager
 {
     public class SystemSettingCacheManager:ISystemSettingCacheManager,IScopeDependency
     {

+ 3 - 2
src/Hotline/Caches/TelCacheManager.cs → src/Hotline.CacheManager/TelCacheManager.cs

@@ -1,9 +1,10 @@
-using Hotline.CallCenter.Tels;
+using Hotline.Caches;
+using Hotline.CallCenter.Tels;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 
-namespace Hotline.Caches;
+namespace Hotline.CacheManager;
 
 public class TelCacheManager : ITelCacheManager, IScopeDependency
 {

+ 3 - 2
src/Hotline/Caches/UserCacheManager.cs → src/Hotline.CacheManager/UserCacheManager.cs

@@ -1,9 +1,10 @@
-using Hotline.Users;
+using Hotline.Caches;
+using Hotline.Users;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 
-namespace Hotline.Caches;
+namespace Hotline.CacheManager;
 
 public class UserCacheManager : IUserCacheManager, IScopeDependency
 {

+ 11 - 0
src/Hotline.Repository.SqlSugar/BaseRepository.cs

@@ -157,6 +157,17 @@ namespace Hotline.Repository.SqlSugar
 
         public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities, UpdateNavRootOptions options) => Db.UpdateNav(entities, options);
 
+        public InsertNavTaskInit<TEntity,TEntity> InsertNav(TEntity entity) => Db.InsertNav(entity);
+
+        public InsertNavTaskInit<TEntity, TEntity> InsertNav(TEntity entity, InsertNavRootOptions options) => Db.InsertNav(entity, options);
+        public InsertNavTaskInit<TEntity, TEntity> InsertNav(List<TEntity> entities) => Db.InsertNav(entities);
+
+        public InsertNavTaskInit<TEntity, TEntity> InsertNav(List<TEntity> entities, InsertNavRootOptions options) => Db.InsertNav(entities, options);
+
+        public DeleteNavTaskInit<TEntity, TEntity> DeleteNav(TEntity entity) => Db.DeleteNav(entity);
+        public DeleteNavTaskInit<TEntity, TEntity> DeleteNav(List<TEntity> entities) => Db.DeleteNav(entities);
+
+        public DeleteNavTaskInit<TEntity, TEntity> DeleteNav(Expression<Func<TEntity, bool>> predicate) => Db.DeleteNav(predicate);
 
         /// <summary>
         /// 基础分页

+ 7 - 0
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs

@@ -72,6 +72,9 @@ namespace Hotline.Repository.SqlSugar.Extensions
                             column.Length = 36;
                         }
 
+                        //if (!column.DbColumnName.Contains("_"))
+                        //    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
+
                         //column.ColumnDescription = (attributes.FirstOrDefault(d => d is DescriptionAttribute) as DescriptionAttribute)?.Description ?? string.Empty;
                     },
                     EntityNameService = (type, entity) =>
@@ -81,7 +84,11 @@ namespace Hotline.Repository.SqlSugar.Extensions
                         //{
                         //    entity.DbTableName = (attributes.First(it => it is TableAttribute) as TableAttribute).UserName;
                         //}
+
                         entity.DbTableName = entity.DbTableName.ToSnakeCase();
+
+                        //if (!entity.DbTableName.Contains("_"))
+                        //    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);//ToUnderLine驼峰转下划线方法
                         if (attributes.Any(d => d is DescriptionAttribute))
                         {
                             entity.TableDescription =

+ 13 - 0
src/Hotline.Repository.SqlSugar/FlowEngine/WorkflowStepRepository.cs

@@ -0,0 +1,13 @@
+using Hotline.FlowEngine.Workflows;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using SqlSugar;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.FlowEngine;
+
+public class WorkflowStepRepository : BaseRepository<WorkflowStep>, IWorkflowStepRepository, IScopeDependency
+{
+    public WorkflowStepRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+}

+ 13 - 0
src/Hotline.Repository.SqlSugar/FlowEngine/WorkflowSupplementRepository.cs

@@ -0,0 +1,13 @@
+using Hotline.FlowEngine.Workflows;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using SqlSugar;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.FlowEngine;
+
+public class WorkflowSupplementRepository : BaseRepository<WorkflowSupplement>, IWorkflowSupplementRepository, IScopeDependency
+{
+    public WorkflowSupplementRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+}

+ 13 - 0
src/Hotline.Repository.SqlSugar/FlowEngine/WorkflowTraceRepository.cs

@@ -0,0 +1,13 @@
+using Hotline.FlowEngine.Workflows;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using SqlSugar;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.FlowEngine;
+
+public class WorkflowTraceRepository : BaseRepository<WorkflowTrace>, IWorkflowTraceRepository, IScopeDependency
+{
+    public WorkflowTraceRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+}

+ 11 - 6
src/Hotline.Share/Dtos/FlowEngine/ApproverDto.cs → src/Hotline.Share/Dtos/FlowEngine/BasicWorkflowDto.cs

@@ -1,10 +1,15 @@
 namespace Hotline.Share.Dtos.FlowEngine;
 
 /// <summary>
-/// 审批结果参数
+/// 工作流流转基础信息
 /// </summary>
-public class ApproverDto : PreviousDto
+public class BasicWorkflowDto : EndWorkflowDto
 {
+    /// <summary>
+    /// 下一节点code
+    /// </summary>
+    public string NextStepCode { get; set; }
+
     /// <summary>
     /// 根据审批者类型不同,此字段为不同内容
     /// <example>
@@ -14,12 +19,12 @@ public class ApproverDto : PreviousDto
     public List<string> Handlers { get; set; }
 
     /// <summary>
-    /// 下一节点code
+    /// 是否短信通知
     /// </summary>
-    public string NextStepCode { get; set; }
+    public bool AcceptSms { get; set; }
 
     /// <summary>
-    /// 是否短信通知
+    /// 下一节点主办
     /// </summary>
-    public bool AcceptSms { get; set; }
+    public string NextMainHandler { get; set; }
 }

+ 24 - 2
src/Hotline.Share/Dtos/FlowEngine/DefinitionDto.cs

@@ -1,10 +1,12 @@
 using Hotline.Share.Enums.FlowEngine;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.FlowEngine
 {
     public class DefinitionDto : UpdateDefinitionDto
     {
         public EDefinitionStatus Status { get; set; }
+        public string StatusText => Status.GetDescription();
 
         public DateTime CreationTime { get; set; }
     }
@@ -26,12 +28,32 @@ namespace Hotline.Share.Dtos.FlowEngine
         /// </summary>
         public string Code { get; set; }
 
+        /// <summary>
+        /// 草稿版本号 == 0
+        /// </summary>
+        public int Version { get; set; }
+
         public string Description { get; set; }
 
-        public List<StepDto> Steps { get; set; }
+        public List<StepDefineDto> Steps { get; set; }
 
-        public string ExternalName { get; set; }
+        public string ModuleName { get; set; }
 
         public string ExternalData { get; set; }
     }
+
+    public class StepDefineDto : StepBasicDto
+    {
+        public List<NextStepDefineDto> NextSteps { get; set; }
+    }
+
+    public class NextStepDefineDto
+    {
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 是否为返回的节点
+        /// </summary>
+        public bool IsPrevious { get; set; }
+    }
 }

+ 14 - 0
src/Hotline.Share/Dtos/FlowEngine/EndWorkflowDto.cs

@@ -0,0 +1,14 @@
+namespace Hotline.Share.Dtos.FlowEngine;
+
+public class EndWorkflowDto
+{
+    /// <summary>
+    /// 办理意见
+    /// </summary>
+    public string Opinion { get; set; }
+
+    /// <summary>
+    /// 附件
+    /// </summary>
+    public List<string> Additions { get; set; }
+}

+ 9 - 0
src/Hotline.Share/Dtos/FlowEngine/NextWorkflowDto.cs

@@ -0,0 +1,9 @@
+namespace Hotline.Share.Dtos.FlowEngine;
+
+/// <summary>
+/// 办理参数
+/// </summary>
+public class NextWorkflowDto : BasicWorkflowDto
+{
+    public string WorkflowId { get; set; }
+}

+ 0 - 15
src/Hotline.Share/Dtos/FlowEngine/PreviousDto.cs

@@ -1,15 +0,0 @@
-namespace Hotline.Share.Dtos.FlowEngine;
-
-public class PreviousDto
-{
-    public string WorkflowId { get; set; }
-
-    public string DepCode { get; set; }
-
-    public string UserId { get; set; }
-
-    /// <summary>
-    /// 办理意见
-    /// </summary>
-    public string Opinion { get; set; }
-}

+ 6 - 0
src/Hotline.Share/Dtos/FlowEngine/PreviousWorkflowDto.cs

@@ -0,0 +1,6 @@
+namespace Hotline.Share.Dtos.FlowEngine;
+
+public class PreviousWorkflowDto : EndWorkflowDto
+{
+    public string WorkflowId { get; set; }
+}

+ 1 - 1
src/Hotline.Share/Dtos/FlowEngine/QueryWorkflowDto.cs

@@ -4,7 +4,7 @@ public class QueryWorkflowDto
 {
     public string WorkflowId { get; set; }
 
-    public string DepCode { get; set; }
+    public string OrgCode { get; set; }
 
     public string UserId { get; set; }
 }

+ 12 - 0
src/Hotline.Share/Dtos/FlowEngine/RecallDto.cs

@@ -0,0 +1,12 @@
+namespace Hotline.Share.Dtos.FlowEngine;
+
+/// <summary>
+/// 撤回
+/// </summary>
+public class RecallDto : NextWorkflowDto
+{
+    /// <summary>
+    /// 撤回的目标节点code
+    /// </summary>
+    public string TargetStepCode { get; set; }
+}

+ 1 - 19
src/Hotline.Share/Dtos/FlowEngine/StartWorkflowDto.cs

@@ -1,25 +1,7 @@
 namespace Hotline.Share.Dtos.FlowEngine
 {
-    public class StartWorkflowDto
+    public class StartWorkflowDto : BasicWorkflowDto
     {
         public string DefinitionCode { get; set; }
-
-        /// <summary>
-        /// 根据审批者类型不同,此字段为不同内容
-        /// <example>
-        /// 部门等级/分类为:depCodes, 角色为:userIds
-        /// </example>
-        /// </summary>
-        public List<string> Handlers { get; set; }
-
-        /// <summary>
-        /// 是否短信通知
-        /// </summary>
-        public bool AcceptSms { get; set; }
-
-        /// <summary>
-        /// 办理意见
-        /// </summary>
-        public string Opinion { get; set; }
     }
 }

+ 4 - 6
src/Hotline.Share/Dtos/FlowEngine/StepBasicDto.cs

@@ -11,22 +11,20 @@ namespace Hotline.Share.Dtos.FlowEngine
         /// </summary>
         public string Code { get; set; }
 
-        public string TypeName { get; init; }
-
         public EStepType StepType { get; init; }
 
         /// <summary>
-        /// 审批者类型
+        /// 办理者类型
         /// </summary>
         public EHandlerType HandlerType { get; set; }
 
         /// <summary>
-        /// 审批者分类(或是直接保存处理者)
+        /// 办理者分类(或是直接保存办理者)
         /// <example>
-        /// 根据类型可能为:roles, depLevels, depTypes, depCodes, userIds
+        /// 根据类型可能为:roles, orgLevels, orgTypes, orgCodes, userIds
         /// </example>
         /// </summary>
-        public List<string> HandlerClassifies { get; set; }
+        public List<string> HandlerClassifies { get; set; } = new();
 
         /// <summary>
         /// 会签模式

+ 0 - 48
src/Hotline.Share/Dtos/FlowEngine/StepDto.cs

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

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

@@ -24,7 +24,7 @@ namespace Hotline.Share.Dtos.FlowEngine
 
         public DateTime? EndTime { get; set; }
 
-        public List<StepDto> Steps { get; set; }
+        public List<StepBasicDto> Steps { get; set; }
     }
 
     public class WorkflowTraceDto : StepBasicDto
@@ -33,7 +33,7 @@ namespace Hotline.Share.Dtos.FlowEngine
 
         public string StepId { get; set; }
 
-        public ApproverDto ApproverDto { get; set; }
+        public NextWorkflowDto NextWorkflowDto { get; set; }
 
         public bool IsBackward { get; set; }
 

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

@@ -1,19 +1,24 @@
-namespace Hotline.Share.Enums.FlowEngine;
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.FlowEngine;
 
 public enum EDefinitionStatus
 {
     /// <summary>
     /// 草稿
     /// </summary>
+    [Description("草稿")]
     Temporary = 0,
 
     /// <summary>
     /// 启用
     /// </summary>
+    [Description("启用")]
     Enable = 1,
 
     /// <summary>
     /// 禁用
     /// </summary>
+    [Description("禁用")]
     Disable = 2,
 }

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

@@ -0,0 +1,24 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.FlowEngine;
+
+public enum EWorkflowStepStatus
+{
+    /// <summary>
+    /// 已指派(待受理)
+    /// </summary>
+    [Description("已指派")]
+    Assigned = 1,
+
+    /// <summary>
+    /// 已受理
+    /// </summary>
+    [Description("已受理")]
+    Accepted = 2,
+
+    /// <summary>
+    /// 已办理
+    /// </summary>
+    [Description("已办理")]
+    Completed = 3,
+}

+ 44 - 0
src/Hotline.Share/Enums/FlowEngine/EWorkflowTraceStatus.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Enums.FlowEngine
+{
+    public enum EWorkflowTraceStatus
+    {
+        //正常流转、退回、撤回、跳转、办理中
+
+        ///// <summary>
+        ///// 流程开始
+        ///// </summary>
+        //[Description("流程开始")]
+        //Start = 0,
+
+        /// <summary>
+        /// 正常流转
+        /// </summary>
+        [Description("正常流转")]
+        Normal = 1,
+
+        /// <summary>
+        /// 撤回
+        /// </summary>
+        [Description("撤回")]
+        Recall = 2,
+
+        /// <summary>
+        /// 退回
+        /// </summary>
+        [Description("退回")]
+        Back = 3,
+
+        /// <summary>
+        /// 跳转
+        /// </summary>
+        [Description("跳转")]
+        Jump = 4,
+    }
+}

+ 1 - 1
src/Hotline.Share/Enums/Order/EFromIdentity.cs

@@ -3,7 +3,7 @@
 /// <summary>
 /// 来电/信人身份
 /// </summary>
-public enum EFromStatus
+public enum ECitizenStatus
 {
     /// <summary>
     /// 未知

+ 48 - 0
src/Hotline.Share/Enums/Order/EOrderStatus.cs

@@ -0,0 +1,48 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Order;
+
+public enum EOrderStatus
+{
+    /// <summary>
+    /// 草稿
+    /// </summary>
+    [Description("草稿")]
+    Temporary = 0,
+
+    /// <summary>
+    /// 待签收
+    /// </summary>
+    [Description("待签收")]
+    WaitForSign = 10,
+
+    /// <summary>
+    /// 办理中
+    /// </summary>
+    [Description("办理中")]
+    Handling = 20,
+
+    /// <summary>
+    /// 会签中
+    /// </summary>
+    [Description("会签中")]
+    Countersigning = 30,
+
+    /// <summary>
+    /// 退回
+    /// </summary>
+    [Description("退回")]
+    Return = 40,
+
+    /// <summary>
+    /// 办理完成
+    /// </summary>
+    [Description("办理完成")]
+    Completed = 50,
+
+    /// <summary>
+    /// 已归档
+    /// </summary>
+    [Description("已归档")]
+    Filed = 60,
+}

+ 31 - 0
src/Hotline.Share/Enums/Order/EPushType.cs

@@ -0,0 +1,31 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Order;
+
+/// <summary>
+/// 推送分类
+/// </summary>
+public enum EPushType
+{
+    //人力资源社会保障厅、交通运输厅、文旅和旅游厅、中小企业款项支付“指引诉求”、中小企业款项支付“跟办诉求”、中小企业款项支付“咨询诉求”、110
+    [Description("人力资源社会保障厅")]
+    Rlzyshbzt = 0,
+
+    [Description("交通运输厅")]
+    Jtyst = 1,
+
+    [Description("文旅和旅游厅")]
+    Wlt = 2,
+
+    [Description("中小企业款项支付“指引诉求”")]
+    ZxqyZysq = 3,
+
+    [Description("中小企业款项支付“跟办诉求”")]
+    ZxqyGbsq = 4,
+
+    [Description("中小企业款项支付“咨询诉求”")]
+    ZxqyZxsq = 5,
+
+    [Description("110")]
+    GongAn110 = 6,
+}

+ 1 - 1
src/Hotline/FlowEngine/Definitions/Definition.cs

@@ -26,7 +26,7 @@ public class Definition : CreationEntity
     [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
     public List<StepDefine> Steps { get; set; } = new();
 
-    public string ExternalName { get; set; }
+    public string ModuleName { get; set; }
 
     [SugarColumn(ColumnDataType = "varchar(4000)", IsNullable = true)]
     public string? ExternalData { get; set; }

+ 22 - 16
src/Hotline/FlowEngine/Definitions/DefinitionDomainService.cs

@@ -1,4 +1,6 @@
-using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.FlowEngine;
+using MapsterMapper;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 
@@ -7,10 +9,18 @@ namespace Hotline.FlowEngine.Definitions;
 public class DefinitionDomainService : IDefinitionDomainService, IScopeDependency
 {
     private readonly IDefinitionRepository _definitionRepository;
+    private readonly IMapper _mapper;
 
-    public DefinitionDomainService(IDefinitionRepository definitionRepository)
+    public DefinitionDomainService(IDefinitionRepository definitionRepository, IMapper mapper)
     {
         _definitionRepository = definitionRepository;
+        _mapper = mapper;
+    }
+
+    public async Task<string> AddAsync(AddDefinitionDto dto, CancellationToken cancellationToken)
+    {
+        var definition = _mapper.Map<Definition>(dto);
+        return await _definitionRepository.AddAsync(definition, cancellationToken);
     }
 
     /// <summary>
@@ -24,19 +34,20 @@ public class DefinitionDomainService : IDefinitionDomainService, IScopeDependenc
         var definitionTemp = await _definitionRepository.GetAsync(id, cancellationToken);
         ValidateDefinition(definitionTemp);
 
-        var lastVersion = await GetLastVersionAsync(definitionTemp.Code, cancellationToken);
+        var lastVersion = await GetLastVersionAsync(definitionTemp!.Code, cancellationToken);
         definitionTemp.Version = lastVersion + 1;
         definitionTemp.Status = EDefinitionStatus.Enable;
 
         await _definitionRepository.UpdateAsync(definitionTemp, cancellationToken);
     }
 
-    public async Task PublishAsync(Definition definition, CancellationToken cancellationToken)
+    /// <summary>
+    /// 发布(保存并发布)
+    /// </summary>
+    public async Task PublishAsync(AddDefinitionDto dto, CancellationToken cancellationToken)
     {
-        var temp = await _definitionRepository.GetAsync(d => d.Code == definition.Code && d.Status == EDefinitionStatus.Temporary, cancellationToken);
-        if (temp is null)
-            await _definitionRepository.AddAsync(definition, cancellationToken);
-        await PublishAsync(temp.Id, cancellationToken);
+        var id = await AddAsync(dto, cancellationToken);
+        await PublishAsync(id, cancellationToken);
     }
 
     public async Task<int> GetLastVersionAsync(string code, CancellationToken cancellationToken)
@@ -52,10 +63,6 @@ public class DefinitionDomainService : IDefinitionDomainService, IScopeDependenc
             .OrderByDescending(d => d.Version)
             .Take(1)
             .FirstAsync();
-
-        //var definitions = await _definitionRepository.GetAllAsync(d => d.Code == code, cancellationToken);
-        //if (!definitions.Any()) return null;
-        //return definitions.MaxBy(d => d.Version);
     }
 
     /// <summary>
@@ -71,7 +78,7 @@ public class DefinitionDomainService : IDefinitionDomainService, IScopeDependenc
     }
 
     #region private
-    private void ValidateDefinition(Definition definition)
+    private void ValidateDefinition(Definition? definition)
     {
         if (definition is null)
             throw new UserFriendlyException("无效 Definition Id");
@@ -96,9 +103,8 @@ public class DefinitionDomainService : IDefinitionDomainService, IScopeDependenc
                 throw UserFriendlyException.SameMessage("其中有节点未配置下一节点");
         }
 
-        var startStep = definition.Steps.First(d => d.StepType == EStepType.Start);
-        if (startStep.NextSteps.Count != 1)
-            throw UserFriendlyException.SameMessage("开始节点只能配置一个下一节点");
+        if (definition.Steps.GroupBy(d => d.Code).Count() < definition.Steps.Count)
+            throw UserFriendlyException.SameMessage("一个模板内不允许出现code相同的节点");
     }
 
     #endregion

+ 11 - 6
src/Hotline/FlowEngine/Definitions/IDefinitionDomainService.cs

@@ -1,21 +1,26 @@
-namespace Hotline.FlowEngine.Definitions
+using Hotline.Share.Dtos.FlowEngine;
+
+namespace Hotline.FlowEngine.Definitions
 {
     public interface IDefinitionDomainService
     {
+        Task<string> AddAsync(AddDefinitionDto dto, CancellationToken cancellationToken);
+
         /// <summary>
         /// 发布流程模板
         /// </summary>
-        /// <param name="id"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         Task PublishAsync(string id, CancellationToken cancellationToken);
-        Task PublishAsync(Definition definition, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 发布(保存并发布)
+        /// </summary>
+        Task PublishAsync(AddDefinitionDto dto, CancellationToken cancellationToken);
 
         Task<int> GetLastVersionAsync(string code, CancellationToken cancellationToken);
         Task<Definition?> GetLastVersionDefinitionAsync(string code, CancellationToken cancellationToken);
 
         /// <summary>
-        /// 未开启的流程,查找第2个节点的模板配置信息,用作开启流程的传参
+        /// 未开启的流程,查找start节点配置的nextSteps信息,用作开启流程的传参
         /// </summary>
         /// <param name="definitionCode"></param>
         /// <param name="cancellationToken"></param>

+ 5 - 3
src/Hotline/FlowEngine/Definitions/NextStepDefine.cs

@@ -1,12 +1,14 @@
 namespace Hotline.FlowEngine.Definitions;
 
-public class NextStepDefine : StepBasic
+public class NextStepDefine
 {
-    //todo 进入到当前下一节点的条件,默认无条件即为人工判定
-    public string? Predicate { get; set; }
+    public string Code { get; set; }
 
     /// <summary>
     /// 是否为返回的节点
     /// </summary>
     public bool IsPrevious { get; set; }
+
+    //todo 进入到当前下一节点的条件,默认无条件即为人工判定
+    public string? Predicate { get; set; }
 }

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

@@ -15,19 +15,17 @@ public class StepBasic
     /// </summary>
     public string Code { get; set; }
 
-    public string TypeName { get; init; }
-
     public EStepType StepType { get; init; }
 
     /// <summary>
-    /// 审批者类型
+    /// 办理者类型
     /// </summary>
     public EHandlerType HandlerType { get; set; }
 
     /// <summary>
-    /// 审批者分类(或是直接保存处理者)
+    /// 办理者分类(或是直接保存办理者)
     /// <example>
-    /// 根据类型可能为:roles, depLevels, depTypes, depCodes, userIds
+    /// 根据类型可能为:roles, orgLevels, orgTypes, orgCodes, userIds
     /// </example>
     /// </summary>
     public List<string> HandlerClassifies { get; set; } = new();

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

@@ -1,6 +1,4 @@
-using SqlSugar;
-
-namespace Hotline.FlowEngine.Definitions;
+namespace Hotline.FlowEngine.Definitions;
 
 /// <summary>
 /// 模板定义节点

+ 2 - 2
src/Hotline/FlowEngine/Notifies/NextStepNotify.cs

@@ -8,6 +8,6 @@ using MediatR;
 
 namespace Hotline.FlowEngine.Notifies
 {
-    public record NextStepNotify(ApproverDto Data) : INotification;
-    public record PreviousStepNotify(PreviousDto Data) : INotification;
+    public record NextStepNotify(string WorkflowId, BasicWorkflowDto Data) : INotification;
+    public record PreviousStepNotify(PreviousWorkflowDto Data) : INotification;
 }

+ 28 - 0
src/Hotline/FlowEngine/WorkflowBusiness.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine;
+
+/// <summary>
+/// 工作流业务模块
+/// </summary>
+public class WorkflowBusiness
+{
+    /// <summary>
+    /// 工单管理
+    /// </summary>
+    public const string OrderManage = "OrderManage";
+
+    /// <summary>
+    /// key:value = 模块名称:工作流模板对应code
+    /// </summary>
+    public static Dictionary<string, string> Modules = new()
+    {
+        { OrderManage, "GongDanShenPi"},
+    };
+}

+ 44 - 14
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -5,18 +5,6 @@ namespace Hotline.FlowEngine.Workflows
 {
     public interface IWorkflowDomainService
     {
-        /// <summary>
-        /// 返回前一级
-        /// </summary>
-        /// <returns></returns>
-        Task PreviousAsync(PreviousDto dto, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// 前往下一级
-        /// </summary>
-        /// <returns></returns>
-        Task NextAsync(ApproverDto dto, CancellationToken cancellationToken);
-
         ///// <summary>
         ///// 获取当前节点(针对某操作人的当前节点)
         ///// </summary>
@@ -29,9 +17,51 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         /// <returns></returns>
         Task<IReadOnlyList<NextStepDefine>> GetNextStepsAsync(QueryWorkflowDto dto, CancellationToken cancellationToken);
+        
+
+        ///////////
+
+        Task<Workflow> CreateWorkflowAsync(Definition definition, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 进行流程的开始节点
+        /// </summary>
+        Task StartAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken);
+
+        Task<Workflow> GetWorkflowAsync(string workflowId, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 受理,接办
+        /// </summary>
+        Task AcceptAsync(Workflow workflow, CancellationToken cancellationToken);
 
-        Workflow CreateWorkflow(Definition definition, StartWorkflowDto initDto);
+        /// <summary>
+        /// 办理(流转至下一节点)
+        /// </summary>
+        Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 退回(返回前一节点)
+        /// </summary>
+        /// <returns></returns>
+        Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 撤回(返回到之前任意节点)
+        /// </summary>
+        Task RecallAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 跳转(直接将流程跳转至任意节点)
+        /// </summary>
+        Task JumpAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 补充
+        /// </summary>
+        /// <returns></returns>
+        Task SupplementAsync(Workflow workflow, EndWorkflowDto dto, CancellationToken cancellationToken);
 
-        void AddStartTrace(Workflow workflow, StartWorkflowDto dto);
+        StepDefine GetStepBoxDefine(Definition definition, string stepCode);
     }
 }

+ 8 - 0
src/Hotline/FlowEngine/Workflows/IWorkflowStepRepository.cs

@@ -0,0 +1,8 @@
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public interface IWorkflowStepRepository : IRepository<WorkflowStep>
+{
+
+}

+ 8 - 0
src/Hotline/FlowEngine/Workflows/IWorkflowSupplementRepository.cs

@@ -0,0 +1,8 @@
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public interface IWorkflowSupplementRepository : IRepository<WorkflowSupplement>
+{
+
+}

+ 8 - 0
src/Hotline/FlowEngine/Workflows/IWorkflowTraceRepository.cs

@@ -0,0 +1,8 @@
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public interface IWorkflowTraceRepository : IRepository<WorkflowTrace>
+{
+
+}

+ 39 - 0
src/Hotline/FlowEngine/Workflows/StepBasicEntity.cs

@@ -0,0 +1,39 @@
+using Hotline.Share.Enums.FlowEngine;
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public class StepBasicEntity : CreationEntity
+{
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 模板内唯一
+    /// </summary>
+    public string Code { get; set; }
+
+    public string TypeName { get; init; }
+
+    public EStepType StepType { get; init; }
+
+    /// <summary>
+    /// 办理人类型
+    /// </summary>
+    public EHandlerType HandlerType { get; set; }
+
+    /// <summary>
+    /// 审批者分类(或是直接保存处理者)
+    /// <example>
+    /// 根据类型可能为:roles, depLevels, depTypes, depCodes, userIds
+    /// </example>
+    /// </summary>
+    [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
+    public List<string> HandlerClassifies { get; set; } = new();
+
+    /// <summary>
+    /// 会签模式
+    /// </summary>
+    public ECountersignMode CountersignMode { get; set; }
+
+}

+ 0 - 87
src/Hotline/FlowEngine/Workflows/StepBox.cs

@@ -13,90 +13,3 @@ namespace Hotline.FlowEngine.Workflows;
 
 //    public List<Step> Steps { get; set; } = new();
 //}
-
-public class WorkflowStep : StepBasicEntity
-{
-    public string WorkflowId { get; set; }
-
-    [SugarColumn(IsNullable = true)]
-    public string? ParentId { get; set; }
-
-    [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
-    public List<NextStep> NextSteps { get; set; }
-    
-    /// <summary>
-    /// 被指派处理对象(依据不同指派方式可能为:depCode或userId)
-    /// </summary>
-    public string HandlerId { get; set; }
-
-    public DateTime? StartTime { get; set; } = DateTime.Now;
-
-    public DateTime? EndTime { get; set; }
-
-    public string PreviousId { get; set; }
-
-    #region 审批参数
-
-    /// <summary>
-    /// (下一节点处理人)根据审批者类型不同,此字段为不同内容
-    /// <example>
-    /// 部门等级/分类为:depCodes, 角色为:userIds
-    /// </example>
-    /// </summary>
-    [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
-    public List<string> Handlers { get; set; }
-
-    /// <summary>
-    /// 下一节点code
-    /// </summary>
-    public string NextStepCode { get; set; }
-
-    /// <summary>
-    /// 是否短信通知
-    /// </summary>
-    public bool AcceptSms { get; set; }
-
-    /// <summary>
-    /// 办理意见
-    /// </summary>
-    public string Opinion { get; set; }
-
-    #endregion
-
-    [SugarColumn(IsIgnore = true)]
-    public List<WorkflowStep> Steps { get; set; }
-}
-
-public class StepBasicEntity : CreationEntity
-{
-    public string Name { get; set; }
-
-    /// <summary>
-    /// 模板内唯一
-    /// </summary>
-    public string Code { get; set; }
-
-    public string TypeName { get; init; }
-
-    public EStepType StepType { get; init; }
-
-    /// <summary>
-    /// 审批者类型
-    /// </summary>
-    public EHandlerType HandlerType { get; set; }
-
-    /// <summary>
-    /// 审批者分类(或是直接保存处理者)
-    /// <example>
-    /// 根据类型可能为:roles, depLevels, depTypes, depCodes, userIds
-    /// </example>
-    /// </summary>
-    [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
-    public List<string> HandlerClassifies { get; set; } = new();
-
-    /// <summary>
-    /// 会签模式
-    /// </summary>
-    public ECountersignMode CountersignMode { get; set; }
-
-}

+ 71 - 27
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -9,44 +9,88 @@ public class Workflow : CreationEntity
 {
     public string DefinitionId { get; set; }
 
-    //public WorkflowPointer Pointer { get; set; }
+    /// <summary>
+    /// 到期时间
+    /// </summary>
+    public DateTime ExpiredTime { get; set; }
 
     public DateTime? CompleteTime { get; set; }
 
     public EWorkflowStatus Status { get; set; }
 
+    /// <summary>
+    /// 当前节点名称(会签状态此字段保存开启会签时办理节点名称)
+    /// </summary>
+    public string CurrentStepName { get; set; }
+
+    /// <summary>
+    /// 到达当前节点时间(stepBox创建时间)
+    /// </summary>
+    public DateTime CurrentStepTime { get; set; }
+
+    /// <summary>
+    /// 当前发起会签节点code(不处于会签状态则无值)
+    /// </summary>
+    public string? CurrentCountersignCode { get; set; }
+
+
     [Navigate(NavigateType.OneToOne, nameof(DefinitionId))]
     public Definition Definition { get; set; }
 
+    /// <summary>
+    /// 补充信息
+    /// </summary>
+    [Navigate(NavigateType.OneToMany, nameof(WorkflowSupplement.WorkflowId))]
+    public List<WorkflowSupplement> Supplements { get; set; }
+
     /// <summary>
     /// 主节点,依据流转进度动态生成
     /// </summary>
-    [Navigate(NavigateType.OneToMany, nameof(WorkflowStep.WorkflowId))]
+    [SugarColumn(IsIgnore = true)]
     public List<WorkflowStep> StepBoxes { get; set; }
 
-    [Navigate(NavigateType.OneToMany, nameof(WorkflowTrace.WorkflowId))]
+    [SugarColumn(IsIgnore = true)]
     public List<WorkflowTrace> Traces { get; set; } = new();
-}
-
-//public class WorkflowPointer
-//{
-//    public WorkflowPointer(string stepId)
-//    {
-//        this.CurrentStepId = stepId;
-//    }
-
-//    /// <summary>
-//    /// 主节点Id
-//    /// </summary>
-//    public string CurrentStepId { get; set; }
-
-//    void Move(StepBasic step)
-//    {
-//        throw new NotImplementedException();
-//    }
-
-//    void Back()
-//    {
-//        throw new NotImplementedException();
-//    }
-//}
+
+    public void Complete()
+    {
+        Status = EWorkflowStatus.Completed;
+        CompleteTime = DateTime.Now;
+    }
+
+    /// <summary>
+    /// 流程进行流转
+    /// </summary>
+    public void FLow(string currentStepName, DateTime currentStepTime, string? currentCountersignCode = null)
+    {
+        CurrentStepName = currentStepName;
+        CurrentStepTime = currentStepTime;
+        if (!string.IsNullOrEmpty(currentCountersignCode))
+            CurrentCountersignCode = currentCountersignCode;
+    }
+
+    /// <summary>
+    /// 检查会签是否结束,并更新当前会签节点
+    /// </summary>
+    public void CheckCountersignStatus()
+    {
+        if (IsInCountersign())
+        {
+            var countersignStepBox = StepBoxes.First(d => d.Code == CurrentCountersignCode);
+            var countersignOver = countersignStepBox.Steps.All(d => d.Status is EWorkflowStepStatus.Completed);
+            if (countersignOver) //会签结束
+                CurrentCountersignCode = null;
+        }
+    }
+
+    /// <summary>
+    /// 流程是否处于会签中
+    /// </summary>
+    /// <returns></returns>
+    public bool IsInCountersign() => !string.IsNullOrEmpty(CurrentCountersignCode);
+
+    /// <summary>
+    /// 结束流程会签状态
+    /// </summary>
+    public void CloseCountersignStatus() => CurrentCountersignCode = null;
+}

+ 501 - 111
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -1,195 +1,505 @@
 using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.Notifies;
+using Hotline.SeedData;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.FlowEngine;
+using Hotline.Users;
 using MapsterMapper;
 using MediatR;
+using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
+using XF.Utility.SequentialId;
 
 namespace Hotline.FlowEngine.Workflows
 {
     public class WorkflowDomainService : IWorkflowDomainService, IScopeDependency
     {
         private readonly IWorkflowRepository _workflowRepository;
+        private readonly IWorkflowStepRepository _workflowStepRepository;
+        private readonly IWorkflowTraceRepository _workflowTraceRepository;
+        private readonly IWorkflowSupplementRepository _workflowSupplementRepository;
+        private readonly ISessionContext _sessionContext;
         private readonly IMapper _mapper;
         private readonly IMediator _mediator;
 
         public WorkflowDomainService(
             IWorkflowRepository workflowRepository,
+            IWorkflowStepRepository workflowStepRepository,
+            IWorkflowTraceRepository workflowTraceRepository,
+            IWorkflowSupplementRepository workflowSupplementRepository,
+            ISessionContext sessionContext,
             IMapper mapper,
             IMediator mediator)
         {
             _workflowRepository = workflowRepository;
+            _workflowStepRepository = workflowStepRepository;
+            _workflowTraceRepository = workflowTraceRepository;
+            _workflowSupplementRepository = workflowSupplementRepository;
+
+            _sessionContext = sessionContext;
             _mapper = mapper;
             _mediator = mediator;
         }
 
         /// <summary>
-        /// 返回前一级
+        /// 根据操作人获取当前未完成节点
         /// </summary>
         /// <returns></returns>
-        public async Task PreviousAsync(PreviousDto dto, CancellationToken cancellationToken)
+        public async Task<(WorkflowStep, WorkflowStep)> GetStepAsync(string workflowId, string orgCode, string userId, CancellationToken cancellationToken)
         {
-            //var workflow = await _workflowRepository.GetAsync(dto.WorkflowId, cancellationToken);
-            var workflow = await GetWorkflowAsync(dto.WorkflowId, cancellationToken);
-
-            var (stepBox, currentStep) = GetStep(workflow.StepBoxes, dto.DepCode, dto.UserId);
-            if (currentStep.StepType is EStepType.Start)
-                throw new UserFriendlyException("当前流程已流转到开始步骤");
-
-            //traces新增记录
-            var trace = CreateTrace(workflow.Id, currentStep, dto, true);
-            workflow.Traces.Add(trace);
+            var workflow = await GetWorkflowAsync(workflowId, cancellationToken);
+            return GetUnCompleteStep(workflow.StepBoxes, orgCode, userId);
+        }
 
-            //取消上一节点stepBox.endtime
-            var (previousStepBox, previousStep) = GetStep(workflow.StepBoxes, currentStep.PreviousId);
-            previousStep.EndTime = null;
-            previousStepBox.EndTime = null;
+        /// <summary>
+        /// 获取可能前往的下一节点
+        /// </summary>
+        /// <returns></returns>
+        public async Task<IReadOnlyList<NextStepDefine>> GetNextStepsAsync(QueryWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            var (_, currentStep) = await GetStepAsync(dto.WorkflowId, dto.OrgCode, dto.UserId, cancellationToken);
+            //获取人工判定的所有下一节点
+            return currentStep.NextSteps.Where(d => string.IsNullOrEmpty(d.Predicate)).ToList();
+        }
 
-            //删除steps节点
-            stepBox.Steps.Remove(currentStep);
+        public async Task<Workflow> CreateWorkflowAsync(Definition definition, CancellationToken cancellationToken)
+        {
+            var workflow = new Workflow
+            {
+                DefinitionId = definition.Id,
+                Status = EWorkflowStatus.Runnable,
+                ExpiredTime = GenerateExpiredTime(definition.Code),
+                StepBoxes = new(),
+                Traces = new(),
+                Definition = definition
+            };
 
-            await _workflowRepository.UpdateNavigateAsync(workflow, cancellationToken);
+            await _workflowRepository.AddAsync(workflow, cancellationToken);
 
-            await _mediator.Publish(new PreviousStepNotify(dto), cancellationToken);
+            return workflow;
         }
 
         /// <summary>
-        /// 前往下一节点
+        /// 进行流程的开始节点
         /// </summary>
+        /// <param name="workflow"></param>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task NextAsync(ApproverDto dto, CancellationToken cancellationToken)
+        public async Task StartAsync(Workflow workflow, BasicWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            if (dto.Handlers.Count > 1)
+            {
+                var startStepDefine = workflow.Definition.Steps.First(d => d.StepType == EStepType.Start);
+                //检查是否支持会签
+                if (startStepDefine.CountersignMode == ECountersignMode.UnSupport)
+                    throw UserFriendlyException.SameMessage($"当前开始节点不支持开启会签, defineCode: {workflow.Definition.Code}");
+            }
+
+            var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+
+            //第二节点的previousId is string.Empty
+            await CreateStepAsync(workflow, nextStepBoxDefine, dto, string.Empty, string.Empty, cancellationToken);
+        }
+
+        public async Task<Workflow> GetWorkflowAsync(string workflowId, CancellationToken cancellationToken)
         {
-            var workflow = await _workflowRepository.GetAsync(dto.WorkflowId, cancellationToken);
+            var workflow = await _workflowRepository.Queryable()
+                .Includes(d => d.Definition)
+                .FirstAsync(d => d.Id == workflowId);
             if (workflow is null)
                 throw new UserFriendlyException("无效workflowId");
 
-            var (stepBox, currentStep) = GetStep(workflow.StepBoxes, dto.DepCode, dto.UserId);
+            var steps = await _workflowStepRepository.Queryable()
+                .Where(d => d.WorkflowId == workflowId)
+                .OrderBy(d => d.CreationTime)
+                .ToTreeAsync(d => d.Steps, d => d.ParentId, null);
+
+            //var traces = await _workflowTraceRepository.Queryable()
+            //    .Where(d => d.WorkflowId == workflowId)
+            //    .ToTreeAsync(d => d.Traces, d => d.ParentId, null);
+
+            workflow.StepBoxes = steps;
+            //workflow.Traces = traces;
+
+            return workflow;
+        }
+
+        /// <summary>
+        /// 受理
+        /// </summary>
+        public async Task AcceptAsync(Workflow workflow, CancellationToken cancellationToken)
+        {
+            //工单完成以后查看的场景
+            if (workflow.Status is not EWorkflowStatus.Runnable) return;
+
+            var (currentStepBox, currentStep) = GetUnCompleteStepOrDefault(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
+            if (currentStep is null) return;
+            if (currentStep.Status is EWorkflowStepStatus.Accepted) return;
+            if (currentStep.StepType is EStepType.End)
+                throw new UserFriendlyException("当前流程已流转到最终步骤");
+
+            if (currentStepBox.Status is EWorkflowStepStatus.Assigned)
+                currentStepBox.Status = EWorkflowStepStatus.Accepted;
+            currentStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName);
+
+            await AcceptTraceAsync(workflow, currentStepBox, currentStep, cancellationToken);
+
+            //todo publish accept
+        }
+
+        /// <summary>
+        /// 办理(流转至下一节点)
+        /// </summary>
+        public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, CancellationToken cancellationToken)
+        {
+            CheckWhetherRunnable(workflow.Status);
+
+            //赋值当前节点
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
+            if (currentStep.Status is EWorkflowStepStatus.Assigned)
+                await AcceptAsync(workflow, cancellationToken);
             if (currentStep.StepType is EStepType.End)
                 throw new UserFriendlyException("当前流程已流转到最终步骤");
 
-            currentStep.EndTime = DateTime.Now;
+            //下一节点是否发起会签
+            var isCountersign = dto.Handlers.Count > 1;
+            //检查是否支持开启会签
+            if (isCountersign && currentStep.CountersignMode == ECountersignMode.UnSupport)
+                throw UserFriendlyException.SameMessage($"当前节点不支持开启会签, code: {currentStep.Code}");
+
             _mapper.Map(dto, currentStep);
 
-            var selectedStep = currentStep.NextSteps.First(d => d.Code == dto.NextStepCode);
-            selectedStep.Selected = true;
-            if (stepBox.Steps.All(d => d.EndTime.HasValue))
-                stepBox.EndTime = currentStep.EndTime;
+            currentStep.Complete(
+                _sessionContext.RequiredUserId,
+                _sessionContext.UserName,
+                _sessionContext.RequiredOrgCode,
+                _sessionContext.OrgName,
+                dto.NextStepCode);
 
-            //创建下一节点
-            //依据nextStepCode,检查box是否存在,不存在则在模板中找到对应define创建box,存在则在box中新增step
-            var stepDefine = workflow.Definition.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode);
-            if (stepDefine is null)
-                throw UserFriendlyException.SameMessage($"模板配置中未找到对应节点, code: {dto.NextStepCode}");
-            var existsStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.NextStepCode);
-            if (existsStepBox == null)
+            if (currentStepBox.Status is not EWorkflowStepStatus.Completed &&
+                currentStepBox.Steps.All(d => d.Status is EWorkflowStepStatus.Completed))
             {
-                var steps = CreateStepByStepDefine(stepDefine, dto.Handlers, currentStep.Id);
-                existsStepBox = CreateStepBox(stepDefine, steps);
-                workflow.StepBoxes.Add(existsStepBox);
+                currentStepBox.Status = EWorkflowStepStatus.Completed;
+                currentStepBox.CompleteTime = currentStepBox.Steps.Max(d => d.CompleteTime);
             }
-            else
+            await _workflowStepRepository.UpdateRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep }, cancellationToken);
+
+            //检查会签是否结束,并更新当前会签节点
+            workflow.CheckCountersignStatus();
+
+            //检查是否流转到流程终点
+            //var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+            if (nextStepBoxDefine.StepType is EStepType.End)
             {
-                var steps = CreateStepByStepDefine(stepDefine, dto.Handlers, currentStep.Id);
-                existsStepBox.Steps.AddRange(steps);
+                workflow.Complete();
+                await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
+                //todo publish workflow end
+                return;
             }
 
+            //判断是否从中心流转出去,重新计算expiredTime 
+            if (isOutOfCallCenter)
+                workflow.ExpiredTime = GenerateExpiredTime(workflow.Definition.Code);
+
+            //创建下一节点
+            var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, currentStepBox.Id, currentStep.Id, cancellationToken);
+
+            //不在会签中的流程,更新当前节点名称、时间、会签节点code
+            if (!workflow.IsInCountersign())
+                SetWorkflowCurrentStepInfo(workflow, isCountersign, nextStepBox);
+
+            await _workflowRepository.UpdateAsync(workflow, cancellationToken);
+
             //trace
-            var trace = CreateTrace(workflow.Id, currentStep, dto);
-            workflow.Traces.Add(trace);
+            await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
 
-            await _workflowRepository.UpdateNavigateAsync(workflow, cancellationToken);
-            
-            await _mediator.Publish(new NextStepNotify(dto), cancellationToken);
+            await _mediator.Publish(new NextStepNotify(workflow.Id, dto), cancellationToken);
         }
 
         /// <summary>
-        /// 根据操作人获取当前节点
+        /// 退回(返回前一节点)
         /// </summary>
         /// <returns></returns>
-        public async Task<(WorkflowStep, WorkflowStep)> GetStepAsync(string workflowId, string depCode, string userId, CancellationToken cancellationToken)
+        public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, CancellationToken cancellationToken)
         {
-            var workflow = await _workflowRepository.GetAsync(workflowId, cancellationToken);
-            if (workflow is null)
-                throw new UserFriendlyException("无效workflowId");
+            CheckWhetherRunnable(workflow.Status);
+            var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
+            if (currentStepBox.StepType is EStepType.Start)
+                throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
+            if (currentStepBox.Steps.Count > 1)
+                throw UserFriendlyException.SameMessage("会签流程不支持退回");
 
-            return GetStep(workflow.StepBoxes, depCode, userId);
+            //update trace
+            await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken);
+
+            //remove workflow.steps
+            await _workflowStepRepository.RemoveRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep },
+                cancellationToken);
+
+            //todo publish
         }
 
-        public (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string depCode, string userId)
+        /// <summary>
+        /// 撤回(返回到之前任意节点)
+        /// </summary>
+        public async Task RecallAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken)
         {
-            foreach (var stepBox in stepBoxes)
+            CheckWhetherRunnable(workflow.Status);
+            var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.TargetStepCode);
+            if (targetStepBox is null)
+                throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
+
+            await RecallAsync(workflow, dto, targetStepBox, cancellationToken);
+
+            //todo publish
+        }
+
+        /// <summary>
+        /// 跳转(直接将流程跳转至任意节点)
+        /// </summary>
+        public async Task JumpAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken)
+        {
+            CheckWhetherRunnable(workflow.Status);
+            //update uncompleted traces
+            await JumpTraceAsync(workflow.Id, dto, cancellationToken);
+
+            var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.TargetStepCode);
+            if (targetStepBox == null)
             {
-                foreach (var step in stepBox.Steps)
-                {
-                    if (step.HandlerId == depCode || step.HandlerId == userId)
-                        return (stepBox, step);
-                }
+                //向后跳转
+                var lastStepBox = workflow.StepBoxes.OrderBy(d => d.CreationTime).Last();
+                if (lastStepBox.StepType is EStepType.Start or EStepType.End)
+                    throw UserFriendlyException.SameMessage("无法跳转至开始或结束节点");
+
+                var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+                var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, lastStepBox.Id, lastStepBox.Id, cancellationToken);
+
+                workflow.CloseCountersignStatus();
+                var isCountersign = dto.Handlers.Count > 1;
+                SetWorkflowCurrentStepInfo(workflow, isCountersign, nextStepBox);
+
+                #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
+            {
+                //返回之前节点
+                await RecallAsync(workflow, dto, targetStepBox, cancellationToken);
             }
 
-            throw new UserFriendlyException("未找到对应节点");
+            //todo publish
         }
 
         /// <summary>
-        /// 获取可能前往的下一节点
+        /// 补充
         /// </summary>
         /// <returns></returns>
-        public async Task<IReadOnlyList<NextStepDefine>> GetNextStepsAsync(QueryWorkflowDto dto, CancellationToken cancellationToken)
+        public async Task SupplementAsync(Workflow workflow, EndWorkflowDto dto, CancellationToken cancellationToken)
         {
-            var (_, currentStep) = await GetStepAsync(dto.WorkflowId, dto.DepCode, dto.UserId, cancellationToken);
-            //获取人工判定的所有下一节点
-            return currentStep.NextSteps.Where(d => string.IsNullOrEmpty(d.Predicate)).ToList();
+            CheckWhetherRunnable(workflow.Status);
+            //todo 检查当前办理人是否为该流程中的办理人
+
+            var supplement = _mapper.Map<WorkflowSupplement>(dto);
+            await _workflowSupplementRepository.AddAsync(supplement, cancellationToken);
         }
 
-        public Workflow CreateWorkflow(Definition definition, StartWorkflowDto dto)
+        /// <summary>
+        /// 根据stepCode查询流程配置中对应的节点
+        /// </summary>
+        public StepDefine GetStepBoxDefine(Definition definition, string stepCode)
         {
-            var workflow = new Workflow
-            {
+            if (definition == null) throw new ArgumentNullException(nameof(definition));
+            if (string.IsNullOrEmpty(stepCode)) throw new ArgumentNullException(nameof(stepCode));
+            if (!definition.Steps.Any())
+                throw new UserFriendlyException($"流程未配置节点,DefineCode: {definition.Code}", "流程配置错误");
+            var stepDefine = definition.Steps.FirstOrDefault(d => d.Code == stepCode);
+            if (stepDefine == null)
+                throw new UserFriendlyException($"未找到流程中对应的节点,DefineCode: {definition.Code}, stepCode: {stepCode}",
+                    "未查询到对应节点");
+            return stepDefine;
+        }
 
-                Definition = definition,
-                Status = EWorkflowStatus.Runnable,
-            };
 
-            var startStep = definition.Steps.First(d => d.StepType == EStepType.Start);
-            if (startStep.NextSteps.Count > 1)
-                throw new UserFriendlyException("开始节点存在多个下一节点");
+        #region private
 
-            //start节点后的首个节点
-            var firstStep = startStep.NextSteps.First();
-            if (firstStep.HandlerType == EHandlerType.AssignUser || firstStep.HandlerType == EHandlerType.AssignDep)
+        /// <summary>
+        /// 更新workflow中当前停留节点,时间和会签开始节点code
+        /// </summary>
+        private static void SetWorkflowCurrentStepInfo(Workflow workflow, bool isCountersign, WorkflowStep stepBox)
+        {
+            //1.不在会签中,未发起会签(普通处理) 2.不在会签中,发起会签(保存会签节点),3.会签中,不更新
+            if (isCountersign)
             {
-                var steps = CreateStepByStepDefine(firstStep, firstStep.HandlerClassifies, "StartStep");
-                workflow.StepBoxes.Add(CreateStepBox(firstStep, steps));
+                workflow.FLow(stepBox.Name, stepBox.CreationTime, stepBox.Code);
             }
             else
             {
-                if (dto.Handlers.Any())
-                {
-                    var steps = CreateStepByStepDefine(firstStep, dto.Handlers, "StartStep");
-                    workflow.StepBoxes.Add(CreateStepBox(firstStep, steps));
-                }
-                else
-                {
-                    throw new UserFriendlyException("未指定节点处理者");
-                }
+                workflow.FLow(stepBox.Name, stepBox.CreationTime);
             }
+        }
 
-            return workflow;
+        private async Task JumpTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
+        {
+            //未办理的traces
+            var uncompleteTraces =
+                await _workflowTraceRepository.QueryAsync(d =>
+                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
+            foreach (var trace in uncompleteTraces)
+            {
+                trace.Jump(
+                    _sessionContext.RequiredUserId,
+                    _sessionContext.UserName,
+                    _sessionContext.RequiredOrgCode,
+                    _sessionContext.OrgName);
+            }
+
+            await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
         }
 
-        public void AddStartTrace(Workflow workflow, StartWorkflowDto dto)
+        private async Task RecallTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
         {
-            var startStep = workflow.Definition.Steps.First(d => d.StepType == EStepType.Start);
-            var trace = _mapper.Map<WorkflowTrace>(startStep);
+            //未办理的traces
+            var uncompleteTraces =
+                await _workflowTraceRepository.QueryAsync(d =>
+                    d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
+            foreach (var trace in uncompleteTraces)
+            {
+                trace.Recall(
+                    _sessionContext.RequiredUserId,
+                    _sessionContext.UserName,
+                    _sessionContext.RequiredOrgCode,
+                    _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.WorkflowId = workflow.Id;
-            workflow.Traces.Add(trace);
+            trace.Previous(
+                _sessionContext.RequiredUserId,
+                _sessionContext.UserName,
+                _sessionContext.RequiredOrgCode,
+                _sessionContext.OrgName);
+            await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
         }
 
-        #region private
+        private async Task 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);
+            trace.ExpiredTime = workflow.ExpiredTime;
+            await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
+        }
+
+        private async Task AcceptTraceAsync(Workflow workflow, WorkflowStep currentStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
+        {
+            var trace = _mapper.Map<WorkflowTrace>(currentStep);//todo ignore parentId, map stepId
+            trace.Status = EWorkflowTraceStatus.Normal;
+            trace.ExpiredTime = workflow.ExpiredTime;
+
+            if (!string.IsNullOrEmpty(currentStep.PreviousId) && currentStepBox.Steps.Count > 1)
+            {
+                //有会签
+                var parentTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken);
+                trace.ParentId = parentTrace.Id;
+            }
+
+            await _workflowTraceRepository.AddAsync(trace, cancellationToken);
+        }
 
-        private (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string stepId)
+        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 RecallAsync(Workflow workflow, RecallDto dto, WorkflowStep targetStepBox, CancellationToken cancellationToken)
+        {
+            //update uncompleted traces
+            await RecallTraceAsync(workflow.Id, dto, cancellationToken);
+
+            //remove completedSteps include target self
+            var completeStepBoxes = workflow.StepBoxes.Where(d =>
+                d.Code == dto.TargetStepCode && d.CreationTime > targetStepBox.CreationTime);
+            var removeSteps = new List<WorkflowStep>();
+            foreach (var stepBox in completeStepBoxes)
+            {
+                removeSteps.Add(stepBox);
+                removeSteps.AddRange(stepBox.Steps);
+            }
+
+            await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
+
+            //recreate targetStep
+            var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
+            await CreateStepAsync(workflow, nextStepBoxDefine, dto, targetStepBox.PreviousId, targetStepBox.Steps.First().PreviousId, cancellationToken);
+        }
+
+        private static void CheckWhetherRunnable(EWorkflowStatus status)
+        {
+            if (status is not EWorkflowStatus.Runnable)
+                throw new UserFriendlyException("当前流程状态不可继续流转");
+        }
+
+        private async Task<WorkflowStep> CreateStepAsync(Workflow workflow, StepDefine stepBoxDefine, BasicWorkflowDto dto, string previousStepBoxId, string previousStepId, CancellationToken cancellationToken)
+        {
+            var nextStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == stepBoxDefine.Code);
+            nextStepBox ??= CreateStepBox(stepBoxDefine, dto, previousStepBoxId);
+            await _workflowStepRepository.AddAsync(nextStepBox, cancellationToken);
+
+            if (stepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.AssignDep)
+            {
+                var subSteps = CreateSubSteps(nextStepBox, nextStepBox.HandlerClassifies, dto.NextMainHandler, previousStepId);
+                nextStepBox.Steps.AddRange(subSteps);
+                await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
+            }
+            else
+            {
+                if (!dto.Handlers.Any())
+                    throw new UserFriendlyException("未指定节点处理者");
+                var subSteps = CreateSubSteps(nextStepBox, dto.Handlers, dto.NextMainHandler, previousStepId);
+                nextStepBox.Steps.AddRange(subSteps);
+                await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
+            }
+
+            return nextStepBox;
+        }
+
+        private (WorkflowStep stepBox, WorkflowStep step) GetStep(List<WorkflowStep> stepBoxes, string stepId)
         {
             foreach (var stepBox in stepBoxes)
             {
@@ -203,6 +513,40 @@ namespace Hotline.FlowEngine.Workflows
             throw new UserFriendlyException("未找到对应节点");
         }
 
+        /// <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.Completed);
+            if (step == null)
+                throw new UserFriendlyException("未找到对应节点");
+            return (stepBox, step);
+        }
+
+        private (WorkflowStep, WorkflowStep) GetUnCompleteStepOrDefault(List<WorkflowStep> stepBoxes, string orgCode, string userId) =>
+            GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed);
+
+        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.HandlerId == orgCode || step.HandlerId == userId))
+                        return (stepBox, step);
+                }
+            }
+
+            return new();
+        }
+
+
         private List<WorkflowStep> CreateStepByStepDefine(StepBasic stepBasic, List<string> handlers, string previousId)
         {
             return handlers.Select(d =>
@@ -220,38 +564,84 @@ namespace Hotline.FlowEngine.Workflows
         {
             var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
             if (steps != null && steps.Any())
-                stepBox.Steps = steps;
+                stepBox.Steps.AddRange(steps);
             return stepBox;
         }
 
-        private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, ApproverDto dto, bool isBackward = false)
+        private WorkflowStep CreateStepBox(StepDefine stepBasic, BasicWorkflowDto dto, string previousStepBoxId)
+        {
+            var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
+            _mapper.Map(dto, stepBox);
+            stepBox.PreviousId = previousStepBoxId;
+            return stepBox;
+        }
+
+        //private WorkflowStep CreateStepBox(
+        //    StepDefine stepBasic,
+        //    NextWorkflowDto dto,
+        //    string workflowId,
+        //    string previousId = "")
+        //{
+        //    var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
+        //    _mapper.Map(dto, stepBox);
+        //    stepBox.WorkflowId = workflowId;
+        //    if (!string.IsNullOrEmpty(previousId))
+        //        stepBox.PreviousId = previousId;
+        //    return stepBox;
+        //}
+
+        private List<WorkflowStep> CreateSubSteps(WorkflowStep stepBox, List<string> nextHandlers, string nextMainHandler, string previousStepId)
+        {
+            return nextHandlers.Select(d =>
+              {
+                  var step = _mapper.Map<WorkflowStep>(stepBox);
+                  step.ParentId = stepBox.Id;
+                  step.HandlerId = d;
+                  step.IsMain = d == nextMainHandler;
+                  step.PreviousId = previousStepId;
+                  return step;
+              }).ToList();
+        }
+
+        private WorkflowStep InitSubStepsInStepBox(WorkflowStep stepBox, List<string> nextHandlers)
+        {
+            var subSteps = nextHandlers.Select(d =>
+            {
+                var step = _mapper.Map<WorkflowStep>(stepBox);
+                step.ParentId = stepBox.Id;
+                step.HandlerId = d;
+                return step;
+            }).ToList();
+
+            stepBox.Steps = subSteps;
+            return stepBox;
+        }
+
+        private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, NextWorkflowDto dto, EWorkflowTraceStatus status = EWorkflowTraceStatus.Normal)
         {
             var trace = _mapper.Map<WorkflowTrace>(currentStep);
             _mapper.Map(dto, trace);
             trace.WorkflowId = workflowId;
-            trace.IsBackward = isBackward;
+            trace.Status = status;
             return trace;
         }
 
-        private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, PreviousDto dto, bool isBackward = false)
+        private WorkflowTrace CreateTrace(string workflowId, WorkflowStep currentStep, PreviousWorkflowDto dto, EWorkflowTraceStatus status = EWorkflowTraceStatus.Normal)
         {
             var trace = _mapper.Map<WorkflowTrace>(currentStep);
             _mapper.Map(dto, trace);
             trace.WorkflowId = workflowId;
-            trace.IsBackward = isBackward;
+            trace.Status = status;
             return trace;
         }
 
-        private async Task<Workflow> GetWorkflowAsync(string workflowId, CancellationToken cancellationToken)
+        /// <summary>
+        /// 依据配置生成过期时间
+        /// </summary>
+        /// <returns></returns>
+        private DateTime GenerateExpiredTime(string defineCode)
         {
-            var workflow = await _workflowRepository.Queryable()
-                .Includes(d => d.Definition)
-                .Includes(d => d.StepBoxes)
-                .Includes(d => d.Traces)
-                .FirstAsync(d => d.Id == workflowId);
-            if (workflow is null)
-                throw new UserFriendlyException("无效workflowId");
-            return workflow;
+            return DateTime.Now.AddDays(7); //todo 依据配置生成, Think about 工作日
         }
 
         #endregion

+ 147 - 0
src/Hotline/FlowEngine/Workflows/WorkflowStep.cs

@@ -0,0 +1,147 @@
+using Hotline.Share.Enums.FlowEngine;
+using SqlSugar;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public class WorkflowStep : StepBasicEntity
+{
+    public string WorkflowId { get; set; }
+
+    [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
+    public List<NextStep> NextSteps { get; set; }
+
+    /// <summary>
+    /// 被指派办理对象(依据不同指派方式可能为:depCode或userId),该字段subStep才会存在,stepBox不存在
+    /// </summary>
+    public string HandlerId { get; set; }
+
+    /// <summary>
+    /// 办理人部门code
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? OrgCode { get; set; }
+
+    [SugarColumn(IsNullable = true)]
+    public string? OrgName { get; set; }
+
+    /// <summary>
+    /// 办理人
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? UserId { get; set; }
+
+    [SugarColumn(IsNullable = true)]
+    public string? UserName { get; set; }
+
+    /// <summary>
+    /// 办理完成时间
+    /// </summary>
+    public DateTime? CompleteTime { get; set; }
+
+    /// <summary>
+    /// 接办人
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? AcceptUserId { get; set; }
+
+    [SugarColumn(IsNullable = true)]
+    public string? AcceptUserName { get; set; }
+
+    /// <summary>
+    /// 接办时间
+    /// </summary>
+    public DateTime? AcceptTime { get; set; }
+
+    /// <summary>
+    /// 上一级节点Id(stepBox此字段为上级stepBoxId,step为上级stepId)
+    /// </summary>
+    public string PreviousId { get; set; }
+
+    /// <summary>
+    /// 主办
+    /// </summary>
+    public bool IsMain { get; set; }
+
+    public EWorkflowStepStatus Status { get; set; } = EWorkflowStepStatus.Assigned;
+
+    #region 审批参数
+
+    /// <summary>
+    /// (下一节点办理人)根据审批者类型不同,此字段为不同内容
+    /// <example>
+    /// 部门等级/分类为:orgCodes, 角色为:userIds
+    /// </example>
+    /// </summary>
+    [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
+    public List<string> NextHandlers { get; set; }
+
+    /// <summary>
+    /// 下一节点主办,(NextHandlers其中一个, 如果不是会签则只有一个)
+    /// </summary>
+    public string NextMainHandler { get; set; }
+
+    /// <summary>
+    /// 下一节点code
+    /// </summary>
+    public string NextStepCode { get; set; }
+
+    /// <summary>
+    /// 是否短信通知
+    /// </summary>
+    public bool AcceptSms { get; set; }
+
+    /// <summary>
+    /// 办理意见
+    /// </summary>
+    [SugarColumn(Length = 2000)]
+    public string Opinion { get; set; }
+
+    /// <summary>
+    /// 附件
+    /// </summary>
+    [SugarColumn(ColumnDataType = "varchar(1000)", IsJson = true)]
+    public List<string> Additions { get; set; }
+
+    #endregion
+
+    /// <summary>
+    /// stepBox此字段无效,记录stepBox与其下subSteps关系
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ParentId { get; set; }
+
+    [SugarColumn(IsIgnore = true)]
+    public List<WorkflowStep> Steps { get; set; }
+
+    /// <summary>
+    /// 接办
+    /// </summary>
+    /// <param name="userId"></param>
+    /// <param name="userName"></param>
+    public void Accept(string userId, string userName)
+    {
+        AcceptUserId = userId;
+        AcceptUserName = userName;
+        AcceptTime = DateTime.Now;
+        Status = EWorkflowStepStatus.Accepted;
+    }
+
+    /// <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 nextStepCode)
+    {
+        UserId = userId;
+        UserName = userName;
+        OrgCode = orgCode;
+        OrgName = orgName;
+        CompleteTime = DateTime.Now;
+        Status = EWorkflowStepStatus.Completed;
+
+        NextSteps.First(d => d.Code == nextStepCode).Selected = true;
+    }
+}

+ 24 - 0
src/Hotline/FlowEngine/Workflows/WorkflowSupplement.cs

@@ -0,0 +1,24 @@
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows;
+
+/// <summary>
+/// 流程补充信息
+/// </summary>
+public class WorkflowSupplement : CreationEntity
+{
+    public string WorkflowId { get; set; }
+
+    /// <summary>
+    /// 补充意见
+    /// </summary>
+    [SugarColumn(Length = 2000)]
+    public string Opinion { get; set; }
+
+    /// <summary>
+    /// 附件
+    /// </summary>
+    [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
+    public List<string> Additions { get; set; } = new();
+}

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

@@ -1,8 +1,12 @@
 using Hotline.FlowEngine.Definitions;
+using Hotline.Share.Enums.FlowEngine;
 using SqlSugar;
 
 namespace Hotline.FlowEngine.Workflows;
 
+/// <summary>
+/// 流转记录
+/// </summary>
 public class WorkflowTrace : StepBasicEntity
 {
     public string WorkflowId { get; set; }
@@ -10,13 +14,65 @@ public class WorkflowTrace : StepBasicEntity
     public string StepId { get; set; }
 
     /// <summary>
-    /// 根据审批者类型不同,此字段为不同内容
+    /// 办理人部门code
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? OrgCode { get; set; }
+
+    [SugarColumn(IsNullable = true)]
+    public string? OrgName { get; set; }
+
+    /// <summary>
+    /// 办理人
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? UserId { get; set; }
+
+    [SugarColumn(IsNullable = true)]
+    public string? UserName { get; set; }
+
+    /// <summary>
+    /// 办理完成时间
+    /// </summary>
+    public DateTime? CompleteTime { get; set; }
+
+    /// <summary>
+    /// 接办人
+    /// </summary>
+    public string AcceptUserId { get; set; }
+
+    public string AcceptUserName { get; set; }
+
+    /// <summary>
+    /// 接办时间
+    /// </summary>
+    public DateTime AcceptTime { get; set; }
+
+    /// <summary>
+    /// 流转记录状态
+    /// </summary>
+    public EWorkflowTraceStatus Status { get; set; }
+
+    /// <summary>
+    /// 过期时间
+    /// </summary>
+    public DateTime ExpiredTime { get; set; }
+
+    #region 审批参数
+
+    /// <summary>
+    /// (下一节点办理人)根据审批者类型不同,此字段为不同内容
     /// <example>
     /// 部门等级/分类为:depCodes, 角色为:userIds
     /// </example>
     /// </summary>
-    [SugarColumn(ColumnDataType = "varchar(4000)", IsJson = true)]
-    public List<string> Handlers { get; set; }
+    [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
+    public List<string> NextHandlers { get; set; }
+
+    /// <summary>
+    /// 下一节点主办,(NextHandlers其中一个, 如果不是会签则只有一个)
+    /// </summary>
+    public string NextMainHandler { get; set; }
 
     /// <summary>
     /// 下一节点code
@@ -28,16 +84,48 @@ public class WorkflowTrace : StepBasicEntity
     /// </summary>
     public bool AcceptSms { get; set; }
 
-    public bool IsBackward { get; set; }
-    
-    public string DepCode { get; set; }
-
-    public string UserId { get; set; }
-
     /// <summary>
     /// 办理意见
     /// </summary>
+    [SugarColumn(Length = 2000)]
     public string Opinion { get; set; }
 
-    public DateTime? CreationTime { get; set; } = DateTime.Now;
+    /// <summary>
+    /// 附件
+    /// </summary>
+    [SugarColumn(ColumnDataType = "varchar(1000)", IsJson = true)]
+    public List<string> Additions { get; set; }
+
+    #endregion
+
+    /// <summary>
+    /// 会签从属关系
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ParentId { get; set; }
+
+    /// <summary>
+    /// 会签流转记录
+    /// </summary>
+    [SugarColumn(IsIgnore = true)]
+    public List<WorkflowTrace> Traces { get; set; }
+
+    public void Previous(string userId, string userName, string orgCode, string orgName) =>
+        Complete(userId, userName, orgCode, orgName, EWorkflowTraceStatus.Back);
+
+    public void Recall(string userId, string userName, string orgCode, string orgName) =>
+        Complete(userId, userName, orgCode, orgName, EWorkflowTraceStatus.Recall);
+
+    public void Jump(string userId, string userName, string orgCode, string orgName) =>
+        Complete(userId, userName, orgCode, orgName, EWorkflowTraceStatus.Jump);
+
+    public void Complete(string userId, string userName, string orgCode, string orgName, EWorkflowTraceStatus status = EWorkflowTraceStatus.Normal)
+    {
+        OrgCode = orgCode;
+        OrgName = orgName;
+        UserId = userId;
+        UserName = userName;
+        CompleteTime = DateTime.Now;
+        Status = status;
+    }
 }

+ 57 - 0
src/Hotline/Orders/Citizen.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Share.Enums.Order;
+using SqlSugar;
+
+namespace Hotline.Orders
+{
+    /// <summary>
+    /// 市民,以电话号为标识
+    /// </summary>
+    public class Citizen
+    {
+        public string PhoneNumber { get; set; }
+
+        /// <summary>
+        /// 姓名
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 性别
+        /// </summary>
+        public EGender Gender { get; set; }
+
+        /// <summary>
+        /// 市民身份
+        /// </summary>
+        public ECitizenStatus CitizenStatus { get; set; }
+
+        /// <summary>
+        /// 证件类型
+        /// </summary>
+        public ELicenceType? Licence { get; set; }
+
+        /// <summary>
+        /// 证件号码
+        /// </summary>
+        public string? LicenceNo { get; set; }
+
+        /// <summary>
+        /// 年龄段
+        /// </summary>
+        public EAgeRange? AgeRange { get; set; }
+
+        /// <summary>
+        /// 联系电话
+        /// </summary>
+        [SugarColumn(ColumnDescription = "联系电话", IsNullable = true)]
+        public string? Contact { get; set; }
+
+        [SugarColumn(ColumnDescription = "联系电话脱敏", IsNullable = true)]
+        public string? ContactMask { get; set; }
+    }
+}

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

@@ -8,16 +8,6 @@ namespace Hotline.Orders
 {
     public interface IOrderDomainService
     {
-        
-    }
-
-    public class OrderDomainService : IOrderDomainService
-    {
-        private readonly IOrderRepository _orderRepository;
-
-        public OrderDomainService(IOrderRepository orderRepository)
-        {
-            _orderRepository = orderRepository;
-        }
+        Task<string> AddAsync(Order order, CancellationToken cancellationToken);
     }
 }

+ 70 - 12
src/Hotline/Orders/Order.cs

@@ -1,4 +1,5 @@
 using Hotline.Share.Enums.Order;
+using Hotline.Users;
 using SqlSugar;
 using XF.Domain.Repository;
 
@@ -17,14 +18,19 @@ namespace Hotline.Orders
         public EChannel Channel { get; set; }
 
         /// <summary>
-        /// 服务人姓名(冗余)
+        /// 接线员工
         /// </summary>
-        public string ServerName { get; set; }
+        public string EmployeeId { get; set; }
 
         /// <summary>
-        /// 服务人工号(冗余)
+        /// 来电号码
         /// </summary>
-        public string StaffNo { get; set; }
+        public string? FromPhone { get; set; }
+
+        /// <summary>
+        /// 转接号码(转接来源)
+        /// </summary>
+        public string? TransferPhone { get; set; }
 
         /// <summary>
         /// 来电/信人姓名
@@ -39,7 +45,7 @@ namespace Hotline.Orders
         /// <summary>
         /// 来电/信人身份
         /// </summary>
-        public EFromStatus FromStatus { get; set; }
+        public ECitizenStatus FromStatus { get; set; }
 
         /// <summary>
         /// 证件类型
@@ -62,6 +68,9 @@ namespace Hotline.Orders
         [SugarColumn(ColumnDescription = "联系电话", IsNullable = true)]
         public string? Contact { get; set; }
 
+        [SugarColumn(ColumnDescription = "联系电话脱敏", IsNullable = true)]
+        public string? ContactMask { get; set; }
+
         /// <summary>
         /// 是否接受短信,勾选校验手机号
         /// </summary>
@@ -78,6 +87,12 @@ namespace Hotline.Orders
         [SugarColumn(ColumnDescription = "工作单位", IsNullable = true)]
         public string? Company { get; set; }
 
+        /// <summary>
+        /// 接线员(服务人)
+        /// </summary>
+        [Navigate(NavigateType.OneToOne, nameof(EmployeeId))]
+        public User Employee { get; set; }
+
         #endregion
 
         #region 诉求信息
@@ -90,7 +105,7 @@ namespace Hotline.Orders
         /// <summary>
         /// 工单类型
         /// </summary>
-        public EOrderType OrderType { get; set; }//todo
+        public EOrderType OrderType { get; set; }
 
         /// <summary>
         /// 受理类型
@@ -104,6 +119,9 @@ namespace Hotline.Orders
 
         public string Title { get; set; }
 
+        /// <summary>
+        /// 热点
+        /// </summary>
         public string HotspotId { get; set; }
         public string Hotspot { get; set; }
         public string HotspotSpliceName { get; set; }
@@ -113,13 +131,49 @@ namespace Hotline.Orders
         /// </summary>
         public DateTime? IncidentTime { get; set; }
 
+        /// <summary>
+        /// 重复工单
+        /// </summary>
+        [SugarColumn(ColumnDescription = "重复工单Id", IsNullable = true)]
+        public string? DuplicateId { get; set; }
+
+        [SugarColumn(ColumnDescription = "重复工单", IsNullable = true)]
+        public string? DuplicateName { get; set; }
+
+        /// <summary>
+        /// 推送分类
+        /// </summary>
+        public EPushType PushType { get; set; }
+
         /// <summary>
         /// 附件
         /// </summary>
         [SugarColumn(ColumnDataType = "varchar(2000)", IsJson = true)]
         public List<string> Additions { get; set; } = new();
 
+        /// <summary>
+        /// 诉求内容
+        /// </summary>
+        public string Content { get; set; }
+
         #endregion
+
+        public string CurrentStep { get; set; }
+
+        /// <summary>
+        /// 工单状态
+        /// </summary>
+        public EOrderStatus Status { get; set; }
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        public DateTime? StartTime { get; set; }
+
+        /// <summary>
+        /// 过期时间
+        /// </summary>
+        public DateTime? EndTime { get; set; }
     }
 
     /// <summary>
@@ -128,15 +182,19 @@ namespace Hotline.Orders
     public partial class Order
     {
         /// <summary>
-        /// 来电号码
+        /// 工单扩展信息(12315-投诉)
         /// </summary>
-        public string? FromPhone { get; set; }
+        [Navigate(NavigateType.OneToOne, nameof(Id))]
+        public OrderComplain OrderComplain { get; set; }
 
-        /// <summary>
-        /// 转接号码(转接来源)
-        /// </summary>
-        public string? TransferPhone { get; set; }
 
+    }
+
+    /// <summary>
+    /// 工单扩展信息 12315-举报
+    /// </summary>
+    public class OrderExt1
+    {
 
     }
 }

+ 310 - 0
src/Hotline/Orders/OrderComplain.cs

@@ -0,0 +1,310 @@
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.Orders;
+
+/// <summary>
+/// 工单扩展信息(12315-投诉)
+/// </summary>
+public class OrderComplain : FullStateEntity
+{
+    #region 投诉人信息
+
+    /// <summary>
+    /// 证件类型
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? LicenceTypeCode { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? LicenceType { get; set; }
+
+    /// <summary>
+    /// 证件号码, 证件类型已选的情况为必填,否则非必填
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? LicenceNo { get; set; }
+
+    /// <summary>
+    /// 提供方类型(投诉人类型)
+    /// </summary>
+    public EProviderType? ProviderType { get; set; }
+
+    /// <summary>
+    /// 提供方身份(投诉人身份)
+    /// </summary>
+    public ECitizenStatus1? ProviderStatus { get; set; }
+
+    /// <summary>
+    /// 国籍或地区
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public ENationality? Nationality { get; set; }
+
+    /// <summary>
+    /// 民族
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? NationCode { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? Nation { get; set; }
+
+    /// <summary>
+    /// 邮政编码
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? PostalCode { get; set; }
+
+    /// <summary>
+    /// 邮箱
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? Email { get; set; }
+
+    /// <summary>
+    /// 其他联系方式
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? OtherContact { get; set; }
+
+    #endregion
+
+    #region 投诉对象信息
+
+    /// <summary>
+    /// 企业名称
+    /// </summary>
+    public string EnterpriseName { get; set; }
+
+    /// <summary>
+    /// 统一社会信用代码
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? UnifiedSocialCreditCode { get; set; }
+
+    /// <summary>
+    /// 注册地址
+    /// </summary>
+    public string RegisterAddress { get; set; }
+
+    /// <summary>
+    /// 注册号
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? RegisterNumber { get; set; }
+
+    /// <summary>
+    /// 企业联系人
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? EnterpriseContact { get; set; }
+
+    /// <summary>
+    /// 市场主体类型
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? MarketTypeCode { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? MarketType { get; set; }
+
+    /// <summary>
+    /// 行业分类
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? IndustryClassifyCode { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? IndustryClassify { get; set; }
+
+    #endregion
+
+    #region 投诉详情
+
+    /// <summary>
+    /// 商品品牌编码
+    /// </summary>
+    public string BrandCode { get; set; }
+
+    /// <summary>
+    /// 商品分类/品牌
+    /// </summary>
+    public string Brand { get; set; }
+
+    /// <summary>
+    /// 消费金额,只能填写数字,且只能填写非负数
+    /// </summary>
+    public decimal Amount { get; set; }
+
+    /// <summary>
+    /// 客体类别
+    /// </summary>
+    public string ObjectClassifyCode { get; set; }
+    public string ObjectClassify { get; set; }
+
+    /// <summary>
+    /// 投诉问题类别
+    /// </summary>
+    public string ComplainClassifyCode { get; set; }
+    public string ComplainClassify { get; set; }
+
+    /// <summary>
+    /// 争议发生时间
+    /// </summary>
+    public DateTime OccurrenceTime { get; set; }
+
+    /// <summary>
+    /// 销售方式
+    /// </summary>
+    public ESalesMode SalesMode { get; set; }
+
+    /// <summary>
+    /// 投诉目标,销售方式为“网购”时展示该字段且必填
+    /// </summary>
+    public EComplainTarget? ComplainTarget { get; set; }
+
+    /// <summary>
+    /// 电商平台,销售方式为“网购”时展示该字段且必填
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ECommercePlatformCode { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? ECommercePlatform { get; set; }
+
+    /// <summary>
+    /// 外部订单号,销售方式为“ 网购”时展示该字段且必填
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ExternalOrderNo { get; set; }
+
+    /// <summary>
+    /// 经营地址,销售方式为“现场”时必填
+    /// </summary>
+    //todo 地址信息不够完善
+    [SugarColumn(IsNullable = true)]
+    public string? BussinessArea { get; set; }
+    [SugarColumn(IsNullable = true)]
+    public string? BussinessAddress { get; set; }
+
+    /// <summary>
+    /// 具体渠道
+    /// <remarks>
+    /// 销售方式为“电视购物”“电话购物”“邮购”时展示该字段,且必填
+    /// 电视购物:请填写购物的电视频道
+    /// 电话购物:请填写商品销售者的热线号码
+    /// 邮购:请填写宣传商品的邮政公司或来件地址
+    /// </remarks>
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? Channel { get; set; }
+
+    /// <summary>
+    /// 专利权人,“投诉问题类别”为“专利”时展示该字段,选填
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? Patentee { get; set; }
+
+    /// <summary>
+    /// 专利名称,“投诉问题类别”为“专利”时展示该字段,选填
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? PatentName { get; set; }
+
+    /// <summary>
+    /// 专利类型,“投诉问题类别”为“专利”时展示该字段,选填
+    /// </summary>
+    public EPatentType? PatentType { get; set; }
+
+    /// <summary>
+    /// 专利号,“投诉问题类别”为“专利”时展示该字段,选填
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? PatentNo { get; set; }
+
+    /// <summary>
+    /// 产品名称
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ProductName { get; set; }
+
+    /// <summary>
+    /// 批准文号
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ApprovalNumber { get; set; }
+
+    /// <summary>
+    /// 产品批号
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ProductBatchNo { get; set; }
+
+    /// <summary>
+    /// 产品规格
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ProductStandard { get; set; }
+
+    /// <summary>
+    /// 产品有效期
+    /// </summary>
+    public DateTime ProductExpriedTime { get; set; }
+
+    /// <summary>
+    /// 生产厂家
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? Manufacturer { get; set; }
+
+    /// <summary>
+    /// 销售企业
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? SalesEnterprise { get; set; }
+
+    /// <summary>
+    /// 消费者地址
+    /// </summary>
+    [SugarColumn(IsNullable = true)]
+    public string? ConsumerAddress { get; set; }
+
+    /// <summary>
+    /// 诉求类型,多选
+    /// </summary>
+    public EComplainType? ComplainType { get; set; }
+
+    #endregion
+}
+
+[Flags]
+public enum EComplainType
+{
+    //修理、重做、更换、退货、补足商品数量、退赔费用、赔偿损失、停止侵权、核定侵权责任
+}
+
+public enum EPatentType
+{
+    //发明专利、实用新型专利、外观设计专利
+}
+
+public enum ENationality
+{
+    //中国、外国、港澳台地区
+}
+
+public enum ECitizenStatus1
+{
+    //城镇、农村、港澳台同胞、外籍、军人、学生、其他
+}
+
+public enum EProviderType
+{
+    //生产企业、销售企业、服务企业、其他企业、个体工商户、自然人、群诉、其他类型
+}
+
+public enum EComplainTarget
+{
+    //平台、入驻商家
+}
+
+public enum ESalesMode
+{
+    //网购、现场、电视购物、电话购物、邮购
+}

+ 53 - 0
src/Hotline/Orders/OrderDomainService.cs

@@ -0,0 +1,53 @@
+using XF.Domain.Cache;
+using XF.Domain.Exceptions;
+using XF.Domain.Extensions;
+
+namespace Hotline.Orders;
+
+public class OrderDomainService : IOrderDomainService
+{
+    private const string OrderNoPrefix = "OrderNo:";
+    private readonly IOrderRepository _orderRepository;
+    private readonly ITypedCache<int> _cacheOrderNo;
+
+    public OrderDomainService(
+        IOrderRepository orderRepository,
+        ITypedCache<int> cacheOrderNo)
+    {
+        _orderRepository = orderRepository;
+        _cacheOrderNo = cacheOrderNo;
+    }
+
+    public async Task<string> AddAsync(Order order, CancellationToken cancellationToken)
+    {
+        if (!string.IsNullOrEmpty(order.Contact))
+            order.ContactMask = order.Contact.MaskPhoneNumber();
+
+        order.No = GetNewOrderNo();
+
+        return await _orderRepository.AddAsync(order, cancellationToken);
+    }
+
+    private string GetNewOrderNo()
+    {
+        var today = DateTime.Today;
+        var cacheKey = $"{OrderNoPrefix}{today:yyyyMMdd}";
+        var count = _cacheOrderNo.GetOrAdd(cacheKey, f =>
+        {
+            var todayOrderCount = _orderRepository.Queryable(true)
+                .CountAsync(d => d.CreationTime.Date == today.Date)
+                .GetAwaiter()
+                .GetResult();
+            return todayOrderCount;
+        }, ExpireMode.Absolute, TimeSpan.FromDays(1));
+        count += 1;
+        var no = GenerateOrderNo(today, count);
+        _cacheOrderNo.Update(cacheKey, d => count);
+        return no;
+    }
+
+    private string GenerateOrderNo(DateTime today, int count)
+    {
+        return $"{today:yyyyMMdd}{count.ToString("000000")}";
+    }
+}

+ 0 - 9
src/Hotline/Orders/OrderTemporary.cs

@@ -1,9 +0,0 @@
-namespace Hotline.Orders;
-
-/// <summary>
-/// 工单草稿
-/// </summary>
-public class OrderTemporary : Order
-{
-
-}

+ 0 - 28
src/Hotline/Position.cs

@@ -1,28 +0,0 @@
-using SqlSugar;
-
-namespace Hotline;
-
-public class Position
-{
-    /// <summary>
-    /// 经度
-    /// </summary>
-    public double Longitude { get; set; }
-
-    /// <summary>
-    /// 维度
-    /// </summary>
-    public double Latitude { get; set; }
-
-    /// <summary>
-    /// 行政区划编码
-    /// </summary>
-    public string AreaCode { get; set; }
-    public string Province { get; set; }
-    public string City { get; set; }
-    public string County { get; set; }
-    public string Street { get; set; }
-
-    [SugarColumn(IsIgnore = true)]
-    public string Address => $"{Province}{City}{County}{Street}";
-}

+ 10 - 3
src/XF.Domain.Repository/IRepositorySqlSugar.cs

@@ -12,8 +12,6 @@ namespace XF.Domain.Repository
     public interface IRepositorySqlSugar<TEntity, TKey> : IRepositoryWithTKey<TEntity, string>
         where TEntity : class, IEntity<string>, new()
     {
-
-
         Task<(int Total, List<TEntity> Items)> QueryPagedAsync(
             Expression<Func<TEntity, bool>> predicate,
             Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> orderByCreator,
@@ -60,10 +58,19 @@ namespace XF.Domain.Repository
         Task UpdateAsync(TEntity entity, bool ignoreNullColumns = true, CancellationToken cancellationToken = default);
 
         ISugarQueryable<TEntity> Queryable(bool permissionVerify = false, bool includeDeleted = false);
-        
+
         UpdateNavTaskInit<TEntity, TEntity> UpdateNav(TEntity entity);
         UpdateNavTaskInit<TEntity, TEntity> UpdateNav(TEntity entity, UpdateNavRootOptions options);
         UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities);
         UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities, UpdateNavRootOptions options);
+
+        InsertNavTaskInit<TEntity, TEntity> InsertNav(TEntity entity);
+        InsertNavTaskInit<TEntity, TEntity> InsertNav(TEntity entity, InsertNavRootOptions options);
+        InsertNavTaskInit<TEntity, TEntity> InsertNav(List<TEntity> entities);
+        InsertNavTaskInit<TEntity, TEntity> InsertNav(List<TEntity> entities, InsertNavRootOptions options);
+
+        DeleteNavTaskInit<TEntity, TEntity> DeleteNav(TEntity entity);
+        DeleteNavTaskInit<TEntity, TEntity> DeleteNav(List<TEntity> entities);
+        DeleteNavTaskInit<TEntity, TEntity> DeleteNav(Expression<Func<TEntity,bool>> predicate);
     }
 }

+ 3 - 1
src/XF.Domain.Repository/IRepositoryWorkflow.cs

@@ -9,7 +9,9 @@ using XF.Domain.Entities;
 
 namespace XF.Domain.Repository
 {
-    public interface IRepositoryWorkflow<TEntity, TKey> where TEntity : class, IEntity<string>, new()
+    public interface IRepositoryWorkflow<TEntity, TKey> : IRepositorySqlSugar<TEntity, TKey>
+        where TEntity : class, IEntity<string>, new()
+        where TKey : IEquatable<TKey>
     {
         ISugarQueryable<TEntity> Queryable(bool includeDeleted = false);
     }

+ 7 - 0
src/XF.Domain/Authentications/ISessionContext.cs

@@ -30,5 +30,12 @@ public interface ISessionContext
     ///// <param name="claimType"></param>
     ///// <returns></returns>
     //string? FindFirstValue(string claimType);
+
+    string? OrgId { get; set; }
+    string? OrgCode { get; set; }
+    string? OrgName { get; set; }
+
+    string RequiredOrgId { get; }
+    string RequiredOrgCode { get; }
 }
 

+ 1 - 1
src/XF.Domain/Cache/ITypedCache.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 
 namespace XF.Domain.Cache
 {
-    public interface ITypedCache<TValue> where TValue : class
+    public interface ITypedCache<TValue> //where TValue : class
     {
         bool Add(string key, TValue value, ExpireMode? expireMode = ExpireMode.None, TimeSpan? timeout = null);
 

+ 33 - 0
src/XF.Domain/Extensions/DataMaskExtensions.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace XF.Domain.Extensions;
+
+public static class DataMaskExtensions
+{
+    public static string Mask(this string original, int startIndex = 0, int length = 4)
+    {
+        var headStr = original.Substring(0, startIndex);
+        var tailStr = original.Substring(startIndex + length);
+        var sb = new StringBuilder(length);
+        for (int i = 0; i < length; i++)
+        {
+            sb.Append('*');
+        }
+
+        return $"{headStr}{sb}{tailStr}";
+    }
+
+    public static string MaskPhoneNumber(this string original)
+    {
+        return original.Mask(3, 4);
+    }
+
+    public static string MaskIdCard(this string original)
+    {
+        return original.Mask(3, 12);
+    }
+}