Browse Source

Merge branch 'release/yibin' of http://110.188.24.182:10023/Fengwo/hotline into release/yibin

xf 10 months ago
parent
commit
12db55f5be
46 changed files with 1114 additions and 276 deletions
  1. 121 3
      src/Hotline.Api/Controllers/AiController.cs
  2. 18 0
      src/Hotline.Api/Controllers/IPPbxController.cs
  3. 1 0
      src/Hotline.Api/Controllers/IdentityController.cs
  4. 198 34
      src/Hotline.Api/Controllers/OrderController.cs
  5. 1 0
      src/Hotline.Api/Controllers/WebPortalController.cs
  6. 18 0
      src/Hotline.Api/Controllers/WorkflowController.cs
  7. 9 1
      src/Hotline.Api/Realtimes/RealtimeMethods.cs
  8. 13 1
      src/Hotline.Api/Realtimes/RealtimeService.cs
  9. 2 2
      src/Hotline.Api/config/appsettings.Development.json
  10. 2 2
      src/Hotline.Api/config/appsettings.json
  11. 1 0
      src/Hotline.Application/Bigscreen/DataScreenRefreshService.cs
  12. 1 0
      src/Hotline.Application/Bigscreen/SeatStateDataRefreshService.cs
  13. 52 0
      src/Hotline.Application/CallCenter/Calls/CurrentWaitNumService.cs
  14. 43 0
      src/Hotline.Application/CallCenter/Calls/ToDayWaitNumService.cs
  15. 1 0
      src/Hotline.Application/CallCenter/Calls/TrApplication.cs
  16. 1 0
      src/Hotline.Application/Handlers/FlowEngine/WorkflowRecallHandler.cs
  17. 27 11
      src/Hotline.Application/Identity/IdentityAppService.cs
  18. 1 0
      src/Hotline.Application/Orders/OrderApplication.cs
  19. 1 0
      src/Hotline.Application/Quality/QualityApplication.cs
  20. 3 1
      src/Hotline.Share/Dtos/Ai/AiDto.cs
  21. 9 0
      src/Hotline.Share/Dtos/FlowEngine/Workflow/ChangeHandlerDto.cs
  22. 20 0
      src/Hotline.Share/Dtos/Order/Migration/GetOrderMigrationDto.cs
  23. 4 0
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  24. 138 0
      src/Hotline.Share/Dtos/Order/OrderVisitDto.cs
  25. 14 0
      src/Hotline.Share/Dtos/Order/Publish/PublishMigrationDto.cs
  26. 74 0
      src/Hotline.Share/Dtos/Order/Publish/QueryOrderPublishDto.cs
  27. 5 0
      src/Hotline.Share/Dtos/Order/PublishedDto.cs
  28. 65 126
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  29. 2 0
      src/Hotline.Share/Enums/Ai/EAiCallOutTaskState.cs
  30. 2 0
      src/Hotline.Share/Enums/Ai/EAiOrderVisitTaskState.cs
  31. 19 0
      src/Hotline.Share/Enums/Order/EVisitState.cs
  32. 12 0
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  33. 32 0
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  34. 3 2
      src/Hotline/Orders/IOrderDomainService.cs
  35. 20 6
      src/Hotline/Orders/Order.cs
  36. 38 76
      src/Hotline/Orders/OrderDomainService.cs
  37. 10 0
      src/Hotline/Orders/OrderPublishHistory.cs
  38. 32 0
      src/Hotline/Orders/OrderVisit.cs
  39. 24 3
      src/Hotline/Permissions/EPermission.cs
  40. 1 0
      src/Hotline/Push/MessageCodeDomainService.cs
  41. 1 0
      src/Hotline/Quality/QualityDomainService.cs
  42. 19 1
      src/Hotline/Realtimes/IRealtimeService.cs
  43. 20 7
      src/Hotline/Settings/SettingConstants.cs
  44. 7 0
      src/Tr.Sdk/Tels/ITrClient.Tel.cs
  45. 28 0
      src/Tr.Sdk/Tels/QueryQueueWaitNumRequest.cs
  46. 1 0
      src/Tr.Sdk/TrClient.cs

+ 121 - 3
src/Hotline.Api/Controllers/AiController.cs

@@ -347,11 +347,10 @@ namespace Hotline.Api.Controllers
         {
             switch (request.TypeId)
             {
-                //待处理子表
                 case 1:
                     var aiVisit = await _aiOrderVisitRepository.Queryable()
                         .Includes(x => x.AiOrderVisitDetails,s=>s.OrderVisit)
-                        .FirstAsync(x => x.Id == request.Id);
+                        .FirstAsync(x => x.Id == request.Id,HttpContext.RequestAborted);
                     if (aiVisit!=null && !string.IsNullOrEmpty(aiVisit.BatchUid))
                     {
                         if (aiVisit.TaskState != EAiOrderVisitTaskState.NoStarted && aiVisit.TaskState != EAiOrderVisitTaskState.InProgress)
@@ -406,6 +405,103 @@ namespace Hotline.Api.Controllers
             }
         }
 
+        /// <summary>
+        /// 暂停外呼任务
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [HttpPost("callout/pausecallouttask")]
+        public async Task PauseCalloutTask([FromBody] CloseCalloutTaskReq request)
+        {
+            switch (request.TypeId)
+            {
+                case 1:
+                    var aiVisit = await _aiOrderVisitRepository.Queryable()
+                        .FirstAsync(x => x.Id == request.Id,HttpContext.RequestAborted);
+                    if (aiVisit != null && !string.IsNullOrEmpty(aiVisit.BatchUid))
+                    {
+                        if (aiVisit.TaskState != EAiOrderVisitTaskState.NoStarted && aiVisit.TaskState != EAiOrderVisitTaskState.InProgress)
+                            throw UserFriendlyException.SameMessage("当前状态不能终止");
+
+                        bool isOk = await _aiVisitService.ChangeStatusAsync(aiVisit.BatchUid, "stop", HttpContext.RequestAborted);
+
+                        if (!isOk)
+                            throw UserFriendlyException.SameMessage("终止失败");
+
+                        aiVisit.TaskState = EAiOrderVisitTaskState.Pause;
+                        await _aiOrderVisitRepository.UpdateAsync(aiVisit, HttpContext.RequestAborted);
+                    }
+                    break;
+                case 2:
+                    var callOut = await _callOutTaskRepository.Queryable()
+                       .FirstAsync(x => x.Id == request.Id);
+                    if (callOut != null && !string.IsNullOrEmpty(callOut.BatchUid))
+                    {
+                        if (callOut.AiCallOutTaskState != EAiCallOutTaskState.NoStarted && callOut.AiCallOutTaskState != EAiCallOutTaskState.InProgress)
+                            throw UserFriendlyException.SameMessage("当前状态不能终止");
+
+                        bool isOk = await _aiVisitService.ChangeStatusAsync(callOut.BatchUid, "stop", HttpContext.RequestAborted);
+
+                        if (!isOk)
+                            throw UserFriendlyException.SameMessage("终止失败");
+                        //处理业务数据
+                        callOut.AiCallOutTaskState = EAiCallOutTaskState.Pause;
+                        await _callOutTaskRepository.UpdateAsync(callOut, HttpContext.RequestAborted);
+                    }
+                    break;
+                default:
+                    throw UserFriendlyException.SameMessage("未知业务");
+            }
+        }
+
+        /// <summary>
+        /// 启动外呼任务
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [HttpPost("callout/startcallouttask")]
+        public async Task StartCalloutTask([FromBody] CloseCalloutTaskReq request)
+        {
+            switch (request.TypeId)
+            {
+                case 1:
+                    var aiVisit = await _aiOrderVisitRepository.Queryable()
+                        .FirstAsync(x => x.Id == request.Id, HttpContext.RequestAborted);
+                    if (aiVisit != null && !string.IsNullOrEmpty(aiVisit.BatchUid))
+                    {
+                        if (aiVisit.TaskState != EAiOrderVisitTaskState.Pause)
+                            throw UserFriendlyException.SameMessage("当前状态不能启动");
+
+                        bool isOk = await _aiVisitService.ChangeStatusAsync(aiVisit.BatchUid, "start", HttpContext.RequestAborted);
+
+                        if (!isOk)
+                            throw UserFriendlyException.SameMessage("启动失败");
+
+                        aiVisit.TaskState = EAiOrderVisitTaskState.InProgress;
+                        await _aiOrderVisitRepository.UpdateAsync(aiVisit, HttpContext.RequestAborted);
+                    }
+                    break;
+                case 2:
+                    var callOut = await _callOutTaskRepository.Queryable()
+                       .FirstAsync(x => x.Id == request.Id);
+                    if (callOut != null && !string.IsNullOrEmpty(callOut.BatchUid))
+                    {
+                        if (callOut.AiCallOutTaskState != EAiCallOutTaskState.Pause)
+                            throw UserFriendlyException.SameMessage("当前状态不能启动");
+
+                        bool isOk = await _aiVisitService.ChangeStatusAsync(callOut.BatchUid, "start", HttpContext.RequestAborted);
+
+                        if (!isOk)
+                            throw UserFriendlyException.SameMessage("启动失败");
+                        //处理业务数据
+                        callOut.AiCallOutTaskState = EAiCallOutTaskState.Pause;
+                        await _callOutTaskRepository.UpdateAsync(callOut, HttpContext.RequestAborted);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
 
         #endregion
 
@@ -804,12 +900,17 @@ namespace Hotline.Api.Controllers
         [HttpGet("aivisit/aivisit-list")]
         public async Task<PagedDto<AiOrderVisitDto>> AiVisitList([FromQuery]AiVisitListDto dto)
         {
-            var (total,items) = await _aiOrderVisitRepository.Queryable()
+            var (total, items) = await _aiOrderVisitRepository.Queryable()
                 .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Name.Contains(dto.Keyword))
+                .WhereIF(dto.AiOrderVisitTaskState != null, x => x.TaskState == dto.AiOrderVisitTaskState)
+                .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
+                .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
                 .OrderByDescending(x => x.CreationTime)
                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
             return new PagedDto<AiOrderVisitDto>(total, _mapper.Map<IReadOnlyList<AiOrderVisitDto>>(items)); 
         }
+
+
         /// <summary>
         /// 智能回访明细
         /// </summary>
@@ -822,6 +923,7 @@ namespace Hotline.Api.Controllers
                 .Includes(x=>x.OrderVisit,x=>x.OrderVisitDetails)
                 .Includes(x=>x.Order)
                 .Where(x => x.AiOrderVisitId == dto.Id)
+                .WhereIF(dto.AiOrderVisitState.HasValue,x=>x.AiOrderVisitState == dto.AiOrderVisitState)
                 .WhereIF(!string.IsNullOrEmpty(dto.Keyword),x=>x.Order.No.Contains(dto.Keyword) || x.Order.Title.Contains(dto.Keyword))
                 .OrderByDescending(x => x.CreationTime)
                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
@@ -847,6 +949,22 @@ namespace Hotline.Api.Controllers
             return _mapper.Map<IReadOnlyList<OrderVisitDto>>(items);
         }
 
+        /// <summary>
+        /// 任务页面基础数据
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("aivisit/taskbase-data")]
+        public async Task<object> AiVisitTaskBaseData()
+        {
+            var rsp = new
+            {
+                AiOrderVisitTaskState = EnumExts.GetDescriptions<EAiOrderVisitTaskState>(),
+                AiOrderVisitState = EnumExts.GetDescriptions<EAiOrderVisitState>()
+            };
+            return rsp;
+        }
+
+
         /// <summary>
         /// 页面基础数据
         /// </summary>

+ 18 - 0
src/Hotline.Api/Controllers/IPPbxController.cs

@@ -8,6 +8,7 @@ using Hotline.CallCenter.Tels;
 using Hotline.Orders;
 using Hotline.Permissions;
 using Hotline.Quality;
+using Hotline.Repository.SqlSugar.CallCenter;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Settings;
 using Hotline.Share.Dtos;
@@ -696,6 +697,23 @@ namespace Hotline.Api.Controllers
         }
         #endregion
 
+        #region 话务队列信息
+        /// <summary>
+        /// 今日等待数
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("query-todaywaitnum")]
+        public async Task<int> QueryToDayWaitNum()
+        {
+            int count = await _trCallRecordRepository.Queryable()
+                        .Where(x => x.CreatedTime.Date == DateTime.Now.Date)
+                        .Where(x => x.QueueTims > 0 && x.Duration == 0)
+                        .CountAsync();
+            return count;
+        }
+
+        #endregion
+
         #endregion
     }
 }

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

@@ -7,6 +7,7 @@ using System.Security.Cryptography;
 using System.Text;
 using Hotline.Api.Filter;
 using Hotline.Application.Systems;
+using Hotline.Settings;
 using XC.RSAUtil;
 using XF.Domain.Constants;
 using XF.Domain.Exceptions;

+ 198 - 34
src/Hotline.Api/Controllers/OrderController.cs

@@ -29,6 +29,8 @@ using Hotline.Share.Dtos.Enterprise;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Order.Migration;
+using Hotline.Share.Dtos.Order.Publish;
 using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.FlowEngine;
@@ -44,7 +46,9 @@ using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.OpenApi.Writers;
 using MiniExcelLibs;
+using MongoDB.Driver;
 using NPOI.SS.Formula.Functions;
 using NPOI.SS.Util;
 using Org.BouncyCastle.Utilities;
@@ -247,12 +251,12 @@ public class OrderController : BaseController
         var (total, items) = await _orderRepository.Queryable()
             .Includes(d => d.OrderPublish)
             .Where(x => x.Status == EOrderStatus.Filed)
-            .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-                d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!))
+            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword!))
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
             .WhereIF(dto.PubState == EPubState.Pub, d => d.Status >= EOrderStatus.Published)
             .WhereIF(dto.PubState == EPubState.NoPub, d => d.Status < EOrderStatus.Published)
-            .WhereIF(!string.IsNullOrEmpty(dto.PubMan),
-                d => d.AcceptorName.Contains(dto.PubMan!) || d.AcceptorStaffNo.Contains(dto.PubMan!))
+            //.WhereIF(!string.IsNullOrEmpty(dto.PubMan),
+            //    d => d.AcceptorName.Contains(dto.PubMan!) || d.AcceptorStaffNo.Contains(dto.PubMan!))
             .WhereIF(dto.PubRange == EPublicState.Pub, d => d.OrderPublish.PublishState)
             .WhereIF(dto.PubRange == EPublicState.NoPub, d => !d.OrderPublish.PublishState)
             .WhereIF(dto.FiledType != null && dto.FiledType == FiledType.CenterFiled, d => d.ProcessType == EProcessType.Zhiban)
@@ -266,6 +270,7 @@ public class OrderController : BaseController
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
             .WhereIF(dto.FiledTimeStart.HasValue, d => d.OrderPublish.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.FiledTimeEnd.HasValue, d => d.OrderPublish.CreationTime <= dto.CreationTimeEnd)
+            .WhereIF(dto.QuerySelf.HasValue && dto.QuerySelf.Value, d => d.WaitForPublisherId == _sessionContext.RequiredUserId)
             .OrderByDescending(d => d.FiledTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
@@ -377,10 +382,7 @@ public class OrderController : BaseController
             throw UserFriendlyException.SameMessage("未找到工单,无法发布");
 
         //新增发布工单
-
-
         var orderPublish = _mapper.Map<OrderPublish>(dto);
-
         orderPublish.OrderId = order.Id;
         orderPublish.No = order.No;
 
@@ -399,6 +401,7 @@ public class OrderController : BaseController
         orderVisit.VisitState = EVisitState.WaitForVisit;
         orderVisit.PublishTime = DateTime.Now;
         orderVisit.IsCanHandle = true;
+        orderVisit.EmployeeId = _sessionContext.RequiredUserId;
 
         if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null })
         {
@@ -553,7 +556,10 @@ public class OrderController : BaseController
     [LogFilter("修改发布内容")]
     public async Task PublishedModify([FromBody] PublishOrderModifyDto dto)
     {
-        var publishOrder = await _orderPublishRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+        var publishOrder = await _orderPublishRepository.Queryable()
+            .Includes(x=>x.Order)
+            .Where(x => x.Id == dto.Id)
+            .FirstAsync(HttpContext.RequestAborted);
 
         if (publishOrder is null)
         {
@@ -567,6 +573,8 @@ public class OrderController : BaseController
         history.ArrangeContentAfter = dto.ArrangeContent;
         history.ArrangeOpinionBefor = publishOrder.ArrangeOpinion;
         history.ArrangeOpinionAfter = dto.ArrangeOpinion;
+        history.PublishStateBefor = publishOrder.PublishState;
+        history.PublishStateAfter = dto.PublishState;
         history.No = publishOrder.No;
         history.OrderId = publishOrder.OrderId;
         history.OrderPublishId = publishOrder.Id;
@@ -575,9 +583,15 @@ public class OrderController : BaseController
         publishOrder.ArrangeTitle = dto.ArrangeTitle;
         publishOrder.ArrangeContent = dto.ArrangeContent;
         publishOrder.ArrangeOpinion = dto.ArrangeOpinion;
+        publishOrder.PublishState = dto.PublishState;
 
         await _orderPublishRepository.UpdateAsync(publishOrder, HttpContext.RequestAborted);
         await _orderPublishHistoryRepository.AddAsync(history, HttpContext.RequestAborted);
+
+        //推省上
+        var publishPublishOrder = _mapper.Map<PublishPublishOrderDto>(publishOrder);
+        publishPublishOrder.Order = _mapper.Map<OrderDto>(publishOrder.Order);
+        await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderPublishOrder, publishPublishOrder);
     }
 
     /// <summary>
@@ -592,6 +606,46 @@ public class OrderController : BaseController
             .OrderByDescending(x => x.CreationTime).ToListAsync(HttpContext.RequestAborted);
     }
 
+    /// <summary>
+    /// 查询发布平移待办理人
+    /// </summary>
+    [HttpGet("published/migration")]
+    public async Task<IReadOnlyList<OrderMigrationHandler>> PublishMigration()
+    {
+        var setting = _systemSettingCacheManager.GetSetting(SettingConstants.RolePaiDan);
+        var roles = setting?.SettingValue.ToList();
+        var users = await _userRepository.Queryable()
+            .Includes(d => d.Organization)
+            .Includes(d => d.Roles)
+            .Where(d => d.Roles.Any(x => roles.Contains(x.Name)))
+            .ToListAsync(HttpContext.RequestAborted);
+        return users.Select(d => new OrderMigrationHandler
+        {
+            UserId = d.Id,
+            Username = d.Name,
+            OrgId = d.OrgId,
+            OrgName = d.Organization.Name,
+            RoleNames = string.Join(',', d.Roles.Select(x => x.DisplayName).ToList())
+        }).ToList();
+    }
+
+    /// <summary>
+    /// 发布平移
+    /// </summary>
+    [HttpPost("publish/migration")]
+    public async Task PublishMigrationBatch([FromBody] PublishMigrationDto dto)
+    {
+        var orders = await _orderRepository.Queryable()
+            .Where(d => dto.OrderIds.Contains(d.Id) && d.Status == EOrderStatus.Filed)
+            .ToListAsync(HttpContext.RequestAborted);
+        foreach (var order in orders)
+        {
+            order.WaitForPublisherId = dto.UserId;
+        }
+
+        await _orderRepository.UpdateRangeAsync(orders, HttpContext.RequestAborted);
+    }
+
     #endregion
 
     #region 工单回访
@@ -609,18 +663,18 @@ public class OrderController : BaseController
             .Includes(x => x.Employee)
             .Includes(x => x.OrderVisitDetails)
             .WhereIF(dto.VisitState == EVisitStateQuery.NoVisit,
-                x => (x.VisitState == Share.Enums.Order.EVisitState.WaitForVisit ||
-                      x.VisitState == Share.Enums.Order.EVisitState.NoSatisfiedWaitForVisit) &&
+                x => (x.VisitState == EVisitState.WaitForVisit ||
+                      x.VisitState == EVisitState.NoSatisfiedWaitForVisit) &&
                      x.Order.IsProvince == false)
-            .WhereIF(dto.VisitState == EVisitStateQuery.Visited,
-                x => x.VisitState == Share.Enums.Order.EVisitState.Visited)
-            .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-                x => x.Order.Title.Contains(dto.Keyword!) || x.Order.No.Contains(dto.Keyword!))
+            .WhereIF(dto.VisitState == EVisitStateQuery.Visited, x => x.VisitState == EVisitState.Visited)
+            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Order.Title.StartsWith(dto.Keyword!))
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
             .WhereIF(dto.VisitType != null, x => x.VisitType == dto.VisitType)
             .WhereIF(dto.FiledType != null && dto.FiledType == FiledType.CenterFiled, d => d.Order.ProcessType == EProcessType.Zhiban)
             .WhereIF(dto.FiledType != null && dto.FiledType == FiledType.OrgFiled, d => d.Order.ProcessType == EProcessType.Jiaoban)
             .WhereIF(dto.IsCountersign != null && dto.IsCountersign == true, d => d.Order.CounterSignType != null)
             .WhereIF(dto.IsCountersign != null && dto.IsCountersign == false, d => d.Order.CounterSignType == null)
+            .WhereIF(dto.QuerySelf.HasValue && dto.QuerySelf.Value, d => d.EmployeeId == _sessionContext.RequiredUserId)
             .OrderByDescending(x => x.PublishTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
         return new PagedDto<OrderVisitDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDto>>(items));
@@ -640,8 +694,6 @@ public class OrderController : BaseController
         return rsp;
     }
 
-
-
     /// <summary>
     /// 回访详情
     /// </summary>
@@ -720,7 +772,6 @@ public class OrderController : BaseController
         return new PagedDto<OrderVisitDetailDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDetailDto>>(items));
     }
 
-
     /// <summary>
     /// 回访保存
     /// </summary>
@@ -769,7 +820,13 @@ public class OrderController : BaseController
         {
             visit.Order.Visited(first.OrgProcessingResults.Key, first.OrgProcessingResults.Value);
         }
+        visit.OrgJudge = dto.OrgJudge;
+        visit.SeatJudge = dto.SeatJudge;
 
+        if (visit.OrgJudge == true || visit.SeatJudge == true)
+        {
+            visit.JudgeState = EJudgeState.Judging;
+        }
         //_mapper.Map(dto.VisitDetails,visit.OrderVisitDetails);
         for (int i = 0; i < visit.OrderVisitDetails.Count; i++)
         {
@@ -835,29 +892,80 @@ public class OrderController : BaseController
     }
 
     /// <summary>
-    /// 批量分配回访人员
+    /// 扭转列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("visit/judge-query")]
+    public async Task<PagedDto<OrderVisitDto>> VisitJudgeQuery([FromQuery] VisitJudgeQueryReq dto)
+    {
+        var (total, items) = await _orderVisitRepository.Queryable()
+            .Includes(x => x.Order)
+            .Includes(x => x.Employee)
+            .Where(x => x.VisitState == EVisitState.Visited)
+            .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No == dto.No)
+            .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Order.Title.Contains(dto.Title))
+            .WhereIF(!string.IsNullOrEmpty(dto.VisitUserName), x => x.Employee.Name.Contains(dto.VisitUserName))
+            .WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.Order.AcceptTypeCode)) //受理类型
+            .WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.Order.HotspotId)) //热点类型
+            .WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.Order.ActualHandleOrgCode)) //接办部门
+            .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.Order.AcceptorName.Contains(dto.NameOrNo!) || d.Order.AcceptorStaffNo.Contains(dto.NameOrNo!)) //受理人/坐席
+             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
+            .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
+            .WhereIF(dto.ActualHandleTimeStart.HasValue, d => d.Order.ActualHandleTime >= dto.ActualHandleTimeStart) //办结时间开始
+            .WhereIF(dto.ActualHandleTimeEnd.HasValue, d => d.Order.ActualHandleTime <= dto.ActualHandleTimeEnd) //办结时间结束
+            .WhereIF(dto.VisitTimeStart.HasValue, d => d.VisitTime >= dto.VisitTimeStart) //回访开始时间
+            .WhereIF(dto.VisitTimeEnd.HasValue, d => d.VisitTime <= dto.VisitTimeEnd)
+            .WhereIF(dto.IsIng == true, d => d.JudgeState == EJudgeState.Judging)
+            .WhereIF(dto.IsIng == false, d => d.JudgeState != EJudgeState.Judging)
+            .WhereIF(dto.JudgeState != null, d => d.JudgeState == dto.JudgeState)
+            .WhereIF(dto.OrgJudge != null, d => d.OrgJudge == dto.OrgJudge)
+            .WhereIF(dto.SeatJudge != null, d => d.SeatJudge == dto.SeatJudge)
+            .OrderByDescending(x => x.VisitTime)
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+        return new PagedDto<OrderVisitDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDto>>(items));
+    }
+
+    /// <summary>
+    /// 扭转满意度
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    [HttpPost("visit/distribution")]
-    public async Task<DistributionVisitRspDto> DistributionVisit([FromBody] DistributionVisitDto dto)
+    [HttpPost("visit/judge")]
+    public async Task<JudgeVisitRsp> JudgeVisit([FromBody] JudgeVisitReq dto)
     {
         int error = 0;
-        foreach (var id in dto.Ids)
+        var list = await _orderVisitRepository.Queryable().Where(x => dto.Ids.Contains(x.Id) && x.VisitState == EVisitState.Visited).ToListAsync(HttpContext.RequestAborted);
+        list.ForEach(visit =>
         {
-            var visit = await _orderVisitRepository.Queryable().FirstAsync(d => d.Id == id, HttpContext.RequestAborted);
-            if (visit != null && visit.VisitState == EVisitState.WaitForVisit)
-            {
-                visit.EmployeeId = dto.EmployeeId;
-                await _orderVisitRepository.UpdateAsync(visit, HttpContext.RequestAborted);
-            }
-            else
-            {
-                error++;
-            }
+            visit.JudgeState = dto.IsAgree ? EJudgeState.Agreed : EJudgeState.UnAgreed;
+            visit.JudgeUserId = _sessionContext.RequiredUserId;
+            visit.JudgeUserName = _sessionContext.UserName;
+            visit.JudgeTime = DateTime.Now;
+            visit.JudgeContent = dto.JudgeContent;
+        });
+        await _orderVisitRepository.UpdateRangeAsync(list, HttpContext.RequestAborted);
+        return new JudgeVisitRsp() { ErrorCount = error, SuccessCount = dto.Ids.Count - error };
+    }
+
+    /// <summary>
+    /// 回访平移
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("visit/migration")]
+    public async Task VisitMigrationBatch([FromBody] DistributionVisitDto dto)
+    {
+        var visits = await _orderVisitRepository.Queryable()
+            .Where(d => d.VisitState == EVisitState.WaitForVisit && dto.Ids.Contains(d.Id))
+            .ToListAsync(HttpContext.RequestAborted);
+
+        foreach (var visit in visits)
+        {
+            visit.EmployeeId = dto.EmployeeId;
         }
 
-        return new DistributionVisitRspDto() { ErrorCount = error, SuccessCount = dto.Ids.Count - error };
+        await _orderVisitRepository.UpdateRangeAsync(visits, HttpContext.RequestAborted);
     }
 
     #endregion
@@ -2266,6 +2374,7 @@ public class OrderController : BaseController
         var order = await _orderRepository.Queryable()
             .Includes(d => d.OrderExtension)
             .Includes(d => d.OrderDelays)
+            .Includes(d=> d.OrderPublish)
             //.Includes(d => d.OrderScreens)
             .Includes(d => d.OrderVisits, x => x.OrderVisitDetails)
             .Includes(d => d.OrderVisits, x => x.Employee)
@@ -2331,6 +2440,11 @@ public class OrderController : BaseController
 
         //dto.CanPrevious = canPrevious;
 
+        if (order.OrderPublish!=null)
+        {
+            dto.PublishState = order.OrderPublish.PublishState;
+        }
+
         if (dto.FileJson != null && dto.FileJson.Any())
         {
             var ids = order.FileJson.Select(x => x.Id).ToList();
@@ -2696,20 +2810,24 @@ public class OrderController : BaseController
                 ExpiredTime = timeResult.EndTime,
                 NearlyExpiredTime = timeResult.NearlyExpiredTime
             };
+            var canUpdateOrderSender = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.CanUpdateOrderSender).SettingValue[0]);
             order.CenterToOrg(
                 expiredTimeConfig.TimeText, expiredTimeConfig.Count,
                 expiredTimeConfig.TimeType, expiredTimeConfig.ExpiredTime,
                 expiredTimeConfig.NearlyExpiredTime, dto.Opinion,
-                _sessionContext.RequiredUserId, _sessionContext.UserName);
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                canUpdateOrderSender);
         }
         else if (dto.FlowDirection is EFlowDirection.CenterToOrg)
         {
             expiredTimeConfig = _timeLimitDomainService.CalcExpiredTime(DateTime.Now, EFlowDirection.CenterToOrg, order.AcceptTypeCode);
+            var canUpdateOrderSender = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.CanUpdateOrderSender).SettingValue[0]);
             order.CenterToOrg(
                 expiredTimeConfig.TimeText, expiredTimeConfig.Count,
                 expiredTimeConfig.TimeType, expiredTimeConfig.ExpiredTime,
                 expiredTimeConfig.NearlyExpiredTime, dto.Opinion,
-                _sessionContext.RequiredUserId, _sessionContext.UserName);
+                _sessionContext.RequiredUserId, _sessionContext.UserName,
+                canUpdateOrderSender);
             //写入质检
             await _qualityApplication.AddQualityAsync(EQualitySource.Send, order.Id, HttpContext.RequestAborted);
         }
@@ -5409,4 +5527,50 @@ public class OrderController : BaseController
         return _mapper.Map<IReadOnlyList<OrderModifyingRecordsDto>>(list);
     }
     #endregion
+
+    #region 工单平移
+
+    /// <summary>
+    /// 查询工单平移待办理人
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("migration/{orderId}")]
+    public async Task<GetOrderMigrationDto> Migration(string orderId)
+    {
+        var steps = await _workflowStepRepository.Queryable()
+            .Where(d => d.ExternalId == orderId && d.Status != EWorkflowStepStatus.Handled)
+            .ToListAsync(HttpContext.RequestAborted);
+
+        if (!steps.Any())
+            throw new UserFriendlyException("未查询到待办理节点");
+        if (steps.Count > 1)
+            throw new UserFriendlyException("多个待办理节点暂不支持平移");
+        var step = steps.First();
+        if (step.BusinessType is not EBusinessType.Center and not EBusinessType.Send)
+            throw UserFriendlyException.SameMessage("当前办理节点非中心暂不支持平移");
+
+        var setting = step.BusinessType is EBusinessType.Center
+            ? _systemSettingCacheManager.GetSetting(SettingConstants.RoleZuoXi)
+            : _systemSettingCacheManager.GetSetting(SettingConstants.RolePaiDan);
+        var roles = setting?.SettingValue.ToList();
+        var users = await _userRepository.Queryable()
+            .Includes(d => d.Organization)
+            .Includes(d => d.Roles)
+            .Where(d => d.Roles.Any(x => roles.Contains(x.Name)))
+            .ToListAsync(HttpContext.RequestAborted);
+        return new GetOrderMigrationDto
+        {
+            StepId = step.Id,
+            Handlers = users.Select(d => new OrderMigrationHandler
+            {
+                UserId = d.Id,
+                Username = d.Name,
+                OrgId = d.OrgId,
+                OrgName = d.Organization.Name,
+                RoleNames = string.Join(',', d.Roles.Select(x => x.DisplayName).ToList())
+            }).ToList()
+        };
+    }
+
+    #endregion
 }

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

@@ -26,6 +26,7 @@ using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
 using Hotline.Share.Dtos.Order;
 using Org.BouncyCastle.Ocsp;
 using System.Threading;
+using Hotline.Settings;
 
 namespace Hotline.Api.Controllers
 {

+ 18 - 0
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -45,6 +45,7 @@ public class WorkflowController : BaseController
     private readonly IRepository<Role> _roleRepository;
     private readonly ISystemDomainService _systemDomainService;
     private readonly IWfModuleDomainService _wfModuleDomainService;
+    private readonly IRepository<WorkflowStep> _workflowStepRepository;
     private readonly IRepository<WorkflowModule> _wfModuleRepository;
     private readonly IRepository<WorkflowTrace> _workflowTraceRepository;
     private readonly IRepository<WorkflowCountersign> _workflowCountersignRepository;
@@ -66,6 +67,7 @@ public class WorkflowController : BaseController
         IRepository<Role> roleRepository,
         ISystemDomainService systemDomainService,
         IWfModuleDomainService wfModuleDomainService,
+        IRepository<WorkflowStep> workflowStepRepository,
         IRepository<WorkflowModule> wfModuleRepository,
         IRepository<WorkflowTrace> workflowTraceRepository,
         IRepository<WorkflowCountersign> workflowCountersignRepository,
@@ -87,6 +89,7 @@ public class WorkflowController : BaseController
         _roleRepository = roleRepository;
         _systemDomainService = systemDomainService;
         _wfModuleDomainService = wfModuleDomainService;
+        _workflowStepRepository = workflowStepRepository;
         _wfModuleRepository = wfModuleRepository;
         _sessionContext = sessionContext;
         _mapper = mapper;
@@ -650,6 +653,21 @@ public class WorkflowController : BaseController
         };
     }
 
+    /// <summary>
+    /// 改变某节点办理人
+    /// </summary>
+    [HttpPost("change-handler")]
+    public async Task ChangeHandler([FromBody] ChangeHandlerDto dto)
+    {
+        var step = await _workflowStepRepository.GetAsync(dto.StepId, HttpContext.RequestAborted);
+        if (step is null)
+            throw new UserFriendlyException("无效节点编号");
+        await _workflowDomainService.ChangeHandlerBatchAsync(new List<(string userId, string username, string orgId, string orgName, ICollection<WorkflowStep> steps)>
+            {
+                new(dto.Handler.UserId,dto.Handler.Username,dto.Handler.OrgId,dto.Handler.OrgName, new List<WorkflowStep>{step})
+            }, HttpContext.RequestAborted);
+    }
+
     #endregion
 
 }

+ 9 - 1
src/Hotline.Api/Realtimes/RealtimeMethods.cs

@@ -73,7 +73,15 @@
         #endregion
 
         #region 司法大屏
-        public static string EnforcementOrderHandlingDetail = "EnforcementOrderHandlingDetail"; 
+        public static string EnforcementOrderHandlingDetail = "EnforcementOrderHandlingDetail";
+        #endregion
+
+        #region 话务排队信息
+
+        public static string ToDayWaitNum = "ToDayWaitNum";
+
+        public static string CurrentWaitNum = "CurrentWaitNum";
+
         #endregion
     }
 }

+ 13 - 1
src/Hotline.Api/Realtimes/RealtimeService.cs

@@ -230,8 +230,17 @@ public class RealtimeService : IRealtimeService, IScopeDependency
     public Task EnforcementOrderHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.EnforcementBigDataScreen, RealtimeMethods.EnforcementOrderHandlingDetail, obj, cancellationToken);
 
-    
 
+    #region 话务排队信息
+
+    public Task TodayWaitNumAsync(int count, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.CallCenter, RealtimeMethods.ToDayWaitNum, count, cancellationToken);
+
+    public Task CurrentWaitNumAsync(int count, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.CallCenter, RealtimeMethods.CurrentWaitNum, count, cancellationToken);
+
+
+    #endregion
     #region private
 
     private async Task SendCallCenterMsgAsync(string userId, string msg, object? value, CancellationToken cancellationToken)
@@ -264,4 +273,7 @@ public class RealtimeService : IRealtimeService, IScopeDependency
         _hubContext.Clients.Group(groupName).SendAsync(method, value, cancellationToken);
 
     #endregion
+
+   
+
 }

+ 2 - 2
src/Hotline.Api/config/appsettings.Development.json

@@ -16,7 +16,7 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
     "Redis": "110.188.24.182:50179",
     "MongoDB": "mongodb://192.168.100.121:27017",
     "Wex": "server=222.212.82.225;Port=4509;Database=fs_kft;Uid=root;Pwd=Wex@12345;"
@@ -25,7 +25,7 @@
     "Host": "110.188.24.182",
     "Port": 50179,
     //"Password": "fengwo22@@",
-    "Database": 3
+    "Database": 5 //release:3, dev:5
   },
   "Swagger": true,
   "Cors": {

+ 2 - 2
src/Hotline.Api/config/appsettings.json

@@ -16,7 +16,7 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
     "Redis": "110.188.24.182:50179,password=fengwo22@@",
     "MongoDB": "mongodb://192.168.100.121:27017",
     "Wex": "server=222.212.82.225;Port=4509;Database=fs_kft;Uid=root;Pwd=Wex@12345;"
@@ -25,7 +25,7 @@
     "Host": "110.188.24.182",
     "Port": 50179,
     "Password": "fengwo22@@",
-    "Database": 3
+    "Database": 5
   },
   "Swagger": true,
   "Cors": {

+ 1 - 0
src/Hotline.Application/Bigscreen/DataScreenRefreshService.cs

@@ -12,6 +12,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Settings;
 using XF.Domain.Constants;
 
 namespace Hotline.Application.Bigscreen

+ 1 - 0
src/Hotline.Application/Bigscreen/SeatStateDataRefreshService.cs

@@ -1,5 +1,6 @@
 using Hotline.Caching.Interfaces;
 using Hotline.Realtimes;
+using Hotline.Settings;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;

+ 52 - 0
src/Hotline.Application/CallCenter/Calls/CurrentWaitNumService.cs

@@ -0,0 +1,52 @@
+using Hotline.Caching.Interfaces;
+using Hotline.Realtimes;
+using Hotline.Settings;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tr.Sdk;
+
+namespace Hotline.Application.CallCenter.Calls
+{
+    public class CurrentWaitNumService : BackgroundService
+    {
+        private readonly IServiceScopeFactory _serviceScopeFactory;
+
+        public CurrentWaitNumService(IServiceScopeFactory serviceScopeFactory)
+        {
+            _serviceScopeFactory = serviceScopeFactory;
+        }
+
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            using var scope = _serviceScopeFactory.CreateScope();
+            var realtimeService = scope.ServiceProvider.GetRequiredService<IRealtimeService>();
+            var _systemSettingCacheManager = scope.ServiceProvider.GetRequiredService<ISystemSettingCacheManager>();
+            var _trClient = scope.ServiceProvider.GetRequiredService<ITrClient>();
+            int times = 3000;//5秒
+            var callinQueueIds = _systemSettingCacheManager.GetSetting(SettingConstants.CallInQueueId)?.SettingValue.ToList();
+            while(!stoppingToken.IsCancellationRequested)
+            {
+                try
+                {
+                    int count = 0;
+                    foreach (var id in callinQueueIds)
+                    {
+                        var rsp = await _trClient.QueryQueueWaitNumAsync(new Tr.Sdk.Tels.QueryQueueWaitNumRequest() { QueueId = id }, stoppingToken);
+                        count += Convert.ToInt32(rsp.Count);
+                    }
+
+                    await realtimeService.CurrentWaitNumAsync(count, stoppingToken);
+                }
+                catch{}
+                await Task.Delay(times);
+            }
+
+
+        }
+    }
+}

+ 43 - 0
src/Hotline.Application/CallCenter/Calls/ToDayWaitNumService.cs

@@ -0,0 +1,43 @@
+using Hotline.CallCenter.Calls;
+using Hotline.Realtimes;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.CallCenter.Calls
+{
+    public class ToDayWaitNumService : BackgroundService
+    {
+        private readonly IServiceScopeFactory _serviceScopeFactory;
+        public ToDayWaitNumService(IServiceScopeFactory serviceScopeFactory)
+        {
+                _serviceScopeFactory = serviceScopeFactory;
+        }
+
+        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            using var scope = _serviceScopeFactory.CreateScope();
+            var realtimeService = scope.ServiceProvider.GetRequiredService<IRealtimeService>();
+            var _trcallrecordRepoository = scope.ServiceProvider.GetRequiredService<IRepository<TrCallRecord>>();
+            int times = 60000;//一分钟
+            while (!stoppingToken.IsCancellationRequested)
+            {
+                try
+                {
+                    int count = await _trcallrecordRepoository.Queryable()
+                        .Where(x => x.CreatedTime.Date == DateTime.Now.Date)
+                        .Where(x => x.QueueTims > 0 && x.Duration == 0)
+                        .CountAsync();
+                    await realtimeService.TodayWaitNumAsync(count, stoppingToken);
+                }
+                catch {}
+                await Task.Delay(times);
+            }
+        }
+    }
+}

+ 1 - 0
src/Hotline.Application/CallCenter/Calls/TrApplication.cs

@@ -1,6 +1,7 @@
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Tels;
+using Hotline.Settings;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Enums.CallCenter;

+ 1 - 0
src/Hotline.Application/Handlers/FlowEngine/WorkflowRecallHandler.cs

@@ -4,6 +4,7 @@ using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
+using Hotline.Settings;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.FlowEngine;

+ 27 - 11
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -1,18 +1,23 @@
 using System.Security.Claims;
+using Hotline.Caching.Interfaces;
 using Hotline.Identity;
 using Hotline.Identity.Accounts;
 using Hotline.Orders;
 using Hotline.Push;
 using Hotline.Schedulings;
 using Hotline.Settings;
+using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Identity;
+using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Identity;
 using Hotline.Users;
 using IdentityModel;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Identity;
 using Microsoft.Extensions.Options;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
+using XF.Domain.Constants;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Options;
@@ -31,8 +36,9 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
     private readonly IMessageCodeDomainService _messageCodeDomainService;
     private readonly IRepository<Scheduling> _schedulingRepository;
     private readonly IOrderDomainService _orderDomainService;
+    private readonly ISystemSettingCacheManager _systemSettingCacheManager;
 
-    public IdentityAppService(
+	public IdentityAppService(
         IAccountRepository accountRepository,
         IAccountDomainService accountDomainService,
         IRepository<User> userRepository,
@@ -41,7 +47,8 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         ITypedCache<AudienceTicket> cacheAudience,
         IMessageCodeDomainService messageCodeDomainService,
         IRepository<Scheduling> schedulingRepository,
-         IOrderDomainService orderDomainService)
+        IOrderDomainService orderDomainService,
+        ISystemSettingCacheManager systemSettingCacheManager)
     {
         _accountRepository = accountRepository;
         _accountDomainService = accountDomainService;
@@ -52,7 +59,9 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         _messageCodeDomainService = messageCodeDomainService;
         _schedulingRepository = schedulingRepository;
         _orderDomainService = orderDomainService;
-    }
+        _systemSettingCacheManager = systemSettingCacheManager;
+
+	}
 
     public async Task<string> LoginAsync(LoginDto dto, CancellationToken cancellationToken)
     {
@@ -99,9 +108,12 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
             .FirstAsync(d => d.Id == account.Id);
         if (user == null)
             throw UserFriendlyException.SameMessage("未查询到用户数据");
-        //平均派单
-        await AverageOrderScheduling(account.Id, cancellationToken);
-
+		//平均派单
+		var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+		if (averageSendOrder)
+		{
+			await AverageOrderScheduling(account.Id, cancellationToken);
+		}
         var jwtOptions = _identityOptionsAccessor.Value.Jwt;
         var claims = new List<Claim>
         {
@@ -141,18 +153,22 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         try
         {
             DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
-
+            
             //&& x.AtWork!.Value != true
             //根据当前时间获取排班信息
-            var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
-                .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && (x.AtWork == true || x.AtWork == null) && x.SchedulingUser.UserId == id)
+            var scheduling = await _schedulingRepository.Queryable()
+                .Includes(x => x.SchedulingUser)
+                .Where(x => x.SchedulingTime == time &&
+                            x.WorkingTime <= DateTime.Now.TimeOfDay && 
+                            x.OffDutyTime >= DateTime.Now.TimeOfDay && 
+                            (x.AtWork == true || x.AtWork == null) && 
+                            x.SchedulingUser.UserId == id)
                 .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
             if (scheduling != null)
             {
                 scheduling.AtWork = true;
-                await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
                 //执行登录平均派单
-                await _orderDomainService.LogAverageOrder(id, cancellationToken);
+                await _orderDomainService.LogAverageOrder(id, scheduling, cancellationToken);
             }
         }
         catch

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

@@ -26,6 +26,7 @@ using Novacode;
 using SqlSugar;
 using System.Data;
 using System.Dynamic;
+using Hotline.Settings;
 using XF.Domain.Authentications;
 using XF.Domain.Constants;
 using XF.Domain.Dependency;

+ 1 - 0
src/Hotline.Application/Quality/QualityApplication.cs

@@ -3,6 +3,7 @@ using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.Orders;
 using Hotline.Quality;
+using Hotline.Settings;
 using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Enums.Quality;
 using MapsterMapper;

+ 3 - 1
src/Hotline.Share/Dtos/Ai/AiDto.cs

@@ -235,12 +235,14 @@ namespace Hotline.Share.Dtos.Ai
 
     public record AiVisitListDto:PagedKeywordRequest
     {
-        
+        public EAiOrderVisitTaskState? AiOrderVisitTaskState { get; set; }
     }
 
     public record AiVisitDetailListDto:PagedKeywordRequest
     {
         public string Id { get; set; }
+
+        public EAiOrderVisitState? AiOrderVisitState { get; set; }
     }
 
     public class AiOrderVisitDto

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

@@ -0,0 +1,9 @@
+using Hotline.Share.Dtos.FlowEngine;
+
+namespace Hotline.Share.Dtos.FlowEngine.Workflow;
+
+public class ChangeHandlerDto
+{
+    public string StepId { get; set; }
+    public FlowStepHandler Handler { get; set; }
+}

+ 20 - 0
src/Hotline.Share/Dtos/Order/Migration/GetOrderMigrationDto.cs

@@ -0,0 +1,20 @@
+using Hotline.Share.Dtos.FlowEngine;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Order.Migration
+{
+    public class GetOrderMigrationDto
+    {
+        public string StepId { get; set; }
+        public IReadOnlyList<OrderMigrationHandler> Handlers { get; set; }
+    }
+
+    public class OrderMigrationHandler : FlowStepHandler
+    {
+        public string RoleNames { get; set; }
+    }
+}

+ 4 - 0
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -355,6 +355,10 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public int DelayingCount => OrderDelays?.Count ?? 0;
 
+        /// <summary>
+        /// 发布范围
+        /// </summary>
+        public bool? PublishState { get; set; }
 
         /// <summary>
         /// 是否延期 

+ 138 - 0
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -33,6 +33,115 @@ namespace Hotline.Share.Dtos.Order
         /// 回访方式
         /// </summary>
         public EVisitType? VisitType { get; set; }
+
+        /// <summary>
+        /// 工单编号
+        /// </summary>
+        public string? No { get; set; }
+
+        /// <summary>
+        /// 是否只查询自己的待回访工单
+        /// </summary>
+        public bool? QuerySelf { get; set; }
+    }
+
+    public record VisitJudgeQueryReq:PagedKeywordRequest
+    {
+        /// <summary>
+        /// 工单编号
+        /// </summary>
+        public string? No { get; set; }
+        /// <summary>
+        /// 工单标题
+        /// </summary>
+        public string? Title { get; set; }
+        /// <summary>
+        /// 回访人
+        /// </summary>
+        public string? VisitUserName { get; set; }
+        /// <summary>
+        /// 来电号码
+        /// </summary>
+        public string? FromPhone { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public List<string> AcceptTypes { get; set; } = new();
+
+        /// <summary>
+        /// 热点分类
+        /// </summary>
+        public List<string> HotspotIds { get; set; } = new();
+
+        /// <summary>
+        /// 接办部门
+        /// </summary>
+        public List<string> OrgCodes { get; set; } = new();
+
+        /// <summary>
+        /// 受理坐席名字或工号
+        /// </summary>
+        public string? NameOrNo { get; set; }
+
+        /// <summary>
+        /// 受理时间(工单创建时间)
+        /// </summary>
+        public DateTime? CreationTimeStart { get; set; }
+        public DateTime? CreationTimeEnd { get; set; }
+
+        /// <summary>
+        /// 办结时间
+        /// </summary>
+        public DateTime? ActualHandleTimeStart { get; set; }
+        public DateTime? ActualHandleTimeEnd { get; set; }
+        /// <summary>
+        /// 回访时间
+        /// </summary>
+        public DateTime? VisitTimeStart { get; set; }
+
+        public DateTime? VisitTimeEnd { get; set; }
+
+        /// <summary>
+        /// 评判状态
+        /// </summary>
+        public EJudgeState? JudgeState { get; set; }
+
+        /// <summary>
+        /// 部门扭转
+        /// </summary>
+        public bool? OrgJudge { get; set; }
+        /// <summary>
+        /// 坐席扭转
+        /// </summary>
+        public bool? SeatJudge { get; set; }
+
+        /// <summary>
+        /// 是否待扭转
+        /// </summary>
+        public bool IsIng { get; set; }
+    }
+
+    public class JudgeVisitReq
+    {
+        public List<string> Ids { get; set; }
+
+        /// <summary>
+        /// 是否同意
+        /// </summary>
+        public bool IsAgree { get; set; }
+
+        /// <summary>
+        /// 是否
+        /// </summary>
+        public string? JudgeContent { get; set; }
+    }
+
+    public class JudgeVisitRsp
+    {
+        public int ErrorCount { get; set; }
+
+        public int SuccessCount { get; set; }
     }
 
     public record VisitDetailListDto : PagedKeywordRequest
@@ -69,6 +178,15 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public bool IsAgain { get; set; }
 
+        /// <summary>
+        /// 部门扭转
+        /// </summary>
+        public bool? OrgJudge { get; set; }
+        /// <summary>
+        /// 坐席扭转
+        /// </summary>
+        public bool? SeatJudge { get; set; }
+
         public List<VisitDetailDto> VisitDetails { get; set; }
     }
 
@@ -362,6 +480,26 @@ namespace Hotline.Share.Dtos.Order
         /// 智能回访录音地址
         /// </summary>
         public string? RecordUrl { get; set; }
+
+        /// <summary>
+        /// 部门扭转
+        /// </summary>
+        public bool? OrgJudge { get; set; }
+        /// <summary>
+        /// 坐席扭转
+        /// </summary>
+        public bool? SeatJudge { get; set; }
+        /// <summary>
+        /// 评判状态
+        /// </summary>
+        public EJudgeState? JudgeState { get; set; }
+
+        public string? JudgeStateText => JudgeState?.GetDescription() ?? string.Empty;
+        
+        /// <summary>
+        /// 评判意见
+        /// </summary>
+        public string? JudgeContent { get; set; }
     }
 
     public class OrderVisitDetailDto

+ 14 - 0
src/Hotline.Share/Dtos/Order/Publish/PublishMigrationDto.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Order.Publish
+{
+    public class PublishMigrationDto
+    {
+        public List<string> OrderIds { get; set; }
+        public string UserId { get; set; }
+    }
+}

+ 74 - 0
src/Hotline.Share/Dtos/Order/Publish/QueryOrderPublishDto.cs

@@ -0,0 +1,74 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.Order.Publish;
+
+public record QueryOrderPublishDto : PagedKeywordRequest
+{
+    /// <summary>
+    /// 发布状态
+    /// </summary>
+    public EPubState? PubState { get; set; }
+
+
+    /// <summary>
+    /// 工单标题
+    /// </summary>
+    public string? OrderTitle { get; set; }
+
+    /// <summary>
+    /// 工单编号
+    /// </summary>
+    public string? No { get; set; }
+
+    /// <summary>
+    /// 发布人
+    /// </summary>
+    public string? PubMan { get; set; }
+
+    /// <summary>
+    /// 发布范围
+    /// </summary>
+    public EPublicState? PubRange { get; set; }
+
+    /// <summary>
+    /// 受理类型
+    /// </summary>
+    public List<string>? AcceptTypes { get; set; } = new();
+
+    /// <summary>
+    /// 热点分类
+    /// </summary>
+    public List<string>? HotspotIds { get; set; } = new();
+
+    /// <summary>
+    /// 受理时间(工单创建时间)
+    /// </summary>
+    public DateTime? CreationTimeStart { get; set; }
+    public DateTime? CreationTimeEnd { get; set; }
+
+    /// <summary>
+    /// 归档时间
+    /// </summary>
+    public DateTime? FiledTimeStart { get; set; }
+    public DateTime? FiledTimeEnd { get; set; }
+
+    /// <summary>
+    /// 归档方式
+    /// </summary>
+    public FiledType? FiledType { get; set; }
+
+    /// <summary>
+    /// 是否会签
+    /// </summary>
+    public bool? IsCountersign { get; set; }
+
+    /// <summary>
+    /// 是否解决
+    /// </summary>
+    public bool? Resolve { get; set; }
+
+    /// <summary>
+    /// 是否只查询自己的待发布工单
+    /// </summary>
+    public bool? QuerySelf { get; set; }
+}

+ 5 - 0
src/Hotline.Share/Dtos/Order/PublishedDto.cs

@@ -294,6 +294,11 @@ public class PublishOrderModifyDto
     /// 整理结果
     /// </summary>
     public string ArrangeOpinion { get; set; }
+
+    /// <summary>
+    /// 发布范围
+    /// </summary>
+    public bool PublishState { get; set; }
 }
 
 public class SuperviseOrderDto

+ 65 - 126
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -151,76 +151,14 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public bool? IsSensitiveWord { get; set; }
 
-		/// <summary>
-		/// 敏感词
-		/// </summary>
-		public string? SensitiveWord { get; set;}
-    }
-
-
-    public record QueryOrderPublishDto : PagedKeywordRequest
-    {
-        /// <summary>
-        /// 发布状态
-        /// </summary>
-        public EPubState? PubState { get; set; }
-
-
-        /// <summary>
-        /// 工单标题
-        /// </summary>
-        public string? OrderTitle { get; set; }
-
-        /// <summary>
-        /// 发布人
-        /// </summary>
-        public string? PubMan { get; set; }
-
-        /// <summary>
-        /// 发布范围
-        /// </summary>
-        public EPublicState? PubRange { get; set; }
-
-        /// <summary>
-        /// 受理类型
-        /// </summary>
-        public List<string>? AcceptTypes { get; set; } = new();
-
-        /// <summary>
-        /// 热点分类
-        /// </summary>
-        public List<string>? HotspotIds { get; set; } = new();
-
-        /// <summary>
-        /// 受理时间(工单创建时间)
-        /// </summary>
-        public DateTime? CreationTimeStart { get; set; }
-        public DateTime? CreationTimeEnd { get; set; }
-
-        /// <summary>
-        /// 归档时间
-        /// </summary>
-        public DateTime? FiledTimeStart { get; set; }
-        public DateTime? FiledTimeEnd { get; set; }
-
-        /// <summary>
-        /// 归档方式
-        /// </summary>
-        public FiledType? FiledType { get; set; }
-
-        /// <summary>
-        /// 是否会签
-        /// </summary>
-        public bool? IsCountersign { get; set; }
-
         /// <summary>
-        /// 是否解决
+        /// 敏感词
         /// </summary>
-        public bool? Resolve { get; set; }
+        public string? SensitiveWord { get; set; }
+    }
 
-	}
 
-    public enum FiledType 
+    public enum FiledType
     {
         [Description("中心归档")]
         CenterFiled = 10,
@@ -229,7 +167,7 @@ namespace Hotline.Share.Dtos.Order
         OrgFiled = 20,
     }
 
-    public record QueryOrderRedoRecordDto: PagedKeywordRequest
+    public record QueryOrderRedoRecordDto : PagedKeywordRequest
     {
 
     }
@@ -466,7 +404,7 @@ namespace Hotline.Share.Dtos.Order
         /// 办理 true  审批 false 
         /// </summary>
         public bool Handle { get; set; }
-	}
+    }
 
 
     public record DelayCalcEndTimeDto
@@ -489,30 +427,30 @@ namespace Hotline.Share.Dtos.Order
     }
 
     public record ApplySuperviseDto
-	{
-	    public string OrderId { get; set; }
+    {
+        public string OrderId { get; set; }
     }
 
     public record SuperviseListDto : PagedKeywordRequest
     {
-	    /// <summary>
-	    /// 督办回复状态
-	    /// </summary>
-	    public int? SuperviseState { get; set; }
+        /// <summary>
+        /// 督办回复状态
+        /// </summary>
+        public int? SuperviseState { get; set; }
 
-	    public DateTime? CreationTimeStart { get; set; }
-	    public DateTime? CreationTimeEnd { get; set; }
-	}
+        public DateTime? CreationTimeStart { get; set; }
+        public DateTime? CreationTimeEnd { get; set; }
+    }
 
     public record ScreenListDto : PagedKeywordRequest
     {
-		/// <summary>
-		/// 甄别申请状态
-		/// </summary>
-		public EScreenStatus? Status { get; set; }
+        /// <summary>
+        /// 甄别申请状态
+        /// </summary>
+        public EScreenStatus? Status { get; set; }
 
-		public DateTime? CreationTimeStart { get; set; }
-	    public DateTime? CreationTimeEnd { get; set; }
+        public DateTime? CreationTimeStart { get; set; }
+        public DateTime? CreationTimeEnd { get; set; }
 
         public string? OrderId { get; set; }
 
@@ -521,12 +459,12 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public int source { get; set; }
 
-	}
+    }
 
-	public record MayScreenListDto : PagedKeywordRequest
+    public record MayScreenListDto : PagedKeywordRequest
     {
-	    public DateTime? CreationTimeStart { get; set; }
-	    public DateTime? CreationTimeEnd { get; set; }
+        public DateTime? CreationTimeStart { get; set; }
+        public DateTime? CreationTimeEnd { get; set; }
         public bool? IsHomePage { get; set; }
 
         /// <summary>
@@ -579,45 +517,45 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public DateTime? EndActualHandleTime { get; set; }
 
-		/// <summary>
-		/// 归档时间(暂为流程结束时间,因流程结束自动归档)
-		/// </summary>
-		public DateTime? FiledTime { get; set; }
+        /// <summary>
+        /// 归档时间(暂为流程结束时间,因流程结束自动归档)
+        /// </summary>
+        public DateTime? FiledTime { get; set; }
 
         /// <summary>
         /// 归档时间(暂为流程结束时间,因流程结束自动归档)
         /// </summary>
         public DateTime? EndFiledTime { get; set; }
 
-		/// <summary>
-		/// 受理时间
-		/// </summary>
-		public DateTime? CreationTime { get; set; }
+        /// <summary>
+        /// 受理时间
+        /// </summary>
+        public DateTime? CreationTime { get; set; }
 
-		/// <summary>
-		/// 受理时间
-		/// </summary>
-		public DateTime? EndCreationTime { get; set; }
+        /// <summary>
+        /// 受理时间
+        /// </summary>
+        public DateTime? EndCreationTime { get; set; }
 
-		/// <summary>
-		/// 回访时间
-		/// </summary>
-		public DateTime? VisitTime { get; set; }
+        /// <summary>
+        /// 回访时间
+        /// </summary>
+        public DateTime? VisitTime { get; set; }
 
-		/// <summary>
-		/// 回访时间
-		/// </summary>
-		public DateTime? EndVisitTime { get; set; }
+        /// <summary>
+        /// 回访时间
+        /// </summary>
+        public DateTime? EndVisitTime { get; set; }
 
-		/// <summary>
-		/// 回访部门名称
-		/// </summary>
-		public string? VisitOrgName { get; set; }
+        /// <summary>
+        /// 回访部门名称
+        /// </summary>
+        public string? VisitOrgName { get; set; }
 
-		/// <summary>
-		/// 部门办件结果
-		/// </summary>
-		public string? OrgProcessingResults { get; set; }
+        /// <summary>
+        /// 部门办件结果
+        /// </summary>
+        public string? OrgProcessingResults { get; set; }
 
         /// <summary>
         /// 部门办件态度
@@ -628,23 +566,24 @@ namespace Hotline.Share.Dtos.Order
         /// 不满意原因
         /// </summary>
         public string? OrgNoSatisfiedReason { get; set; }
-	}
+    }
 
-	public record UrgeListDto : PagedKeywordRequest
+    public record UrgeListDto : PagedKeywordRequest
     {
-	    /// <summary>
-	    /// 回复状态
-	    /// </summary>
-	    public int? UrgeState { get; set; }
-	    public DateTime? CreationTimeStart { get; set; }
-	    public DateTime? CreationTimeEnd { get; set; }
+        /// <summary>
+        /// 回复状态
+        /// </summary>
+        public int? UrgeState { get; set; }
+        public DateTime? CreationTimeStart { get; set; }
+        public DateTime? CreationTimeEnd { get; set; }
     }
 
-    public class QueryRepeatableEventDto {
-		public string? HotspotSpliceName { get; set; }
+    public class QueryRepeatableEventDto
+    {
+        public string? HotspotSpliceName { get; set; }
 
         public string? Address { get; set; }
-	}
+    }
 
 
     public enum EPublicState

+ 2 - 0
src/Hotline.Share/Enums/Ai/EAiCallOutTaskState.cs

@@ -20,5 +20,7 @@ namespace Hotline.Share.Enums.Ai
 
         [Description("已终止")]
         Close = 4,
+        [Description("已暂停")]
+        Pause = 5,
     }
 }

+ 2 - 0
src/Hotline.Share/Enums/Ai/EAiOrderVisitTaskState.cs

@@ -15,6 +15,8 @@ namespace Hotline.Share.Enums.Ai
         Ended = 3,
         [Description("已终止")]
         Close = 4,
+        [Description("已暂停")]
+        Pause = 5,
     }
 
 }

+ 19 - 0
src/Hotline.Share/Enums/Order/EVisitState.cs

@@ -39,4 +39,23 @@ namespace Hotline.Share.Enums.Order
         [Description("失效")]
         None = 50,
     }
+
+    public enum EJudgeState
+    {
+        /// <summary>
+        /// 评判中
+        /// </summary>
+        [Description("评判中")]
+        Judging = 0,
+        /// <summary>
+        /// 已同意
+        /// </summary>
+        [Description("已同意")]
+        Agreed = 1,
+        /// <summary>
+        /// 不同意
+        /// </summary>
+        [Description("不同意")]
+        UnAgreed =2,
+    }
 }

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

@@ -214,6 +214,11 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         Task<IReadOnlyList<string>> GetUnhandleStepIdsFromSendPoolAsync(string sendPoolId, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 查询归属某用户的所有流程节点
+        /// </summary>
+        Task<List<WorkflowStep>> GetStepsBelongsToAsync(string userId, CancellationToken cancellationToken);
+
         /// <summary>
         /// 批量改派工单至指定用户
         /// </summary>
@@ -221,6 +226,13 @@ namespace Hotline.FlowEngine.Workflows
             IReadOnlyList<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers,
             CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 批量修改工单办理对象
+        /// </summary>
+        Task ChangeHandlerBatchAsync(
+            IReadOnlyList<(string userId, string username, string orgId, string orgName, ICollection<WorkflowStep> steps)> handlers,
+            CancellationToken cancellationToken);
+
         /// <summary>
         /// 查询工单办理中的一级部门
         /// </summary>

+ 32 - 0
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -647,6 +647,17 @@ namespace Hotline.FlowEngine.Workflows
                 .ToListAsync(cancellationToken);
         }
 
+        /// <summary>
+        /// 查询归属某用户的所有流程节点
+        /// </summary>
+        public async Task<List<WorkflowStep>> GetStepsBelongsToAsync(string userId, CancellationToken cancellationToken)
+        {
+            return await _workflowStepRepository.Queryable()
+                .Where(d => d.HandlerId == userId)
+                .OrderBy(d=>d.CreationTime)
+                .ToListAsync(cancellationToken);
+        }
+
         /// <summary>
         /// 批量改变办理对象
         /// </summary>
@@ -705,6 +716,27 @@ namespace Hotline.FlowEngine.Workflows
             return steps.Select(d => d.WorkflowId).ToList();
         }
 
+        /// <summary>
+        /// 批量修改工单办理对象
+        /// </summary>
+        public async Task ChangeHandlerBatchAsync(
+            IReadOnlyList<(string userId, string username, string orgId, string orgName, ICollection<WorkflowStep> steps)> handlers,
+            CancellationToken cancellationToken)
+        {
+            foreach (var handler in handlers)
+            {
+                foreach (var step in handler.steps)
+                {
+                    step.FlowAssignType = EFlowAssignType.User;
+                    step.Assign(handler.userId, handler.username, 
+                        handler.orgId, handler.orgName);
+                }
+            }
+
+            await _workflowStepRepository.UpdateRangeAsync(handlers.SelectMany(d => d.steps).ToList(),
+                cancellationToken);
+        }
+
         /// <summary>
         /// 查询工单办理中的一级部门
         /// </summary>

+ 3 - 2
src/Hotline/Orders/IOrderDomainService.cs

@@ -1,4 +1,5 @@
-using Hotline.Share.Dtos;
+using Hotline.Schedulings;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
 
@@ -68,7 +69,7 @@ namespace Hotline.Orders
         /// </summary>
         /// <param name="userId"></param>
         /// <returns></returns>
-        Task LogAverageOrder(string userId, CancellationToken cancellationToken);
+        Task LogAverageOrder(string userId, Scheduling scheduling, CancellationToken cancellationToken);
 
         /// <summary>
         /// 触发平均派单

+ 20 - 6
src/Hotline/Orders/Order.cs

@@ -322,7 +322,7 @@ namespace Hotline.Orders
         public string? CenterToOrgOpinion { get; set; }
 
         /// <summary>
-        /// 交
+        /// 交
         /// </summary>
         public string? CenterToOrgHandlerId { get; set; }
         public string? CenterToOrgHandlerName { get; set; }
@@ -607,7 +607,9 @@ namespace Hotline.Orders
         public string? AcceptorOrgName { get; set; }
 
         #endregion
+
         #region 派单人
+
         /// <summary>
         /// 派单次数
         /// </summary>
@@ -740,6 +742,11 @@ namespace Hotline.Orders
         /// </summary>
         [SugarColumn(DefaultValue = "f")]
         public bool IsResolved { get; set; }
+
+        /// <summary>
+        /// 待发布人Id
+        /// </summary>
+        public string? WaitForPublisherId { get; set; }
     }
 
     public partial class Order
@@ -903,19 +910,26 @@ namespace Hotline.Orders
 
         public void CenterToOrg(string timelimit, int timelimitCount, ETimeType timilimitUnit,
             DateTime expiredTime, DateTime nearlyExpiredTime,
-            string opinion, string handlerId, string handlerName)
+            string opinion, string handlerId, string handlerName,
+            bool canUpdateOrderSender)
         {
             ProcessType = EProcessType.Jiaoban;
             TimeLimit = timelimit;
             TimeLimitCount = timelimitCount;
             TimeLimitUnit = timilimitUnit;
             ExpiredTime = expiredTime;
-            CenterToOrgTime = DateTime.Now;
-            CenterToOrgOpinion = opinion;
-            CenterToOrgHandlerId = handlerId;
-            CenterToOrgHandlerName = handlerName;
+            if (canUpdateOrderSender)
+            {
+                CenterToOrgTime = DateTime.Now;
+                CenterToOrgOpinion = opinion;
+                CenterToOrgHandlerId = handlerId;
+                CenterToOrgHandlerName = handlerName;
+            }
             NearlyExpiredTime = nearlyExpiredTime;
             SendOrderNumber += 1;
+
+            if (string.IsNullOrEmpty(WaitForPublisherId))
+                WaitForPublisherId = handlerId;
         }
 
         public void OrgToCenter(string timelimit, int timelimitCount, ETimeType timilimitUnit, DateTime expiredTime, DateTime nearlyExpiredTime)

+ 38 - 76
src/Hotline/Orders/OrderDomainService.cs

@@ -17,6 +17,7 @@ using Hotline.Users;
 using Hotline.Share.Dtos;
 using Hotline.Settings.Hotspots;
 using Hotline.Share.Dtos.FlowEngine;
+using Microsoft.AspNetCore.Http;
 
 namespace Hotline.Orders;
 
@@ -190,7 +191,6 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.AtWork == true)
             .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
         if (scheduling is null)
-            //return new List<Kv> { new(OrderDefaults.SourceChannel.SendPoolId, "待派单池") };
             return new FlowStepHandler
             {
                 Key = OrderDefaults.SourceChannel.SendPoolId,
@@ -198,13 +198,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
                 UserId = OrderDefaults.SourceChannel.SendPoolId,
                 Username = "待派单池",
             };
-
-        //var user = await _userRepository.GetAsync(x => x.Id == scheduling.SchedulingUser.UserId, cancellationToken);
-        //if (user is null)
-        //    throw new UserFriendlyException("无效用户编号");
         scheduling.SendOrderNum++;
         await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
-        //return new List<Kv> { new(user.Id, user.Name) };
         var user = scheduling.SchedulingUser;
         return new FlowStepHandler
         {
@@ -222,45 +217,36 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     /// </summary>
     /// <param name="userId"></param>
     /// <returns></returns>
-    public async Task LogAverageOrder(string userId, CancellationToken cancellationToken)
+    public async Task LogAverageOrder(string userId, Scheduling scheduling, CancellationToken cancellationToken)
     {
         //1.获取默认派单员所属的工单
         //2.获取今天上班的人员
         //3.给当前这个用户平均派单
-        var steps = await _workflowDomainService.GetUnhandleStepIdsFromSendPoolAsync(OrderDefaults.SourceChannel.SendPoolId, cancellationToken);
-        var stepsList = steps.ToList();
+
+        var steps = await _workflowDomainService.GetStepsBelongsToAsync(OrderDefaults.SourceChannel.SendPoolId,
+            cancellationToken);
 
         var user = await _userRepository.Queryable()
             .Includes(d => d.Organization)
             .FirstAsync(d => d.Id == userId, cancellationToken);
-        DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
+        var time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
         var schedulings = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
             .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay).CountAsync(cancellationToken);
-
         if (schedulings > 0)
         {
-            List<string> stepIds = new List<string>();
-            var sendNum = stepsList.Count() / schedulings;
-            for (int i = 0; i < sendNum; i++)
-            {
-                stepIds.Add(stepsList[0]);
-                stepsList.Remove(stepsList[0]);
-            }
-            List<(string, string, string, string, IReadOnlyList<string> stepIds)> handlers = new();
-            ; handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(user.Id, user.Name, user.OrgId, user.Organization.Name, stepIds));
-            var workflowIds = await _workflowDomainService.ChangeHandlerRangeAsync(OrderDefaults.SourceChannel.SendPoolId, handlers, cancellationToken);
-            var orders = await _orderRepository.Queryable().Includes(d => d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
-                .ToListAsync(cancellationToken);
-            foreach (var order in orders)
+            var sendNum = steps.Count / schedulings;
+            if (sendNum <= 0) return;
+            var sendSteps = steps.Take(sendNum).ToList();
+            await _workflowDomainService.ChangeHandlerBatchAsync(new List<(string userId, string username, string orgId, string orgName, ICollection<WorkflowStep> steps)>
             {
-                _mapper.Map(order.Workflow, order);
-            }
-            var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
-                .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.SchedulingUser.UserId == userId).FirstAsync(cancellationToken);
+                new(user.Id, user.Name, user.OrgId, user.Organization.Name, sendSteps)
+            }, cancellationToken);
             scheduling.SendOrderNum += sendNum;
-            await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
-            await _orderRepository.UpdateRangeAsync(orders, cancellationToken);
+            await _schedulingRepository.Updateable()
+                .SetColumns(s => new Scheduling() { SendOrderNum = scheduling.SendOrderNum, AtWork = scheduling.AtWork } )
+                .Where(s=> s.Id == scheduling.Id).ExecuteCommandAsync(cancellationToken);
         }
+
     }
 
     /// <summary>
@@ -279,48 +265,30 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
 
         if (schedulings.Any())
         {
-            var steps = await _workflowDomainService.GetUnhandleStepIdsFromSendPoolAsync(OrderDefaults.SourceChannel.SendPoolId, cancellationToken);
-            var stepsList = steps.ToList();
-
-            var sendNum = steps.Count() / schedulings.Count();
-            List<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers = new();
-            if (sendNum > 0)
-            {
-
-                foreach (var scheduling in schedulings)
-                {
-                    List<string> stepIds = new List<string>();
-                    for (int i = 0; i < sendNum; i++)
-                    {
-                        stepIds.Add(stepsList[0]);
-                        stepsList.Remove(stepsList[0]);
-                    }
-                    handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(scheduling.SchedulingUser.UserId, scheduling.SchedulingUser.UserName, scheduling.SchedulingUser.OrgId, scheduling.SchedulingUser.OrgIdName, stepIds));
-
-                }
-            }
-            sendNum = steps.Count() % schedulings.Count();
-            if (sendNum > 0)
+            var steps = await _workflowDomainService.GetStepsBelongsToAsync(OrderDefaults.SourceChannel.SendPoolId,
+                cancellationToken);
+            if (steps.Any())
             {
-                List<string> stepIds = new List<string>();
-                for (int i = 0; i < sendNum; i++)
+                List<(string userId, string username, string orgId, string orgName, ICollection<WorkflowStep> steps)> handlers = new();
+                var avg = steps.Count / schedulings.Count;
+                var remaining = steps.Count % schedulings.Count;
+                for (var i = 0; i < schedulings.Count; i++)
                 {
-                    stepIds.Add(stepsList[0]);
-                    stepsList.Remove(stepsList[0]);
-                }
-                handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(schedulings[0].SchedulingUser.UserId, schedulings[0].SchedulingUser.UserName, schedulings[0].SchedulingUser.OrgId, schedulings[0].SchedulingUser.OrgIdName, stepIds));
-            }
-            if (handlers.Any())
-            {
-                var workflowIds = await _workflowDomainService.ChangeHandlerRangeAsync(OrderDefaults.SourceChannel.SendPoolId, handlers, cancellationToken);
-                var orders = await _orderRepository.Queryable().Includes(d => d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
-                    .ToListAsync(cancellationToken);
-                foreach (var order in orders)
-                {
-                    _mapper.Map(order.Workflow, order);
-                }
-
-                await _orderRepository.UpdateRangeAsync(orders, cancellationToken);
+                    var scheduling = schedulings[i];
+                    var size = avg + (i < remaining ? 1 : 0);
+                    handlers.Add(new(
+                        scheduling.SchedulingUser.UserId,
+                        scheduling.SchedulingUser.UserName,
+                        scheduling.SchedulingUser.OrgId,
+                        scheduling.SchedulingUser.OrgIdName,
+                        steps.Take(size).ToList()));
+                    scheduling.SendOrderNum += size;
+					await _schedulingRepository.Updateable()
+	                    .SetColumns(s => new Scheduling() { SendOrderNum = scheduling.SendOrderNum })
+	                    .Where(s => s.Id == scheduling.Id).ExecuteCommandAsync(cancellationToken);
+				}
+                if (handlers.Any())
+                    await _workflowDomainService.ChangeHandlerBatchAsync(handlers, cancellationToken);
             }
         }
     }
@@ -403,12 +371,6 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             valid.Result = "标题或受理内容出现限制词,请检查!";
         return valid;
     }
-    #endregion
-
-    #region SchedulingSendOrder
-
-
-
     #endregion
 
     #region private

+ 10 - 0
src/Hotline/Orders/OrderPublishHistory.cs

@@ -60,5 +60,15 @@ namespace Hotline.Orders
         [SugarColumn(ColumnDataType = "varchar(2000)")]
         public string ArrangeOpinionAfter { get; set; }
 
+        /// <summary>
+        /// 发布范围
+        /// </summary>
+        public bool? PublishStateBefor { get; set; }
+
+        /// <summary>
+        /// 发布范围
+        /// </summary>
+        public bool? PublishStateAfter { get; set; }
+
     }
 }

+ 32 - 0
src/Hotline/Orders/OrderVisit.cs

@@ -11,6 +11,7 @@ namespace Hotline.Orders;
 /// 已回访工单
 /// </summary>
 [Description("工单回访")]
+[SugarIndex("index_order_no", nameof(OrderVisit.No), OrderByType.Desc)]
 public class OrderVisit : CreationEntity
 {
     /// <summary>
@@ -125,6 +126,37 @@ public class OrderVisit : CreationEntity
     /// </summary>
     public string? RecordUrl { get; set; }
 
+    /// <summary>
+    /// 部门扭转
+    /// </summary>
+    public bool? OrgJudge { get; set; }
+    /// <summary>
+    /// 坐席扭转
+    /// </summary>
+    public bool? SeatJudge { get; set; }
+    /// <summary>
+    /// 评判状态
+    /// </summary>
+    public EJudgeState? JudgeState { get; set;}
+    /// <summary>
+    /// 评判意见
+    /// </summary>
+    public string? JudgeContent { get; set;}
+
+    /// <summary>
+    /// 评判人Id
+    /// </summary>
+    public string? JudgeUserId { get; set; }
+
+    /// <summary>
+    /// 评判人名称
+    /// </summary>
+    public string? JudgeUserName { get; set; }
+
+    /// <summary>
+    /// 评判时间
+    /// </summary>
+    public DateTime? JudgeTime { get; set; }
 
     public void AiVisitTime()
     {

+ 24 - 3
src/Hotline/Permissions/EPermission.cs

@@ -566,6 +566,12 @@ namespace Hotline.Permissions
         /// </summary>
         [Display(GroupName = "智能回访", Name = "新增智能回访任务", Description = "新增智能回访任务")]
         AddAiVisitTask = 200906,
+        /// <summary>
+        /// 终止回访任务
+        /// </summary>
+        [Display(GroupName ="智能回访",Name ="终止回访任务",Description ="终止回访任务")]
+        CloseAiVisit = 200910,
+
 
         #endregion
 
@@ -573,18 +579,18 @@ namespace Hotline.Permissions
         /// <summary>
         /// 扭转评判
         /// </summary>
-        [Display(GroupName ="智能回访",Name ="扭转待评判",Description ="扭转待评判")]
+        [Display(GroupName ="扭转评价",Name ="扭转待评判",Description ="扭转待评判")]
         VisitReverseList = 200907,
 
         /// <summary>
         /// 批量扭转评判
         /// </summary>
-        [Display(GroupName ="智能回访",Name ="批量扭转评判",Description ="批量扭转评判")]
+        [Display(GroupName = "扭转评价", Name ="批量扭转评判",Description ="批量扭转评判")]
         BatchVisitReverse = 200908,
         /// <summary>
         /// 扭转评判
         /// </summary>
-        [Display(GroupName ="智能回访",Name ="扭转评判",Description ="扭转评判")]
+        [Display(GroupName = "扭转评价", Name ="扭转评判",Description ="扭转评判")]
         VisitReverse = 200909,
         #endregion
 
@@ -1485,6 +1491,11 @@ namespace Hotline.Permissions
         /// </summary>
         [Display(GroupName = "类型管理", Name = "删除分类", Description = "删除分类")]
         RemoveKnowledgeType = 600904,
+        /// <summary>
+        /// 关联组织
+        /// </summary>
+        [Display(GroupName ="类型管理",Name ="关联组织",Description ="关联组织")]
+        CorrelationOrg = 600905,
         #endregion
 
         #endregion
@@ -2164,6 +2175,16 @@ namespace Hotline.Permissions
         /// </summary>
         [Display(GroupName ="数据统计",Name ="派单量统计",Description ="派单量统计")]
         DispatchQuantityStatistics = 110214,
+        /// <summary>
+        /// 信件来源统计
+        /// </summary>
+        [Display(GroupName ="数据统计",Name ="信件来源统计",Description ="信件来源统计")]
+        OrderSourceStatistics = 110215,
+        /// <summary>
+        /// 信件来源分时统计
+        /// </summary>
+        [Display(GroupName ="数据统计",Name ="信件来源分时统计",Description ="信件来源分时统计")]
+        OrderSourceTimeStatistics = 110216,
         #endregion
 
         #region 知识库统计(11,03,00)

+ 1 - 0
src/Hotline/Push/MessageCodeDomainService.cs

@@ -2,6 +2,7 @@
 using Hotline.Caching.Interfaces;
 using Hotline.Identity.Accounts;
 using Hotline.Push.Notifies;
+using Hotline.Settings;
 using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.Push;
 using Hotline.Users;

+ 1 - 0
src/Hotline/Quality/QualityDomainService.cs

@@ -2,6 +2,7 @@
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.Orders;
+using Hotline.Settings;
 using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Enums.Quality;
 using MapsterMapper;

+ 19 - 1
src/Hotline/Realtimes/IRealtimeService.cs

@@ -63,7 +63,25 @@ namespace Hotline.Realtimes
 
         #region 司法大屏
 
-        Task EnforcementOrderHandlingDetailAsync(object obj, CancellationToken cancellationToken); 
+        Task EnforcementOrderHandlingDetailAsync(object obj, CancellationToken cancellationToken);
+        #endregion
+
+        #region 话务排队信息
+        /// <summary>
+        /// 今日排队
+        /// </summary>
+        /// <param name="count"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task TodayWaitNumAsync(int count, CancellationToken cancellationToken);
+        /// <summary>
+        /// 当前等待
+        /// </summary>
+        /// <param name="count"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task CurrentWaitNumAsync(int count, CancellationToken cancellationToken);
+
         #endregion
     }
 }

+ 20 - 7
src/XF.Domain/Constants/SettingConstants.cs → src/Hotline/Settings/SettingConstants.cs

@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace XF.Domain.Constants
+namespace Hotline.Settings
 {
     public class SettingConstants
     {
@@ -329,5 +323,24 @@ namespace XF.Domain.Constants
         /// 录音文件前缀地址
         /// </summary>
 		public const string ViteRecordPrefix = "ViteRecordPrefix";
+
+        /// <summary>
+        /// 派单员角色
+        /// </summary>
+		public const string RolePaiDan = "RolePaiDan";
+
+        /// <summary>
+        /// 坐席角色
+        /// </summary>
+		public const string RoleZuoXi = "RoleZuoXi";
+        /// <summary>
+        /// 呼入分机组号
+        /// </summary>
+        public const string CallInQueueId = "CallInQueueId";
+
+        /// <summary>
+        /// 更新工单派单员信息
+        /// </summary>
+        public const string CanUpdateOrderSender = "CanUpdateOrderSender";
 	}
 }

+ 7 - 0
src/Tr.Sdk/Tels/ITrClient.Tel.cs

@@ -32,5 +32,12 @@ namespace Tr.Sdk
         Task<List<QueryBlacklistResponse>> QueryBlacklistAsync(QueryBlacklistRequest request, CancellationToken cancellationToken) =>
             ExecuteAsync<QueryBlacklistRequest,List<QueryBlacklistResponse>>(request, cancellationToken);
         #endregion
+
+        #region 队列信息
+
+        Task<QueryQueueWaitNumResponse> QueryQueueWaitNumAsync(QueryQueueWaitNumRequest request, CancellationToken cancellationToken) =>
+            ExecuteAsync<QueryQueueWaitNumRequest, QueryQueueWaitNumResponse>(request, cancellationToken);
+
+        #endregion
     }
 }

+ 28 - 0
src/Tr.Sdk/Tels/QueryQueueWaitNumRequest.cs

@@ -0,0 +1,28 @@
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Tr.Sdk.Tels
+{
+    public class QueryQueueWaitNumRequest : TrRequest
+    {
+        public override Method HttpMethod() => Method.Get;
+
+        public override string Path() => $"/api/ola/queues/{QueueId}/get_callers";
+
+        [RequestProperty(Name = "args")]
+        public string QueueId { get; set; }
+    }
+
+
+    public class QueryQueueWaitNumResponse
+    {
+        [JsonPropertyName("count")]
+        public string Count { get; set; }
+    }
+    
+}

+ 1 - 0
src/Tr.Sdk/TrClient.cs

@@ -38,6 +38,7 @@ public class TrClient : ITrClient, IDisposable
     {
         var req = new RestRequest(request.Path(), request.HttpMethod())
             .AddObject(request);
+        string url = "http://222.213.23.229:29003/api/ola/queues/10010/get-callers";
         try
         {
             var response = await _client.ExecuteAsync<TResponse>(req, cancellationToken);