فهرست منبع

Merge branch 'release' into dev

Dun.Jason 6 ماه پیش
والد
کامیت
6a3d439579
98فایلهای تغییر یافته به همراه2583 افزوده شده و 1070 حذف شده
  1. 7 0
      Hotline.sln
  2. 1 1
      src/Hotline.Api/Controllers/AiController.cs
  3. 1 1
      src/Hotline.Api/Controllers/BaseController.cs
  4. 1 1
      src/Hotline.Api/Controllers/Bi/BiCallController.cs
  5. 2 2
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  6. 166 102
      src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs
  7. 6 6
      src/Hotline.Api/Controllers/Bigscreen/SeatController.cs
  8. 12 9
      src/Hotline.Api/Controllers/CommonPController.cs
  9. 2 1
      src/Hotline.Api/Controllers/HomeController.cs
  10. 4 4
      src/Hotline.Api/Controllers/IPPbxController.cs
  11. 18 74
      src/Hotline.Api/Controllers/KnowledgeController.cs
  12. 84 42
      src/Hotline.Api/Controllers/OldHotlineController.cs
  13. 110 85
      src/Hotline.Api/Controllers/OrderController.cs
  14. 42 34
      src/Hotline.Api/Controllers/OrderRevocationController.cs
  15. 17 6
      src/Hotline.Api/Controllers/PushMessageController.cs
  16. 1 1
      src/Hotline.Api/Controllers/SysController.cs
  17. 76 1
      src/Hotline.Api/Controllers/WebPortalController.cs
  18. 8 0
      src/Hotline.Api/Hotline.Api.csproj
  19. 10 3
      src/Hotline.Api/StartupExtensions.cs
  20. 6 2
      src/Hotline.Api/config/appsettings.Development.json
  21. 0 0
      src/Hotline.Api/logs/20241028-log.txt
  22. 4 0
      src/Hotline.Api/logs/acc-log-20241028.txt
  23. 0 0
      src/Hotline.Api/logs/acc-log20241028.txt
  24. 0 0
      src/Hotline.Api/logs/log20241028.txt
  25. 4 0
      src/Hotline.Application.Contracts/Validators/FlowEngine/NextWorkflowDtoValidator.cs
  26. 122 1
      src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs
  27. 16 0
      src/Hotline.Application.Contracts/Validators/Order/UpdateOrderDtoValidator.cs
  28. 3 0
      src/Hotline.Application.Contracts/Validators/ValidatorExtensions.cs
  29. 51 0
      src/Hotline.Application.Tests/Controller/OrderControllerTest.cs
  30. 80 0
      src/Hotline.Application.Tests/Controller/PushMessageControllerTest.cs
  31. 1 1
      src/Hotline.Application.Tests/Domain/ZiGongExpireTimeTest.cs
  32. 2 0
      src/Hotline.Application.Tests/Hotline.Application.Tests.csproj
  33. 7 1
      src/Hotline.Application.Tests/Startup.cs
  34. 9 116
      src/Hotline.Application/Bigscreen/SeatStateDataService.cs
  35. 128 0
      src/Hotline.Application/Bigscreen/SeatStateDataServiceBase.cs
  36. 124 0
      src/Hotline.Application/Bigscreen/YiBinSeatStateDataService.cs
  37. 33 15
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  38. 2 0
      src/Hotline.Application/ExportExcel/ExportApplication.cs
  39. 110 89
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  40. 1 1
      src/Hotline.Application/Hotline.Application.csproj
  41. 6 3
      src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs
  42. 7 0
      src/Hotline.Application/Knowledge/IKnowApplication.cs
  43. 80 0
      src/Hotline.Application/Knowledge/KnowApplication.cs
  44. 5 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  45. 234 154
      src/Hotline.Application/Orders/OrderApplication.cs
  46. 1 1
      src/Hotline.Application/Orders/OrderVisitApplication.cs
  47. 3 3
      src/Hotline.Application/StatisticalReport/CallReport/CallReportApplicationBase.cs
  48. 38 3
      src/Hotline.Application/Subscribers/DatasharingSubscriber.cs
  49. 84 79
      src/Hotline.Application/Subscribers/InternalCapSubscriber.cs
  50. 20 0
      src/Hotline.Logger/Hotline.Logger.csproj
  51. 62 0
      src/Hotline.Logger/Models/AccModel.cs
  52. 43 0
      src/Hotline.Logger/Models/BaseLogModel.cs
  53. 167 0
      src/Hotline.Logger/RequestResponseLoggingMiddleware.cs
  54. 11 6
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  55. 5 1
      src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs
  56. 5 0
      src/Hotline.Share/Dtos/CallCenter/CallNativeDto.cs
  57. 1 1
      src/Hotline.Share/Dtos/CallCenter/QueryCallsDetailDto.cs
  58. 8 0
      src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs
  59. 5 0
      src/Hotline.Share/Dtos/FlowEngine/NextStepsDto.cs
  60. 8 0
      src/Hotline.Share/Dtos/Identity/LoginDto.cs
  61. 11 10
      src/Hotline.Share/Dtos/Knowledge/KnowledgePagedDto.cs
  62. 5 0
      src/Hotline.Share/Dtos/Knowledge/KnowledgeWordDto.cs
  63. 2 0
      src/Hotline.Share/Dtos/Order/HomeOrderDto.cs
  64. 6 1
      src/Hotline.Share/Dtos/Order/OrderBiDto.cs
  65. 82 24
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  66. 5 0
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  67. 9 2
      src/Hotline.Share/Dtos/Push/MessageDto.cs
  68. 3 3
      src/Hotline.Share/Enums/Order/EComplainType.cs
  69. 1 0
      src/Hotline.Share/Hotline.Share.csproj
  70. 5 0
      src/Hotline.Share/Requests/PagedKeywordRequest.cs
  71. 5 23
      src/Hotline.Share/Tools/ObjectExtensions.cs
  72. 26 11
      src/Hotline.Share/Tools/StringExtensions.cs
  73. 12 0
      src/Hotline.XingTang/CallTelClient.cs
  74. 17 0
      src/Hotline.XingTang/ServiceCollectionExtensions.cs
  75. 5 0
      src/Hotline/Authentications/SessionContextProvider.cs
  76. 1 0
      src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs
  77. 5 0
      src/Hotline/Caching/Interfaces/ISystemSettingCacheManager.cs
  78. 2 0
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  79. 1 1
      src/Hotline/Caching/Services/SystemSettingCacheManager.cs
  80. 17 0
      src/Hotline/CallCenter/Tels/CallTelDomain/QueryTelRequest.cs
  81. 23 0
      src/Hotline/CallCenter/Tels/CallTelDomain/QueryTelResponse.cs
  82. 12 0
      src/Hotline/CallCenter/Tels/ICallTelClient.cs
  83. 4 0
      src/Hotline/Configurations/AppConfiguration.cs
  84. 1 1
      src/Hotline/File/File.cs
  85. 1 1
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  86. 1 1
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  87. 131 124
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  88. 6 6
      src/Hotline/Orders/OrderDomainService.cs
  89. 1 1
      src/Hotline/Orders/OrderVisitDomainService.cs
  90. 5 0
      src/Hotline/Settings/SettingConstants.cs
  91. 17 6
      src/Hotline/Settings/TimeLimitDomain/ExpireTimeLimitBase.cs
  92. 1 1
      src/Hotline/Settings/TimeLimitDomain/ZiGongExpireTimeLimit.cs
  93. 38 0
      src/Hotline/Tools/ObjectExtensions.cs
  94. 25 0
      src/Tr.Sdk/CallTelClient.cs
  95. 5 1
      src/Tr.Sdk/Tr.Sdk.csproj
  96. 3 1
      src/Tr.Sdk/TrSdkStartupExtensions.cs
  97. 2 0
      src/XF.Domain/Authentications/ISessionContextProvider.cs
  98. 23 2
      src/XF.Domain/Extensions/StringExtensions.cs

+ 7 - 0
Hotline.sln

@@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.XingTang", "src\Hot
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Application.Tests", "src\Hotline.Application.Tests\Hotline.Application.Tests.csproj", "{801A8807-F95E-428B-B8C3-3F9244B9E080}"
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Logger", "src\Hotline.Logger\Hotline.Logger.csproj", "{37784861-ABC0-41F4-87B4-2E08A89A2C42}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -141,6 +143,10 @@ Global
 		{801A8807-F95E-428B-B8C3-3F9244B9E080}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{801A8807-F95E-428B-B8C3-3F9244B9E080}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{801A8807-F95E-428B-B8C3-3F9244B9E080}.Release|Any CPU.Build.0 = Release|Any CPU
+		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -170,6 +176,7 @@ Global
 		{CF2A8B80-FF4E-4291-B383-D735BB629F32} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{9F99C272-5BC2-452C-9D97-BC756AF04669} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{801A8807-F95E-428B-B8C3-3F9244B9E080} = {08D63205-1445-430F-A4AB-EF1744E3AC11}
+		{37784861-ABC0-41F4-87B4-2E08A89A2C42} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

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

@@ -1065,7 +1065,7 @@ namespace Hotline.Api.Controllers
         public async Task AddAiVisit([FromBody]AddAiVisitDto dto)
         {
             //验证是否有重复电话
-            if (dto.AiOrderVisitDetails.DistinctBy(x=>x.OuterNo).Count() != dto.AiOrderVisitDetails.Count)
+            if (dto.AiOrderVisitDetails.DistinctBy(x=>x.OuterNo.Trim()).Count() != dto.AiOrderVisitDetails.Count)
             {
                 throw UserFriendlyException.SameMessage("任务中存在重复外呼号码,请检查后重新提交");
             }

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

@@ -17,7 +17,7 @@ public class BaseController : ControllerBase
         fileName = string.IsNullOrEmpty(fileName)
             ? tail
             : $"{fileName}_{tail}";
-        HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
+        HttpContext.Response.Headers.TryAdd("Access-Control-Expose-Headers", "Content-Disposition");
         return new FileStreamResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
         {
             FileDownloadName = fileName

+ 1 - 1
src/Hotline.Api/Controllers/Bi/BiCallController.cs

@@ -112,7 +112,7 @@ public class BiCallController : BaseController
         var total = new QueryCallsDetailStatistics
         {
             Date = "合计",
-            InTotal = items.Sum(m => m.InTotal),
+            //InTotal = items.Sum(m => m.InTotal),
             NotAcceptedHang = items.Sum(m => m.NotAcceptedHang),
             InConnectionQuantity = items.Sum(m => m.InConnectionQuantity),
             InNotAnswered = items.Sum(m => m.InNotAnswered),

+ 2 - 2
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -1738,8 +1738,8 @@ namespace Hotline.Api.Controllers.Bi
                    QueueByeCount = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.WaitDuration > 0 && p.RingDuration == 0 && p.AnsweredTime == null, 1, 0)), //队列挂断
                    IvrByeCount = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.BeginIvrTime.HasValue && !p.BeginQueueTime.HasValue && !p.BeginRingTime.HasValue && p.AnsweredTime == null, 1, 0)), //IVR挂断
                    OutTotal = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.Out, 1, 0)),//呼出总量
-                   OutConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.Out && p.AnsweredTime != null, 1, 0)),//呼出接通量
-                   OutHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.Out && p.AnsweredTime == null, 1, 0)),//呼出未接通
+                   OutConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.TelNo != "0" && p.Direction == ECallDirection.Out && p.AnsweredTime != null, 1, 0)),//呼出接通量
+                   OutHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(p.TelNo != "0" && p.Direction == ECallDirection.Out && p.AnsweredTime == null, 1, 0)),//呼出未接通
 
                })
                .FirstAsync();

+ 166 - 102
src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs

@@ -1,4 +1,5 @@
-using Hotline.KnowledgeBase;
+using Hotline.Configurations;
+using Hotline.KnowledgeBase;
 using Hotline.Orders;
 using Hotline.Repository.SqlSugar.Orders;
 using Hotline.Settings;
@@ -10,12 +11,13 @@ using Hotline.Share.Enums.Order;
 using MapsterMapper;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
 using SqlSugar;
 using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers.Bigscreen
 {
-    public class DataScreenController: BaseController
+    public class DataScreenController : BaseController
     {
         private readonly IOrderRepository _orderRepository;
         private readonly IRepository<OrderDelay> _orderDelayRepository;
@@ -25,8 +27,12 @@ namespace Hotline.Api.Controllers.Bigscreen
         private readonly IMapper _mapper;
         private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
         private readonly IRepository<SystemArea> _systemAreaRepository;
+        private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
 
-        public DataScreenController(IOrderRepository orderRepository, IRepository<OrderVisit> orderVisitRepository, IRepository<OrderDelay> orderDelayRepository, IRepository<Knowledge> knowledgeRepository, IRepository<KnowledgePv> knowledgePvRepository,IMapper  mapper,IRepository<OrderVisitDetail> orderVisitDetailRepository, IRepository<SystemArea> systemAreaRepository)
+        public DataScreenController(IOrderRepository orderRepository, IRepository<OrderVisit> orderVisitRepository,
+            IRepository<OrderDelay> orderDelayRepository, IRepository<Knowledge> knowledgeRepository, IRepository<KnowledgePv> knowledgePvRepository,
+            IMapper mapper, IRepository<OrderVisitDetail> orderVisitDetailRepository, IRepository<SystemArea> systemAreaRepository,
+            IOptionsSnapshot<AppConfiguration> appOptions)
         {
             _orderRepository = orderRepository;
             _orderVisitRepository = orderVisitRepository;
@@ -36,6 +42,7 @@ namespace Hotline.Api.Controllers.Bigscreen
             _mapper = mapper;
             _orderVisitDetailRepository = orderVisitDetailRepository;
             _systemAreaRepository = systemAreaRepository;
+            _appOptions = appOptions;
         }
 
         /// <summary>
@@ -44,15 +51,18 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("order-statistics")]
-        public async Task<OrderStatisticsDto> OrderStatistics(DateTime StartTime,DateTime EndTime)
+        public async Task<OrderStatisticsDto> OrderStatistics(DateTime StartTime, DateTime EndTime)
         {
             EndTime = EndTime.AddDays(1).AddSeconds(-1);
             var dto = new OrderStatisticsDto();
 
             #region 办结工单
-            dto.CompletionCount =await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status>= EOrderStatus.Filed).CountAsync();
-            int CompletionSum = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status >= EOrderStatus.Handling).CountAsync();
-            if (CompletionSum==0)
+
+            dto.CompletionCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status >= EOrderStatus.Filed).CountAsync();
+            int CompletionSum = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status >= EOrderStatus.Handling).CountAsync();
+            if (CompletionSum == 0)
             {
                 dto.CompletionRate = 0;
             }
@@ -60,11 +70,15 @@ namespace Hotline.Api.Controllers.Bigscreen
             {
                 dto.CompletionRate = Math.Round((dto.CompletionCount / (double)CompletionSum) * 100, 2);
             }
+
             #endregion
 
             #region 待受理工单
-            dto.HaveToAcceptCount =await _orderRepository.Queryable(false, false, false).Where(x => x.CreationTime > StartTime && x.CreationTime <= EndTime && x.Status == EOrderStatus.WaitForAccept).CountAsync();
-            int HaveToAcceptSum = await _orderRepository.Queryable(false, false, false).Where(x => x.CreationTime >= StartTime && x.CreationTime <= EndTime).CountAsync();
+
+            dto.HaveToAcceptCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.CreationTime > StartTime && x.CreationTime <= EndTime && x.Status == EOrderStatus.WaitForAccept).CountAsync();
+            int HaveToAcceptSum = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.CreationTime >= StartTime && x.CreationTime <= EndTime).CountAsync();
             if (HaveToAcceptSum == 0)
             {
                 dto.HaveToAcceptRate = 0;
@@ -73,11 +87,14 @@ namespace Hotline.Api.Controllers.Bigscreen
             {
                 dto.HaveToAcceptRate = Math.Round((dto.HaveToAcceptCount / (double)HaveToAcceptSum) * 100, 2);
             }
+
             #endregion
 
             #region 超期工单
-            dto.OverTimeCount = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.ExpiredStatus == EExpiredStatus.Expired).CountAsync();
-            if (CompletionSum==0)
+
+            dto.OverTimeCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.ExpiredStatus == EExpiredStatus.Expired).CountAsync();
+            if (CompletionSum == 0)
             {
                 dto.OverTimeRate = 0;
             }
@@ -85,11 +102,14 @@ namespace Hotline.Api.Controllers.Bigscreen
             {
                 dto.OverTimeRate = Math.Round((dto.OverTimeCount / (double)CompletionSum) * 100, 2);
             }
+
             #endregion
 
             #region 延期工单
-            dto.DelayCount = await _orderDelayRepository.Queryable().Where(x => x.ApplyDelayTime >= StartTime && x.ApplyDelayTime <= EndTime && x.DelayState == EDelayState.Pass).CountAsync();
-            if (CompletionSum==0)
+
+            dto.DelayCount = await _orderDelayRepository.Queryable()
+                .Where(x => x.ApplyDelayTime >= StartTime && x.ApplyDelayTime <= EndTime && x.DelayState == EDelayState.Pass).CountAsync();
+            if (CompletionSum == 0)
             {
                 dto.DelayRate = 0;
             }
@@ -101,9 +121,13 @@ namespace Hotline.Api.Controllers.Bigscreen
             #endregion
 
             #region 满意工单
-            dto.SatisfiedCount =await _orderVisitRepository.Queryable().Where(x => x.VisitTime >= StartTime && x.VisitTime <= EndTime && x.VisitState == EVisitState.Visited && SqlFunc.JsonField(x.NowEvaluate, "Key") != "1" && SqlFunc.JsonField(x.NowEvaluate,"Key")!="2" ).CountAsync();
-            int SatisfiedSum = await _orderVisitRepository.Queryable().Where(x => x.VisitTime >= StartTime && x.VisitTime <= EndTime && x.VisitState == EVisitState.Visited).CountAsync();
-            if (SatisfiedSum==0)
+
+            dto.SatisfiedCount = await _orderVisitRepository.Queryable().Where(x =>
+                x.VisitTime >= StartTime && x.VisitTime <= EndTime && x.VisitState == EVisitState.Visited &&
+                SqlFunc.JsonField(x.NowEvaluate, "Key") != "1" && SqlFunc.JsonField(x.NowEvaluate, "Key") != "2").CountAsync();
+            int SatisfiedSum = await _orderVisitRepository.Queryable()
+                .Where(x => x.VisitTime >= StartTime && x.VisitTime <= EndTime && x.VisitState == EVisitState.Visited).CountAsync();
+            if (SatisfiedSum == 0)
             {
                 dto.SatisfiedRate = 0;
             }
@@ -111,12 +135,16 @@ namespace Hotline.Api.Controllers.Bigscreen
             {
                 dto.SatisfiedRate = Math.Round((dto.SatisfiedCount / (double)SatisfiedSum) * 100, 2);
             }
+
             #endregion
 
             #region 省工单量
 
-            dto.ProvinceOrderCount =await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.IsProvince).CountAsync();
-            dto.ProvinceOrderCompletionCount = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.IsProvince && x.Status >= EOrderStatus.Filed).CountAsync();
+            dto.ProvinceOrderCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.IsProvince).CountAsync();
+            dto.ProvinceOrderCompletionCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.IsProvince && x.Status >= EOrderStatus.Filed).CountAsync();
+
             #endregion
 
             return dto;
@@ -131,10 +159,14 @@ namespace Hotline.Api.Controllers.Bigscreen
         public async Task<KnowledgeStatisticsDto> KnowledgeStatistics()
         {
             var dto = new KnowledgeStatisticsDto();
-            dto.KnowledgeCount = await _knowledgeRepository.Queryable().Where(x => x.Status == EKnowledgeStatus.OnShelf).CountAsync();//总数
-            dto.TodayAddCount = await _knowledgeRepository.Queryable().Where(x => x.Renewaln==false && x.Status == EKnowledgeStatus.OnShelf && x.OnShelfTime.Value.Date== DateTime.Now.Date).CountAsync();//今日新增
-            dto.ThisMonthModifyCount = await _knowledgeRepository.Queryable().Where(x => x.Renewaln == true && x.Status == EKnowledgeStatus.OnShelf && x.OnShelfTime.Value.Year
-            == DateTime.Now.Year && x.OnShelfTime.Value.Month == DateTime.Now.Month).CountAsync();//本月修改
+            dto.KnowledgeCount = await _knowledgeRepository.Queryable().Where(x => x.Status == EKnowledgeStatus.OnShelf).CountAsync(); //总数
+            dto.TodayAddCount = await _knowledgeRepository.Queryable().Where(x =>
+                x.Renewaln == false && x.Status == EKnowledgeStatus.OnShelf && x.OnShelfTime.Value.Date == DateTime.Now.Date).CountAsync(); //今日新增
+            dto.ThisMonthModifyCount = await _knowledgeRepository.Queryable().Where(x => x.Renewaln == true && x.Status == EKnowledgeStatus.OnShelf &&
+                                                                                         x.OnShelfTime.Value.Year
+                                                                                         == DateTime.Now.Year &&
+                                                                                         x.OnShelfTime.Value.Month == DateTime.Now.Month)
+                .CountAsync(); //本月修改
             dto.TodayReadCount = await _knowledgePvRepository.Queryable().Where(x => x.CreationTime.Date == DateTime.Now.Date).CountAsync();
             return dto;
         }
@@ -147,18 +179,19 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("ordertype-statistics")]
-        public async Task<List<OrderTypeHandleStatisticsDto>> OrderTypeHandleStatistics(DateTime StartTime,DateTime EndTime)
+        public async Task<List<OrderTypeHandleStatisticsDto>> OrderTypeHandleStatistics(DateTime StartTime, DateTime EndTime)
         {
             EndTime = EndTime.AddDays(1).AddSeconds(-1);
-            var list =await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.Handling && !string.IsNullOrEmpty(x.AcceptType))
-                .GroupBy(x=>x.AcceptType)
+            var list = await _orderRepository.Queryable(false, false, false).Where(x =>
+                    x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.Handling && !string.IsNullOrEmpty(x.AcceptType))
+                .GroupBy(x => x.AcceptType)
                 .Select(x => new OrderTypeHandleStatisticsDto
-                { 
-                     AcceptType = x.AcceptType,
-                     SumCount = SqlFunc.AggregateCount(x.Id),
-                     HandlingCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status>= EOrderStatus.Handling && x.Status < EOrderStatus.Filed,1,0)),
-                     FiledCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status>= EOrderStatus.Filed,1,0)),
-                     OverTimeCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.ExpiredStatus == EExpiredStatus.Expired,1,0))
+                {
+                    AcceptType = x.AcceptType,
+                    SumCount = SqlFunc.AggregateCount(x.Id),
+                    HandlingCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Handling && x.Status < EOrderStatus.Filed, 1, 0)),
+                    FiledCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed, 1, 0)),
+                    OverTimeCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.ExpiredStatus == EExpiredStatus.Expired, 1, 0))
                 }).ToListAsync();
             return list;
         }
@@ -171,8 +204,9 @@ namespace Hotline.Api.Controllers.Bigscreen
         [AllowAnonymous]
         public async Task<object> GetSystemAreaAsync()
         {
+            var areaCode = _appOptions.Value.GetDefaultAppScopeConfiguration().AreaCode;
             return await _systemAreaRepository.Queryable()
-                .Where(p => p.Id == "511500" || p.ParentId == "511500")
+                .Where(p => p.Id == areaCode || p.ParentId == areaCode)
                 .Select(p => new
                 {
                     p.AreaName,
@@ -191,23 +225,25 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("earlywarning-statistics")]
-        public async Task<List<EarlyWarningHotsPotsStatisticsDto>> EarlyWarningHotsPotsStatistics(DateTime StartTime, DateTime EndTime,string AreaCode)
+        public async Task<List<EarlyWarningHotsPotsStatisticsDto>> EarlyWarningHotsPotsStatistics(DateTime StartTime, DateTime EndTime,
+            string AreaCode)
         {
             EndTime = EndTime.AddDays(1).AddSeconds(-1);
-            if (AreaCode.Length==6 && AreaCode.IndexOf("00") == 4)
+            if (AreaCode.Length == 6 && AreaCode.IndexOf("00") == 4)
             {
                 AreaCode = AreaCode.Remove(4);
             }
-             var list = await _orderRepository.Queryable(false, false, false)
+
+            var list = await _orderRepository.Queryable(false, false, false)
                 .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.AreaCode.StartsWith(AreaCode) && !x.HotspotId.StartsWith("18"))
-                .GroupBy(x => new { x.HotspotId, x.HotspotName,x.HotspotSpliceName })
+                .GroupBy(x => new { x.HotspotId, x.HotspotName, x.HotspotSpliceName })
                 .Select(x => new EarlyWarningHotsPotsStatisticsDto()
                 {
-                     HotspotId = x.HotspotId,
-                     HotspotName = x.HotspotName,
-                     HotspotSpliceName = x.HotspotSpliceName,
-                     SumCount = SqlFunc.AggregateCount(x.Id)
-                }).OrderByDescending(x=>x.SumCount).Take(5).ToListAsync();
+                    HotspotId = x.HotspotId,
+                    HotspotName = x.HotspotName,
+                    HotspotSpliceName = x.HotspotSpliceName,
+                    SumCount = SqlFunc.AggregateCount(x.Id)
+                }).OrderByDescending(x => x.SumCount).Take(5).ToListAsync();
             return list;
         }
 
@@ -221,14 +257,16 @@ namespace Hotline.Api.Controllers.Bigscreen
         {
             var today = DateTime.Now;
             var dto = new OrderCountStatisticsDto();
+
             #region 当日工单量
 
-            dto.ToDayCount = await _orderRepository.Queryable(false,false,false).Where(x => x.StartTime.Value.Date == DateTime.Now.Date && x.Status > EOrderStatus.WaitForAccept).CountAsync();
+            dto.ToDayCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime.Value.Date == DateTime.Now.Date && x.Status > EOrderStatus.WaitForAccept).CountAsync();
             var beforToDayCount = await _orderRepository.Queryable(false, false, false)
                 //.Where(x => x.StartTime.Value.Date == today.AddDays(-1).Date && x.Status > EOrderStatus.WaitForAccept)
-                .Where(x=>x.StartTime.Value.Date == DateTime.Now.AddDays(-1).Date && x.Status > EOrderStatus.WaitForAccept)
+                .Where(x => x.StartTime.Value.Date == DateTime.Now.AddDays(-1).Date && x.Status > EOrderStatus.WaitForAccept)
                 .CountAsync();
-                
+
 
             if (beforToDayCount == 0)
             {
@@ -238,15 +276,19 @@ namespace Hotline.Api.Controllers.Bigscreen
             {
                 dto.ToDayQoQ = Math.Round(((dto.ToDayCount - beforToDayCount) / (double)beforToDayCount) * 100, 2);
             }
+
             #endregion
 
             #region 当月工单量
 
-            dto.ToMonthCount = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime.Value.ToString("yyyy-MM") == today.ToString("yyyy-MM") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
+            dto.ToMonthCount = await _orderRepository.Queryable(false, false, false).Where(x =>
+                x.StartTime.Value.ToString("yyyy-MM") == today.ToString("yyyy-MM") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
 
-            var beforToMonthCount = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime.Value.ToString("yyyy-MM") == today.AddMonths(-1).ToString("yyyy-MM") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
+            var beforToMonthCount = await _orderRepository.Queryable(false, false, false).Where(x =>
+                    x.StartTime.Value.ToString("yyyy-MM") == today.AddMonths(-1).ToString("yyyy-MM") && x.Status > EOrderStatus.WaitForAccept)
+                .CountAsync();
 
-            if (beforToMonthCount==0)
+            if (beforToMonthCount == 0)
             {
                 dto.ToMonthQoQ = 0;
             }
@@ -259,11 +301,13 @@ namespace Hotline.Api.Controllers.Bigscreen
 
             #region 当年工单量
 
-            dto.ToYearCount = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime.Value.ToString("yyyy") == today.ToString("yyyy") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
+            dto.ToYearCount = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime.Value.ToString("yyyy") == today.ToString("yyyy") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
 
-            var beforToYearCount = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime.Value.ToString("yyyy") == today.AddYears(-1).ToString("yyyy") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
+            var beforToYearCount = await _orderRepository.Queryable(false, false, false).Where(x =>
+                x.StartTime.Value.ToString("yyyy") == today.AddYears(-1).ToString("yyyy") && x.Status > EOrderStatus.WaitForAccept).CountAsync();
 
-            if (beforToYearCount==0)
+            if (beforToYearCount == 0)
             {
                 dto.ToYearQoQ = 0;
             }
@@ -271,6 +315,7 @@ namespace Hotline.Api.Controllers.Bigscreen
             {
                 dto.ToYearQoQ = Math.Round(((dto.ToYearCount - beforToYearCount) / (double)beforToYearCount) * 100, 2);
             }
+
             #endregion
 
             return dto;
@@ -282,24 +327,26 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("orderarea-accept-statistics")]
-        public async Task<List<OrderAreaAcceptStatisticsDto>> OrderAreaAcceptStatistics(DateTime StartTime,DateTime EndTime)
+        public async Task<List<OrderAreaAcceptStatisticsDto>> OrderAreaAcceptStatistics(DateTime StartTime, DateTime EndTime)
         {
             EndTime = EndTime.AddDays(1).AddSeconds(-1);
 
-            var list = await _orderRepository.Queryable(false, false, false).Where(x => x.StartTime>= StartTime && x.StartTime<= EndTime && x.Status > EOrderStatus.WaitForAccept)
-                .LeftJoin<SystemArea>((it,o)=> it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) == o.Id)
-                .GroupBy((it,o) => new {
+            var list = await _orderRepository.Queryable(false, false, false)
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept)
+                .LeftJoin<SystemArea>((it, o) => it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) == o.Id)
+                .GroupBy((it, o) => new
+                {
                     AreaCode = it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
                     o.AreaName,
                 })
-                .Select((it,o) => new OrderAreaAcceptStatisticsDto()
+                .Select((it, o) => new OrderAreaAcceptStatisticsDto()
                 {
                     AreaCode = it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
                     AreaName = o.AreaName,
                     AcceptedCount = SqlFunc.AggregateCount(it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6"))),
-                    CompletionCount = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status>= EOrderStatus.Filed,1,0))
+                    CompletionCount = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status >= EOrderStatus.Filed, 1, 0))
                 }).MergeTable()
-                .Where(x=> x.AreaCode!= "519800" && x.AreaCode!= "519900").OrderByDescending(it=> it.AcceptedCount).ToListAsync();
+                .Where(x => x.AreaCode != "519800" && x.AreaCode != "519900").OrderByDescending(it => it.AcceptedCount).ToListAsync();
             return list;
         }
 
@@ -312,24 +359,27 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("orderareaaccept-query")]
-        public async Task<List<OrderAreaAcceptQueryDto>> OrderAreaAcceptQuery(DateTime StartTime,DateTime EndTime)
+        public async Task<List<OrderAreaAcceptQueryDto>> OrderAreaAcceptQuery(DateTime StartTime, DateTime EndTime)
         {
             EndTime = EndTime.AddDays(1).AddSeconds(-1);
 
 
-            var areaList =await _systemAreaRepository.Queryable()
+            var areaList = await _systemAreaRepository.Queryable()
                 .Where(x => !x.Id.EndsWith("00"))
-                .GroupBy(x => new { 
+                .GroupBy(x => new
+                {
                     Id = x.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
                 })
-                .Select(x => new {
+                .Select(x => new
+                {
                     Id = x.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
                 })
                 .MergeTable()
-                .LeftJoin<SystemArea>((it,o)=> it.Id == o.Id)
-                .Select((it, o) => new {
+                .LeftJoin<SystemArea>((it, o) => it.Id == o.Id)
+                .Select((it, o) => new
+                {
                     Id = it.Id,
-                    Name=o.AreaName
+                    Name = o.AreaName
                 })
                 .ToListAsync();
 
@@ -342,13 +392,20 @@ namespace Hotline.Api.Controllers.Bigscreen
                 var dto = new OrderAreaAcceptQueryDto();
                 dto.AreaCode = item.Id;
                 dto.AreaName = item.Name;
-                dto.HandlingCount = await _orderRepository.Queryable(false, false, false).Where(x => x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept && x.Status < EOrderStatus.Filed).CountAsync();
+                dto.HandlingCount = await _orderRepository.Queryable(false, false, false).Where(x =>
+                    x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept &&
+                    x.Status < EOrderStatus.Filed).CountAsync();
 
-                dto.FiledCount = await _orderRepository.Queryable(false, false, false).Where(x => x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status >= EOrderStatus.Filed).CountAsync();
+                dto.FiledCount = await _orderRepository.Queryable(false, false, false).Where(x =>
+                    x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status >= EOrderStatus.Filed).CountAsync();
 
-                dto.OverTimeCount = await _orderRepository.Queryable(false, false, false).Where(x => x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.ExpiredStatus == EExpiredStatus.Expired).CountAsync();
+                dto.OverTimeCount = await _orderRepository.Queryable(false, false, false).Where(x =>
+                        x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.ExpiredStatus == EExpiredStatus.Expired)
+                    .CountAsync();
 
-                var hotsPot = await _orderRepository.Queryable(false, false, false).Where(x => x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept).GroupBy(x => new { x.HotspotId, x.HotspotName })
+                var hotsPot = await _orderRepository.Queryable(false, false, false)
+                    .Where(x => x.AreaCode == item.Id && x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept)
+                    .GroupBy(x => new { x.HotspotId, x.HotspotName })
                     .Select(x => new
                     {
                         HotsPotName = x.HotspotName,
@@ -358,22 +415,24 @@ namespace Hotline.Api.Controllers.Bigscreen
                 dto.HotspotName = hotsPot?.HotsPotName;
 
                 #region 满意度
+
                 var SatisfiedCount = await _orderRepository.Queryable(false, false, false)
                     .LeftJoin<OrderVisit>((it, o) => it.Id == o.OrderId && o.VisitState == EVisitState.Visited)
                     .Where((it, o) => it.AreaCode == item.Id && o.VisitTime >= StartTime && o.VisitTime <= EndTime
-                    && SqlFunc.JsonField(o.NowEvaluate, "Key") != "1" && SqlFunc.JsonField(o.NowEvaluate, "Key") != "2")
+                                      && SqlFunc.JsonField(o.NowEvaluate, "Key") != "1" && SqlFunc.JsonField(o.NowEvaluate, "Key") != "2")
                     .CountAsync();
                 var VisitCount = await _orderRepository.Queryable(false, false, false)
                     .LeftJoin<OrderVisit>((it, o) => it.Id == o.OrderId && o.VisitState == EVisitState.Visited)
                     .Where((it, o) => it.AreaCode == item.Id && o.VisitTime >= StartTime && o.VisitTime <= EndTime)
                     .CountAsync();
 
-                if (SatisfiedCount!=0 && VisitCount!=0)
+                if (SatisfiedCount != 0 && VisitCount != 0)
                 {
                     dto.SatisfiedRate = Math.Round((SatisfiedCount / (double)VisitCount) * 100, 2);
                 }
 
                 #endregion
+
                 list.Add(dto);
 
                 #endregion
@@ -392,9 +451,9 @@ namespace Hotline.Api.Controllers.Bigscreen
         {
             var list = await _orderRepository
                 .Queryable(false, false, false)
-                .Where(x => x.Status > EOrderStatus.WaitForAccept  && x.StartTime.Value.Date == DateTime.Now.Date )
+                .Where(x => x.Status > EOrderStatus.WaitForAccept && x.StartTime.Value.Date == DateTime.Now.Date)
                 //.Where(x => x.Status > EOrderStatus.WaitForAccept && x.StartTime.Value.Date == DateTime.Parse("2024-03-14").Date)
-                .OrderByDescending(x=>x.StartTime)
+                .OrderByDescending(x => x.StartTime)
                 .Take(50)
                 .ToListAsync();
             return _mapper.Map<List<OrderDto>>(list);
@@ -406,7 +465,7 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("highmatter-warning")]
-        public async Task<List<HighMatterWarningDto>> HighMatterWarning(DateTime StartTime,DateTime EndTime)
+        public async Task<List<HighMatterWarningDto>> HighMatterWarning(DateTime StartTime, DateTime EndTime)
         {
             //var endDate = DateTime.Now.Date.AddDays(1).AddSeconds(-1);
             //var startDate = endDate.AddDays(-30).Date;
@@ -421,7 +480,7 @@ namespace Hotline.Api.Controllers.Bigscreen
 
             var list = await _orderRepository.Queryable(false, false, false)
                 .Where(x => x.CreationTime >= StartTime && x.CreationTime <= EndTime)
-                .Where(x=> filterTitle.Any(s=> x.Title.Contains(s)) == false)
+                .Where(x => filterTitle.Any(s => x.Title.Contains(s)) == false)
                 .LeftJoin<SystemArea>((it, o) => it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) == o.Id)
                 .GroupBy((it, o) => new
                 {
@@ -431,7 +490,7 @@ namespace Hotline.Api.Controllers.Bigscreen
                     AreaCode = it.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
                     o.AreaName,
                 })
-                .Having((it,o) => SqlFunc.AggregateCount(it.HotspotName)>=5)
+                .Having((it, o) => SqlFunc.AggregateCount(it.HotspotName) >= 5)
                 .Select((it, o) => new HighMatterWarningDto()
                 {
                     AreaName = o.AreaName,
@@ -442,8 +501,9 @@ namespace Hotline.Api.Controllers.Bigscreen
                 })
                 .MergeTable()
                 //.Where(x=>x.SumCount>=5)
-                .LeftJoin<Order>((x,d)=>x.Id==d.Id)
-                .Select((x,d)=>new HighMatterWarningDto() { 
+                .LeftJoin<Order>((x, d) => x.Id == d.Id)
+                .Select((x, d) => new HighMatterWarningDto()
+                {
                     AreaName = x.AreaName,
                     HotspotName = x.HotspotName,
                     Title = d.Title,
@@ -462,11 +522,12 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("ordervisit-orgsatisfaction-rank")]
-        public async Task<List<OrderVisitOrgSatisfactionRankDto>> OrderVisitOrgSatisfactionRank(DateTime StartTime,DateTime EndTime)
+        public async Task<List<OrderVisitOrgSatisfactionRankDto>> OrderVisitOrgSatisfactionRank(DateTime StartTime, DateTime EndTime)
         {
-           var list = await _orderVisitDetailRepository.Queryable()
+            var list = await _orderVisitDetailRepository.Queryable()
                 .Includes(x => x.OrderVisit)
-                .Where(x => x.OrderVisit.VisitTime >= StartTime && x.OrderVisit.VisitTime <= EndTime && x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode.Length >= 6 && x.OrderVisit.VisitState == EVisitState.Visited)
+                .Where(x => x.OrderVisit.VisitTime >= StartTime && x.OrderVisit.VisitTime <= EndTime && x.VisitTarget == EVisitTarget.Org &&
+                            x.VisitOrgCode.Length >= 6 && x.OrderVisit.VisitState == EVisitState.Visited)
                 .GroupBy(x => new
                 {
                     VisitOrgCode = x.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
@@ -476,9 +537,12 @@ namespace Hotline.Api.Controllers.Bigscreen
                 {
                     VisitOrgCode = x.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
                     VisitOrgName = x.VisitOrgName,
-                    SatisfiedCount = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") != "1" && SqlFunc.JsonField(x.OrgProcessingResults, "Key") != "2", 1, 0)),
+                    SatisfiedCount =
+                        SqlFunc.AggregateSum(SqlFunc.IIF(
+                            SqlFunc.JsonField(x.OrgProcessingResults, "Key") != "1" && SqlFunc.JsonField(x.OrgProcessingResults, "Key") != "2", 1,
+                            0)),
                     VisitCount = SqlFunc.AggregateCount(x.VisitOrgName)
-                }).MergeTable().OrderByDescending(x=>x.SatisfiedCount).Take(10).ToListAsync();
+                }).MergeTable().OrderByDescending(x => x.SatisfiedCount).Take(10).ToListAsync();
             list = list.OrderByDescending(x => x.SatisfiedRate).ToList();
             return list;
         }
@@ -492,39 +556,39 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// <returns></returns>
         [AllowAnonymous]
         [HttpGet("order-source-accepttype-statistics")]
-        public async Task<List<OrderSourceAndAcceptTtoeStatisticsDto>> OrderSourceAndAcceptTtoeStatistics(DateTime StartTime, DateTime EndTime, bool IsSource)
+        public async Task<List<OrderSourceAndAcceptTtoeStatisticsDto>> OrderSourceAndAcceptTtoeStatistics(DateTime StartTime, DateTime EndTime,
+            bool IsSource)
         {
             EndTime = EndTime.AddDays(1).AddSeconds(-1);
 
             int SumCount = await _orderRepository.Queryable(false, false, false)
-               .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept).CountAsync();
+                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept).CountAsync();
             if (IsSource)
             {
                 var list = await _orderRepository.Queryable(false, false, false)
-                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept)
-                .GroupBy(x => new { x.SourceChannelCode, x.SourceChannel })
-                .Select(x => new OrderSourceAndAcceptTtoeStatisticsDto()
-                {
-                    Name = x.SourceChannel,
-                    SumCount = SumCount,
-                    HasCount = SqlFunc.AggregateCount(x.SourceChannel)
-                }).ToListAsync();
+                    .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept)
+                    .GroupBy(x => new { x.SourceChannelCode, x.SourceChannel })
+                    .Select(x => new OrderSourceAndAcceptTtoeStatisticsDto()
+                    {
+                        Name = x.SourceChannel,
+                        SumCount = SumCount,
+                        HasCount = SqlFunc.AggregateCount(x.SourceChannel)
+                    }).ToListAsync();
                 return list;
             }
             else
             {
                 var list = await _orderRepository.Queryable(false, false, false)
-                .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept)
-                .GroupBy( x => new { x.AcceptTypeCode, x.AcceptType })
-                .Select(x => new OrderSourceAndAcceptTtoeStatisticsDto()
-                {
-                    Name = x.AcceptType,
-                    SumCount = SumCount,
-                    HasCount = SqlFunc.AggregateCount(x.AcceptTypeCode),
-                }).ToListAsync();
+                    .Where(x => x.StartTime >= StartTime && x.StartTime <= EndTime && x.Status > EOrderStatus.WaitForAccept)
+                    .GroupBy(x => new { x.AcceptTypeCode, x.AcceptType })
+                    .Select(x => new OrderSourceAndAcceptTtoeStatisticsDto()
+                    {
+                        Name = x.AcceptType,
+                        SumCount = SumCount,
+                        HasCount = SqlFunc.AggregateCount(x.AcceptTypeCode),
+                    }).ToListAsync();
                 return list;
             }
         }
-
     }
-}
+}

+ 6 - 6
src/Hotline.Api/Controllers/Bigscreen/SeatController.cs

@@ -1,26 +1,26 @@
 using Hotline.Application.Bigscreen;
 using Hotline.Caching.Interfaces;
+using Hotline.CallCenter.Tels;
+using Hotline.CallCenter.Tels.CallTelDomain;
 using Hotline.Settings;
 using Hotline.Share.Dtos.TrCallCenter;
 using Mapster;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
-using Tr.Sdk;
-using Tr.Sdk.Tels;
 
 namespace Hotline.Api.Controllers.Bigscreen
 {
     public class SeatController : BaseController
     {
         private readonly ISeatStateDataService _seatStateDataService;
-        private readonly ITrClient _trClient;
+        private readonly ICallTelClient _callTelClient;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
 
-        public SeatController(ISeatStateDataService seatStateDataService, ISystemSettingCacheManager systemSettingCacheManager, ITrClient trClient)
+        public SeatController(ISeatStateDataService seatStateDataService, ISystemSettingCacheManager systemSettingCacheManager, ICallTelClient callTelClient)
         {
             _seatStateDataService = seatStateDataService;
             _systemSettingCacheManager = systemSettingCacheManager;
-            _trClient = trClient;
+            _callTelClient = callTelClient;
         }
 
         /// <summary>
@@ -51,7 +51,7 @@ namespace Hotline.Api.Controllers.Bigscreen
         {
             var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue;
             var callOuttQueueId = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutQueueId)?.SettingValue;
-            var list = (await _trClient.QueryTelsAsync(new QueryTelRequest(), HttpContext.RequestAborted))
+            var list = (await _callTelClient.QueryTelsAsync(new QueryTelRequest(), HttpContext.RequestAborted))
                 .Where(m => listenTels.Contains(m.TelNo))
                 .ToList()
                 .Adapt<List<TelOutDto>>();

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

@@ -183,7 +183,7 @@ namespace Hotline.Api.Controllers
                     aboutExpire = SqlFunc.AggregateSum(SqlFunc.IIF(DateTime.Now > o.NearlyExpiredTime!.Value && DateTime.Now < o.ExpiredTime!.Value,
                         1, 0)),
                     havExpired = SqlFunc.AggregateSum(SqlFunc.IIF(DateTime.Now > o.ExpiredTime!.Value , 1, 0)),
-                    countersignHandle = SqlFunc.AggregateSum(SqlFunc.IIF(o.CounterSignType.HasValue, 1, 0)),
+                    countersignHandle = SqlFunc.AggregateSum(SqlFunc.IIF(o.Status == EOrderStatus.Countersigning, 1, 0)),
                 }).ToListAsync();
             var aboutExpire = order?.Sum(x=>x.aboutExpire) ?? 0;
             var havExpired = order?.Sum(x=>x.havExpired) ?? 0;
@@ -255,13 +255,14 @@ namespace Hotline.Api.Controllers
 						CounterSignType = d.CounterSignType
 					})
 					.ToListAsync();
-				var waitedList = waitedDataList.Take(40).ToList();
+				var waitedList = waitedDataList.Where(x => x.Status != EOrderStatus.Countersigning && 
+				((x.Time > DateTime.Now && x.Status < EOrderStatus.Filed) || (x.Time > x.ActualHandleTime && x.Status >= EOrderStatus.Filed))).OrderBy(x=> x.Time).Take(40).ToList();
 				waitedList = waitedList.Count > 0 ? waitedList.Copy() : waitedList;
 				//allNum += waitedList.Count > 40 ? 40 : waitedList.Count;
 				//allList.AddRange(waitedList);
 				// 已超期
 				var waitedExpiredDataList = waitedDataList.Where(x => (x.Time < DateTime.Now && x.Status < EOrderStatus.Filed) ||
-				                                                      (x.Time < x.ActualHandleTime && x.Status >= EOrderStatus.Filed)).Take(40).ToList();
+				                                                      (x.Time < x.ActualHandleTime && x.Status >= EOrderStatus.Filed)).OrderBy(x=>x.Time).Take(40).ToList();
 				waitedExpiredDataList = waitedExpiredDataList.Count > 0 ? waitedExpiredDataList.Copy() : waitedExpiredDataList;
 				waitedExpiredDataList.ForEach(x => x.Type = "WaitedExpired");
 				//allNum += waitedExpiredDataList.Count;
@@ -289,7 +290,7 @@ namespace Hotline.Api.Controllers
 				//allNum += visitDataList.Count;
 				//allList.AddRange(visitDataList);
 				//会签待办
-				var signDataList = waitedDataList.Where(x => x.CounterSignType == ECounterSignType.Center || x.CounterSignType == ECounterSignType.Department).Take(40).ToList();
+				var signDataList = waitedDataList.Where(x => x.Status == EOrderStatus.Countersigning).OrderBy(x => x.Time).Take(40).ToList();
 				signDataList = signDataList.Count > 0 ? signDataList.Copy() : signDataList;
 				signDataList.ForEach(x => x.Type = "Sign");
 				//allNum += signDataList.Count;
@@ -324,7 +325,7 @@ namespace Hotline.Api.Controllers
 						                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
 						.Any())
 					.Where(d => d.Status < EOrderStatus.Filed && DateTime.Now > d.NearlyExpiredTime && DateTime.Now < d.ExpiredTime)
-					.OrderByDescending(d => d.CreationTime)
+					.OrderBy(d => d.NearlyExpiredTime)
 					.Select(d => new HomeOrderDto
 					{
 						No = d.No,
@@ -434,19 +435,21 @@ namespace Hotline.Api.Controllers
 						CounterSignType = d.CounterSignType
 					})
 					.ToListAsync();
-				var waitedList = waitedDataList.Take(40).ToList();
+				var waitedList = waitedDataList.Where(x => x.Status != EOrderStatus.Countersigning &&
+				((x.Time > DateTime.Now && x.Status < EOrderStatus.Filed) || (x.Time > x.ActualHandleTime && x.Status >= EOrderStatus.Filed))).OrderBy(x=>x.Time).Take(40).ToList();
 				waitedList = waitedList.Count > 0 ? waitedList.Copy() : waitedList;
 				//allNum += waitedList.Count > 40 ? 40 : waitedList.Count;
 				//allList.AddRange(waitedList);
 				//已超期
 				var waitedExpiredDataList = waitedDataList.Where(x => (x.Time < DateTime.Now && x.Status < EOrderStatus.Filed) ||
-				                                                      (x.Time < x.ActualHandleTime && x.Status >= EOrderStatus.Filed)).Take(40).ToList();
+				                                                      (x.Time < x.ActualHandleTime && x.Status >= EOrderStatus.Filed))
+														  .OrderBy(x=> x.Time).Take(40).ToList();
 				waitedExpiredDataList = waitedExpiredDataList.Count > 0 ? waitedExpiredDataList.Copy() : waitedExpiredDataList;
 				waitedExpiredDataList.ForEach(x => x.Type = "WaitedExpired");
 				//allNum += waitedExpiredDataList.Count;
 				//allList.AddRange(waitedExpiredDataList);
 				//会签待办
-				var signDataList = waitedDataList.Where(x => x.CounterSignType == ECounterSignType.Center || x.CounterSignType == ECounterSignType.Department).Take(40).ToList();
+				var signDataList = waitedDataList.Where(x => x.Status == EOrderStatus.Countersigning).OrderBy(x => x.Time).Take(40).ToList();
 				signDataList = signDataList.Count > 0 ? signDataList.Copy() : signDataList;
 				signDataList.ForEach(x => x.Type = "Sign");
 				
@@ -552,7 +555,7 @@ namespace Hotline.Api.Controllers
 						                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
 						.Any())
 					.Where(d => d.Status < EOrderStatus.Filed && DateTime.Now > d.NearlyExpiredTime && DateTime.Now < d.ExpiredTime)
-					.OrderByDescending(d => d.CreationTime)
+					.OrderBy(d => d.NearlyExpiredTime)
 					.Select(d => new HomeOrderDto
 					{
 						No = d.No,

+ 2 - 1
src/Hotline.Api/Controllers/HomeController.cs

@@ -188,7 +188,8 @@ public class HomeController : BaseController
             ApplyDelayTime = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ApplyDelayTime).SettingValue[0]),
             IsOpenRepeatedWorkOrders = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsOpenRepeatedWorkOrders).SettingValue[0]),
             OldHotlineUrl = _systemSettingCacheManager.GetSetting(SettingConstants.OldHotlineUrl).SettingValue[0],
-            OldHotlineOrderState  = _systemSettingCacheManager.GetSetting(SettingConstants.OldHotlineOrderState).SettingValue[0]
+            OldHotlineOrderState = _systemSettingCacheManager.GetSetting(SettingConstants.OldHotlineOrderState).SettingValue[0],
+            FileExt = _systemSettingCacheManager.GetSetting(SettingConstants.FileExt).SettingValue[0],
         };
         return rsp;
     }

+ 4 - 4
src/Hotline.Api/Controllers/IPPbxController.cs

@@ -171,10 +171,10 @@ namespace Hotline.Api.Controllers
                             WorkUserId = (work != null) ? work.UserId: "",
                             WorkUserName = (work != null) ? work.UserName: "",
                         };
-            if (hasListen == false)
-            {
-                query = query.Where(m => listenTels.Contains(m.TelNo) == false);
-            }
+            //if (hasListen == false)
+            //{
+            //    query = query.Where(m => listenTels.Contains(m.TelNo) == false);
+            //}
             var list = query.OrderBy(x=>x.TelNo).OrderByDescending(x=>x.CreatedAt).ToList();
             return list;// _mapper.Map<IReadOnlyList<TrTelStateDto>>(tels.AgentList);
         }

+ 18 - 74
src/Hotline.Api/Controllers/KnowledgeController.cs

@@ -330,7 +330,7 @@ namespace Hotline.Api.Controllers
         /// <returns></returns>
         [HttpPost("batch_audit")]
         public async Task KnowledgeBatchAuditAsync()
-        { 
+        {
             // TODO: qcy 批量审核 
         }
 
@@ -340,8 +340,8 @@ namespace Hotline.Api.Controllers
         /// <param name="title"></param>
         /// <returns></returns>
         [HttpGet("participle")]
-        public async Task<IList<KnowledgeWordOutDto>> KnowledgeKeyWord([FromQuery]string title)
-        { 
+        public async Task<IList<KnowledgeWordOutDto>> KnowledgeKeyWord([FromQuery] string title)
+        {
             return await _knowApplication.TitleParticiple(title);
         }
 
@@ -620,6 +620,17 @@ namespace Hotline.Api.Controllers
             return _exportApplication.GetExcelFile(dto, items, "知识明细导出");
         }
 
+        /// <summary>
+        /// 来电弹窗知识检索
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("knowretrieval")]
+        public async Task<PagedDto<KnowledgeRetrievalDataDto>> KnowRetrievalWindow([FromBody] KnowledgeRetrievalPagedListDto dto)
+        {
+            return (await _knowApplication.KnowRetrievalAsync(dto)).ToPaged();
+        }
+
         /// <summary>
         /// 知识检索
         /// </summary>
@@ -628,75 +639,7 @@ namespace Hotline.Api.Controllers
         [HttpGet("knowretrieval")]
         public async Task<PagedDto<KnowledgeRetrievalDataDto>> KnowRetrieval([FromQuery] KnowledgeRetrievalPagedListDto dto)
         {
-            var typeSpliceName = string.Empty;
-            var hotspotHotSpotFullName = string.Empty;
-            if (!string.IsNullOrEmpty(dto.KnowledgeTypeId))
-            {
-                var type = await _knowledgeTypeRepository.GetAsync(x => x.Id == dto.KnowledgeTypeId);
-                typeSpliceName = type?.SpliceName;
-            }
-            if (!string.IsNullOrEmpty(dto.HotspotId))
-            {
-                var hotspot = await _hotspotTypeRepository.GetAsync(x => x.Id == dto.HotspotId);
-                hotspotHotSpotFullName = hotspot?.HotSpotFullName;
-            }
-
-            var sugar = _knowledgeRepository
-                .Queryable(false, false, false)
-                .Includes(x => x.User)
-                .Includes(x => x.SystemOrganize)
-                .Includes(x => x.HotspotType)
-                .Where(x => x.IsDeleted == false)
-                .Where(x => x.Status == EKnowledgeStatus.OnShelf)
-                .Where(x => x.KnowledgeType.Any(t => t.KnowledgeType.KnowledgeTypeOrgs.Any(to => to.OrgId == _sessionContext.RequiredOrgId) || t.KnowledgeType.KnowledgeTypeOrgs.Any() == false))
-                // .WhereIF(dto.RetrievalType == EKnowledgeRetrievalType.All && !string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!) || d.Content.Contains(dto.Keyword!))
-                //.WhereIF(dto.RetrievalType == EKnowledgeRetrievalType.Title && !string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!))
-                //.WhereIF(dto.RetrievalType == EKnowledgeRetrievalType.Content && !string.IsNullOrEmpty(dto.Keyword), d => d.Content.Contains(dto.Keyword!))
-                //.WhereIF(dto.RetrievalType == EKnowledgeRetrievalType.Summary && !string.IsNullOrEmpty(dto.Keyword), d => d.Summary != null && d.Summary.Contains(dto.Keyword!))
-                //.WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => SqlFunc.JsonLike(x.KnowledgeType, typeSpliceName))
-                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith(typeSpliceName)))
-                .WhereIF(!string.IsNullOrEmpty(hotspotHotSpotFullName), x => x.HotspotType.HotSpotFullName.EndsWith(hotspotHotSpotFullName!))
-                .WhereIF(!string.IsNullOrEmpty(dto.HotspotName), x => x.HotspotType.HotSpotFullName.EndsWith(dto.HotspotName!))
-                .WhereIF(!string.IsNullOrEmpty(dto.CreateOrgId), x => x.CreatorOrgId != null && x.CreatorOrgId.EndsWith(dto.CreateOrgId!))
-                .WhereIF(!string.IsNullOrEmpty(dto.Attribution), x => x.Attribution == dto.Attribution!);
-            if (dto.Keyword.NotNullOrEmpty())
-            {
-                var keywords = dto.Keyword!.SplitKeywords();
-                var exp = Expressionable.Create<Knowledge>();
-                foreach (var keyword in keywords)
-                {
-                    if (dto.RetrievalType == EKnowledgeRetrievalType.All)
-                        exp.Or(m => m.Title.Contains(keyword) || m.Content.Contains(keyword));
-                    if (dto.RetrievalType == EKnowledgeRetrievalType.Title)
-                        exp.Or(m => m.Title.Contains(keyword));
-                    if (dto.RetrievalType == EKnowledgeRetrievalType.Content)
-                        exp.Or(m => m.Content.Contains(keyword));
-                    if (dto.RetrievalType == EKnowledgeRetrievalType.Summary)
-                        exp.Or(m => m.Summary != null && m.Summary.Contains(keyword));
-                    if (dto.RetrievalType == EKnowledgeRetrievalType.KeyWord)
-                    {
-                        var keywordEntity = await _knowledgeWordRepository.GetAsync(m => m.Tag == keyword && m.IsEnable == 0);
-                        if (keywordEntity is null) continue;
-                        exp.Or(m => SqlFunc.JsonArrayAny(m.Keywords, keywordEntity.Id));
-                    }
-                }
-                sugar.Where(exp.ToExpression());
-            }
-
-            switch (dto.Sort)
-            {
-                case "2":
-                    sugar = sugar.OrderByDescending(p => p.CollectCount);
-                    break;
-                case "3":
-                    sugar = sugar.OrderByDescending(p => p.CreationTime);
-                    break;
-                default:
-                    sugar = sugar.OrderByDescending(p => p.PageView);
-                    break;
-            }
-            var (total, temp) = await sugar.ToPagedListAsync(dto.PageIndex, dto.PageSize);
-            return new PagedDto<KnowledgeRetrievalDataDto>(total, _mapper.Map<IReadOnlyList<KnowledgeRetrievalDataDto>>(temp));
+            return (await _knowApplication.KnowRetrievalAsync(dto)).ToPaged();
         }
 
         /// <summary>
@@ -707,7 +650,7 @@ namespace Hotline.Api.Controllers
         public async Task<Dictionary<string, dynamic>> GetKnowretrievalBaseData()
         {
             return _baseDataApplication
-                .KnowledgeRetrievalType(new[] { 3, 4})
+                .KnowledgeRetrievalType([3, 4])
                 .Build();
         }
 
@@ -957,7 +900,7 @@ namespace Hotline.Api.Controllers
             dto.DefinitionModuleCode = moduleCode;
             dto.Title = knowledge.Title;
             return await _workflowApplication.StartWorkflowAsync(dto, id, cancellationToken: HttpContext.RequestAborted);
-		}
+        }
         #endregion
 
         #region 知识库词库
@@ -1017,6 +960,7 @@ namespace Hotline.Api.Controllers
             var (total, items) = await _knowledgeWrodRepository.Queryable()
                 .WhereIF(!string.IsNullOrEmpty(dto.Tag), x => x.Tag == dto.Tag!)
                 .WhereIF(!string.IsNullOrEmpty(dto.Classify), x => x.Classify == dto.Classify!)
+                .WhereIF(dto.IsEnable.HasValue, x => x.IsEnable == dto.IsEnable)
                 .WhereIF(!string.IsNullOrEmpty(dto.Synonym), x => x.Synonym != null && x.Synonym.Contains(dto.Synonym!))
                 .OrderByDescending(x => x.CreationTime)
                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);

+ 84 - 42
src/Hotline.Api/Controllers/OldHotlineController.cs

@@ -1,21 +1,32 @@
-using Hotline.Api.Filter;
+using DocumentFormat.OpenXml.Spreadsheet;
+using Fw.Utility.ConfigClient;
+using Hotline.Api.Filter;
 using Hotline.Application.Identity;
 using Hotline.Application.Systems;
 using Hotline.Application.Tools;
+using Hotline.Authentications;
 using Hotline.Caching.Interfaces;
 using Hotline.Caching.Services;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Identity.Accounts;
 using Hotline.Orders;
+using Hotline.Settings;
 using Hotline.Share.Dtos.Identity;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
 using Hotline.Users;
+using IdentityModel;
 using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using MongoDB.Bson.IO;
+using NewRock.Sdk.Extensions;
 using NPOI.POIFS.Macros;
 using SqlSugar;
+using System.Security.Claims;
+using System.Threading;
 using XF.Domain.Authentications;
 using XF.Domain.Entities;
 using XF.Domain.Exceptions;
@@ -35,8 +46,15 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<OrderVisitDetail> _orderVisitedDetailRepository;
         private readonly ISessionContext _sessionContext;
         private readonly IRepository<Account> _accountRepository;
-
-        public OldHotlineController(IIdentityAppService identityAppService, ISystemLogApplication iSystemLogApplication, IOrderRepository orderRepository, IOrderVisitRepository orderVisitRepository, IOrderDelayRepository orderDelayRepository, IOrderScreenRepository orderScreenRepository, IRepository<OrderSendBackAudit> orderSendBackAuditRepository, IRepository<OrderVisitDetail> orderVisitedDetailRepository, ISessionContext sessionContext, IRepository<Account> accountRepository)
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IOrderDomainService _orderDomainService;
+
+        public OldHotlineController(IIdentityAppService identityAppService, ISystemLogApplication iSystemLogApplication,
+            IOrderRepository orderRepository, IOrderVisitRepository orderVisitRepository,
+            IOrderDelayRepository orderDelayRepository, IOrderScreenRepository orderScreenRepository,
+            IRepository<OrderSendBackAudit> orderSendBackAuditRepository, IRepository<OrderVisitDetail> orderVisitedDetailRepository,
+            IRepository<Account> accountRepository, IHttpClientFactory httpClientFactory, ISystemSettingCacheManager systemSettingCacheManager, ISessionContext sessionContext, IOrderDomainService orderDomainService)
         {
             _identityAppService = identityAppService;
             _iSystemLogApplication = iSystemLogApplication;
@@ -46,8 +64,11 @@ namespace Hotline.Api.Controllers
             _orderScreenRepository = orderScreenRepository;
             _orderSendBackAuditRepository = orderSendBackAuditRepository;
             _orderVisitedDetailRepository = orderVisitedDetailRepository;
-            _sessionContext = sessionContext;
             _accountRepository = accountRepository;
+            _httpClientFactory = httpClientFactory;
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _sessionContext = sessionContext;
+            _orderDomainService = orderDomainService;
         }
 
         /// <summary>
@@ -78,24 +99,13 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="dto"></param>
         /// <returns></returns>
-        [AllowAnonymous]
         [HttpGet("ishas_canhandler")]
-        public async Task<bool> IsHasCanHandler([FromQuery] HotlineLoginOldToNewDto dto)
+        public async Task<bool> IsHasCanHandler()
         {
-            try
-            {
-                dto.UserName = RSA.RSADecrypt(dto.UserName, RSA_Create.RSA_PRIVATE_KEY, "PEM");
-            }
-            catch
-            {
-                throw UserFriendlyException.SameMessage("帐号解密失败");
-            }
-
             var isHas = false;
-
-            var (isAdmin, isCenter, user) = await _identityAppService.IsCheckAdmin(dto.UserName);
-            List<string> Roles = user.Roles.Select(x => x.Name).ToList();
-            if (isCenter)
+            var isAdmin = _orderDomainService.IsCheckAdmin();
+            var IsCenter = _sessionContext.OrgIsCenter;
+            if (IsCenter)
             {
                 #region 待办
                 //待办
@@ -103,9 +113,9 @@ namespace Hotline.Api.Controllers
                     .Queryable(hasHandled: false, isAdmin: isAdmin)
                     .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
                         .Where(step => step.ExternalId == d.Id && step.Status != EWorkflowStepStatus.Handled &&
-                                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == user.Id) ||
-                                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == user.OrgId) ||
-                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && Roles.Contains(step.RoleId))))
+                                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
                         .Any())
                     .Includes(d => d.OrderSpecials)
                     .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept && d.Status != EOrderStatus.HandOverToUnAccept)
@@ -150,7 +160,7 @@ namespace Hotline.Api.Controllers
                 #region 回访待办
                 //回访待办
                 isHas = await _orderVisitRepository.Queryable()
-                    .AnyAsync(d => (d.VisitState == EVisitState.WaitForVisit || d.VisitState == EVisitState.NoSatisfiedWaitForVisit) && d.EmployeeId == user.Id);
+                    .AnyAsync(d => (d.VisitState == EVisitState.WaitForVisit || d.VisitState == EVisitState.NoSatisfiedWaitForVisit) && d.EmployeeId == _sessionContext.RequiredUserId);
 
                 //回访待办是否有数据
                 if (isHas)
@@ -185,12 +195,12 @@ namespace Hotline.Api.Controllers
 
                 #region 部门即将超期
                 //部门即将超期
-                isHas = await _orderRepository.Queryable(canView: !isCenter)
+                isHas = await _orderRepository.Queryable(canView: !IsCenter)
                 .AnyAsync(d => SqlFunc.Subqueryable<WorkflowStep>()
                         .Where(step => step.ExternalId == d.Id && step.Status != EWorkflowStepStatus.Handled &&
-                                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == user.Id) ||
-                                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == user.OrgId) ||
-                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && Roles.Contains(step.RoleId))))
+                                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
                         .Any()
                     && d.Status < EOrderStatus.Filed && DateTime.Now > d.NearlyExpiredTime && DateTime.Now < d.ExpiredTime);
 
@@ -205,7 +215,7 @@ namespace Hotline.Api.Controllers
                 //甄别待审批
                 isHas = await _orderScreenRepository.Queryable(hasHandled: !true, isAdmin: isAdmin)
                     .Includes(d => d.Order)
-                    .Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == user.Id).OrderByDescending(sd => sd.AuditTime).Take(1).ToList())
+                    .Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == _sessionContext.RequiredUserId).OrderByDescending(sd => sd.AuditTime).Take(1).ToList())
                     .AnyAsync(d => (d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval || (d.Status == EScreenStatus.SendBack && d.SendBackApply == false)));
 
                 //甄别待审批是否有数据
@@ -219,7 +229,7 @@ namespace Hotline.Api.Controllers
                 //退回待审批
                 isHas = await _orderSendBackAuditRepository.Queryable()
                     .Where(d => d.State == ESendBackAuditState.Apply)
-                    .WhereIF(Roles.Contains("role_sysadmin") == false, x => x.SendBackOrgId == user.Id) // 123 系统管理员;
+                    .WhereIF(_sessionContext.Roles.Contains("role_sysadmin") == false, x => x.SendBackOrgId == _sessionContext.RequiredUserId) // 123 系统管理员;
                     .AnyAsync();
 
                 if (isHas)
@@ -236,9 +246,9 @@ namespace Hotline.Api.Controllers
                     .Queryable(hasHandled: false, isAdmin: isAdmin)
                     .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
                         .Where(step => step.ExternalId == d.Id && step.Status != EWorkflowStepStatus.Handled &&
-                                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == user.Id) ||
-                                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == user.OrgId) ||
-                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && Roles.Contains(step.RoleId))))
+                                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
                         .Any())
                     .Includes(d => d.OrderSpecials)
                     .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept && d.Status != EOrderStatus.HandOverToUnAccept)
@@ -292,7 +302,7 @@ namespace Hotline.Api.Controllers
                 //甄别待审批
                 isHas = await _orderScreenRepository.Queryable(hasHandled: !true, isAdmin: isAdmin)
                     .Includes(d => d.Order)
-                    .Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == user.Id).OrderByDescending(sd => sd.AuditTime).Take(1).ToList())
+                    .Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == _sessionContext.RequiredUserId).OrderByDescending(sd => sd.AuditTime).Take(1).ToList())
                     .AnyAsync(d => (d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval || (d.Status == EScreenStatus.SendBack && d.SendBackApply == false)));
 
                 if (isHas)
@@ -322,7 +332,7 @@ namespace Hotline.Api.Controllers
                 .Includes(x => x.OrderScreens)
                 .Where(x => x.OrderScreens.Any(s => s.Status == EScreenStatus.SendBack && s.SendBackApply == true) || x.OrderScreens.Any(s => (s.Status != EScreenStatus.SendBack && s.SendBackApply != true)) == false)
                 .Where(x => x.OrderVisit.VisitState == EVisitState.Visited && x.OrderVisit.IsCanHandle)
-                .Where(x => x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode == user.OrgId && (
+                .Where(x => x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode == _sessionContext.RequiredUserId && (
                     SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
                     SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
                     SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
@@ -340,7 +350,7 @@ namespace Hotline.Api.Controllers
                 //退回待审批
                 isHas = await _orderSendBackAuditRepository.Queryable()
                     .Where(d => d.State == ESendBackAuditState.Apply)
-                    .WhereIF(Roles.Contains("role_sysadmin") == false, x => x.SendBackOrgId == user.OrgId) // 123 系统管理员;
+                    .WhereIF(_sessionContext.Roles.Contains("role_sysadmin") == false, x => x.SendBackOrgId == _sessionContext.RequiredOrgId) // 123 系统管理员;
                     .AnyAsync();
                 if (isHas)
                 {
@@ -351,13 +361,13 @@ namespace Hotline.Api.Controllers
 
                 #region 部门即将超期
                 //部门即将超期
-                isHas = await _orderRepository.Queryable(canView: !isCenter)
+                isHas = await _orderRepository.Queryable(canView: !IsCenter)
                 .Includes(d => d.OrderDelays)
                 .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
                         .Where(step => step.ExternalId == d.Id && step.Status != EWorkflowStepStatus.Handled &&
-                        ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == user.Id) ||
-                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == user.OrgId) ||
-                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && Roles.Contains(step.RoleId))))
+                        ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
+                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
+                                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
                         .Any())
                     .Where(d => d.Status < EOrderStatus.Filed && DateTime.Now > d.NearlyExpiredTime && DateTime.Now < d.ExpiredTime)
                     .AnyAsync();
@@ -377,7 +387,7 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <returns></returns>
         [HttpGet("get_rsa_loginname")]
-        public async Task<string> GetRASLoginName()
+        public async Task<string> GetRASLoginName([FromQuery]bool IsUrlEncode)
         {
             var account = await _accountRepository.GetAsync(_sessionContext.RequiredUserId,HttpContext.RequestAborted);
             if (account == null)
@@ -386,8 +396,8 @@ namespace Hotline.Api.Controllers
             }
             try
             {
-                string userName = System.Web.HttpUtility.UrlEncode(RSA.RSAEncrypt(account.UserName, RSA_Create.RSA_PUBLIC_KEY, "PEM"));
-                //string userName = RSA.RSAEncrypt(account.UserName, RSA_Create.RSA_PUBLIC_KEY, "PEM");
+                string userName = String.Empty;
+                userName = System.Web.HttpUtility.UrlEncode(RSA.RSAEncrypt(account.UserName, RSA_Create.RSA_PUBLIC_KEY, "PEM"));
                 return userName;
             }
             catch
@@ -395,5 +405,37 @@ namespace Hotline.Api.Controllers
                 throw UserFriendlyException.SameMessage("账号加密失败!");
             }
         }
+
+        /// <summary>
+        /// 获取老系统状态
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("ishas_canhandler_old")]
+        public async Task<bool?> IsHasCanHandlerOld()
+        {
+            var OldHotlineOrderState = _systemSettingCacheManager.GetSetting(SettingConstants.OldHotlineOrderState).SettingValue[0];
+
+            var account = await _accountRepository.GetAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
+            if (account == null)
+            {
+                throw UserFriendlyException.SameMessage("获取用户加密失败");
+            }
+            try
+            {
+                string userName = String.Empty;
+                userName = System.Web.HttpUtility.UrlEncode(RSA.RSAEncrypt(account.UserName, RSA_Create.RSA_PUBLIC_KEY, "PEM"));
+                var client = _httpClientFactory.CreateClient();
+                client.DefaultRequestHeaders.ConnectionClose = true;
+                using var responseMessage = await client.GetAsync(OldHotlineOrderState + userName,HttpContext.RequestAborted);
+                using var respContent = responseMessage.Content;
+                var respContentString = await respContent.ReadAsStringAsync(HttpContext.RequestAborted);
+                var result = Newtonsoft.Json.JsonConvert.DeserializeObject<HotlineStateResult>(respContentString);
+                return result?.Result;
+            }
+            catch (Exception ex)
+            {
+                throw UserFriendlyException.SameMessage("账号加密失败!"+ex.Message);
+            }
+        }
     }
 }

+ 110 - 85
src/Hotline.Api/Controllers/OrderController.cs

@@ -354,7 +354,7 @@ public class OrderController : BaseController
         foreach (var item in dto.Ids)
         {
             var order = await _orderRepository.GetAsync(item, HttpContext.RequestAborted);
-            if (order != null)
+            if (order != null && order.Status == EOrderStatus.Filed)
             {
                 try
                 {
@@ -892,7 +892,7 @@ public class OrderController : BaseController
             .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)
-            .WhereIF(!string.IsNullOrEmpty(dto.EmployeeName), d => !string.IsNullOrEmpty(d.EmployeeId) && d.Employee.Name == dto.EmployeeName)
+            .WhereIF(!string.IsNullOrEmpty(dto.EmployeeName), d => !string.IsNullOrEmpty(d.EmployeeId) && d.Employee.Name.Contains(dto.EmployeeName))
             .WhereIF(dto.IsProvince != null && dto.IsProvince == true, d => d.Order.IsProvince == true)
             .WhereIF(dto.IsProvince != null && dto.IsProvince == false, d => d.Order.IsProvince == false)
             .WhereIF(dto.IsEffectiveAiVisit != null, d => d.IsEffectiveAiVisit == dto.IsEffectiveAiVisit)
@@ -910,6 +910,8 @@ public class OrderController : BaseController
             .WhereIF(dto.IsOverTime == false,
                 d => (d.Order.ExpiredTime > DateTime.Now && d.Order.Status < EOrderStatus.Filed) ||
                      (d.Order.ExpiredTime > d.Order.ActualHandleTime && d.Order.Status >= EOrderStatus.Filed)) //否 超期
+            .WhereIF(dto.StartTime.HasValue, d => d.VisitTime >= dto.StartTime)
+            .WhereIF(dto.EndTime.HasValue, d => d.VisitTime <= dto.EndTime)
             .OrderByDescending(x => x.PublishTime)
             .WhereIF(dto.Channel.NotNullOrEmpty(), d => d.Order.SourceChannelCode == dto.Channel)
             .OrderByDescending(d => d.PublishTime)
@@ -1977,7 +1979,7 @@ public class OrderController : BaseController
             .Where(x => x.OrderScreens.Any(s => s.Status == EScreenStatus.SendBack && s.ScreenType == dto.ScreenType && s.SendBackApply == true) || x.OrderScreens.Any() == false
             //|| x.OrderScreens.Any(s => (s.Status != EScreenStatus.SendBack && s.SendBackApply != true)) == false
             )
-            .WhereIF(dto.ScreenType == EOrderScreenType.Seat , x=>x.OrderVisit.Order.IsProvince == false)
+            .WhereIF(dto.ScreenType == EOrderScreenType.Seat, x => x.OrderVisit.Order.IsProvince == false)
             .WhereIF(dto.ScreenSendBack is 1, x => x.OrderScreens.Any(s => s.Status == EScreenStatus.SendBack && s.ScreenType == dto.ScreenType && s.SendBackApply == true))
             .WhereIF(dto.ScreenSendBack is 2, x => x.OrderScreens.Any(s => (s.Status != EScreenStatus.SendBack && s.ScreenType == dto.ScreenType && s.SendBackApply != true)) == false)
             .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order!.No!.Contains(dto.No!))
@@ -2027,14 +2029,14 @@ public class OrderController : BaseController
             query.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
                     x => x.OrderVisit.Order.Title.Contains(dto.Keyword!) ||
                          x.OrderVisit.Order.No.Contains(dto.Keyword!))
-                .WhereIF(dto.ScreenType == EOrderScreenType.Org,x => x.VisitTarget == EVisitTarget.Org && (
+                .WhereIF(dto.ScreenType == EOrderScreenType.Org, x => x.VisitTarget == EVisitTarget.Org && (
                     SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
                     SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
                     SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
                     SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
                 ))
-                .WhereIF(dto.ScreenType == EOrderScreenType.Seat, x => x.VisitTarget == EVisitTarget.Seat && (x.SeatEvaluate == ESeatEvaluate.VeryNoSatisfied || x.SeatEvaluate  == ESeatEvaluate.NoSatisfied))
-				;
+                .WhereIF(dto.ScreenType == EOrderScreenType.Seat, x => x.VisitTarget == EVisitTarget.Seat && (x.SeatEvaluate == ESeatEvaluate.VeryNoSatisfied || x.SeatEvaluate == ESeatEvaluate.NoSatisfied))
+                ;
         }
 
         var (total, items) = await query
@@ -2060,50 +2062,50 @@ public class OrderController : BaseController
     [HttpGet("screen")]
     public async Task<PagedDto<OrderScreenListDto>> ScreenList([FromQuery] ScreenListDto dto)
     {
-        var (total, items) = await  _orderApplication.OrderScreenList(dto)
+        var (total, items) = await _orderApplication.OrderScreenList(dto)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
         return new PagedDto<OrderScreenListDto>(total, _mapper.Map<IReadOnlyList<OrderScreenListDto>>(items));
     }
 
 
-	/// <summary>
-	/// 工单甄别列表导出
-	/// </summary>
-	/// <returns></returns>
-	[HttpPost("screen_list/_export")]
+    /// <summary>
+    /// 工单甄别列表导出
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("screen_list/_export")]
     public async Task<FileStreamResult> ScreenListExport([FromBody] ExportExcelDto<ScreenListDto> dto)
     {
-	    var query = _orderApplication.OrderScreenList(dto.QueryDto);
-	    List<OrderScreen> data;
-	    if (dto.IsExportAll)
-	    {
-		    data = await query.ToListAsync(HttpContext.RequestAborted);
-	    }
-	    else
-	    {
-		    var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
-		    data = items;
-	    }
+        var query = _orderApplication.OrderScreenList(dto.QueryDto);
+        List<OrderScreen> data;
+        if (dto.IsExportAll)
+        {
+            data = await query.ToListAsync(HttpContext.RequestAborted);
+        }
+        else
+        {
+            var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+            data = items;
+        }
 
-	    var dataDtos = _mapper.Map<ICollection<OrderScreenListDto>>(data);
+        var dataDtos = _mapper.Map<ICollection<OrderScreenListDto>>(data);
 
-	    dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
 
-	    var dtos = dataDtos
-		    .Select(stu => _mapper.Map(stu, typeof(OrderScreenListDto), dynamicClass))
-		    .Cast<object>()
-		    .ToList();
+        var dtos = dataDtos
+            .Select(stu => _mapper.Map(stu, typeof(OrderScreenListDto), dynamicClass))
+            .Cast<object>()
+            .ToList();
 
-	    var stream = ExcelHelper.CreateStream(dtos);
+        var stream = ExcelHelper.CreateStream(dtos);
 
-	    return ExcelStreamResult(stream, "工单甄别列表数据");
+        return ExcelStreamResult(stream, "工单甄别列表数据");
     }
 
 
-	/// <summary>
-	/// 开始工单甄别流程
-	/// </summary>
-	[Permission(EPermission.ApplyScreen)]
+    /// <summary>
+    /// 开始工单甄别流程
+    /// </summary>
+    [Permission(EPermission.ApplyScreen)]
     [HttpPost("screen/startflow")]
     [LogFilter("开始工单甄别流程")]
     public async Task StartFlow([FromBody] StartWorkflowDto<OrderScreenDto> dto)
@@ -2145,6 +2147,18 @@ public class OrderController : BaseController
             }
         }
 
+        var order = await _orderRepository.GetAsync(dto.Data.OrderId, HttpContext.RequestAborted);
+        if (order.IsProvince)
+        {
+            var orderAny = await _orderScreenRepository.AnyAsync(x =>
+                x.OrderId == dto.Data.OrderId &&
+                (x.Status == EScreenStatus.Apply || x.Status == EScreenStatus.Approval));
+            if (orderAny)
+            {
+                throw UserFriendlyException.SameMessage("当前工单为省工单,已发起甄别申请,请等待省上返回结果!");
+            }
+        }
+
         var model = _mapper.Map<OrderScreen>(dto.Data);
         model.Status = EScreenStatus.Approval;
         model.ApplyEndTime = endTime;
@@ -2290,7 +2304,7 @@ public class OrderController : BaseController
         {
             ScreenStatus = EnumExts.GetDescriptions<EScreenStatus>(),
             OrderScreenType = EnumExts.GetDescriptions<EOrderScreenType>(),
-			ScreenType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.ScreenType),
+            ScreenType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.ScreenType),
             CounterSignType = EnumExts.GetDescriptions<ECounterSignType>(),
             AcceptType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
             SourceChannel = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.SourceChannel),
@@ -2803,7 +2817,7 @@ public class OrderController : BaseController
     public async Task<IReadOnlyList<OrderDto>> QueryFixed([FromQuery] QueryOrderFixedDto dto)
     {
         var hasSetting = Int32.TryParse(
-            _systemSettingCacheManager.GetSetting(SettingConstants.FixedQueryCount)?.SettingValue[0], out var queryCount);
+         _systemSettingCacheManager.GetSetting(SettingConstants.FixedQueryCount)?.SettingValue[0], out var queryCount);
         var query = _orderApplication.QueryOrders(dto);
         var orders = await query.ToFixedListAsync(dto.QueryIndex, hasSetting ? queryCount : null, HttpContext.RequestAborted);
         return _mapper.Map<IReadOnlyList<OrderDto>>(orders);
@@ -2823,7 +2837,7 @@ public class OrderController : BaseController
     public async Task<FileStreamResult> ExportOrders([FromBody] ExportExcelDto<QueryOrderDto> dto)
     {
         var query = _orderApplication.QueryOrders(dto.QueryDto);
-        List<Orders.Order> orders;
+        List<Order> orders;
         if (dto.IsExportAll)
         {
             orders = await query.ToListAsync(HttpContext.RequestAborted);
@@ -2917,8 +2931,8 @@ public class OrderController : BaseController
             .Includes(d => d.OrderPublish)
             .Includes(d => d.OrderPushTypes)
             //.Includes(d => d.OrderScreens)
-            .Includes(d => d.OrderVisits, x => x.OrderVisitDetails)
-            .Includes(d => d.OrderVisits, x => x.Employee)
+            .Includes(d => d.OrderVisits.Where(x => x.VisitState == EVisitState.Visited).ToList(), x => x.OrderVisitDetails)
+            .Includes(d => d.OrderVisits.Where(x => x.VisitState == EVisitState.Visited).ToList(), x => x.Employee)
             .FirstAsync(d => d.Id == id);
         if (order == null) return new();
 
@@ -2954,8 +2968,6 @@ public class OrderController : BaseController
                     {
                         canInsteadHandle = false;
                     }
-
-                    ;
                 }
 
                 if (canInsteadHandle)
@@ -3291,7 +3303,7 @@ public class OrderController : BaseController
         //copy.AuditUserName = _sessionContext.UserName;
         //copy.InitId();
         //await _orderCopyRepository.AddAsync(copy, HttpContext.RequestAborted);
-        return new { Id = order.Id, No = order.No, Password = order.Password };
+        return new { Id = order.Id, No = order.No, Password = order.Password, CallId = order.CallId };
     }
 
     /// <summary>
@@ -3366,8 +3378,11 @@ public class OrderController : BaseController
             .FirstAsync(d => d.Id == dto.Id);
         if (order == null)
             throw UserFriendlyException.SameMessage("无效工单编号");
-        if (dto.IsEdit != true && order.Status > EOrderStatus.HandOverToUnAccept)
-            throw UserFriendlyException.SameMessage("工单已发起流程,不可编辑");
+        if (_appOptions.Value.IsZiGong != true)
+        {
+            if (dto.IsEdit != true && order.Status > EOrderStatus.HandOverToUnAccept)
+                throw UserFriendlyException.SameMessage("工单已发起流程,不可编辑");
+        }
 
         // 副本工单
         var copy = new OrderCopy();
@@ -3467,7 +3482,7 @@ public class OrderController : BaseController
         //敏感分词
         await _orderApplication.OrderSensitiveParticiple(dto.Content, order.Id, HttpContext.RequestAborted);
 
-        return new { Id = order.Id, No = order.No, Password = order.Password };
+        return new { Id = order.Id, No = order.No, Password = order.Password , CallId = order.CallId};
     }
 
     /// <summary>
@@ -3515,10 +3530,10 @@ public class OrderController : BaseController
         }
         order.IsForwarded = dto.Workflow.IsForwarded;
         _mapper.Map(expiredTimeConfig, order);
-        
+
         if (dto.Workflow.BusinessType is EBusinessType.Department or EBusinessType.DepartmentLeader)
             order.ProcessType = EProcessType.Jiaoban;
-        
+
         await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted);
 
         try
@@ -3562,12 +3577,12 @@ public class OrderController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpGet("startflow")]
-    public async Task<NextStepsDto> GetFlowStartOptions([FromQuery]string? orderId)
+    public async Task<NextStepsDto> GetFlowStartOptions([FromQuery] string? orderId)
     {
         var outDto = await _workflowApplication.GetStartStepsAsync(WorkflowModuleConsts.OrderHandle,
             HttpContext.RequestAborted);
         if (orderId.NotNullOrEmpty())
-        { 
+        {
             outDto.Opinion = await _typeCache.GetAsync($"tmp_opinion_{orderId}{_sessionContext.UserId}", HttpContext.RequestAborted);
         }
         return outDto;
@@ -3613,11 +3628,11 @@ public class OrderController : BaseController
                 var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
                 if (averageSendOrder)
                 {
-	                if (!dto.Workflow.NextHandlers.Any())
-	                {
-						var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
-						dto.Workflow.NextHandlers = new List<FlowStepHandler> { handler };
-					}
+                    if (!dto.Workflow.NextHandlers.Any())
+                    {
+                        var handler = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
+                        dto.Workflow.NextHandlers = new List<FlowStepHandler> { handler };
+                    }
                 }
             }
         }
@@ -3651,10 +3666,11 @@ public class OrderController : BaseController
                     .SettingValue[0]);
                 if (workflowDto.BusinessType == EBusinessType.Send && averageSendOrder)
                 {
-                    if (!nextDto.NextHandlers.Any()) {
-						var handler = await _orderDomainService.AverageOrder(cancellationToken);
-						nextDto.NextHandlers = new List<FlowStepHandler> { handler };
-					}
+                    if (!nextDto.NextHandlers.Any())
+                    {
+                        var handler = await _orderDomainService.AverageOrder(cancellationToken);
+                        nextDto.NextHandlers = new List<FlowStepHandler> { handler };
+                    }
                 }
 
                 await _workflowDomainService.NextAsync(_sessionContext, nextDto, order.ExpiredTime, cancellationToken);
@@ -3666,7 +3682,7 @@ public class OrderController : BaseController
                 orderHandleFlowDto.CrossSteps = orderHandleFlowDto.CrossSteps.OrderBy(d => d.Sort).ToList();
                 var stepCount = orderHandleFlowDto.CrossSteps.Count;
                 var unhandleSteps = new List<WorkflowStep> { startStep };
-                for (int i = 0; i < stepCount; i++)
+                for (int i = 0;i < stepCount;i++)
                 {
                     var crossStep = orderHandleFlowDto.CrossSteps[i];
                     var tempSteps = new List<WorkflowStep>();
@@ -3679,7 +3695,8 @@ public class OrderController : BaseController
                         nextflowDto.StepId = unhandleStep.Id;
                         nextflowDto.IsStartCountersign = lowerLevelHandlers.Count > 1;
                         nextflowDto.NextHandlers = lowerLevelHandlers;
-                        nextflowDto.Opinion = "跨级派单,自动办理";
+                        if (unhandleStep.Id != startStep.Id)
+                            nextflowDto.Opinion = "跨级派单,自动办理";
 
                         var operater = new FakeSessionContext
                         {
@@ -3741,11 +3758,11 @@ public class OrderController : BaseController
 
         rsp.TranspondCity = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.TranspondCity)
             .ToList().Adapt<List<SystemDicDataOutDto>>();
+
+        rsp.CounterSignType = order.CounterSignType;
         return rsp;
     }
 
-
-
     /// <summary>
     /// 临时保存
     /// </summary>
@@ -3841,7 +3858,7 @@ public class OrderController : BaseController
             CurrentStepOptions = definition?.Steps.Select(x => new Kv(x.Code, x.Name)),
             IdentityTypeOptions = EnumExts.GetDescriptions<EIdentityType>(),
             OrderTags = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.OrderTag)
-		};
+        };
         return rsp;
     }
 
@@ -3971,19 +3988,19 @@ public class OrderController : BaseController
         var query = _orderRepository
             .Queryable(hasHandled: isHandled, isAdmin: isAdmin)
             .Includes(d => d.OrderSpecials);
-        if (dto.QueryType is 1  || dto.QueryType is 2)
-		{
+        if (dto.QueryType is 1 || dto.QueryType is 2)
+        {
             query.WhereIF(dto.QueryType is 1, d => d.IsForwarded == false)
-	            .WhereIF(dto.QueryType is 2, d => d.IsForwarded == true)
-				.Where(d=>SqlFunc.Subqueryable<OrderSpecial>().Where(os=>os.OrderId == d.Id && os.SpecialType == ESpecialType.ReTransact).NotAny());
+                .WhereIF(dto.QueryType is 2, d => d.IsForwarded == true)
+                .Where(d => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == d.Id && os.SpecialType == ESpecialType.ReTransact).NotAny());
         }
 
-		var (total, items) = await query
-			.Where(d => d.Status != EOrderStatus.WaitForAccept &&
+        var (total, items) = await query
+            .Where(d => d.Status != EOrderStatus.WaitForAccept &&
                         d.Status != EOrderStatus.BackToUnAccept &&
                         d.Status != EOrderStatus.SpecialToUnAccept &&
                         d.Status != EOrderStatus.HandOverToUnAccept)
-		    .WhereIF(dto.QueryType is 3, d => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == d.Id && os.SpecialType == ESpecialType.ReTransact).Any())
+            .WhereIF(dto.QueryType is 3, d => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == d.Id && os.SpecialType == ESpecialType.ReTransact).Any())
             .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword))
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
@@ -4512,7 +4529,7 @@ public class OrderController : BaseController
         var order = await _orderDomainService.GetOrderAsync(orderId, cancellationToken: HttpContext.RequestAborted);
         if (string.IsNullOrEmpty(order.WorkflowId))
             throw UserFriendlyException.SameMessage("该工单未开启流程");
-        
+
         var (currentStep, prevStep, isOrgToCenter, isSecondToFirstOrgLevel) = await _workflowApplication.GetPreviousInformationAsync(
             order.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, _sessionContext.Roles, HttpContext.RequestAborted);
 
@@ -4522,7 +4539,7 @@ public class OrderController : BaseController
             TargetBusinessType = prevStep.BusinessType
         };
     }
-    
+
     #endregion
 
     #region 省工单退回
@@ -4732,6 +4749,11 @@ public class OrderController : BaseController
             throw UserFriendlyException.SameMessage("该工单存在正在审核中的退回,不能办理");
         }
 
+        if (_appOptions.Value.IsZiGong && string.IsNullOrEmpty(dto.Cause))
+        {
+            dto.Cause = dto.Reason;
+        }
+
         var order = await _orderRepository
             .Queryable()
             .Includes(d => d.Workflow)
@@ -5008,7 +5030,7 @@ public class OrderController : BaseController
             //todo
             //宜宾需求:1. 坐席申请特提:指派给申请人办理 2. 派单员申请特提:所有派单员都能办 3.其他特提场景:按节点原配置办理
 
-            var processType = dto.FlowDirection == EFlowDirection.OrgToCenter || dto.FlowDirection == EFlowDirection.CenterToCenter
+            var processType = dto.FlowDirection is EFlowDirection.OrgToCenter or EFlowDirection.CenterToCenter or EFlowDirection.FiledToCenter
                 ? EProcessType.Zhiban
                 : EProcessType.Jiaoban;
             await _workflowApplication.RecallAsync(recall, endTime, order.Status >= EOrderStatus.Filed, EWorkflowTraceType.Redo,
@@ -5456,18 +5478,21 @@ public class OrderController : BaseController
         if (order == null) throw UserFriendlyException.SameMessage("无效工单信息!");
 
         var baseTypeId = string.Empty;
-        if (step != null && step.Steps.Any() && _sessionContext.Roles.Contains("zuoxi") && specialSeats &&
-            !_sessionContext.Roles.Contains("paidanyuan"))
+        if (!_sessionContext.Roles.Contains("banzhang"))
         {
-            step.Steps = step.Steps.Where(x => x.Key.ToLower() == "start").ToList();
-            if (step.Steps.Any()) baseTypeId = step.Steps[0].Key;
-        }
+            if (step != null && step.Steps.Any() && _sessionContext.Roles.Contains("zuoxi") && specialSeats &&
+                !_sessionContext.Roles.Contains("paidanyuan"))
+            {
+                step.Steps = step.Steps.Where(x => x.Key.ToLower() == "start").ToList();
+                if (step.Steps.Any()) baseTypeId = step.Steps[0].Key;
+            }
 
-        if (step != null && step.Steps.Any() && _sessionContext.Roles.Contains("paidanyuan") && specialSendOrder &&
-            !_sessionContext.Roles.Contains("zuoxi"))
-        {
-            step.Steps = step.Steps.Where(d => d.BusinessType is EBusinessType.Send).ToList();
-            if (step.Steps.Any()) baseTypeId = step.Steps[0].Key;
+            if (step != null && step.Steps.Any() && _sessionContext.Roles.Contains("paidanyuan") && specialSendOrder &&
+                !_sessionContext.Roles.Contains("zuoxi"))
+            {
+                step.Steps = step.Steps.Where(d => d.BusinessType is EBusinessType.Send).ToList();
+                if (step.Steps.Any()) baseTypeId = step.Steps[0].Key;
+            }
         }
 
         var rsp = new
@@ -6361,7 +6386,7 @@ public class OrderController : BaseController
                 i++;
                 try
                 {
-                    var validationResult = item.ValidateObject();
+                    var validationResult = item.ValidateObject(false);
                     if (validationResult.NotNullOrEmpty())
                     {
                         errorMessage.Append($"第{i + 1}行: {validationResult}\r\n");

+ 42 - 34
src/Hotline.Api/Controllers/OrderRevocationController.cs

@@ -101,6 +101,40 @@ namespace Hotline.Api.Controllers
                         var id = await _orderRevocationRepository.AddAsync(orderRevocation, HttpContext.RequestAborted);
                         if (!string.IsNullOrEmpty(id))
                         {
+                            #region 处理短信业务
+                            //如果需要发短信、处理短信业务
+                            if (dto.IsSendSms && !string.IsNullOrEmpty(order.WorkflowId))
+                            {  //查询当前工单的实际办理节点,如果在热线中心不处理,如果在部门需要更新期满时间
+                                var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withSteps: true, withTraces: true, cancellationToken: HttpContext.RequestAborted);
+                                var nowWorkflow = workflow.Steps.Where(p => p.Id == order.ActualHandleStepId && p.BusinessType >= EBusinessType.Department && p.BusinessType <= EBusinessType.DepartmentLeader).FirstOrDefault();
+                                //在部门才需要发送短信
+                                if (nowWorkflow != null && order.CenterToOrgTime.HasValue)
+                                {
+                                    //处理短信业务
+                                    var acceptSmsRoleIds = _systemSettingCacheManager.GetSetting(SettingConstants.AcceptSmsRoleIds)?.SettingValue;
+                                    //查询部门所有账号
+                                    var userlist = await _userRepository.Queryable().Where(x =>
+                                        x.OrgId == order.CurrentHandleOrgId && !string.IsNullOrEmpty(x.PhoneNo) &&
+                                        x.Roles.Any(d => acceptSmsRoleIds.Contains(d.Id))).ToListAsync();
+                                    //发送短信
+                                    foreach (var user in userlist)
+                                    {
+                                        var messageDto = new Share.Dtos.Push.MessageDto
+                                        {
+                                            PushBusiness = EPushBusiness.OrderRevocationSms,
+                                            PushPlatform = EPushPlatform.Sms,
+                                            Name = user.Name,
+                                            TemplateCode = "1016",
+                                            Params = new List<string>() { order.No },
+                                            TelNumber = user.PhoneNo,
+                                        };
+                                        await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
+                                    }
+                                }
+
+                            }
+                            #endregion
+
                             #region 处理流程业务
                             //处理流程业务
                             //如果开启了流程直接归档,如果没开启流程,开启流程到归档
@@ -130,6 +164,9 @@ namespace Hotline.Api.Controllers
                             order.ActualHandleOrgName = OrgSeedData.CenterName;
                             order.OrgLevelOneCode = OrgSeedData.CenterId;
                             order.OrgLevelOneName = OrgSeedData.CenterName;
+                            order.ActualHandlerName = _sessionContext.UserName;
+                            order.ActualHandleTime = DateTime.Now;
+                            order.ActualHandlerId = _sessionContext.UserId;
 
                             await _orderRepository.Updateable(order).UpdateColumns(it => new
                             {
@@ -138,43 +175,14 @@ namespace Hotline.Api.Controllers
                                 it.OrgLevelOneCode,
                                 it.OrgLevelOneName,
                                 it.ActualHandleOrgAreaCode,
-                                it.ActualHandleOrgAreaName
+                                it.ActualHandleOrgAreaName,
+                                it.ActualHandlerName,
+                                it.ActualHandleTime,
+                                it.ActualHandlerId
                             }).ExecuteCommandAsync();
                             #endregion
 
-                            #region 处理短信业务
-                            //如果需要发短信、处理短信业务
-                            if (dto.IsSendSms && !string.IsNullOrEmpty(order.WorkflowId))
-                            {  //查询当前工单的实际办理节点,如果在热线中心不处理,如果在部门需要更新期满时间
-                                var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withSteps: true, withTraces: true, cancellationToken: HttpContext.RequestAborted);
-                                var nowWorkflow = workflow.Steps.Where(p => p.Id == order.ActualHandleStepId && p.BusinessType >= EBusinessType.Department && p.BusinessType <= EBusinessType.DepartmentLeader).FirstOrDefault();
-                                //在部门才需要发送短信
-                                if (nowWorkflow != null && order.CenterToOrgTime.HasValue)
-                                {
-                                    //处理短信业务
-                                    var acceptSmsRoleIds = _systemSettingCacheManager.GetSetting(SettingConstants.AcceptSmsRoleIds)?.SettingValue;
-                                    //查询部门所有账号
-                                    var userlist = await _userRepository.Queryable().Where(x =>
-                                        x.OrgId == order.CurrentHandleOrgId && !string.IsNullOrEmpty(x.PhoneNo) &&
-                                        x.Roles.Any(d => acceptSmsRoleIds.Contains(d.Id))).ToListAsync();
-                                    //发送短信
-                                    foreach (var user in userlist)
-                                    {
-                                        var messageDto = new Share.Dtos.Push.MessageDto
-                                        {
-                                            PushBusiness = EPushBusiness.OrderRevocationSms,
-                                            PushPlatform = EPushPlatform.Sms,
-                                            Name = user.Name,
-                                            TemplateCode = "1016",
-                                            Params = new List<string>() { order.No },
-                                            TelNumber = user.PhoneNo,
-                                        };
-                                        await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
-                                    }
-                                }
-
-                            }
-                            #endregion
+                           
 
                             successNum++;
                         }

+ 17 - 6
src/Hotline.Api/Controllers/PushMessageController.cs

@@ -10,8 +10,10 @@ using Hotline.Share.Dtos.Push.FWMessage;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Mq;
 using Hotline.Share.Tools;
+using Hotline.Tools;
 using Hotline.Users;
 using J2N.Text;
+using JiebaNet.Segmenter.Common;
 using MapsterMapper;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
@@ -131,14 +133,23 @@ namespace Hotline.Api.Controllers
         [HttpPost("send")]
         public async Task<string> SendMessage([FromBody] MessageInDto dto)
         {
-            var telNumbers = dto.TelNumbers.Split(",");
+            dto.ValidateObject();
+            if (dto.TelNumbers == null && dto.UserIds == null)
+                throw UserFriendlyException.SameMessage("电话号码和用户Id不能同时为空");
+            var telNumbers = new List<Kv>();
+            if (dto.TelNumbers.NotNullOrEmpty())
+                telNumbers = dto.TelNumbers.Split(",").Select(m => new Kv(m, m)).ToList();
+            if (dto.UserIds.IsNotEmpty())
+                telNumbers = await _userRepository.Queryable()
+                    .Where(m => dto.UserIds.Contains(m.Id))
+                    .Select(m => new Kv { Key = m.PhoneNo, Value = m.Name })
+                    .ToListAsync();
             var count = 0;
             var errorSB = new StringBuilder("失败原因: ");
             var errorCount = 0;
-            for (int i = 0;i < telNumbers.Length;i++)
+            foreach (var telNumber in telNumbers)
             {
-                var telNumber = telNumbers[i];
-                if (telNumber.IsPhoneNumber() == false)
+                if (telNumber.Key.IsPhoneNumber() == false)
                 {
                     errorCount++;
                     errorSB.Append($" {telNumber}不是合法的手机号码.");
@@ -147,10 +158,10 @@ namespace Hotline.Api.Controllers
 
                 var inDto = new MessageDto
                 {
-                    TelNumber = telNumber,
+                    TelNumber = telNumber.Key,
                     Content = dto.Content,
                     PushBusiness = EPushBusiness.ManualSms,
-                    Name = "",
+                    Name = telNumber.Value,
                 };
                 try
                 {

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

@@ -10,6 +10,7 @@ using Hotline.Share.Dtos.Menu;
 using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Requests;
+using Hotline.Share.Tools;
 using MapsterMapper;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
@@ -594,6 +595,5 @@ namespace Hotline.Api.Controllers
         {
             return await _systemMobilAreaApplication.VaildMobile(mobile);
         }
-
     }
 }

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

@@ -4,8 +4,10 @@ using Hotline.Article;
 using Hotline.Caching.Interfaces;
 using Hotline.Orders;
 using Hotline.Push.Notifies;
+using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.WebPortal;
@@ -60,7 +62,7 @@ namespace Hotline.Api.Controllers
            ISessionContext sessionContext,
             IRepository<OrderVisitDetail> orderVisitDetailRepository,
             IBulletinApplication bulletinApplication,
-             IRepository<OldPublicData> oldPublicDataRepository
+            IRepository<OldPublicData> oldPublicDataRepository
             )
         {
             _mapper = mapper;
@@ -648,6 +650,79 @@ namespace Hotline.Api.Controllers
             return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(returnDto, "成功"));
         }
 
+        /// <summary>
+        /// 查询工单发布后公开的数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("get_order_list_publish_all")]
+        [AllowAnonymous]
+        public async Task<OpenResponse> GetOrderByListAllOpen([FromBody] QueryOrderListDto dto)
+        {
+            var queryNew = _orderRepository.Queryable()
+                .LeftJoin<OrderPublish>((p,op)=>p.Id==op.OrderId)
+                .Where(p => p.IsPublicity == true)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowCode), (p, op) => p.No == dto.FlowCode)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowName), (p, op) => p.Title.Contains(dto.FlowName))
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowSType), (p, op) => p.AcceptTypeCode == dto.FlowSType)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowRType), (p, op) => p.HotspotId == dto.FlowRType)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowSDate), (p, op) => p.StartTime >= DateTime.Parse(DateTime.Parse(dto.FlowSDate).ToString("yyyy-MM-dd 00:00:00")))//dto.FlowSDate
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowEDate), (p, op) => p.StartTime <= DateTime.Parse(DateTime.Parse(dto.FlowEDate).ToString("yyyy-MM-dd 00:00:00")))// dto.FlowEDate
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowFrom), (p, op) => p.FromName.Contains(dto.FlowFrom))
+                .OrderByDescending((p, op) => p.CreationTime)
+               .Select((p, op) => new OrderListDto
+               {
+                   FlowID = p.Id,
+                   FlowCode = p.No,
+                   FlowPwd = p.Password,
+                   FlowTitle = p.Title,
+                   FlowFromName = p.SourceChannel,
+                   FlowPurTypeName = p.AcceptType,
+                   ConTypeName = p.HotspotName,
+                   FlowAddDate = p.CreationTime,
+                   PubDate = op.CreationTime,
+                   RSFlagName = p.Status >= EOrderStatus.Filed ? "办理完成" : "办理中"
+               });
+
+            var queryold = _oldPublicDataRepository.Queryable()
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowCode), p => p.OrderNo == dto.FlowCode)
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowName), p => p.Title.Contains(dto.FlowName))
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowSType), p => p.AcceptTypeCode == dto.FlowSType)
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowRType), p => p.HotspotId == dto.FlowRType)
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowSDate), p => p.AcceptTime >= DateTime.Parse(DateTime.Parse(dto.FlowSDate).ToString("yyyy-MM-dd 00:00:00")))//dto.FlowSDate
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowEDate), p => p.AcceptTime <= DateTime.Parse(DateTime.Parse(dto.FlowEDate).ToString("yyyy-MM-dd 00:00:00")))// dto.FlowEDate
+                      .WhereIF(!string.IsNullOrEmpty(dto.FlowFrom), p => p.FromName.Contains(dto.FlowFrom))
+                      .OrderByDescending(p => p.PubDate)
+                        .Select(p => new OrderListDto
+                        {
+                            FlowID = p.OrderId,
+                            FlowCode = p.OrderNo,
+                            FlowPwd = p.OrderPwd,
+                            FlowTitle = p.Title,
+                            FlowFromName = p.SourceChannel,
+                            FlowPurTypeName = p.AcceptType,
+                            ConTypeName = p.HotspotName,
+                            FlowAddDate = p.CreationTime,
+                            PubDate = p.CreationTime,
+                            RSFlagName = p.State == "1" ? "办理完成" : "办理中"
+                        });
+
+            var (total, items) = await _orderRepository.UnionAll(queryNew, queryold)
+                .OrderByDescending(p => p.PubDate)
+                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
+            //计算总页数
+            int nPageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(total) / dto.PageSize));
+            OrderListReturnDto returnDto = new()
+            {
+                PageNum = dto.PageIndex,
+                PageCount = nPageCount,
+                Data = _mapper.Map<IReadOnlyList<OrderListDto>>(items)
+            };
+
+            return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(returnDto, "成功"));
+        }
+
         /// <summary>
         /// 办件摘编详情
         /// </summary>

+ 8 - 0
src/Hotline.Api/Hotline.Api.csproj

@@ -7,6 +7,13 @@
     <GenerateDocumentationFile>True</GenerateDocumentationFile>
     <NoWarn>$(NoWarn);1591;8618;1803;</NoWarn>
   </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Remove="logs\**" />
+    <Content Remove="logs\**" />
+    <EmbeddedResource Remove="logs\**" />
+    <None Remove="logs\**" />
+  </ItemGroup>
   
   <ItemGroup>
     <PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
@@ -25,6 +32,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Hotline.Application\Hotline.Application.csproj" />
+    <ProjectReference Include="..\Hotline.Logger\Hotline.Logger.csproj" />
   </ItemGroup>
 
   <ItemGroup>

+ 10 - 3
src/Hotline.Api/StartupExtensions.cs

@@ -32,6 +32,8 @@ using Hotline.Share.Tools;
 using Hotline.Settings.TimeLimitDomain.ExpireTimeSupplier;
 using Hotline.Api.Middleware;
 using XF.Domain.Authentications;
+using Hotline.XingTang;
+using Hotline.Logger;
 
 namespace Hotline.Api;
 
@@ -165,6 +167,7 @@ internal static class StartupExtensions
                 break;
             case AppDefaults.CallCenterType.XingTang:
                 services.AddXingTangDb(callCenterConfiguration.XingTang)
+                    .AddXingTangSDK()
                     .AddScoped<ICallApplication, XingTangCallApplication>()
                     .AddScoped<CallIdManager>()
                     ;
@@ -228,9 +231,13 @@ internal static class StartupExtensions
         app.MapControllers()
             .RequireAuthorization();
         //app.MapSubscribeHandler();
-        
-        // 为特定返回结果添加 头部信息 的中间件
-        app.UseMiddleware<HeaderMiddleware>(); 
+
+        // 记录交互日志
+        var isAccLog = app.Configuration.GetSection("AccLog").Get<bool>();
+        if (isAccLog)
+        {
+            app.UseRequestResponseLogging();
+        }
         return app;
     }
 }

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

@@ -3,6 +3,7 @@
   "AppConfiguration": {
     "AppScope": "ZiGong",
     "YiBin": {
+      "AreaCode": "511500",
       "CallCenterType": "TianRun", //XunShi、WeiErXin、TianRun、XingTang
       //智能回访
       "AiVisit": {
@@ -24,9 +25,11 @@
       }
     },
     "ZiGong": {
+      "AreaCode": "510300",
       "CallCenterType": "XingTang"
     },
     "LuZhou": {
+      "AreaCode": "510500",
       "CallCenterType": "XingTang"
     }
   },
@@ -59,15 +62,16 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
+    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
   },
   "Cache": {
     "Host": "110.188.24.182",
     "Port": 50179,
     "Password": "fengwo123!$!$",
-    "Database": 5 //release:3, dev:5
+    "Database": 3 //release:3, dev:5
   },
   "Swagger": true,
+  "AccLog":  true,
   "Cors": {
     "Origins": [ "http://localhost:8888", "http://admin.hotline.fw.com", "http://localhost:80", "http://localhost:8113" ]
   },

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
src/Hotline.Api/logs/20241028-log.txt


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 4 - 0
src/Hotline.Api/logs/acc-log-20241028.txt


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
src/Hotline.Api/logs/acc-log20241028.txt


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
src/Hotline.Api/logs/log20241028.txt


+ 4 - 0
src/Hotline.Application.Contracts/Validators/FlowEngine/NextWorkflowDtoValidator.cs

@@ -14,6 +14,10 @@ namespace Hotline.Application.Contracts.Validators.FlowEngine
         {
             Include(new BasicWorkflowDtoValidator());
             RuleFor(d => d.WorkflowId).NotEmpty();
+            RuleFor(d => d.Opinion)
+                .Cascade(CascadeMode.Stop)
+                .NotEmpty()
+                .MaximumLength(2000);
             //RuleFor(d=>d.ExpiredTime).NotEmpty();
             //RuleFor(d=>d.NextStepCode).NotEmpty();
         }

+ 122 - 1
src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs

@@ -5,8 +5,129 @@ namespace Hotline.Application.Contracts.Validators.Order;
 
 public class AddOrderDtoValidator : AbstractValidator<AddOrderDto>
 {
+    //新增验证
     public AddOrderDtoValidator()
     {
         RuleFor(d => d.Content).NotEmpty().WithMessage("请填写工单内容");
+
+        #region 医疗服务中心受理单
+
+        RuleFor(d => d.OrderExtension.MedName).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("医院名称最多200字符");
+        RuleFor(d => d.OrderExtension.MedAddress).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("医院地址最多500字符");
+        RuleFor(d => d.OrderExtension.MedDepartment).MaxLengthWithChineseChar(100).When(d => d.OrderExtension != null).WithMessage("科室最多100字符");
+        RuleFor(d => d.OrderExtension.MedDoctor).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("医生最多50字符");
+        RuleFor(d => d.OrderExtension.MedPatient).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("病人姓名最多200字符");
+        RuleFor(d => d.OrderExtension.MedNo).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("就诊卡号最多50字符");
+        RuleFor(d => d.OrderExtension.MedIdNo).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("身份证号最多50字符");
+        #endregion
+
+        #region 电视购物及商铺购买退换货中心受理单
+        RuleFor(d => d.OrderExtension.ExchTv).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("电视台(商铺)最多50字符");
+        RuleFor(d => d.OrderExtension.ExchProduct).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("产品名称最多200字符");
+        RuleFor(d => d.OrderExtension.ExchName).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("商家名称最多200字符");
+        RuleFor(d => d.OrderExtension.ExchAddress).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("商家地址最多500字符");
+        RuleFor(d => d.OrderExtension.ExchConsignee).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("收货人最多200字符");
+        RuleFor(d => d.OrderExtension.ExchConsigneeAddress).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("收货地址最多200字符");
+        #endregion
+
+        #region 高速公路投诉中心受理单
+        RuleFor(d => d.OrderExtension.ExpwyEntrance).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("高速入口最多200字符");
+        RuleFor(d => d.OrderExtension.ExpwyExit).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("高速出口最多200字符");
+        RuleFor(d => d.OrderExtension.ExpwyNo).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("高速入口最多50字符");
+        #endregion
+
+        #region 电视台虚假广告中心受理单
+        RuleFor(d => d.OrderExtension.AdTv).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("电视台最多50字符");
+        RuleFor(d => d.OrderExtension.AdProduct).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("产品名称最多200字符");
+        #endregion
+
+        #region 四川政务服务网技术中心受理单
+
+        RuleFor(d => d.OrderExtension.ZwfwwAccount).MaxLengthWithChineseChar(20).When(d => d.OrderExtension != null).WithMessage("服务账号最多20字符");
+        RuleFor(d => d.OrderExtension.ZwfwwContact).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("联系方式最多50字符");
+        #endregion
+
+        #region 四川省12366热线诉求交办单
+        RuleFor(d => d.OrderExtension.Location12366Sq).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("主管税务机关最多500字符");
+        RuleFor(d => d.OrderExtension.Info12366Sq).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("被投诉单位或个人信息最多200字符");
+        RuleFor(d => d.OrderExtension.Type12366Sq).MaxLengthWithChineseChar(10).When(d => d.OrderExtension != null).WithMessage("投诉类型最多10字符");
+        #endregion
+
+        #region 四川省12366热线咨询交办单
+        RuleFor(d => d.OrderExtension.Location12366Zx).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("主管税务机关最多500字符");
+        RuleFor(d => d.OrderExtension.Info12366Zx).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("被投诉单位或个人信息最多200字符");
+        #endregion
+
+        #region 12328服务监督中心受理单
+        RuleFor(d => d.OrderExtension.AcceptType12328).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("受理方式最多50字符");
+        RuleFor(d => d.OrderExtension.Client12328).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("客户最多200字符");
+        #endregion
+
+        #region 邮政业消费者申诉受理单
+        RuleFor(d => d.OrderExtension.MailClaimantName).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("申诉人姓名最多50字符");
+        RuleFor(d => d.OrderExtension.MailClaimantPhone).MaxLengthWithChineseChar(12).When(d => d.OrderExtension != null).WithMessage("申诉人电话最多12字符");
+        RuleFor(d => d.OrderExtension.MailClaimantEnterprise).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("申诉企业最多200字符");
+        RuleFor(d => d.OrderExtension.MailSenderName).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("寄件人姓名最多50字符");
+        RuleFor(d => d.OrderExtension.MailSenderPhone).MaxLengthWithChineseChar(12).When(d => d.OrderExtension != null).WithMessage("寄件人电话最多12字符");
+        RuleFor(d => d.OrderExtension.MailSenderAddress).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("寄件人地址最多500字符");
+        RuleFor(d => d.OrderExtension.MailReceiverName).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("收件人姓名最多50字符");
+        RuleFor(d => d.OrderExtension.MailReceiverPhone).MaxLengthWithChineseChar(12).When(d => d.OrderExtension != null).WithMessage("收件人电话最多12字符");
+        RuleFor(d => d.OrderExtension.MailReceiverAddress).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("收件人地址最多500字符");
+        #endregion
+
+        #region 环保举报业务受理单
+        RuleFor(d => d.OrderExtension.EpEmail).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("邮箱最多50字符");
+        RuleFor(d => d.OrderExtension.EpAddress).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("通讯地址最多500字符");
+        RuleFor(d => d.OrderExtension.EpObject).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("举报对象最多200字符");
+        RuleFor(d => d.OrderExtension.EpObjectAddress).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("详细地址最多200字符");
+        RuleFor(d => d.OrderExtension.EpIndustryType).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("行业类型最多50字符");
+        RuleFor(d => d.OrderExtension.EpKeyPoint).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("重点要素最多50字符");
+        #endregion
+
+        #region 投诉人信息
+        RuleFor(d => d.OrderExtension.LicenceTypeCode).MaxLengthWithChineseChar(10).When(d => d.OrderExtension != null).WithMessage("证件类型最多10字符");
+        RuleFor(d => d.OrderExtension.LicenceType).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("证件类型名称最多50字符");
+        RuleFor(d => d.OrderExtension.IdentityTypeCode).MaxLengthWithChineseChar(4).When(d => d.OrderExtension != null).WithMessage("提供方类型最多4字符");
+        RuleFor(d => d.OrderExtension.IdentityType).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("提供方类型名称最多50字符");
+        RuleFor(d => d.OrderExtension.IdentityCode).MaxLengthWithChineseChar(10).When(d => d.OrderExtension != null).WithMessage("提供方身份最多10字符");
+        RuleFor(d => d.OrderExtension.Identity).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("提供方身份名称最多50字符");
+        RuleFor(d => d.OrderExtension.NationalityCode).MaxLengthWithChineseChar(1).When(d => d.OrderExtension != null).WithMessage("国籍最多1字符");
+        RuleFor(d => d.OrderExtension.Nationality).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("国籍名称最多50字符");
+        RuleFor(d => d.OrderExtension.NationCode).MaxLengthWithChineseChar(4).When(d => d.OrderExtension != null).WithMessage("民族最多4字符");
+        RuleFor(d => d.OrderExtension.Nation).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("民族名称最多50字符");
+        RuleFor(d => d.OrderExtension.LicenceNo).MaxLengthWithChineseChar(64).When(d => d.OrderExtension != null).WithMessage("证件号码最多64字符");
+        RuleFor(d => d.OrderExtension.PostalCode).MaxLengthWithChineseChar(10).When(d => d.OrderExtension != null).WithMessage("邮政编码最多10字符");
+        RuleFor(d => d.OrderExtension.Email).MaxLengthWithChineseChar(100).When(d => d.OrderExtension != null).WithMessage("电子邮箱最多100字符");
+        RuleFor(d => d.OrderExtension.OtherContact).MaxLengthWithChineseChar(64).When(d => d.OrderExtension != null).WithMessage("其他联系方式最多64字符");
+        #endregion
+
+        #region 投诉对象信息
+        RuleFor(d => d.OrderExtension.EnterpriseName).MaxLengthWithChineseChar(100).When(d => d.OrderExtension != null).WithMessage("企业名称最多100字符");
+        RuleFor(d => d.OrderExtension.UnifiedSocialCreditCode).MaxLengthWithChineseChar(30).When(d => d.OrderExtension != null).WithMessage("统一社会信用代码最多30字符");
+        RuleFor(d => d.OrderExtension.RegisterAddress).MaxLengthWithChineseChar(500).When(d => d.OrderExtension != null).WithMessage("企业注册地址最多500字符");
+        RuleFor(d => d.OrderExtension.RegisterNumber).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("注册号最多50字符");
+        RuleFor(d => d.OrderExtension.EnterpriseContact).MaxLengthWithChineseChar(70).When(d => d.OrderExtension != null).WithMessage("联系人最多70字符");
+        RuleFor(d => d.OrderExtension.MarketTypeCode).MaxLengthWithChineseChar(64).When(d => d.OrderExtension != null).WithMessage("市场主体类型代码最多64字符");
+        RuleFor(d => d.OrderExtension.MarketType).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("市场主体类型名称最多200字符");
+        RuleFor(d => d.OrderExtension.IndustryClassifyCode).MaxLengthWithChineseChar(64).When(d => d.OrderExtension != null).WithMessage("行业类型代码最多64字符");
+        RuleFor(d => d.OrderExtension.IndustryClassify).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("行业类型名称最多200字符");
+        #endregion
+
+        #region 投诉详情
+        RuleFor(d => d.OrderExtension.ExternalOrderNo).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("订单号最多50字符");
+        RuleFor(d => d.OrderExtension.Patentee).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("专利权人最多50字符");
+        RuleFor(d => d.OrderExtension.PatentName).MaxLengthWithChineseChar(200).When(d => d.OrderExtension != null).WithMessage("专利名称最多200字符");
+        RuleFor(d => d.OrderExtension.PatentNo).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("专利号最多50字符");
+        RuleFor(d => d.OrderExtension.ProductName).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("产品名称最多50字符");
+        RuleFor(d => d.OrderExtension.ApprovalNumber).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("批准文号最多50字符");
+        RuleFor(d => d.OrderExtension.ProductBatchNo).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("产品批号最多50字符");
+        RuleFor(d => d.OrderExtension.ProductStandard).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("产品规格最多50字符");
+        RuleFor(d => d.OrderExtension.Manufacturer).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("生产厂家最多50字符");
+        RuleFor(d => d.OrderExtension.SalesEnterprise).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("销售企业最多50字符");
+        RuleFor(d => d.OrderExtension.ConsumerAddress).MaxLengthWithChineseChar(100).When(d => d.OrderExtension != null).WithMessage("消费者地址最多100字符");
+        RuleFor(d => d.OrderExtension.BusinessPosition.Street).MaxLengthWithChineseChar(100).When(d => d.OrderExtension != null && d.OrderExtension.BusinessPosition != null).WithMessage("经营详细地址最多100字符");
+        #endregion
     }
-}
+
+}
+

+ 16 - 0
src/Hotline.Application.Contracts/Validators/Order/UpdateOrderDtoValidator.cs

@@ -0,0 +1,16 @@
+using FluentValidation;
+using Hotline.Share.Dtos.Order;
+
+namespace Hotline.Application.Contracts.Validators.Order
+{
+    /// <summary>
+    /// 编辑验证
+    /// </summary>
+    public class UpdateOrderDtoValidator : AbstractValidator<UpdateOrderDto>
+    {
+        public UpdateOrderDtoValidator()
+        {
+            Include(new AddOrderDtoValidator());
+        }
+    }
+}

+ 3 - 0
src/Hotline.Application.Contracts/Validators/ValidatorExtensions.cs

@@ -10,4 +10,7 @@ public static class ValidatorExtensions
     /// </summary>
     public static IRuleBuilderOptions<TDto, string> IsGuidStructureString<TDto>(this IRuleBuilderInitial<TDto, string> initial) =>
         initial.Cascade(CascadeMode.Stop).NotEmpty().Must(d => d.IsGuidString());
+    
+    public static IRuleBuilderOptions<TDto, string> MaxLengthWithChineseChar<TDto>(this IRuleBuilderInitial<TDto, string> initial, int maxLength) =>
+        initial.Cascade(CascadeMode.Stop).Must(d => d.GetChineseCharLength() < maxLength);
 }

+ 51 - 0
src/Hotline.Application.Tests/Controller/OrderControllerTest.cs

@@ -0,0 +1,51 @@
+using AutoFixture;
+using Hotline.Api.Controllers;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.Settings;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Moq;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Tests.Controller;
+public class OrderControllerTest : IClassFixture<WebApplicationFactory<Startup>>
+{
+    private readonly OrderController _orderController;
+    private readonly IFixture _fixture;
+    private readonly WebApplicationFactory<Startup> _factory;
+
+    public OrderControllerTest(OrderController orderController, WebApplicationFactory<Startup> factory)
+    //public OrderControllerTest(HttpClient testClient)
+    {
+        _fixture = new Fixture();
+        _orderController = orderController;
+        //_testClient = testClient;
+        _factory = factory;
+        _orderController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
+    }
+
+    [Fact]
+    public async Task SendSMS_Test()
+    {
+        var inDto = _fixture.Create<VisitSmsInDto>();
+        await _orderController.VisitPushSMSAsync(inDto);
+    }
+
+    [Fact]
+    public async Task GetOrderHandleTimeConfigByAcceptType_Test()
+    {
+        var result = await _orderController.GetOrderHandleTimeConfigByAcceptType(Share.Enums.FlowEngine.EFlowDirection.OrgToCenter, "10");
+        result.Count.ShouldBe(1);
+        result.TimeText.ShouldBe("1个工作日");
+        result.TimeType.ShouldBe(ETimeType.WorkDay);
+    }
+}

+ 80 - 0
src/Hotline.Application.Tests/Controller/PushMessageControllerTest.cs

@@ -0,0 +1,80 @@
+using Hotline.Api.Controllers;
+using Hotline.Share.Dtos.Push;
+using Hotline.Users;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Tests.Controller;
+
+public class PushMessageControllerTest
+{
+    private readonly PushMessageController _pushMessageController;
+    private readonly IRepository<User> _userRepository;
+
+    public PushMessageControllerTest(PushMessageController pushMessageController, IRepository<User> userRepository)
+    {
+        _pushMessageController = pushMessageController;
+        pushMessageController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
+        _userRepository = userRepository;
+    }
+
+    [Fact]
+    public async Task SendMessage_Test()
+    {
+        try
+        {
+            await _pushMessageController.SendMessage(new MessageInDto { Content = "123"});
+        }
+        catch (UserFriendlyException e)
+        {
+            e.Message.ShouldBe("电话号码和用户Id不能同时为空");
+        }
+
+        try
+        {
+            await _pushMessageController.SendMessage(new MessageInDto());
+        }
+        catch (UserFriendlyException e)
+        {
+            e.Message.ShouldBe("内容不能为空");
+        }
+
+        var phoneNo = await _userRepository.Queryable()
+            .Where(m => m.Name.Contains("测试账号"))
+            .Select(m => m.PhoneNo)
+            .FirstAsync();
+
+        var inDto = new MessageInDto 
+        {
+            TelNumbers = phoneNo,
+            Content = "单元测试, 输入的手机号码发送的短信"
+        };
+        // await _pushMessageController.SendMessage(inDto);
+
+
+        var ids = await _userRepository.Queryable()
+            .Where(m => m.Name.Contains("测试账号"))
+            .Select(m => m.Id)
+            .Take(2)
+            .ToListAsync();
+
+        inDto = new MessageInDto
+        {
+            UserIds = ids,
+            Content = "单元测试, 选择的联系人发送的短信"
+        };
+        await _pushMessageController.SendMessage(inDto);
+    }
+}
+

+ 1 - 1
src/Hotline.Application.Tests/Domain/ZiGongExpireTimeTest.cs

@@ -66,7 +66,7 @@ public class ZiGongExpireTimeTest
 
     [Theory]
     [InlineData("求助三个工作日", "2024-09-12 22:01:28", "CenterToOrg", "2024-09-20 08:30:00")]
-    [InlineData("咨询小三问题", "2024-10-20 14:21:53", "CenterToCenter", "2024/10/21 14:21:53")]
+    [InlineData("string", "2024-09-12 22:01:28", "CenterToOrg", "2024-09-20 08:30:00")]
     public async Task CalcExpiredTime_Release_Test(string title, string beginTxt, string flowTxt, string expected)
     {
         var beginTime = DateTime.Parse(beginTxt);

+ 2 - 0
src/Hotline.Application.Tests/Hotline.Application.Tests.csproj

@@ -22,9 +22,11 @@
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="AutoFixture" Version="4.18.1" />
     <PackageReference Include="coverlet.collector" Version="3.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="7.0.20" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
+    <PackageReference Include="Moq" Version="4.20.72" />
     <PackageReference Include="Shouldly" Version="4.2.1" />
     <PackageReference Include="xunit" Version="2.4.2" />
     <PackageReference Include="Xunit.DependencyInjection" Version="9.3.0" />

+ 7 - 1
src/Hotline.Application.Tests/Startup.cs

@@ -47,6 +47,8 @@ using Hotline.CallCenter.Configs;
 using Tr.Sdk;
 using Hotline.Application.StatisticalReport.CallReport;
 using XF.Domain.Authentications;
+using Hotline.Api.Controllers;
+using Hotline.Application.ExportExcel;
 
 namespace Hotline.Application.Tests;
 public class Startup
@@ -95,6 +97,8 @@ public class Startup
             services.Configure<CityBaseConfiguration>(d => configuration.GetSection(nameof(CityBaseConfiguration)).Bind(d));
 
 			services.RegisterMapper();
+            //services.AddControllers()
+            //    .AddControllersAsServices();
 
             //sqlsugar
             services.AddSqlSugar(configuration);
@@ -150,8 +154,10 @@ public class Startup
             services.AddScoped<ZiGongCallReportApplication>();
             services.AddScoped<YiBinCallReportApplication>();
             services.AddScoped<IMediator, MediatorMock>();
-
             services.AddScoped<ISessionContext, DefaultHttpContextAccessor>();
+            services.AddScoped<IExportApplication, ExportApplication>();
+            services.AddScoped<OrderController>();
+            services.AddScoped<PushMessageController>();
 
             //ServiceLocator.Instance = services.BuildServiceProvider();
         }

+ 9 - 116
src/Hotline.Application/Bigscreen/SeatStateDataService.cs

@@ -1,121 +1,14 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Hotline.CallCenter.Calls;
-using SqlSugar;
+using Hotline.CallCenter.Calls;
+using Hotline.DI;
 using XF.Domain.Dependency;
 using XF.Domain.Repository;
 
-namespace Hotline.Application.Bigscreen
-{
-	public  class SeatStateDataService: ISeatStateDataService , IScopeDependency
-	{
-		private readonly IRepository<TrCallRecord> _callRepository;
-		public SeatStateDataService(IRepository<TrCallRecord> callRepository) {
-			_callRepository= callRepository;
-		}
-
-		public async Task<object> GetCall24(CancellationToken stoppingToken) {
-			List<string> timeList = new List<string>();
-			for (int i = 0; i < 24; i++)
-			{
-				var time = i < 10 ? $"0{i}" : i.ToString();
-				timeList.Add(time);
-			}
-			var call24 = await _callRepository.Queryable()
-				.Where(x => x.CreatedTime.Date == DateTime.Now.Date
-					&& x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
-				)
-				.Select(x => new { time = x.CreatedTime.ToString("hh"), x.CallDirection }).MergeTable()
-				.GroupBy(x => x.time)
-				.Select(x => new
-				{
-					Time = x.time.ToString(),
-					In = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0)),
-					Out = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)),
-				}).MergeTable().ToListAsync(stoppingToken);
-
-			var call24List = (from t1 in timeList
-				join t2 in call24 on t1 equals t2.Time into t1_t2
-				from item in t1_t2.DefaultIfEmpty()
-				select new { 
-					Time = t1 + ":00",
-					In = t1_t2.Select(x => x.In).FirstOrDefault() ,
-					Out = t1_t2.Select(x => x.Out).FirstOrDefault() 
-				}).ToList();
-			return call24List;
-		}
-
-		public async Task<object> GetCallTop10(CancellationToken stoppingToken)
-		{
-
-			var callTop10 = await _callRepository.Queryable()
-				.Where(x => x.CreatedTime.Date == DateTime.Now.Date
-					&& x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
-				)
-				.GroupBy(x => x.UserName)
-				.Select(x => new
-				{
-					UserName = x.UserName,
-					In = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0))
-				}).MergeTable().OrderByDescending(x => x.In).Take(10).ToListAsync(stoppingToken);
-			return callTop10;
-		}
+namespace Hotline.Application.Bigscreen;
 
-		public async Task<object> GetCallList(CancellationToken stoppingToken)
-		{
-			var callList = await _callRepository.Queryable()
-				.Where(x => x.CreatedTime.Date == DateTime.Now.Date
-                    && x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
-				)
-				.Select(x => new
-				{
-					InOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.On, 1, 0)),
-					ValidOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.On && x.Duration > 5, 1, 0)),
-					InNoOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.NoOn, 1, 0)),
-					OutOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out && x.OnState == Share.Enums.CallCenter.EOnState.On, 1, 0)),
-					InQueueNoOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.NoOn && x.QueueTims > 0, 1, 0)),
-				}).MergeTable().ToListAsync(stoppingToken);
-			return callList;
-		}
-
-		public async Task<object> GetCallAverage(CancellationToken stoppingToken)
-		{
-			List<string> timeList = new List<string>();
-			for (int i = 0; i < 24; i++)
-			{
-				var time = i < 10 ? $"0{i}" : i.ToString();
-				timeList.Add(time);
-			}
-			var callAverage = await _callRepository.Queryable()
-				.Where(x => x.CreatedTime.Date == DateTime.Now.Date
-                    && x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
-				)
-				.Select(x => new { time = x.CreatedTime.ToString("hh"), x.CallDirection }).MergeTable()
-				.GroupBy(x => x.time)
-				.Select(x => new
-				{
-					Time = x.time.ToString(),
-					In = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0)),
-					InAverag = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0)) / 60,
-					Out = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)),
-					OutAverag = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)) / 60,
-				}).MergeTable().ToListAsync(stoppingToken);
-			var callAverageList = (from t1 in timeList
-				join t2 in callAverage on t1 equals t2.Time into t1_t2
-				from item in t1_t2.DefaultIfEmpty()
-				select new {
-					Time = t1 + ":00",
-					In = t1_t2.Select(x => x.In).FirstOrDefault(),
-					InAverag = t1_t2.Select(x => x.InAverag).FirstOrDefault(),
-					Out = t1_t2.Select(x => x.Out).FirstOrDefault(),
-					OutAverag = t1_t2.Select(x => x.OutAverag).FirstOrDefault() 
-				}).ToList();
-			return callAverageList;
-		}
-
-
-	}
+[Injection(AppScopes = EAppScope.ZiGong | EAppScope.LuZhou)]
+public class SeatStateDataService : SeatStateDataServiceBase, ISeatStateDataService, IScopeDependency
+{
+    public SeatStateDataService(IRepository<CallNative> repository) : base(repository)
+    {
+    }
 }

+ 128 - 0
src/Hotline.Application/Bigscreen/SeatStateDataServiceBase.cs

@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.CallCenter.Calls;
+using Hotline.Orders;
+using SqlSugar;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Bigscreen
+{
+    public abstract class SeatStateDataServiceBase
+    {
+        private readonly IRepository<CallNative> _callRepository;
+
+        public SeatStateDataServiceBase(IRepository<CallNative> callRepository)
+        {
+            _callRepository = callRepository;
+        }
+
+        public virtual async Task<object> GetCall24(CancellationToken stoppingToken)
+        {
+            List<string> timeList = new List<string>();
+            for (int i = 0;i < 24;i++)
+            {
+                var time = i < 10 ? $"0{i}" : i.ToString();
+                timeList.Add(time);
+            }
+            var call24 = await _callRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.CallId)
+                .Where((x, o) => x.CreationTime.Date == DateTime.Now.Date
+                    && string.IsNullOrEmpty(o.Id) == false
+                )
+                .Select(x => new { time = x.CreationTime.ToString("hh"), x.Direction }).MergeTable()
+                .GroupBy(x => x.time)
+                .Select(x => new
+                {
+                    Time = x.time.ToString(),
+                    In = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In, 1, 0)),
+                    Out = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)),
+                }).MergeTable().ToListAsync(stoppingToken);
+
+            var call24List = (from t1 in timeList
+                              join t2 in call24 on t1 equals t2.Time into t1_t2
+                              from item in t1_t2.DefaultIfEmpty()
+                              select new
+                              {
+                                  Time = t1 + ":00",
+                                  In = t1_t2.Select(x => x.In).FirstOrDefault(),
+                                  Out = t1_t2.Select(x => x.Out).FirstOrDefault()
+                              }).ToList();
+            return call24List;
+        }
+
+        public virtual async Task<object> GetCallTop10(CancellationToken stoppingToken)
+        {
+            var callTop10 = await _callRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.CallId)
+                .Where((x, o) => x.CreationTime.Date == DateTime.Now.Date
+                    && string.IsNullOrEmpty(o.Id) == false
+                )
+                .GroupBy(x => x.UserName)
+                .Select(x => new
+                {
+                    UserName = x.UserName,
+                    In = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In, 1, 0))
+                }).MergeTable().OrderByDescending(x => x.In).Take(10).ToListAsync(stoppingToken);
+            return callTop10;
+        }
+
+        public virtual async Task<object> GetCallList(CancellationToken stoppingToken)
+        {
+            var callList = await _callRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.CallId)
+                .Where((x, o) => x.CreationTime.Date == DateTime.Now.Date
+                    && string.IsNullOrEmpty(o.Id) == false
+                )
+                .Select(x => new
+                {
+                    InOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In && x.AnsweredTime != null, 1, 0)),
+                    ValidOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In && x.AnsweredTime != null && x.Duration > 5, 1, 0)),
+                    InNoOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In && x.AnsweredTime == null, 1, 0)),
+                    OutOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.Out && x.AnsweredTime != null, 1, 0)),
+                    InQueueNoOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In && x.AnsweredTime == null && x.WaitDuration > 0, 1, 0)),
+                }).MergeTable().ToListAsync(stoppingToken);
+            return callList;
+        }
+
+        public virtual async Task<object> GetCallAverage(CancellationToken stoppingToken)
+        {
+            List<string> timeList = new List<string>();
+            for (int i = 0;i < 24;i++)
+            {
+                var time = i < 10 ? $"0{i}" : i.ToString();
+                timeList.Add(time);
+            }
+            var callAverage = await _callRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.CallId)
+                .Where((x, o) => x.CreationTime.Date == DateTime.Now.Date
+                    && string.IsNullOrEmpty(o.Id) == false
+                )
+                .Select(x => new { time = x.CreationTime.ToString("hh"), x.Direction }).MergeTable()
+                .GroupBy(x => x.time)
+                .Select(x => new
+                {
+                    Time = x.time.ToString(),
+                    In = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In, 1, 0)),
+                    InAverag = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.In, 1, 0)) / 60,
+                    Out = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)),
+                    OutAverag = SqlFunc.AggregateSum(SqlFunc.IIF(x.Direction == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)) / 60,
+                }).MergeTable().ToListAsync(stoppingToken);
+            var callAverageList = (from t1 in timeList
+                                   join t2 in callAverage on t1 equals t2.Time into t1_t2
+                                   from item in t1_t2.DefaultIfEmpty()
+                                   select new
+                                   {
+                                       Time = t1 + ":00",
+                                       In = t1_t2.Select(x => x.In).FirstOrDefault(),
+                                       InAverag = t1_t2.Select(x => x.InAverag).FirstOrDefault(),
+                                       Out = t1_t2.Select(x => x.Out).FirstOrDefault(),
+                                       OutAverag = t1_t2.Select(x => x.OutAverag).FirstOrDefault()
+                                   }).ToList();
+            return callAverageList;
+        }
+    }
+}

+ 124 - 0
src/Hotline.Application/Bigscreen/YiBinSeatStateDataService.cs

@@ -0,0 +1,124 @@
+using Hotline.CallCenter.Calls;
+using Hotline.DI;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Bigscreen;
+
+[Injection(AppScopes = EAppScope.YiBin)]
+public class YiBinSeatStateDataService : SeatStateDataServiceBase, ISeatStateDataService, IScopeDependency
+{
+    private readonly IRepository<TrCallRecord> _callRepository;
+    public YiBinSeatStateDataService(IRepository<TrCallRecord> callRepository, IRepository<CallNative> repository) : base(repository)
+    {
+        _callRepository = callRepository;
+    }
+
+    public override async Task<object> GetCall24(CancellationToken stoppingToken)
+    {
+        List<string> timeList = new List<string>();
+        for (int i = 0;i < 24;i++)
+        {
+            var time = i < 10 ? $"0{i}" : i.ToString();
+            timeList.Add(time);
+        }
+        var call24 = await _callRepository.Queryable()
+            .Where(x => x.CreatedTime.Date == DateTime.Now.Date
+                && x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
+            )
+            .Select(x => new { time = x.CreatedTime.ToString("hh"), x.CallDirection }).MergeTable()
+            .GroupBy(x => x.time)
+            .Select(x => new
+            {
+                Time = x.time.ToString(),
+                In = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0)),
+                Out = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)),
+            }).MergeTable().ToListAsync(stoppingToken);
+
+        var call24List = (from t1 in timeList
+                          join t2 in call24 on t1 equals t2.Time into t1_t2
+                          from item in t1_t2.DefaultIfEmpty()
+                          select new
+                          {
+                              Time = t1 + ":00",
+                              In = t1_t2.Select(x => x.In).FirstOrDefault(),
+                              Out = t1_t2.Select(x => x.Out).FirstOrDefault()
+                          }).ToList();
+        return call24List;
+    }
+
+    public override async Task<object> GetCallTop10(CancellationToken stoppingToken)
+    {
+
+        var callTop10 = await _callRepository.Queryable()
+            .Where(x => x.CreatedTime.Date == DateTime.Now.Date
+                && x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
+            )
+            .GroupBy(x => x.UserName)
+            .Select(x => new
+            {
+                UserName = x.UserName,
+                In = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0))
+            }).MergeTable().OrderByDescending(x => x.In).Take(10).ToListAsync(stoppingToken);
+        return callTop10;
+    }
+
+    public override async Task<object> GetCallList(CancellationToken stoppingToken)
+    {
+        var callList = await _callRepository.Queryable()
+            .Where(x => x.CreatedTime.Date == DateTime.Now.Date
+                && x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
+            )
+            .Select(x => new
+            {
+                InOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.On, 1, 0)),
+                ValidOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.On && x.Duration > 5, 1, 0)),
+                InNoOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.NoOn, 1, 0)),
+                OutOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out && x.OnState == Share.Enums.CallCenter.EOnState.On, 1, 0)),
+                InQueueNoOn = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && x.OnState == Share.Enums.CallCenter.EOnState.NoOn && x.QueueTims > 0, 1, 0)),
+            }).MergeTable().ToListAsync(stoppingToken);
+        return callList;
+    }
+
+    public override async Task<object> GetCallAverage(CancellationToken stoppingToken)
+    {
+        List<string> timeList = new List<string>();
+        for (int i = 0;i < 24;i++)
+        {
+            var time = i < 10 ? $"0{i}" : i.ToString();
+            timeList.Add(time);
+        }
+        var callAverage = await _callRepository.Queryable()
+            .Where(x => x.CreatedTime.Date == DateTime.Now.Date
+                && x.CallOrderType == Share.Enums.CallCenter.ECallOrderType.Order
+            )
+            .Select(x => new { time = x.CreatedTime.ToString("hh"), x.CallDirection }).MergeTable()
+            .GroupBy(x => x.time)
+            .Select(x => new
+            {
+                Time = x.time.ToString(),
+                In = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0)),
+                InAverag = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.In, 1, 0)) / 60,
+                Out = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)),
+                OutAverag = SqlFunc.AggregateSum(SqlFunc.IIF(x.CallDirection == Share.Enums.CallCenter.ECallDirection.Out, 1, 0)) / 60,
+            }).MergeTable().ToListAsync(stoppingToken);
+        var callAverageList = (from t1 in timeList
+                               join t2 in callAverage on t1 equals t2.Time into t1_t2
+                               from item in t1_t2.DefaultIfEmpty()
+                               select new
+                               {
+                                   Time = t1 + ":00",
+                                   In = t1_t2.Select(x => x.In).FirstOrDefault(),
+                                   InAverag = t1_t2.Select(x => x.InAverag).FirstOrDefault(),
+                                   Out = t1_t2.Select(x => x.Out).FirstOrDefault(),
+                                   OutAverag = t1_t2.Select(x => x.OutAverag).FirstOrDefault()
+                               }).ToList();
+        return callAverageList;
+    }
+}

+ 33 - 15
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -11,6 +11,7 @@ using Hotline.Share.Enums.CallCenter;
 using Hotline.Users;
 using MapsterMapper;
 using Microsoft.Extensions.Logging;
+using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Exceptions;
@@ -71,8 +72,8 @@ public abstract class DefaultCallApplication : ICallApplication
     public virtual async Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
     {
         return _mapper.Map<IReadOnlyList<TelDto>>(await _telRepository.Queryable()
-	        .Includes(x => x.Groups)
-	        .ToListAsync(cancellationToken)) ;
+            .Includes(x => x.Groups)
+            .ToListAsync(cancellationToken));
     }
 
     /// <summary>
@@ -203,8 +204,9 @@ public abstract class DefaultCallApplication : ICallApplication
     /// </summary>
     public virtual async Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
     {
-        return await _callNativeRepository.Queryable(includeDeleted: true)
+        var query = _callNativeRepository.Queryable(includeDeleted: true)
             .LeftJoin<Order>((d, o) => d.Id == o.CallId)
+            .Where((d, o) => d.GroupId == "1")
             .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), (d, o) => o.No == dto.OrderNo)
             .WhereIF(!string.IsNullOrEmpty(dto.FromNo), d => d.FromNo == dto.FromNo)
             .WhereIF(!string.IsNullOrEmpty(dto.ToNo), d => d.ToNo == dto.ToNo)
@@ -217,16 +219,32 @@ public abstract class DefaultCallApplication : ICallApplication
             .WhereIF(dto.Direction != null, d => d.Direction == dto.Direction)
             .WhereIF(dto.WaitDurationStart != null && dto.WaitDurationStart > 0, d => d.WaitDuration >= dto.WaitDurationStart)
             .WhereIF(dto.WaitDurationEnd != null && dto.WaitDurationEnd > 0, d => d.WaitDuration <= dto.WaitDurationEnd)
-            .WhereIF(dto.IsMissOrder != null && dto.IsMissOrder.Value == true, (d, o) => o.Id == null )
-            .WhereIF(dto.IsMissOrder != null && dto.IsMissOrder.Value == false, (d, o) => o.Id != null )
-            .OrderByDescending(d => d.Id)
-            .Select((d, o) => new CallNativeDto
+            .WhereIF(dto.IsMissOrder != null && dto.IsMissOrder.Value == true, (d, o) => string.IsNullOrEmpty(o.Id) == true)
+            .WhereIF(dto.IsMissOrder != null && dto.IsMissOrder.Value == false, (d, o) => string.IsNullOrEmpty(o.Id) == false)
+            .OrderByDescending(d => d.Id);
+
+        query = query.WhereIF(dto.Type == 3, (d, o) => d.AnsweredTime == null);
+        query = query.WhereIF(dto.Type == 1, (d, o) => d.Direction == ECallDirection.In);
+        query = query.WhereIF(dto.Type == 2, (d, o) => d.Direction == ECallDirection.Out);
+        var items = await query.Select((d, o) => new CallNativeDto
+        {
+            OrderId = o.Id,
+            OrderNo = o.No,
+            Title = o.Title,
+            Gateway = SqlFunc.Subqueryable<CallNative>()
+            .Where(m => m.GroupId == "0" && m.CallNo == d.CallNo)
+            .Select(m => m.ToNo)
+        }, true)
+        .ToFixedListAsync(dto, cancellationToken);
+
+        items.Where(m => m.Gateway != null)
+            .ToList().ForEach(m => 
             {
-                OrderId = o.Id,
-                OrderNo = o.No,
-                Title = o.Title,
-            }, true)
-            .ToFixedListAsync(dto, cancellationToken);
+                var toNo = m.Gateway;
+                m.Gateway = m.ToNo;
+                m.ToNo = toNo;
+            });
+        return items;
     }
 
     /// <summary>
@@ -294,7 +312,7 @@ public abstract class DefaultCallApplication : ICallApplication
     public virtual async Task<CallNative> GetCallAsync(string callId, CancellationToken cancellationToken)
     {
         if (string.IsNullOrEmpty(callId)) return null;
-        return await _callNativeRepository.GetAsync(callId,cancellationToken);
+        return await _callNativeRepository.GetAsync(callId, cancellationToken);
     }
 
     /// <summary>
@@ -303,9 +321,9 @@ public abstract class DefaultCallApplication : ICallApplication
     /// <param name="callId"></param>
     /// <param name="cancellationToken"></param>
     /// <returns></returns>
-    public virtual async Task<List<CallNative>> GetCallListAsync(string callId,CancellationToken cancellationToken)
+    public virtual async Task<List<CallNative>> GetCallListAsync(string callId, CancellationToken cancellationToken)
     {
-        if(string.IsNullOrEmpty(callId)) return null;
+        if (string.IsNullOrEmpty(callId)) return null;
         return await _callNativeRepository.Queryable().Where(x => x.Id == callId).ToListAsync(cancellationToken);
     }
 

+ 2 - 0
src/Hotline.Application/ExportExcel/ExportApplication.cs

@@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.DependencyInjection;
 using MiniExcelLibs;
+using System.Net.Http;
 using System.Reflection;
 using XF.Domain.Dependency;
 using XF.Utility.EnumExtensions;
@@ -53,6 +54,7 @@ namespace Hotline.Application.ExportExcel
                 items.Add(func.Invoke(items));
             }
 
+            _httpContextAccessor.HttpContext.Response.Headers.TryAdd("Access-Control-Expose-Headers", "Content-Disposition");
             dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
 
             var dtos = items

+ 110 - 89
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -262,6 +262,11 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task<Workflow> NextAsync(NextWorkflowDto dto, DateTime? expiredTime = null, CancellationToken cancellationToken = default)
     {
+        var validator = new NextWorkflowDtoValidator();
+        var validResult = await validator.ValidateAsync(dto, cancellationToken);
+        if (!validResult.IsValid)
+            throw new UserFriendlyException(
+                $"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
             withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
 
@@ -536,7 +541,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withDefine: true, withSteps: true,
             cancellationToken: cancellationToken);
         var currentStep = _workflowDomainService.FindCurrentStepWaitForHandle(workflow,
-            _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.Roles);
+            _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.RequiredOrgId,
+            _sessionContextProvider.SessionContext.Roles);
         if (currentStep.StepType is EStepType.End)
             throw new UserFriendlyException("结束节点无需办理");
 
@@ -573,6 +579,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, cancellationToken: cancellationToken);
             definition = workflow.WorkflowDefinition;
         }
+
         if (definition == null)
             throw new UserFriendlyException("无效模板编码");
         if (definition.Status is not EDefinitionStatus.Enable)
@@ -599,16 +606,16 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             };
             var orgs = await _organizeRepository.Queryable()
                 .Where(d => d.IsEnable &&
-                d.Level == orgLevel &&
-                dto.OrgIds.Contains(d.ParentId))
+                            d.Level == orgLevel &&
+                            dto.OrgIds.Contains(d.ParentId))
                 .ToListAsync(cancellationToken);
             nextStepOption.Items = orgs.Select(d => new FlowStepHandler
-            {
-                Key = d.Id,
-                Value = d.Name,
-                OrgId = d.Id,
-                OrgName = d.Name
-            })
+                {
+                    Key = d.Id,
+                    Value = d.Name,
+                    OrgId = d.Id,
+                    OrgName = d.Name
+                })
                 .ToList();
             nextStepOption.FlowDirection = _workflowDomainService.GetFlowDirection(dto.BusinessType, stepDefine.BusinessType);
             stepOptions.Add(nextStepOption);
@@ -967,8 +974,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             case EHandlerType.Role:
                 //当前操作人所属部门的下级部门并且属于配置包含角色
                 var roles = await _roleRepository.Queryable()
-                    .Includes(
-                        d => d.Accounts.Where(x =>
+                    .Includes(d => d.Accounts.Where(x =>
                             !x.IsDeleted && x.Status == EAccountStatus.Normal && x.AccountType == EAccountType.Personal).ToList(),
                         x => x.User, s => s.Organization)
                     .Where(d => stepDefine.HandlerTypeItems.Select(x => x.Key).Contains(d.Name))
@@ -976,10 +982,20 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 _logger.LogInformation($"角色: {string.Join(",", roles.Select(d => d.Name))}");
                 var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
 
-                //工单办理:除一级部门选择中心汇总(中心会签流程,返回topCountersignStep场景),其余只能选下级部门
-                if (flowType is EFlowType.Handle
-                    && (stepDefine.StepType != EStepType.Summary && stepDefine.BusinessType != EBusinessType.Seat))
-                    users1 = users1.Where(d => d.OrgId.StartsWith(levelOneOrgId));
+                if (flowType is EFlowType.Handle)
+                {
+                    //工单办理:除一级部门选择中心汇总(中心会签流程,返回topCountersignStep场景),其余只能选下级部门
+                    if (stepDefine.BusinessType is EBusinessType.Department &&
+                        stepDefine.StepType != EStepType.Summary)
+                    {
+                        users1 = users1.Where(d => d.OrgId.StartsWith(levelOneOrgId));
+                    }
+                    else if(stepDefine.BusinessType is EBusinessType.DepartmentLeader)
+                    {
+                        users1 = users1.Where(d => d.OrgId == orgId);
+                    }
+                }
+
                 var defineTypeItem = stepDefine.HandlerTypeItems.First();
 
                 handlers = users1.Where(d => d != null && !string.IsNullOrEmpty(d.Id))
@@ -1025,12 +1041,12 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 }
 
                 handlers = orgs1.Select(d => new FlowStepHandler
-                {
-                    Key = d.Id,
-                    Value = d.Name,
-                    OrgId = d.Id,
-                    OrgName = d.Name
-                })
+                    {
+                        Key = d.Id,
+                        Value = d.Name,
+                        OrgId = d.Id,
+                        OrgName = d.Name
+                    })
                     .ToList();
                 break;
             case EHandlerType.OrgType:
@@ -1042,12 +1058,12 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     .ToListAsync(cancellationToken);
 
                 handlers = orgs2.Select(d => new FlowStepHandler
-                {
-                    Key = d.Id,
-                    Value = d.Name,
-                    OrgId = d.Id,
-                    OrgName = d.Name
-                })
+                    {
+                        Key = d.Id,
+                        Value = d.Name,
+                        OrgId = d.Id,
+                        OrgName = d.Name
+                    })
                     .ToList();
                 break;
             default:
@@ -1289,66 +1305,66 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     .ToListAsync(cancellationToken);
                 break;
             case EDynamicPolicy.OrgUpLeadCenterTop:
-                orgLevel = _sessionContextProvider.SessionContext.OrgLevel - 1;
-                if (orgLevel < 0) orgLevel = 0;
-
-                if (orgLevel == 0)
-                {
-                    businessType = EBusinessType.Send;
-                    if (currentBusinessType == EBusinessType.Department)
-                        flowDirection = EFlowDirection.OrgToCenter;
-
-                    items = await _organizeRepository.Queryable()
-                        .Where(d => d.IsCenter)
-                        .Select(d => new FlowStepHandler
-                        {
-                            Key = d.Id,
-                            Value = d.Name,
-                            OrgId = d.Id,
-                            OrgName = d.Name
-                        })
-                        .ToListAsync(cancellationToken);
-                }
-                else
-                {
-                    businessType = EBusinessType.Department;
-                    upperOrgId = _sessionContextProvider.SessionContext.RequiredOrgId.GetHigherOrgId(_sessionContextProvider.SessionContext.OrgLevel);
-                    isLead = _sessionContextProvider.SessionContext.Roles.Any(x => x == leadRoleCode);
-                    if (!isLead)
-                    {
-                        isSkip = await _userRepository.Queryable()
-                            .AnyAsync(x => x.OrgId == _sessionContextProvider.SessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
-                        if (isSkip)
-                        {
-                            roleId = leadRoleCode;
-                            roleName = leadRoleName;
-                        }
-                    }
-
-                    if (isLead || !isSkip)
-                    {
-                        //上级部门Id
-                        upperOrgId = _sessionContextProvider.SessionContext.RequiredOrgId.GetHigherOrgId(orgLevel);
-                        roleId = handleRoleCode;
-                        roleName = handleRoleName;
-                    }
-
-                    items = await _organizeRepository.Queryable()
-                        .Where(d => d.Id == upperOrgId)
-                        .Select(d => new FlowStepHandler
-                        {
-                            Key = d.Id,
-                            Value = d.Name,
-                            OrgId = d.Id,
-                            OrgName = d.Name,
-                            RoleId = roleId,
-                            RoleName = roleName
-                        })
-                        .ToListAsync(cancellationToken);
-                }
-
-                break;
-            case EDynamicPolicy.OrgUpLead:
+				orgLevel = _sessionContextProvider.SessionContext.OrgLevel - 1;
+				if (orgLevel < 0) orgLevel = 0;
+				isLead = _sessionContextProvider.SessionContext.Roles.Any(x => x == leadRoleCode);
+				isSkip = await _userRepository.Queryable().AnyAsync(x => x.OrgId == _sessionContextProvider.SessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
+				if (orgLevel == 0 && (isLead || !isSkip))
+				{
+					businessType = EBusinessType.Send;
+					if (currentBusinessType == EBusinessType.Department)
+						flowDirection = EFlowDirection.OrgToCenter;
+
+					items = await _organizeRepository.Queryable()
+						.Where(d => d.IsCenter)
+						.Select(d => new FlowStepHandler
+						{
+							Key = d.Id,
+							Value = d.Name,
+							OrgId = d.Id,
+							OrgName = d.Name
+						})
+						.ToListAsync(cancellationToken);
+				}
+				else
+				{
+					businessType = EBusinessType.Department;
+					upperOrgId = _sessionContextProvider.SessionContext.RequiredOrgId.GetHigherOrgId(_sessionContextProvider.SessionContext.OrgLevel);
+					if (!isLead)
+					{
+						if (isSkip)
+						{
+							roleId = leadRoleCode;
+							roleName = leadRoleName;
+						}
+					}
+					if (isLead || !isSkip)
+					{
+						//上级部门Id
+						upperOrgId = _sessionContextProvider.SessionContext.RequiredOrgId.GetHigherOrgId(orgLevel);
+						roleId = handleRoleCode;
+						roleName = handleRoleName;
+					}
+					else
+					{
+						orgLevel += 1;
+					}
+					items = await _organizeRepository.Queryable()
+						.Where(d => d.Id == upperOrgId)
+						.Select(d => new FlowStepHandler
+						{
+							Key = d.Id,
+							Value = d.Name,
+							OrgId = d.Id,
+							OrgName = d.Name,
+							RoleId = roleId,
+							RoleName = roleName
+						})
+						.ToListAsync(cancellationToken);
+				}
+
+				break;
+			case EDynamicPolicy.OrgUpLead:
                 businessType = _sessionContextProvider.SessionContext.OrgIsCenter
                     ? EBusinessType.Send
                     : _sessionContextProvider.SessionContext.RequiredOrgId.CalcOrgLevel() == 1
@@ -1361,7 +1377,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 if (!isLead)
                 {
                     isSkip = await _userRepository.Queryable()
-                        .AnyAsync(x => x.OrgId == _sessionContextProvider.SessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode), cancellationToken);
+                        .AnyAsync(x => x.OrgId == _sessionContextProvider.SessionContext.RequiredOrgId && x.Roles.Any(r => r.Name == leadRoleCode),
+                            cancellationToken);
                     if (isSkip)
                     {
                         roleId = leadRoleCode;
@@ -1376,8 +1393,12 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     roleId = handleRoleCode;
                     roleName = handleRoleName;
                 }
+                else
+                {
+	                orgLevel += 1;
+                }
 
-                items = await _organizeRepository.Queryable()
+				items = await _organizeRepository.Queryable()
                     .Where(d => d.Id == upperOrgId)
                     .Select(d => new FlowStepHandler
                     {

+ 1 - 1
src/Hotline.Application/Hotline.Application.csproj

@@ -9,7 +9,6 @@
   <ItemGroup>
     <PackageReference Include="HtmlToOpenXml.dll" Version="3.2.0" />
     <PackageReference Include="JiebaAspNetCore.Segmenter" Version="1.0.1" />
-    <PackageReference Include="JV.PanGu.Core" Version="1.0.1" />
     <PackageReference Include="NPOI" Version="2.7.0" />
     <PackageReference Include="OpenHtmlToPdf" Version="1.12.0" />
     <PackageReference Include="XC.RSAUtil" Version="1.3.6" />
@@ -22,6 +21,7 @@
     <ProjectReference Include="..\Hotline.NewRock\Hotline.NewRock.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
     <ProjectReference Include="..\Hotline.Wex\Hotline.Wex.csproj" />
+    <ProjectReference Include="..\Hotline.XingTang\Hotline.XingTang.csproj" />
     <ProjectReference Include="..\Hotline.YbEnterprise.Sdk\Hotline.YbEnterprise.Sdk.csproj" />
     <ProjectReference Include="..\Hotline\Hotline.csproj" />
     <ProjectReference Include="..\Tr.Sdk\Tr.Sdk.csproj" />

+ 6 - 3
src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs

@@ -51,12 +51,14 @@ namespace Hotline.Application.Jobs
         public async Task Execute(IJobExecutionContext context)
         {
             var xingtangCalls = await _db.Queryable<XingtangCall>()
-                .Where(d => (d.IsSync == null || !d.IsSync) && (d.Tries == null || d.Tries <= 50))
+                .Where(d => !string.IsNullOrEmpty(d.CallGuid) &&
+                                (d.IsSync == null || !d.IsSync) &&
+                                (d.Tries == null || d.Tries <= 50))
                 .OrderBy(d => d.Id)
                 .Take(10)
                 .ToListAsync(context.CancellationToken);
 
-            if(!xingtangCalls.Any()) return;
+            if (!xingtangCalls.Any()) return;
             var occupyCalls = new List<XingtangCall>();
             foreach (var call in xingtangCalls)
             {
@@ -69,7 +71,7 @@ namespace Hotline.Application.Jobs
                     occupyCalls.Add(call);
             }
 
-            if(!occupyCalls.Any()) return;
+            if (!occupyCalls.Any()) return;
             try
             {
                 var calls = _mapper.Map<List<CallNative>>(occupyCalls);
@@ -122,6 +124,7 @@ namespace Hotline.Application.Jobs
 
         private async Task<string> GetCallIdAsync(string callNo, CancellationToken cancellation)
         {
+            if (string.IsNullOrEmpty(callNo)) return string.Empty;
             var relation = await _callApplication.GetRelationAsync(callNo, cancellation);
             if (relation is null)
             {

+ 7 - 0
src/Hotline.Application/Knowledge/IKnowApplication.cs

@@ -70,5 +70,12 @@ namespace Hotline.Application.Knowledge
         /// <returns></returns>
         Task UpdateKnowledgeHotWordAsync(UpdateKnowledgeHotWordInDto dto, CancellationToken requestAborted = default);
         Task AddKnowledgeHotWordAsync(AddKnowledgeHotWordInDto dto, CancellationToken requestAborted = default);
+
+        /// <summary>
+        /// 知识检索
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<(int, List<KnowledgeRetrievalDataDto>)> KnowRetrievalAsync(KnowledgeRetrievalPagedListDto dto);
     }
 }

+ 80 - 0
src/Hotline.Application/Knowledge/KnowApplication.cs

@@ -257,5 +257,85 @@ namespace Hotline.Application.Knowledge
             var entity = dto.Adapt<KnowledgeHotWord>();
             await _knowledgeHotWordRepository.AddAsync(entity, requestAborted);
         }
+
+        public async Task<(int, List<KnowledgeRetrievalDataDto>)> KnowRetrievalAsync(KnowledgeRetrievalPagedListDto dto)
+        {
+            var typeSpliceName = string.Empty;
+            var hotspotHotSpotFullName = string.Empty;
+            if (!string.IsNullOrEmpty(dto.KnowledgeTypeId))
+            {
+                var type = await _knowledgeTypeRepository.GetAsync(x => x.Id == dto.KnowledgeTypeId);
+                typeSpliceName = type?.SpliceName;
+            }
+            if (!string.IsNullOrEmpty(dto.HotspotId))
+            {
+                var hotspot = await _hotspotTypeRepository.GetAsync(x => x.Id == dto.HotspotId);
+                hotspotHotSpotFullName = hotspot?.HotSpotFullName;
+            }
+
+            var sugar = _knowledgeRepository
+                .Queryable(false, false, false)
+                .Includes(x => x.User)
+                .Includes(x => x.SystemOrganize)
+                .Includes(x => x.HotspotType)
+                .Where(x => x.IsDeleted == false)
+                .Where(x => x.Status == EKnowledgeStatus.OnShelf)
+                .Where(x => x.KnowledgeType.Any(t => t.KnowledgeType.KnowledgeTypeOrgs.Any(to => to.OrgId == _sessionContext.RequiredOrgId) || t.KnowledgeType.KnowledgeTypeOrgs.Any() == false))
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith(typeSpliceName)))
+                .WhereIF(!string.IsNullOrEmpty(hotspotHotSpotFullName), x => x.HotspotType.HotSpotFullName.EndsWith(hotspotHotSpotFullName!))
+                .WhereIF(!string.IsNullOrEmpty(dto.HotspotName), x => x.HotspotType.HotSpotFullName.EndsWith(dto.HotspotName!))
+                .WhereIF(!string.IsNullOrEmpty(dto.CreateOrgId), x => x.CreatorOrgId != null && x.CreatorOrgId.EndsWith(dto.CreateOrgId!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Attribution), x => x.Attribution == dto.Attribution!);
+            if (dto.Keyword.NotNullOrEmpty())
+            {
+                var keywords = dto.Keyword!.SplitKeywords();
+                var exp = Expressionable.Create<KnowledgeBase.Knowledge>();
+                foreach (var keyword in keywords)
+                {
+                    if (dto.RetrievalType == EKnowledgeRetrievalType.All)
+                        exp.Or(x => x.Title.Contains(keyword) || x.Content.Contains(keyword));
+                    if (dto.RetrievalType == EKnowledgeRetrievalType.Title)
+                        exp.Or(x => x.Title.Contains(keyword));
+                    if (dto.RetrievalType == EKnowledgeRetrievalType.Content)
+                        exp.Or(x => x.Content.Contains(keyword));
+                    if (dto.RetrievalType == EKnowledgeRetrievalType.Summary)
+                        exp.Or(x => x.Summary != null && x.Summary.Contains(keyword));
+                    if (dto.RetrievalType == EKnowledgeRetrievalType.KeyWord)
+                    {
+                        var keywordEntity = await _knowledgeWordRepository.GetAsync(m => m.Tag == keyword && m.IsEnable == 0);
+                        if (keywordEntity is null) continue;
+                        exp.Or(x => SqlFunc.JsonArrayAny(x.Keywords, keywordEntity.Id));
+                    }
+                }
+                sugar = sugar.Where(exp.ToExpression());
+            }
+            if (dto.Content.NotNullOrEmpty())
+            {
+                var keywords = dto.Content!.GetSegment();
+                var exp = Expressionable.Create<KnowledgeBase.Knowledge>();
+                 _knowledgeWordRepository.Queryable()
+                    .Where(m => keywords.Contains(m.Tag) && m.IsEnable == 0)
+                    .Select(m => m.Id)
+                    .ToList()
+                    .ForEach(m => 
+                        exp.Or(x => SqlFunc.JsonArrayAny(x.Keywords, m))
+                    );
+                sugar = sugar.Where(exp.ToExpression());
+            }
+
+            switch (dto.Sort)
+            {
+                case "2":
+                    sugar = sugar.OrderByDescending(p => p.CollectCount);
+                    break;
+                case "3":
+                    sugar = sugar.OrderByDescending(p => p.CreationTime);
+                    break;
+                default:
+                    sugar = sugar.OrderByDescending(p => p.PageView);
+                    break;
+            }
+            return await sugar.Select<KnowledgeRetrievalDataDto>().ToPagedListAsync(dto.PageIndex, dto.PageSize);
+        }
     }
 }

+ 5 - 0
src/Hotline.Application/Mappers/MapperConfigs.cs

@@ -1,4 +1,5 @@
 using Hotline.CallCenter.BlackLists;
+using Hotline.CallCenter.Tels.CallTelDomain;
 using Hotline.Import;
 using Hotline.JudicialManagement;
 using Hotline.Orders;
@@ -44,6 +45,10 @@ namespace Hotline.Application.Mappers
                 .Map(m => m.TelPwd, x => x.Password)
                 .Map(m =>m.Queue, x => x.QueueId);
 
+            config.ForType<QueryTelResponse, TelOutDto>()
+                .Map(m => m.TelPwd, x => x.Password)
+                .Map(m => m.Queue, x => x.QueueId);
+
             config.ForType<ExcelContent, Order>()
                 .Map(d => d.FirstVisitResult, x => x.VisitResult)
                 .IgnoreNullValues(true);

+ 234 - 154
src/Hotline.Application/Orders/OrderApplication.cs

@@ -67,6 +67,8 @@ using Hotline.Orders.Notifications;
 using Hotline.OrderTranspond;
 using XF.Utility.EnumExtensions;
 using Newtonsoft.Json;
+using static NPOI.SS.Format.CellNumberFormatter;
+using System.Linq;
 
 namespace Hotline.Application.Orders;
 
@@ -82,7 +84,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IOrderDomainService _orderDomainService;
     private readonly IWorkflowDomainService _workflowDomainService;
     private readonly ISessionContext _sessionContext;
-	private readonly IOrderRepository _orderRepository;
+    private readonly IOrderRepository _orderRepository;
 
     //private readonly ITimeLimitDomainService _timeLimitDomainService;
     private readonly IMapper _mapper;
@@ -108,8 +110,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly ICalcExpireTime _expireTime;
     private readonly IRepository<OrderObserve> _orderObserveRepository;
     private readonly IOrderTerminateRepository _orderTerminateRepository;
+    private readonly IRepository<OrderPublishHistory> _orderPublishHistoryRepository;
 
-	public OrderApplication(
+    public OrderApplication(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowDomainService workflowDomainService,
@@ -144,7 +147,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         ISessionContextProvider sessionContextProvider,
         IRepository<TranspondCityRawData> transpondCityRawDataRepository,
         IRepository<OrderObserve> orderObserveRepository,
-        IOrderTerminateRepository orderTerminateRepository)
+        IOrderTerminateRepository orderTerminateRepository,
+        IRepository<OrderPublishHistory> orderPublishHistoryRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -180,8 +184,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _transpondCityRawDataRepository = transpondCityRawDataRepository;
         _orderObserveRepository = orderObserveRepository;
         _orderTerminateRepository = orderTerminateRepository;
+        _orderPublishHistoryRepository = orderPublishHistoryRepository;
         _sessionContext = sessionContext;
-	}
+    }
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -206,6 +211,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         order.TimeLimitUnit = expiredTimeConfig.TimeType;
         order.ExpiredTime = expiredTimeConfig.ExpiredTime;
         order.NearlyExpiredTime = expiredTimeConfig.NearlyExpiredTime;
+        order.NearlyExpiredTimeOne = expiredTimeConfig.NearlyExpiredTimeOne;
         //TODO发送短信即将超期
         //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
         if (IsProDelay)
@@ -319,7 +325,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 
         return _orderRepository.Queryable(canView: !IsCenter).Includes(d => d.OrderDelays)
             .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
-                .Where(step => step.ExternalId == d.Id &&
+                .Where(step => step.ExternalId == d.Id && step.Status != EWorkflowStepStatus.Handled &&
                                ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) &&
                                  step.HandlerId == _sessionContextProvider.SessionContext.RequiredUserId) ||
                                 (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) &&
@@ -336,7 +342,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             //.Where(d => d.ExpiredTime != null &&
             //         d.Status != EOrderStatus.Filed && d.Status != EOrderStatus.Published && d.Status != EOrderStatus.Visited && stTime >= d.ExpiredTime.Value && stTime2 <= d.ExpiredTime.Value)
             .Where(d => d.Status < EOrderStatus.Filed && dateTime > d.NearlyExpiredTime && dateTime < d.ExpiredTime)
-            .OrderByDescending(d => d.CreationTime);
+            .OrderBy(d => d.NearlyExpiredTime);
     }
 
     // /// <summary>
@@ -380,7 +386,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 
         return _orderRepository.Queryable(canView: false).Includes(d => d.OrderDelays)
             .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
-                .Where(step => step.ExternalId == d.Id &&
+                .Where(step => step.ExternalId == d.Id && step.Status != EWorkflowStepStatus.Handled &&
                                ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) &&
                                  step.HandlerId == _sessionContextProvider.SessionContext.RequiredUserId) ||
                                 (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) &&
@@ -399,7 +405,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                           d.FiledTime >= d.ExpiredTime) ||
                          ((d.Status != EOrderStatus.Filed && d.Status != EOrderStatus.Published && d.Status != EOrderStatus.Visited) &&
                           stTime >= d.ExpiredTime.Value)))
-            .OrderByDescending(x => x.CreationTime);
+            .OrderBy(x => x.ExpiredTime);
     }
 
     //     /// <summary>
@@ -784,7 +790,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 
         if (dto.Data.LeaderSMSKey != null)
         {
-            var dic = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.LeaderSMS).First(m => m.Id == dto.Data.LeaderSMSKey);
+            var dic = _sysDicDataCacheManager.LeaderSMS.First(m => m.Id == dto.Data.LeaderSMSKey);
             _capPublisher.Publish(EventNames.HotlineLeaderSMS, new PublishLeaderSMSDto(order.Id, dic.DicDataName, dic.DicDataValue));
         }
 
@@ -944,6 +950,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 .Where(m => m.Id == item.Id)
                 .SetColumns(m => m.VisitState == EVisitState.SMSVisiting)
                 .SetColumns(m => m.VisitType == EVisitType.SmsVisit)
+                .SetColumns(m => m.EmployeeId == _sessionContextProvider.SessionContext.RequiredUserId)
                 .ExecuteCommandAsync();
 
             // 发送短信后推送一个 48小时的延迟消息队列. 当消息队列收到消息时, 判断用户是否回复了, 如果未回复短信就 默认满意
@@ -955,6 +962,36 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
     }
 
+    //public async Task<List<OrderDto>> QueryOrdersAsync(QueryOrderDto dto, int queryIndex)
+    //{
+    //    var query = QueryOrders(dto);
+    //    var items = await query.ToFixedListAsync(queryIndex);
+
+    //    var dtoItems = new List<OrderDto>();
+    //    foreach (var item in items)
+    //    {
+    //        var orderDto = item.Adapt<OrderDto>();
+    //        var visitEntity = item.OrderVisits
+    //                        .OrderByDescending(m => m.CreationTime)
+    //                        .FirstOrDefault();
+    //        if (visitEntity != null)
+    //        {
+    //            var now = visitEntity?.NowEvaluate;
+    //            if (now != null) 
+    //            {
+    //                orderDto.OrgEvaluate = now;
+    //                orderDto.OrgEvaluateValue = now.Value;
+    //            }
+    //            orderDto.SeatEvaluate = visitEntity.OrderVisitDetails.Where(m => m.VisitTarget == EVisitTarget.Seat)
+    //                .FirstOrDefault()?.SeatEvaluate;
+    //        }
+
+    //        orderDto.OrderVisits = null;
+    //        dtoItems.Add(orderDto);
+    //    }
+    //    return dtoItems;
+    //}
+
     public ISugarQueryable<Order> QueryOrders(QueryOrderDto dto)
     {
         var isCenter = _sessionContextProvider.SessionContext.OrgIsCenter;
@@ -972,73 +1009,78 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
 
         return query
-            .Includes(x => x.OrderScreens)
-            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!)) //标题
-            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), d => d.ProvinceNo == dto.ProvinceNo) //省本地编号
-            .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), d => d.ReceiveProvinceNo == dto.ReceiveProvinceNo) //省编号
-            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No) //工单编码
-            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType) //受理类型
-                                                                                                     //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
-            .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannelCode == dto.Channel)
-            //.WhereIF(dto.Channels.Any(), d => dto.Channels.Contains(d.SourceChannelCode)) //来源渠道
-            //.WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId)) //热点类型
-            .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.HotspotSpliceName != null && d.HotspotSpliceName.Contains(dto.Hotspot))
-            .WhereIF(!string.IsNullOrEmpty(dto.TransferPhone), d => d.TransferPhone == dto.TransferPhone!) //转接号码
-                                                                                                           //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
-                                                                                                           //.WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.ActualHandleOrgCode)) //接办部门
-                                                                                                           //.WhereIF(!string.IsNullOrEmpty(dto.OrgId), d => d.CurrentHandleOrgId == dto.OrgId)//接办部门
-            .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), d => d.OrgLevelOneName.Contains(dto.OrgLevelOneName)) //一级部门
-            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
-            .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
-            .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
-            .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
-                                                                                               //.WhereIF(dto.EmergencyLevels.Any(), d => dto.EmergencyLevels.Contains(d.EmergencyLevel))  //紧急程度
-            .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), d => d.FromPhone == dto.FromPhone) //来电号码
-            .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.Contact == dto.PhoneNo!) //联系电话
-                                                                                         //.WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), d => d.PushTypeCode == dto.PushTypeCode) //推送分类
-            .WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), x => x.OrderPushTypes.Any(opt => opt.PushTypeCode == dto.PushTypeCode)) //推送分类
-            .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart) //超期时间开始
-            .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.ExpiredTime <= dto.ExpiredTimeEnd) //超期时间结束
-                                                                                            //.WhereIF(dto.Statuses.Any(), d => dto.Statuses.Contains(d.Status))  //工单状态
-            .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status) //工单状态
-                                                                       //.WhereIF(dto.Statuses.Any(d => d == EOrderStatus.SpecialToUnAccept), d => d.Status <= EOrderStatus.SpecialToUnAccept)
-            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandlerName), d => d.ActualHandlerName == dto.ActualHandlerName) //接办人
-            .WhereIF(dto.IsScreen == true, d => d.OrderScreens.Any(x => x.Status != EScreenStatus.Refuse)) //有甄别
-            .WhereIF(dto.IsScreen == false, d => !d.OrderScreens.Any(x => x.Status != EScreenStatus.Refuse)) //无甄别
-            .WhereIF(!string.IsNullOrEmpty(dto.CurrentStepCode), d => d.CurrentStepCode == dto.CurrentStepCode) //当前办理节点
-            .WhereIF(dto.ActualHandleTimeStart.HasValue, d => d.ActualHandleTime >= dto.ActualHandleTimeStart) //办结时间开始
-            .WhereIF(dto.ActualHandleTimeEnd.HasValue, d => d.ActualHandleTime <= dto.ActualHandleTimeEnd) //办结时间结束
-            .WhereIF(dto.IsOverTime == true,
-                d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) ||
-                     (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //是 超期
-            .WhereIF(dto.IsOverTime == false,
-                d => (d.ExpiredTime > DateTime.Now && d.Status < EOrderStatus.Filed) ||
-                     (d.ExpiredTime > d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //否 超期
-            .WhereIF(dto.IdentityType != null, d => d.IdentityType == dto.IdentityType) //来电主体
-            .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName == dto.FromName) //来电人姓名
-                                                                                           //.WhereIF(dto.AreaCodes.Any(), d => dto.AreaCodes.Contains(d.AreaCode)) //区域
-                                                                                           //.WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode)//区域
-            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00") > 0,
-                d => d.AreaCode.StartsWith(SqlFunc.Substring(dto.AreaCode, 0, 4)))
-            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00") <= 0, d => d.AreaCode.StartsWith(dto.AreaCode))
-            .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, d => d.Source == ESource.ProvinceStraight)
-            .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, d => d.Source != ESource.ProvinceStraight)
-            .WhereIF(!string.IsNullOrEmpty(dto.SensitiveWord), d => SqlFunc.JsonArrayAny(d.Sensitive, dto.SensitiveWord))
-            .WhereIF(dto.IsSensitiveWord.HasValue && dto.IsSensitiveWord == true,
-                d => d.Sensitive != null && SqlFunc.JsonArrayLength(d.Sensitive) > 0)
-            .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
-            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "1", d => d.Source == ESource.ProvinceStraight &&
-                d.SourceChannelCode == "SZMHD" && d.IsProvince == false) //政民互动直派
-            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "2", d => d.Source == ESource.ProvinceStraight &&
-                d.SourceChannelCode == "SZMHD" && d.IsProvince == true) //政民互动
-            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "3", d => d.Source == ESource.ProvinceStraight &&
-                d.SourceChannelCode == "S12345" && d.IsProvince == true) //省12345
-            .WhereIF(!string.IsNullOrEmpty(dto.ContentRetrieval),
-                d => d.Title.Contains(dto.ContentRetrieval) || d.Content.Contains(dto.ContentRetrieval))
-            .WhereIF(dto.FiledType is FiledType.CenterFiled, d => d.ProcessType == EProcessType.Zhiban)
-            .WhereIF(dto.FiledType is FiledType.OrgFiled, d => d.ProcessType == EProcessType.Jiaoban)
-            .WhereIF(!string.IsNullOrEmpty(dto.OrderTagCode),d=>d.OrderTagCode == dto.OrderTagCode)
-            .OrderByDescending(d => d.CreationTime);
+             .Includes(x => x.OrderScreens)
+             .Includes(x => x.OrderVisits.Where(m => m.VisitState == EVisitState.Visited).ToList())
+             .Includes(x => x.OrderVisits.Where(m => m.VisitState == EVisitState.Visited).ToList(), ovd => ovd.OrderVisitDetails)
+             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!)) //标题
+             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), d => d.ProvinceNo == dto.ProvinceNo) //省本地编号
+             .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), d => d.ReceiveProvinceNo == dto.ReceiveProvinceNo) //省编号
+             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No) //工单编码
+             .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType) //受理类型
+                                                                                                      //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型
+             .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannelCode == dto.Channel)
+             //.WhereIF(dto.Channels.Any(), d => dto.Channels.Contains(d.SourceChannelCode)) //来源渠道
+             //.WhereIF(dto.HotspotIds.Any(), d => dto.HotspotIds.Contains(d.HotspotId)) //热点类型
+             .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.HotspotSpliceName != null && d.HotspotSpliceName.Contains(dto.Hotspot))
+             .WhereIF(!string.IsNullOrEmpty(dto.TransferPhone), d => d.TransferPhone == dto.TransferPhone!) //转接号码
+                                                                                                            //.WhereIF(dto.OrgCodes.Any(), d => d.Workflow.Assigns.Any(s => dto.OrgCodes.Contains(s.OrgCode)))
+                                                                                                            //.WhereIF(dto.OrgCodes.Any(), d => dto.OrgCodes.Contains(d.ActualHandleOrgCode)) //接办部门
+                                                                                                            //.WhereIF(!string.IsNullOrEmpty(dto.OrgId), d => d.CurrentHandleOrgId == dto.OrgId)//接办部门
+             .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), d => d.OrgLevelOneName.Contains(dto.OrgLevelOneName)) //一级部门
+             .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
+             .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
+             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
+             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
+                                                                                                //.WhereIF(dto.EmergencyLevels.Any(), d => dto.EmergencyLevels.Contains(d.EmergencyLevel))  //紧急程度
+             .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), d => d.FromPhone == dto.FromPhone) //来电号码
+             .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.Contact == dto.PhoneNo!) //联系电话
+                                                                                          //.WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), d => d.PushTypeCode == dto.PushTypeCode) //推送分类
+             .WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), x => x.OrderPushTypes.Any(opt => opt.PushTypeCode == dto.PushTypeCode)) //推送分类
+             .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart) //超期时间开始
+             .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.ExpiredTime <= dto.ExpiredTimeEnd) //超期时间结束
+                                                                                             //.WhereIF(dto.Statuses.Any(), d => dto.Statuses.Contains(d.Status))  //工单状态
+             .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status) //工单状态
+                                                                        //.WhereIF(dto.Statuses.Any(d => d == EOrderStatus.SpecialToUnAccept), d => d.Status <= EOrderStatus.SpecialToUnAccept)
+             .WhereIF(!string.IsNullOrEmpty(dto.ActualHandlerName), d => d.ActualHandlerName == dto.ActualHandlerName) //接办人
+             .WhereIF(dto.IsScreen == true, d => d.OrderScreens.Any(x => x.Status != EScreenStatus.Refuse)) //有甄别
+             .WhereIF(dto.IsScreen == false, d => !d.OrderScreens.Any(x => x.Status != EScreenStatus.Refuse)) //无甄别
+             .WhereIF(!string.IsNullOrEmpty(dto.CurrentStepCode), d => d.CurrentStepCode == dto.CurrentStepCode) //当前办理节点
+             .WhereIF(dto.ActualHandleTimeStart.HasValue, d => d.ActualHandleTime >= dto.ActualHandleTimeStart) //办结时间开始
+             .WhereIF(dto.ActualHandleTimeEnd.HasValue, d => d.ActualHandleTime <= dto.ActualHandleTimeEnd) //办结时间结束
+             .WhereIF(dto.IsOverTime == true,
+                 d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) ||
+                      (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //是 超期
+             .WhereIF(dto.IsOverTime == false,
+                 d => (d.ExpiredTime > DateTime.Now && d.Status < EOrderStatus.Filed) ||
+                      (d.ExpiredTime > d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //否 超期
+             .WhereIF(dto.IdentityType != null, d => d.IdentityType == dto.IdentityType) //来电主体
+             .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName == dto.FromName) //来电人姓名
+                                                                                            //.WhereIF(dto.AreaCodes.Any(), d => dto.AreaCodes.Contains(d.AreaCode)) //区域
+                                                                                            //.WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode)//区域
+             .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00") > 0,
+                 d => d.AreaCode.StartsWith(SqlFunc.Substring(dto.AreaCode, 0, 4)))
+             .WhereIF(!string.IsNullOrEmpty(dto.AreaCode) && dto.AreaCode.LastIndexOf("00") <= 0, d => d.AreaCode.StartsWith(dto.AreaCode))
+             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, d => d.Source == ESource.ProvinceStraight)
+             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, d => d.Source != ESource.ProvinceStraight)
+             .WhereIF(!string.IsNullOrEmpty(dto.SensitiveWord), d => SqlFunc.JsonArrayAny(d.Sensitive, dto.SensitiveWord))
+             .WhereIF(dto.IsSensitiveWord.HasValue && dto.IsSensitiveWord == true,
+                 d => d.Sensitive != null && SqlFunc.JsonArrayLength(d.Sensitive) > 0)
+             .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
+             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "1", d => d.Source == ESource.ProvinceStraight &&
+                 d.SourceChannelCode == "SZMHD" && d.IsProvince == false) //政民互动直派
+             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "2", d => d.Source == ESource.ProvinceStraight &&
+                 d.SourceChannelCode == "SZMHD" && d.IsProvince == true) //政民互动
+             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "3", d => d.Source == ESource.ProvinceStraight &&
+                 d.SourceChannelCode == "S12345" && d.IsProvince == true) //省12345
+             .WhereIF(!string.IsNullOrEmpty(dto.ContentRetrieval),
+                 d => d.Title.Contains(dto.ContentRetrieval) || d.Content.Contains(dto.ContentRetrieval) || d.FileOpinion.Contains(dto.ContentRetrieval) || d.ActualOpinion.Contains(dto.ContentRetrieval))
+             .WhereIF(dto.IsSgin.HasValue && dto.IsSgin == true, d => d.CurrentStepAcceptTime != null)
+             .WhereIF(dto.IsSgin.HasValue && dto.IsSgin == false, d => d.CurrentStepAcceptTime == null)
+             .WhereIF(dto.FiledType is FiledType.CenterFiled, d => d.ProcessType == EProcessType.Zhiban)
+             .WhereIF(dto.FiledType is FiledType.OrgFiled, d => d.ProcessType == EProcessType.Jiaoban)
+             .WhereIF(!string.IsNullOrEmpty(dto.OrderTagCode), d => d.OrderTagCode == dto.OrderTagCode)
+             .OrderByDescending(d => d.CreationTime);
+
     }
 
     /// <summary>
@@ -2259,41 +2301,42 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     }
 
     #region 甄别
-    public ISugarQueryable<OrderScreen> OrderScreenList(ScreenListDto dto) {
-	    var handler = dto.TabStatus is EScreenStatus.Apply;
-	    var isAdmin = _orderDomainService.IsCheckAdmin();
-		ISugarQueryable<OrderScreen> query;
-
-		if (dto.source == 1)
-		{
-			query = _orderScreenRepository.Queryable(hasHandled: !handler, isAdmin: isAdmin);
-		}
-		else
-		{
-			query = _orderScreenRepository.Queryable(isAdmin: isAdmin)
-				.WhereIF(!isAdmin, x => x.CreatorOrgId.StartsWith(_sessionContext.RequiredOrgId));
-		}
-
-		query = query
-			.Includes(d => d.Order)
-			.Includes(d => d.VisitDetail)
-			.Includes(d => d.Visit, v => v.Order)
-			.Includes(d => d.Workflow)
-			.Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == _sessionContext.UserId).OrderByDescending(sd => sd.AuditTime).Take(1)
-				.ToList())
-			.WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Visit.Order.Title.Contains(dto.Title!))
-			.WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Visit.Order.No.Contains(dto.No!));
-		if (dto.TabStatus is EScreenStatus.Apply)
-		{
-			query.Where(d =>
-				(d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval ||
-				 (d.Status == EScreenStatus.SendBack && d.SendBackApply == false)));
-		}
-
-		if (dto.TabStatus.HasValue && dto.Status == EScreenStatus.MyHandle)
-		{
-			query.Where(d => (d.Status != EScreenStatus.Apply));
-		}
+    public ISugarQueryable<OrderScreen> OrderScreenList(ScreenListDto dto)
+    {
+        var handler = dto.TabStatus is EScreenStatus.Apply;
+        var isAdmin = _orderDomainService.IsCheckAdmin();
+        ISugarQueryable<OrderScreen> query;
+
+        if (dto.source == 1)
+        {
+            query = _orderScreenRepository.Queryable(hasHandled: !handler, isAdmin: isAdmin);
+        }
+        else
+        {
+            query = _orderScreenRepository.Queryable(isAdmin: isAdmin)
+                .WhereIF(!isAdmin, x => x.CreatorOrgId.StartsWith(_sessionContext.RequiredOrgId));
+        }
+
+        query = query
+            .Includes(d => d.Order)
+            .Includes(d => d.VisitDetail)
+            .Includes(d => d.Visit, v => v.Order)
+            .Includes(d => d.Workflow)
+            .Includes(d => d.ScreenDetails.Where(sd => sd.AuditUserId == _sessionContext.UserId).OrderByDescending(sd => sd.AuditTime).Take(1)
+                .ToList())
+            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Visit.Order.Title.Contains(dto.Title!))
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Visit.Order.No.Contains(dto.No!));
+        if (dto.TabStatus is EScreenStatus.Apply)
+        {
+            query.Where(d =>
+                (d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval ||
+                 (d.Status == EScreenStatus.SendBack && d.SendBackApply == false)));
+        }
+
+        if (dto.TabStatus.HasValue && dto.Status == EScreenStatus.MyHandle)
+        {
+            query.Where(d => (d.Status != EScreenStatus.Apply));
+        }
         return query
             .WhereIF(dto.DataScope is 1, x => x.CreatorId == _sessionContext.RequiredUserId)
             .WhereIF(dto.Status.HasValue, x => x.Status == dto.Status)
@@ -2324,17 +2367,17 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), x => x.Order!.FromPhone! == dto.FromPhone!)
             .OrderByIF(dto is { SortRule: 0, SortField: "creationTime" }, x => x.CreationTime, OrderByType.Asc)
             .OrderByIF(dto is { SortRule: 1, SortField: "creationTime" } || dto.SortRule is null, x => x.CreationTime, OrderByType.Desc);
-	}
-	#endregion
-
-	#region private
-	/// <summary>
-	/// 接受外部工单(除省平台)
-	/// </summary>
-	/// <param name="dto"></param>
-	/// <param name="cancellationToken"></param>
-	/// <returns></returns>
-	private async Task<AddOrderResponse> ReceiveOrderFromOtherPlatformAsync(AddOrderDto dto, List<FileDto> files, CancellationToken cancellationToken)
+    }
+    #endregion
+
+    #region private
+    /// <summary>
+    /// 接受外部工单(除省平台)
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    private async Task<AddOrderResponse> ReceiveOrderFromOtherPlatformAsync(AddOrderDto dto, List<FileDto> files, CancellationToken cancellationToken)
     {
         if (string.IsNullOrEmpty(dto.ExternalId))
             throw new UserFriendlyException("工单外部编号不能为空");
@@ -2425,9 +2468,45 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 
             //特提(撤回至发起)
             if (!string.IsNullOrEmpty(order.WorkflowId))
+            {
                 //await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, order.Status >= EOrderStatus.Filed, cancellationToken);
-                await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, "省工单重派", order.Status >= EOrderStatus.Filed,
+                var isPaiDan = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, "省工单重派", order.Status >= EOrderStatus.Filed,
                     order.ExpiredTime, cancellationToken);
+                order.FileEmpty();
+                if (isPaiDan)
+                {
+                    order.Status = EOrderStatus.Handling;
+                }
+                else
+                {
+                    order.Status = EOrderStatus.WaitForAccept;
+                }
+                await _orderRepository.UpdateAsync(order, cancellationToken);
+                //处理回访和发布信息
+
+                var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == order.Id);
+                if (publish != null)
+                {
+                    var publishHistory = _mapper.Map<OrderPublishHistory>(publish);
+                    publishHistory.OrderPublishId = publish.Id;
+                    publishHistory.ArrangeTitleAfter = publish.ArrangeTitle;
+                    publishHistory.ArrangeTitleBefor = publish.ArrangeTitle;
+                    publishHistory.ArrangeContentAfter = publish.ArrangeContent;
+                    publishHistory.ArrangeContentBefor = publish.ArrangeContent;
+                    publishHistory.ArrangeOpinionAfter = publish.ArrangeOpinion;
+                    publishHistory.ArrangeOpinionBefor = publish.ArrangeOpinion;
+                    await _orderPublishHistoryRepository.AddAsync(publishHistory, cancellationToken);
+                    await _orderPublishRepository.RemoveAsync(publish, false, cancellationToken);
+                }
+
+                var visit = await _orderVisitRepository.GetAsync(x => x.OrderId == order.Id && x.VisitState != EVisitState.None);
+                if (visit != null)
+                {
+                    visit.VisitState = EVisitState.None;
+                    await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
+                }
+
+            }
             //await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, cancellationToken);
         }
         return _mapper.Map<AddOrderResponse>(order);
@@ -2598,11 +2677,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    public ISugarQueryable<OrderTerminate> OrderTerminateList(OrderTerminateListDto dto) 
+    public ISugarQueryable<OrderTerminate> OrderTerminateList(OrderTerminateListDto dto)
     {
-	    var handler = dto.AuditStatus is 2;
-		var isAdmin = _orderDomainService.IsCheckAdmin();
-		return _orderTerminateRepository.Queryable(hasHandled: handler, isAdmin: isAdmin)
+        var handler = dto.AuditStatus is 2 or 0 || dto.QueryType is 0;
+        var isAdmin = _orderDomainService.IsCheckAdmin();
+        return _orderTerminateRepository.Queryable(hasHandled: handler, isAdmin: isAdmin)
             .Includes(d => d.Order)
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Order.No!.Contains(dto.No!))
             .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Order.Title!.Contains(dto.Title!))
@@ -2610,12 +2689,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 d => d.CreationTime >= dto.ApplyStartTime && d.CreationTime <= dto.ApplyEndTime)
             //.WhereIF(dto.AuditStatus is 1 , d=>d.Status == ETerminateStatus.Approval || d.Status == ETerminateStatus.SendBack )
             //.WhereIF(dto.AuditStatus is 2, d => d.Status == ETerminateStatus.End || dto.Status == ETerminateStatus.Refuse)
-			.WhereIF(dto.QueryType is 1, d => d.CreatorId == _sessionContextProvider.SessionContext.UserId)
+            .WhereIF(dto.QueryType is 1, d => d.CreatorId == _sessionContextProvider.SessionContext.UserId)
             .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)
             .WhereIF(dto.StartTime.HasValue && dto.EndTime.HasValue, d => d.Order.StartTime >= dto.StartTime && d.Order.StartTime <= dto.EndTime)
             .OrderByDescending(d => d.CreationTime);
     }
-    #endregion
 
     public ISugarQueryable<OrderListOutDto> QueryWaitedForSeat(QueryOrderWaitedDto dto)
     {
@@ -2630,34 +2708,34 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         if (dto.IsHandled.HasValue)
         {
             var hasHandled = dto.IsHandled.Value;
-            query = query.Where(d => SqlFunc.Subqueryable<WorkflowTrace>() 
-            .Where(step => step.ExternalId == d.Id && 
-                (hasHandled || step.Status != EWorkflowStepStatus.Handled) && 
-                (!hasHandled || step.Status == EWorkflowStepStatus.Handled && 
-                step.TraceState != EWorkflowTraceState.StepRemoveByPrevious) && 
-                ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && 
-                step.HandlerId == _sessionContext.RequiredUserId) || 
-                (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && 
-                step.HandlerOrgId == _sessionContext.RequiredOrgId) || 
-                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && 
-                _sessionContext.Roles.Contains(step.RoleId)))).Any() || 
-                (string.IsNullOrEmpty(d.WorkflowId) && 
-                (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId))
+            query = query.Where(d => SqlFunc.Subqueryable<WorkflowTrace>()
+            .Where(step => step.ExternalId == d.Id &&
+                (hasHandled || step.Status != EWorkflowStepStatus.Handled) &&
+                (!hasHandled || step.Status == EWorkflowStepStatus.Handled &&
+                step.TraceState != EWorkflowTraceState.StepRemoveByPrevious) &&
+                ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) &&
+                step.HandlerId == _sessionContextProvider.SessionContext.RequiredUserId) ||
+                (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) &&
+                step.HandlerOrgId == _sessionContextProvider.SessionContext.RequiredOrgId) ||
+                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) &&
+                _sessionContextProvider.SessionContext.Roles.Contains(step.RoleId)))).Any() ||
+                (string.IsNullOrEmpty(d.WorkflowId) &&
+                (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContextProvider.SessionContext.RequiredUserId))
             );
         }
 
         return query
             // 交办件:已派单其他节点的工单,该选项卡下工单若办结就不显示
-            .WhereIF(dto.TypeCode.HasValue && dto.TypeCode == 1, d => d.ProcessType == EProcessType.Jiaoban && d.Status < EOrderStatus.Filed)
+            .WhereIF(dto.TypeCode.HasValue == true && dto.TypeCode == 1, d => d.ProcessType == EProcessType.Jiaoban && d.Status < EOrderStatus.Filed)
             // 办结件:当前登录坐席作为最初受理人已办结的工单
-            .WhereIF(dto.TypeCode.HasValue && dto.TypeCode == 2, d=> d.Status >= EOrderStatus.Filed && d.AcceptorId == _sessionContext.RequiredUserId)
-            .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince) 
-            .WhereIF(dto.IsHandled.HasValue, d => handleStatuses.Contains(d.Status)) 
-            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword!)) 
-            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No) 
-            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode) 
-            .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue) 
-            .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => d.CounterSignType == null) 
+            .WhereIF(dto.TypeCode.HasValue == true && dto.TypeCode == 2, d => d.Status >= EOrderStatus.Filed && d.AcceptorId == _sessionContextProvider.SessionContext.RequiredUserId)
+            .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
+            .WhereIF(dto.IsHandled.HasValue, d => handleStatuses.Contains(d.Status))
+            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword!))
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
+            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode)
+            .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
+            .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => d.CounterSignType == null)
             .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == true,
                d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) ||
                     (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //超期 未办
@@ -2665,14 +2743,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now) //即将超期 未办
             .WhereIF(dto.StartTime.HasValue, d => d.CreationTime >= dto.StartTime)
             .WhereIF(dto.EndTime.HasValue, d => d.CreationTime <= dto.EndTime)
-           //.Where(d => (string.IsNullOrEmpty(d.WorkflowId) && (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)))
-           //.Where(d => string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)
+            //.Where(d => (string.IsNullOrEmpty(d.WorkflowId) && (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)))
+            //.Where(d => string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)
             .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
             .Where(x => x.Source < ESource.MLSQ || x.Source > ESource.WZSC)
-            .Where(x => x.Status != EOrderStatus.BackToProvince && x.Status < EOrderStatus.Filed)
+            .Where(x => x.Status != EOrderStatus.BackToProvince)
+            .WhereIF(dto.TypeCode.HasValue == false, m => m.Status < EOrderStatus.Filed)
             .OrderBy(d => d.Status)
             .OrderByIF(dto.IsHandled == true, d => d.StartTime, OrderByType.Desc)
             .OrderByIF(dto.IsHandled == false, d => new { IsUrgent = d.IsUrgent, CreationTime = d.CreationTime }, OrderByType.Desc)
             .Select<OrderListOutDto>();
     }
+    #endregion
 }

+ 1 - 1
src/Hotline.Application/Orders/OrderVisitApplication.cs

@@ -24,7 +24,7 @@ public class OrderVisitApplication : IOrderVisitApplication, IScopeDependency
         var query = _orderVisitRepository
             .Queryable()
             .WhereIF(dto.EmployeeName.NotNullOrEmpty(), m => m.Employee.Name.Contains(dto.EmployeeName))
-            .Where(m => m.CreationTime >= dto.StartTime && m.CreationTime <= dto.EndTime && m.VisitType != null)
+            .Where(m => m.VisitTime >= dto.StartTime && m.VisitTime <= dto.EndTime && m.VisitType != null && m.EmployeeId != "" && m.EmployeeId != null)
             .GroupBy(m => m.EmployeeId)
             .Select(m => new OrderVisitQuantityOutDto
             {

+ 3 - 3
src/Hotline.Application/StatisticalReport/CallReport/CallReportApplicationBase.cs

@@ -161,12 +161,12 @@ public abstract class CallReportApplicationBase : ICallReportApplication
         int connectByeTimes = _systemSettingCacheManager.ConnectByeTimes;
 
         var callData = await _callNativeRepository.Queryable()
-                .Where(p => p.CreationTime >= dto.StartTime && p.CreationTime <= dto.EndTime)
-                 .GroupBy(p => p.CreationTime.ToString("yyyy-MM-dd"))
+                .Where(p => p.CreationTime >= dto.StartTime && p.CreationTime <= dto.EndTime && p.GroupId == "1")
+                .GroupBy(p => p.CreationTime.ToString("yyyy-MM-dd"))
                 .Select(p => new QueryCallsDetailStatistics
                 {
                     Date = p.CreationTime.ToString("yyyy-MM-dd"),
-                    InTotal = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In, 1, 0)),//呼入总量
+                    //InTotal = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In, 1, 0)),//呼入总量
                     InConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.AnsweredTime != null, 1, 0)),//呼入接通量
                     NotAcceptedHang = SqlFunc.AggregateSum(SqlFunc.IIF(p.Duration == 0 && p.RingDuration <= noConnectByeTimes && p.RingDuration > 0 && p.Direction == ECallDirection.In, 1, 0)), //呼入队列挂断
                     InNotAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(p.Duration == 0 && p.TelNo != "0" && p.Direction == ECallDirection.In, 1, 0)), // 挂机量

+ 38 - 3
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -31,6 +31,7 @@ using MapsterMapper;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
+using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -297,8 +298,18 @@ namespace Hotline.Application.Subscribers
             }
             else
             {
-                await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion,
+                var isPaiDan = await _workflowDomainService.RecallToCenterFirstToSendAsync(order.WorkflowId, dto.Opinion,
                     order.Status >= EOrderStatus.Filed, order.ExpiredTime, cancellationToken);
+                order.FiledTime = null;
+                if (isPaiDan)
+                {
+                    order.Status = EOrderStatus.Handling;
+                }
+                else
+                {
+                    order.Status = EOrderStatus.WaitForAccept;
+                }
+                await _orderRepository.UpdateAsync(order, cancellationToken);
             }
         }
 
@@ -487,7 +498,27 @@ namespace Hotline.Application.Subscribers
                 orderScreen.FileJson =
                     await _fileRepository.AddFileAsync(dto.Files, orderScreen.Id, "", cancellationToken);
             await _orderScreenRepository.UpdateAsync(orderScreen, cancellationToken);
-        }
+            //多部门甄别回访 省上结果处理
+            if (orderScreen.Status == Share.Enums.Order.EScreenStatus.End)
+            {
+                var visitDetails = await _orderVisitedDetailRepository.Queryable().Where(x=>x.OrderVisit.OrderId == orderScreen.OrderId && x.VisitTarget == EVisitTarget.Org  && (
+	                SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
+	                SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
+	                SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
+	                SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
+                ) && x.Id != orderScreen.VisitDetailId).ToListAsync();
+                foreach (var visitDetail in visitDetails)
+                {
+					if (visitDetail != null)
+					{
+						var screenSatisfy = new Kv() { Key = "-1", Value = "视为满意" };
+						visitDetail.OrgProcessingResults = screenSatisfy;
+						visitDetail.OrgHandledAttitude = screenSatisfy;
+						await _orderVisitedDetailRepository.UpdateAsync(visitDetail, cancellationToken);
+					}
+				}
+            }
+		}
 
         /// <summary>
         /// 更新工单受理附件
@@ -542,6 +573,11 @@ namespace Hotline.Application.Subscribers
                         orderVisit.IsCanHandle = orgProcessingResults.Key == "2";
                         orderVisit.IsCanAiVisit = false;
                         orderVisit.NowEvaluate = orgProcessingResults;
+                        if (_appOptions.Value.IsZiGong)
+                        {
+                            orderVisit.EmployeeId = _systemSettingCacheManager.DefaultVisitEmployeeId;
+                        }
+
                         await _orderVisitRepository.UpdateAsync(orderVisit, cancellationToken);
                         //子表
                         for (int i = 0; i < orderVisit.OrderVisitDetails.Count; i++)
@@ -628,7 +664,6 @@ namespace Hotline.Application.Subscribers
                     orderVisit.VisitTime = dto.VisitTime;
                     orderVisit.VisitType = dto.VisitType;
                     orderVisit.IsCanAiVisit = false;
-                    orderVisit.IsCanHandle = false;
                     var VisitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction)
                         .Where(x => x.DicDataValue == dto.OrgProcessingResults).Select(m => new Kv { Key = m.DicDataValue, Value = m.DicDataName })
                         .FirstOrDefault();

+ 84 - 79
src/Hotline.Application/Subscribers/InternalCapSubscriber.cs

@@ -238,96 +238,101 @@ namespace Hotline.Application.Subscribers
         [CapSubscribe(EventNames.HotlineOrderAutomaticDelay)]
         public async Task AutomaticDelay(PublishAutomaticDelayDto dto, CancellationToken cancellationToken) 
         {
-            var order = await _orderRepository.GetAsync(dto.OrderId, cancellationToken);
-			var expiredTime = DateTime.Now.AddHours(1);
-			if (order != null && order.Status < EOrderStatus.Filed && order.ExpiredTime >= DateTime.Now)
-			{
-				if (order.ExpiredTime <= expiredTime)
+            var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+            if (bool.Parse(enabled))
+            {
+				var order = await _orderRepository.GetAsync(dto.OrderId, cancellationToken);
+				var expiredTime = DateTime.Now.AddHours(1);
+				if (order != null && order.Status < EOrderStatus.Filed && order.ExpiredTime >= DateTime.Now)
 				{
-                    var delayAny= await _orderDelayRepository.Queryable().Where(x => x.OrderId == order.Id && x.DelayState == EDelayState.Examining).AnyAsync();
-                    if (!delayAny)
-                    {
-						var delays = await _orderDelayRepository.Queryable().Where(x => x.OrderId == order.Id && x.AutomaticDelayNum > 0).ToListAsync(cancellationToken);
-						var delay = new OrderDelay();
-						if (delays.Any())
+					if (order.ExpiredTime <= expiredTime)
+					{
+						var delayAny = await _orderDelayRepository.Queryable().Where(x => x.OrderId == order.Id && x.DelayState == EDelayState.Examining).AnyAsync();
+						if (!delayAny)
 						{
-							delay = delays.First();
-							delay.AfterDelay = (await _expireTime
-								.CalcEndTime(delay.BeforeDelay.Value, delay.DelayUnit, delay.DelayNum, order.AcceptTypeCode))?.EndTime; //todo
-							await _orderDelayRepository.Updateable().SetColumns(x => new OrderDelay() { AutomaticDelayNum = x.AutomaticDelayNum + 1, ApplyDelayTime = DateTime.Now,AfterDelay = delay.AfterDelay })
-								.Where(x => x.Id == delay.Id).ExecuteCommandAsync(cancellationToken);
-						}
-						else
-						{
-                            delay.OrderId = order.Id;
-							delay.EmployeeId = "";
-							delay.EmployeeName = "系统自动延期";
-							delay.ApplyOrgName = OrgSeedData.CenterName;
-							delay.ApplyOrgCode = OrgSeedData.CenterId;
-							delay.DelayApplyType = EDelayApplyType.LocalApply;
-							delay.BeforeDelay = order.ExpiredTime;
-							delay.DelayState = EDelayState.Pass;
-							delay.DelayReason = "系统自动延期";
-							delay.ApplyDelayTime = DateTime.Now;
-							delay.No = order.No;
-							delay.AutomaticDelayNum = 1;
-							delay.DelayNum = 1;
-							delay.DelayUnit = Share.Enums.Settings.ETimeType.WorkDay;
-							delay.IsProDelay = false;
-                            delay.CreatorOrgId = OrgSeedData.CenterId;
-							delay.CreatorOrgName = OrgSeedData.CenterName;
-							delay.CreatorName = "系统自动延期";
-							if (delay.BeforeDelay != null)
+							var delays = await _orderDelayRepository.Queryable().Where(x => x.OrderId == order.Id && x.AutomaticDelayNum > 0).ToListAsync(cancellationToken);
+							var delay = new OrderDelay();
+							if (delays.Any())
 							{
+								delay = delays.First();
 								delay.AfterDelay = (await _expireTime
 									.CalcEndTime(delay.BeforeDelay.Value, delay.DelayUnit, delay.DelayNum, order.AcceptTypeCode))?.EndTime; //todo
+								await _orderDelayRepository.Updateable().SetColumns(x => new OrderDelay() { AutomaticDelayNum = x.AutomaticDelayNum + 1, ApplyDelayTime = DateTime.Now, AfterDelay = delay.AfterDelay })
+									.Where(x => x.Id == delay.Id).ExecuteCommandAsync(cancellationToken);
 							}
-							await _orderDelayRepository.AddAsync(delay,false, cancellationToken);
-						}
-						//处理工单延期
-						await _orderApplication.DelayOrderExpiredTimeAsync(order.Id, delay.DelayNum,
-							delay.DelayUnit, delay.IsProDelay, cancellationToken);
-						//发送短信
-						var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withSteps: true, cancellationToken: cancellationToken);
-						var steps = workflow.Steps.Where(x => x.Status == EWorkflowStepStatus.WaitForAccept || x.Status == EWorkflowStepStatus.WaitForHandle).ToList();
-						if (steps.Any())
-						{
-                            foreach (var step in steps)
-                            {
-	                            var setting = step.HandlerOrgId == OrgSeedData.CenterId ? SettingConstants.AutomaticDelayCenterRoles : SettingConstants.AutomaticDelayDepartmentRoles;
-	                            var roleIds = _systemSettingCacheManager.GetSetting(setting)?.SettingValue;
-	                            if (step.HandlerOrgId == OrgSeedData.CenterId && string.IsNullOrEmpty(step.RoleId))
-	                            {
-		                            roleIds.Add(step.RoleId);
-	                            }
-	                            var userList = await _userRepository.Queryable().Where(x => x.OrgId == step.HandlerOrgId && x.Roles.Any(r => roleIds.Contains(r.Name))).ToListAsync(cancellationToken);
-	                            foreach (var user in userList)
-	                            {
-		                            if (!string.IsNullOrEmpty(user.PhoneNo))
-		                            {
-			                            //发送短信
-			                            var messageDto = new Share.Dtos.Push.MessageDto
-			                            {
-				                            PushBusiness = EPushBusiness.AutomaticDelay,
-				                            ExternalId = order.Id,
-				                            OrderId = order.Id,
-				                            PushPlatform = EPushPlatform.Sms,
-				                            Remark = order.Title,
-				                            Name = user.Name,
-				                            TemplateCode = "1015",
-				                            Params = new List<string>() { order.No },
-				                            TelNumber = user.PhoneNo,
-			                            };
-			                            await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
-		                            }
-	                            }
+							else
+							{
+								delay.OrderId = order.Id;
+								delay.EmployeeId = "";
+								delay.EmployeeName = "系统自动延期";
+								delay.ApplyOrgName = OrgSeedData.CenterName;
+								delay.ApplyOrgCode = OrgSeedData.CenterId;
+								delay.DelayApplyType = EDelayApplyType.LocalApply;
+								delay.BeforeDelay = order.ExpiredTime;
+								delay.DelayState = EDelayState.Pass;
+								delay.DelayReason = "系统自动延期";
+								delay.ApplyDelayTime = DateTime.Now;
+								delay.No = order.No;
+								delay.AutomaticDelayNum = 1;
+								delay.DelayNum = 1;
+								delay.DelayUnit = Share.Enums.Settings.ETimeType.WorkDay;
+								delay.IsProDelay = false;
+								delay.CreatorOrgId = OrgSeedData.CenterId;
+								delay.CreatorOrgName = OrgSeedData.CenterName;
+								delay.CreatorName = "系统自动延期";
+								if (delay.BeforeDelay != null)
+								{
+									delay.AfterDelay = (await _expireTime
+										.CalcEndTime(delay.BeforeDelay.Value, delay.DelayUnit, delay.DelayNum, order.AcceptTypeCode))?.EndTime; //todo
+								}
+								await _orderDelayRepository.AddAsync(delay, false, cancellationToken);
+							}
+							//处理工单延期
+							await _orderApplication.DelayOrderExpiredTimeAsync(order.Id, delay.DelayNum,
+								delay.DelayUnit, delay.IsProDelay, cancellationToken);
+							//发送短信
+							var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withSteps: true, cancellationToken: cancellationToken);
+							var steps = workflow.Steps.Where(x => x.Status == EWorkflowStepStatus.WaitForAccept || x.Status == EWorkflowStepStatus.WaitForHandle).ToList();
+							if (steps.Any())
+							{
+								foreach (var step in steps)
+								{
+									var setting = step.HandlerOrgId == OrgSeedData.CenterId ? SettingConstants.AutomaticDelayCenterRoles : SettingConstants.AutomaticDelayDepartmentRoles;
+									var roleIds = _systemSettingCacheManager.GetSetting(setting)?.SettingValue;
+									if (step.HandlerOrgId == OrgSeedData.CenterId && string.IsNullOrEmpty(step.RoleId))
+									{
+										roleIds.Add(step.RoleId);
+									}
+									var userList = await _userRepository.Queryable().Where(x => x.OrgId == step.HandlerOrgId && x.Roles.Any(r => roleIds.Contains(r.Name))).ToListAsync(cancellationToken);
+									foreach (var user in userList)
+									{
+										if (!string.IsNullOrEmpty(user.PhoneNo))
+										{
+											//发送短信
+											var messageDto = new Share.Dtos.Push.MessageDto
+											{
+												PushBusiness = EPushBusiness.AutomaticDelay,
+												ExternalId = order.Id,
+												OrderId = order.Id,
+												PushPlatform = EPushPlatform.Sms,
+												Remark = order.Title,
+												Name = user.Name,
+												TemplateCode = "1015",
+												Params = new List<string>() { order.No },
+												TelNumber = user.PhoneNo,
+											};
+											await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
+										}
+									}
+								}
 							}
 						}
 					}
+					order = await _orderRepository.GetAsync(dto.OrderId, cancellationToken);
+					_capPublisher.PublishDelay(order.ExpiredTime.Value - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay, new PublishAutomaticDelayDto() { OrderId = order.Id });
 				}
-                order = await _orderRepository.GetAsync(dto.OrderId, cancellationToken);
-				_capPublisher.PublishDelay(order.ExpiredTime.Value - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay, new PublishAutomaticDelayDto() { OrderId = order.Id });
 			}
+		
 		}
 
         /// <summary>

+ 20 - 0
src/Hotline.Logger/Hotline.Logger.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
+    <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
+    <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Hotline.Share\Hotline.Share.csproj" />
+  </ItemGroup>
+
+</Project>

+ 62 - 0
src/Hotline.Logger/Models/AccModel.cs

@@ -0,0 +1,62 @@
+namespace Hotline.Logger.Models
+{
+    /// <summary>
+    /// 日志类
+    /// </summary>
+    public class AccModel : BaseLogModel
+    {
+        /// <summary>
+        /// 时间间隔
+        /// </summary>
+        /// <returns></returns>
+        public long Interval { get; set; }
+
+        /// <summary>
+        /// 方向 (Response, Request)
+        /// </summary>
+        public string Direction { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 服务地址
+        /// 请求/响应服务的地址
+        /// </summary>
+        public string ServiceUrl { get; set; }
+
+        /// <summary>
+        /// 被调用方法
+        /// 被调用的方法,直接API地址
+        /// </summary>
+        public string Method { get; set; }
+
+        /// <summary>
+        /// 说明
+        /// 调用方法注释说明,可为空
+        /// </summary>
+        public string MethodNote { get; set; } = string.Empty;
+
+        /// <summary>
+        /// 请求入参
+        /// 请求(Request)的请求参数,响应时为空
+        /// </summary>
+        public string InParam { get; set; }
+
+        /// <summary>
+        /// 异常级别
+        /// 有异常时的异常级别,通常为Error
+        /// </summary>
+        public string ErrorLevel { get; set; }
+
+        /// <summary>
+        /// 异常ID
+        /// 调用服务有异常时,异常信息的日志ID,没异常日志时为空
+        /// </summary>
+        public string ErrorId { get; set; }
+
+        /// <summary>
+        /// 返回值
+        /// 响应结果
+        /// </summary>
+        public string OutResult { get; set; } = string.Empty;
+
+    }
+}

+ 43 - 0
src/Hotline.Logger/Models/BaseLogModel.cs

@@ -0,0 +1,43 @@
+using System.Net;
+
+namespace Hotline.Logger.Models
+{
+    public class BaseLogModel
+    {
+        /// <summary>
+        /// 日志id
+        /// </summary>
+        public Guid Id => Guid.NewGuid();
+
+        /// <summary>
+        /// 时间点
+        /// 发起请求时间点
+        /// </summary>
+        public string RequestTime => DateTime.Now.ToString("yyyy年MM月dd日 hh:mm:ss");
+
+        /// <summary>
+        /// 时间戳
+        /// </summary>
+        public long Timestamp =>
+            (long)(DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1))).TotalMilliseconds;
+
+        /// <summary>
+        /// 主机名
+        /// 服务所在的机器名
+        /// </summary>
+        public string Host
+        {
+            get
+            {
+                try
+                {
+                    return Dns.GetHostName();
+                }
+                catch (Exception)
+                {
+                    return "获取主机名失败";
+                }
+            }
+        }
+    }
+}

+ 167 - 0
src/Hotline.Logger/RequestResponseLoggingMiddleware.cs

@@ -0,0 +1,167 @@
+using Hotline.Logger.Models;
+using Hotline.Share.Tools;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Serilog;
+using System.Text;
+
+namespace Hotline.Logger
+{
+
+    /// <summary>
+    /// Acc 日志记录
+    /// </summary>
+    public class RequestResponseLoggingMiddleware
+    {
+        /// <summary>
+        /// 组件
+        /// </summary>
+        private readonly RequestDelegate _next;
+
+        /// <summary>
+        /// 日志记录器
+        /// </summary>
+        private readonly Serilog.ILogger _logger;
+
+        public RequestResponseLoggingMiddleware(RequestDelegate next)
+        {
+            _next = next;
+            _logger = new LoggerConfiguration()
+
+                .WriteTo.Logger(configure => configure // 输出到文件
+                .MinimumLevel.Debug()
+                .WriteTo.RollingFile( //每天生成一个新的日志,按天来存日志
+                    Path.Combine("logs", "acc-log-{Date}.txt"), //定输出到滚动日志文件中,每天会创建一个新的日志,按天来存日志
+                    retainedFileCountLimit: 7,
+                    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
+                ))
+                        .CreateLogger();
+        }
+
+        /// <summary>
+        /// 日志记录器
+        /// </summary>
+        /// <param name="context"> The context.  </param>
+        /// <returns> The <see cref="Task"/>.  </returns>
+        public async Task Invoke(HttpContext context)
+        {
+            var injectedRequestStream = new MemoryStream();
+            try
+            {
+                var requestTime = DateTime.Now;
+                var accModel = new AccModel();
+                await GetMethod(context, injectedRequestStream, accModel);
+                var originalBodyStream = context.Response.Body;
+                using (var responseBody = new MemoryStream())
+                {
+                    context.Response.Body = responseBody;
+                    await _next(context);
+
+                    accModel = await FormatResponse(context.Response, requestTime, accModel);
+                    _logger.Information(accModel.ToJson());
+                    await responseBody.CopyToAsync(originalBodyStream);
+                }
+
+            }
+            catch (Exception e)
+            {
+                throw new Exception("Hotline.Logger.RequestResponseLoggingMiddleware.GetMethod", e);
+            }
+            finally
+            {
+                injectedRequestStream.Dispose();
+            }
+        }
+
+        /// <summary>
+        /// 获取 响应日志数据
+        /// </summary>
+        /// <param name="response"> The response.  </param>
+        /// <param name="requestTime"> The request time.  </param>
+        /// <returns> The <see cref="Task"/>.  </returns>
+        private async Task<AccModel> FormatResponse(HttpResponse response, DateTime requestTime, AccModel accModel)
+        {
+            response.Body.Seek(0, SeekOrigin.Begin);
+            var text = await new StreamReader(response.Body).ReadToEndAsync();
+            response.Body.Seek(0, SeekOrigin.Begin);
+            accModel.OutResult = text;
+            accModel.Interval = (long)(DateTime.Now - requestTime).TotalMilliseconds;
+            return accModel;
+        }
+
+        /// <summary>
+        /// 获取请求入参和方法
+        /// </summary>
+        /// <param name="context">
+        /// The context.
+        /// </param>
+        /// <param name="injectedRequestStream">
+        /// The injected Request Stream.
+        /// </param>
+        private async Task GetMethod(HttpContext context, MemoryStream injectedRequestStream, AccModel accModel)
+        {
+            try
+            {
+                if (context.Request.Method.ToUpper() == "GET")
+                {
+                    try
+                    {
+                        accModel.InParam = context.Request.QueryString.ToString();
+                        accModel.Method = context.Request.Path;
+                        accModel.ServiceUrl = context.Request.Host.ToString();
+                    }
+                    catch (Exception e)
+                    {
+                        accModel.InParam = "GetMethod.Get 代码块异常;";
+                        accModel.Method = "GetMethod.Get 代码块异常;";
+                        accModel.ServiceUrl = e.ToString();
+                    }
+                }
+
+                if (context.Request.Method.ToUpper() == "POST" || context.Request.Method.ToUpper() == "PUT" || context.Request.Method.ToUpper() == "DELETE")
+                {
+                    if (context.Request.ContentType?.IndexOf("multipart/form-data", StringComparison.Ordinal) == -1)
+                    {
+                        using var bodyReader = new StreamReader(context.Request.Body);
+                        var bodyAsText = await bodyReader.ReadToEndAsync();
+
+                        var bytesToWrite = Encoding.UTF8.GetBytes(bodyAsText);
+                        injectedRequestStream.Write(bytesToWrite, 0, bytesToWrite.Length);
+                        injectedRequestStream.Seek(0, SeekOrigin.Begin);
+                        context.Request.Body = injectedRequestStream;
+
+                        accModel.InParam = bodyAsText;
+                    }
+                    accModel.Method = context.Request.Path;
+                    accModel.ServiceUrl = context.Request.Host.ToString();
+                }
+            }
+            catch (Exception e)
+            {
+                _logger.Error($"Hotline.Logger.RequestResponseLoggingMiddleware.GetMethod: {e}");
+            }
+
+        }
+    }
+
+    /// <summary>
+    /// 注册 acc 日志记录器
+    /// </summary>
+    public static class RequestResponseLoggingMiddlewareExtensions
+    {
+        /// <summary>
+        /// 注册日志记录服务
+        /// </summary>
+        /// <param name="builder">
+        /// The builder.
+        /// </param>
+        /// <returns>
+        /// The <see cref="IApplicationBuilder"/>.
+        /// </returns>
+        public static IApplicationBuilder UseRequestResponseLogging(this IApplicationBuilder builder)
+        {
+            return builder.UseMiddleware<RequestResponseLoggingMiddleware>();
+        }
+    }
+}

+ 11 - 6
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs

@@ -1,12 +1,14 @@
 using System.Collections;
 using System.ComponentModel;
 using System.ComponentModel.DataAnnotations;
+using System.Diagnostics;
 using System.Linq.Dynamic.Core;
 using System.Linq.Expressions;
 using System.Reflection;
-using Hotline.FlowEngine.Workflows;
-using Hotline.Orders;
-using Hotline.SeedData.Codes;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.Text.Unicode;
 using Hotline.Users;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
@@ -75,7 +77,7 @@ namespace Hotline.Repository.SqlSugar.Extensions
                             column.IsPrimarykey = true;
                             column.Length = 36;
                         }
-                        
+
                         //if (!column.DbColumnName.Contains("_"))
                         //    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
 
@@ -207,7 +209,11 @@ namespace Hotline.Repository.SqlSugar.Extensions
             /***写AOP等方法***/
             db.Aop.OnLogExecuting = (sql, pars) =>
             {
-                //获取原生SQL推荐 5.1.4.63  性能OK
+
+                //Log.Warning($"sql参数:{JsonSerializer.Serialize(db.Ado.SqlStackTrace, new JsonSerializerOptions { Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs) })}");
+
+
+                ////获取原生SQL推荐 5.1.4.63  性能OK
                 //Log.Information(UtilMethods.GetNativeSql(sql, pars));
 
                 //Log.Information("Sql: {0}", sql);
@@ -250,7 +256,6 @@ namespace Hotline.Repository.SqlSugar.Extensions
                     Log.Warning(UtilMethods.GetNativeSql(sql, p));
                     Log.Warning("slow query totalSeconds: {sec}", db.Ado.SqlExecutionTime.TotalSeconds);
                 }
-                //相当于EF的 PrintToMiniProfiler
             };
 
             db.Aop.DataExecuting = (oldValue, entityInfo) =>

+ 5 - 1
src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs

@@ -10,6 +10,7 @@ using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
+using Hotline.Share.Tools;
 using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
 using SqlSugar;
 using System.Data;
@@ -1249,6 +1250,8 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .Where(x => x.OrderVisit.VisitState == EVisitState.Visited && x.VisitTarget == EVisitTarget.Org)
                 .WhereIF(IsCenter == false, x => x.VisitOrgCode.StartsWith(_sessionContext.RequiredOrgId))
                 .WhereIF(dto.OrgVisitStatisticsType.HasValue, x => x.OrderVisit.Order.ProcessType == (EProcessType)((int)dto.OrgVisitStatisticsType))
+                .WhereIF(dto.Keyword.NotNullOrEmpty(), x => x.OrderVisit.Order.Title.Contains(dto.Keyword)) // 根据关键字匹配
+                .WhereIF(dto.TypeCode != 0, x => x.OrderVisit.Order.IdentityType == (EIdentityType)dto.TypeCode)
                 .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), dto.AttitudeType == EAttitudeType.ProcessingResult ? x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults : x => SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == dto.OrgProcessingResults)
                 .WhereIF(!string.IsNullOrEmpty(dto.VisitUser), x => x.OrderVisit.Employee.Name.Contains(dto.VisitUser))
                 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order.No == dto.No)
@@ -1284,7 +1287,8 @@ namespace Hotline.Repository.SqlSugar.Orders
                     Content = x.OrderVisit.Order.Content,
                     FileOpinion = x.OrderVisit.Order.FileOpinion,
                     FiledTime = x.OrderVisit.Order.FiledTime,
-                    VisitOrgName = x.VisitOrgName
+                    VisitOrgName = x.VisitOrgName,
+                    ActualHandleOrgName = x.OrderVisit.Order.ActualHandleOrgName
                 });
 
         }

+ 5 - 0
src/Hotline.Share/Dtos/CallCenter/CallNativeDto.cs

@@ -132,5 +132,10 @@ namespace Hotline.Share.Dtos.CallCenter
         public string? OrderNo { get; set; }
 
         public string? Title { get; set; }
+
+        /// <summary>
+        /// 中继号码
+        /// </summary>
+        public string Gateway { get; set; }
     }
 }

+ 1 - 1
src/Hotline.Share/Dtos/CallCenter/QueryCallsDetailDto.cs

@@ -137,7 +137,7 @@
         /// <summary>
         /// 呼入总量 (呼入接通 + 挂机量 + 呼入队列挂断)
         /// </summary>
-        public int InTotal { get; set; }
+        public int InTotal => InConnectionQuantity + InNotAnswered + NotAcceptedHang;
 
         /// <summary>
         /// 呼入队列挂断

+ 8 - 0
src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs

@@ -57,5 +57,13 @@ namespace Hotline.Share.Dtos.CallCenter
         /// true: 无订单; false: 有订单
         /// </summary>
         public bool? IsMissOrder { get; set; }
+
+        /// <summary>
+        /// 呼入类型
+        /// 1: 呼入
+        /// 2: 呼出
+        /// 3: 未接
+        /// </summary>
+        public int Type { get; set; }
     }
 }

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

@@ -69,6 +69,11 @@ public class NextStepsWithOpinionDto<TSteps> : NextStepsDto<TSteps>
     /// 市州转办信息
     /// </summary>
     public List<SystemDicDataOutDto>? TranspondCity { get; set; }
+
+    /// <summary>
+    /// 会签类型:null:非会签,0:中心会签,1:部门会签
+    /// </summary>
+    public ECounterSignType? CounterSignType { get; set; }
 }
 
 public class StepTempInDto

+ 8 - 0
src/Hotline.Share/Dtos/Identity/LoginDto.cs

@@ -18,6 +18,14 @@ namespace Hotline.Share.Dtos.Identity
         public string UserName { get; set; }
     }
 
+    public class HotlineStateResult { 
+        public string Code { get; set; }
+
+        public bool? Result { get; set; }
+
+        public string Message { get; set; }
+    }
+
     public class LoginPageInfoDto
     {
 

+ 11 - 10
src/Hotline.Share/Dtos/Knowledge/KnowledgePagedDto.cs

@@ -21,17 +21,18 @@ namespace Hotline.Share.Dtos.Knowledge
 	/// <param name="EKnowledgeWorkFlowStatus">审核状态</param>
 	public record KnowledgeApprovalPagedListDto(EKnowledgeApplyType? EKnowledgeApplyType, EKnowledgeWorkFlowStatus? EKnowledgeWorkFlowStatus) : PagedKeywordRequest;
 
-	/// <summary>
-	/// 知识检索
-	/// </summary>
-	/// <param name="RetrievalType">检索类型</param>
-	/// <param name="Attribution">归属</param>
-	/// <param name="Sort">排序字段</param>
-	/// <param name="CreateOrgId">部门id</param>
-	/// <param name="HotspotId">热点id</param>
+    /// <summary>
+    /// 知识检索
+    /// </summary>
+    /// <param name="RetrievalType">检索类型</param>
+    /// <param name="Attribution">归属</param>
+    /// <param name="Sort">排序字段</param>
+    /// <param name="CreateOrgId">部门id</param>
+    /// <param name="HotspotId">热点id</param>
     /// <param name="HotspotName">热点名称</param>;
-	/// <param name="KnowledgeTypeId">类型id</param>
-	public record KnowledgeRetrievalPagedListDto(EKnowledgeRetrievalType? RetrievalType,string? Attribution,string Sort, string? CreateOrgId, string? HotspotId, string? HotspotName, string? KnowledgeTypeId) : PagedKeywordRequest;
+    /// <param name="KnowledgeTypeId">类型id</param>
+    /// <param name="Content">受理内容</param>
+    public record KnowledgeRetrievalPagedListDto(EKnowledgeRetrievalType? RetrievalType,string? Attribution,string Sort, string? CreateOrgId, string? HotspotId, string? HotspotName, string? KnowledgeTypeId, string? Content) : PagedKeywordRequest;
 
     /// <summary>
     /// 来电弹屏知识库查询

+ 5 - 0
src/Hotline.Share/Dtos/Knowledge/KnowledgeWordDto.cs

@@ -94,6 +94,11 @@ namespace Hotline.Share.Dtos.Knowledge
 		/// 近义词
 		/// </summary>
 		public string? Synonym { get; set; }
+
+		/// <summary>
+		/// 启禁用  0  启用  1 禁用
+		/// </summary>
+		public int? IsEnable { get; set; }
 	}
 	public class KnowledgeWordBaseDto
 	{

+ 2 - 0
src/Hotline.Share/Dtos/Order/HomeOrderDto.cs

@@ -60,5 +60,7 @@ namespace Hotline.Share.Dtos.Order
 		/// </summary>
 		public DateTime? ActualHandleTime { get; set; }
 
+		public string Key => Type + OrderId;
+
 	}
 }

+ 6 - 1
src/Hotline.Share/Dtos/Order/OrderBiDto.cs

@@ -376,7 +376,12 @@ namespace Hotline.Share.Dtos.Order
 		/// 办结时间
 		/// </summary>
 		public DateTime? FiledTime { get; set; }
-    }
+
+		/// <summary>
+		/// 接办部门
+		/// </summary>
+		public string ActualHandleOrgName { get; set; }
+	}
 
 
 

+ 82 - 24
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -5,6 +5,7 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Requests;
+using Novacode;
 using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.Order
@@ -86,12 +87,12 @@ namespace Hotline.Share.Dtos.Order
 
         public string? OrderTagCode { get; set; }
 
-		#region 流程信息
+        #region 流程信息
 
-		/// <summary>
-		/// 工单开始时间(受理/接办时间=流程开启时间)
-		/// </summary>
-		public DateTime? StartTime { get; set; }
+        /// <summary>
+        /// 工单开始时间(受理/接办时间=流程开启时间)
+        /// </summary>
+        public DateTime? StartTime { get; set; }
 
         /// <summary>
         /// 交办时间(中心交部门办理时间)
@@ -121,8 +122,8 @@ namespace Hotline.Share.Dtos.Order
         public double HandleDurationWorkday { get; set; }
 
         /// <summary>
-        /// 全流程时长(分钟
-        /// 归档时间-创建时间
+        /// 全流程时长(
+        /// 归档时间-受理时间
         /// </summary>
         public double AllDuration { get; set; }
 
@@ -130,19 +131,20 @@ namespace Hotline.Share.Dtos.Order
         public string AllDurationHour => GetAllDurationHour();
 
 
-		public string GetAllDurationHour() {
+        public string GetAllDurationHour()
+        {
 
-			if (Status >= EOrderStatus.Filed)
-			{
-                return  Math.Round(AllDuration / 60, 2).ToString() + "小时";
-			}
+            if (Status >= EOrderStatus.Filed)
+            {
+                return Math.Round(Math.Round((FiledTime - CreationTime).Value.TotalSeconds) / 60 / 60, 2).ToString() + "小时";
+            }
             return "-";
         }
 
-		/// <summary>
-		/// 办结时长(秒) 归档时间-受理时间(工单创建时间)
-		/// </summary>
-		public double? CreationTimeHandleDuration { get; set; }
+        /// <summary>
+        /// 办结时长(秒) 归档时间-受理时间(工单创建时间)
+        /// </summary>
+        public double? CreationTimeHandleDuration { get; set; }
 
         /// <summary>
         /// 办结工作日时长(秒)归档时间-受理时间(工单创建时间)
@@ -623,7 +625,7 @@ namespace Hotline.Share.Dtos.Order
 
         public List<RepeatableEventDetailOpDto> RepeatableEventDetails { get; set; } = new();
 
-        public List<OrderVisitDto> OrderVisits { get; set; }
+        public List<OrderVisitDto>? OrderVisits { get; set; }
 
         public List<FileJson>? FileJson { get; set; }
 
@@ -765,17 +767,73 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public bool? ProvinceSendBack { get; set; }
 
-		/// <summary>
-		/// 终止
-		/// </summary>
-		public List<OrderTerminateDto> OrderTerminates { get; set; }
+        /// <summary>
+        /// 终止
+        /// </summary>
+        public List<OrderTerminateDto> OrderTerminates { get; set; }
 
         /// <summary>
         /// 终止状态
         /// </summary>
         public string? OrderTerminateStatus { get; set; }
 
-	}
+        #region 回访信息
+
+        /// <summary>
+        /// 部门满意度
+        /// </summary>
+        public Kv? OrgEvaluate
+        {
+            get
+            {
+                if (this.OrderVisits == null) return null;
+                var visitEntity = this.OrderVisits
+                            .OrderByDescending(m => m.CreationTime)
+                            .FirstOrDefault();
+                if (visitEntity == null) return null;
+                return visitEntity.NowEvaluate;
+            }
+        }
+
+
+        /// <summary>
+        /// 部门满意度
+        /// </summary>
+        public string? OrgEvaluateValue
+        {
+            get
+            {
+                if (this.OrderVisits == null) return null;
+                var visitEntity = this.OrderVisits
+                           .OrderByDescending(m => m.CreationTime)
+                           .FirstOrDefault();
+                if (visitEntity == null) return null;
+                var now = visitEntity.NowEvaluate;
+                if (now == null) return null;
+                return now.Value;
+            }
+        }
+
+        /// <summary>
+        /// 坐席满意度
+        /// </summary>
+        public ESeatEvaluate? SeatEvaluate
+        {
+            get
+            {
+                if (this.OrderVisits == null) return null;
+                var visitEntity = this.OrderVisits
+                          .OrderByDescending(m => m.CreationTime)
+                          .FirstOrDefault();
+                if (visitEntity == null) return null;
+                return visitEntity.OrderVisitDetails.Where(m => m.VisitTarget == EVisitTarget.Seat)
+                    .FirstOrDefault()?.SeatEvaluate;
+            }
+        }
+
+        public string? SeatEvaluateTxt => SeatEvaluate?.GetDescription();
+        #endregion
+    }
 
     public class UpdateOrderDto : AddOrderDto
     {
@@ -1212,8 +1270,8 @@ namespace Hotline.Share.Dtos.Order
         public int ExpiredTimeOrderCount { get; set; }
     }
     public class PublishAutomaticDelayDto
-	{
-	    public string OrderId { get; set; }
+    {
+        public string OrderId { get; set; }
     }
 
 

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

@@ -185,6 +185,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public string? ReceiveProvinceNo { get; set; }
 
+        /// <summary>
+        /// 受理情况 true :已签收  false:未签收  空:全部
+        /// </summary>
+        public bool? IsSgin { get; set; }
+
         /// <summary>
         /// 工单标签Code
         /// </summary>

+ 9 - 2
src/Hotline.Share/Dtos/Push/MessageDto.cs

@@ -1,4 +1,5 @@
 using Hotline.Share.Enums.Push;
+using System.ComponentModel.DataAnnotations;
 
 namespace Hotline.Share.Dtos.Push;
 
@@ -66,10 +67,16 @@ public class MessageInDto
     /// <summary>
     /// 接收手机号码(多个逗号分隔)
     /// </summary>
-    public string TelNumbers { get; set; }
+    public string? TelNumbers { get; set; }
+
+    /// <summary>
+    /// 用户Id集合
+    /// </summary>
+    public IList<string>? UserIds { get; set; }
 
     /// <summary>
     /// 内容
     /// </summary>
-    public string? Content { get; set; }
+    [Required(ErrorMessage = "内容不能为空")]
+    public string Content { get; set; }
 }

+ 3 - 3
src/Hotline.Share/Enums/Order/EComplainType.cs

@@ -27,9 +27,9 @@ public enum EComplainType
     [Description("赔偿损失")]
     Compensation = 7,
 
-    [Description("停止侵权")]
+    [Description("停止侵权、核定侵权责任")]
     StopInfringement = 8,
 
-    [Description("核定侵权责任")]
-    AuthorizedInfringement = 9
+    //[Description("核定侵权责任")]
+    //AuthorizedInfringement = 9
 }

+ 1 - 0
src/Hotline.Share/Hotline.Share.csproj

@@ -12,6 +12,7 @@
 
   <ItemGroup>
     <PackageReference Include="DocXCore" Version="1.0.10" />
+    <PackageReference Include="JV.PanGu.Core" Version="1.0.1" />
     <PackageReference Include="Mapster" Version="7.4.0" />
     <PackageReference Include="MediatR.Contracts" Version="2.0.1" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />

+ 5 - 0
src/Hotline.Share/Requests/PagedKeywordRequest.cs

@@ -497,6 +497,11 @@ public record OrgVisitDetailListReq: PagedKeywordRequest
     /// 默认 办件结果;
     /// </summary>
     public EAttitudeType AttitudeType { get; set; } = EAttitudeType.ProcessingResult;
+
+    /// <summary>
+    /// 来电主体
+    /// </summary>
+    public int TypeCode { get; set; }
 }
 
 

+ 5 - 23
src/Hotline.Share/Tools/ObjectExtensions.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Reflection;
 using System.Text;
 using System.Threading.Tasks;
+using static Lucene.Net.Util.Fst.Util;
 
 namespace Hotline.Share.Tools;
 public static class ObjectExtensions
@@ -29,32 +30,13 @@ public static class ObjectExtensions
         }
     }
 
-    /// <summary>
-    /// 扩展方法用于手动验证对象,并返回验证结果。
-    /// </summary>
-    /// <param name="obj">需要验证的对象</param>
-    /// <returns>如果验证成功返回 string.Empty,否则返回错误信息</returns>
-    public static string ValidateObject(this object obj)
+    public static string ToJson(this object obj)
     {
-        var validationResults = new List<ValidationResult>();
-        var validationContext = new ValidationContext(obj);
-
-        // 验证对象
-        bool isValid = Validator.TryValidateObject(obj, validationContext, validationResults, true);
-
-        if (isValid)
-        {
-            return string.Empty;
-        }
-        else
-        {
-            // 如果验证失败,返回所有错误信息的拼接
-            return string.Join("; ", validationResults.Select(result => result.ErrorMessage));
-        }
+        return obj == null ? default(string) : JsonConvert.SerializeObject(obj);
     }
 
-    public static string ToJson(this object obj)
+    public static T FromJson<T>(this string json)
     {
-        return JsonConvert.SerializeObject(obj);
+        return json.IsNullOrEmpty() ? default(T) : JsonConvert.DeserializeObject<T>(json);
     }
 }

+ 26 - 11
src/Hotline.Share/Tools/StringExtensions.cs

@@ -1,4 +1,5 @@
-using System.Text.RegularExpressions;
+using PanGu;
+using System.Text.RegularExpressions;
 
 namespace Hotline.Share.Tools;
 public static class StringExtensions
@@ -30,16 +31,6 @@ public static class StringExtensions
         return (TEnum)Enum.Parse(typeof(TEnum), value);
     }
 
-    public static IList<string> SplitKeywords(this string value)
-    {
-        var regex = new Regex(@"[ ,,]+");
-
-        return regex.Split(value)
-                    .Where(s => !string.IsNullOrWhiteSpace(s))
-                    .Select(s => s.Trim())
-                    .ToList();
-    }
-
     /// <summary>
     /// 是否手机号码
     /// </summary>
@@ -53,4 +44,28 @@ public static class StringExtensions
         var phoneNumberPattern = @"^1[3-9]\d{9}$";
         return Regex.IsMatch(value, phoneNumberPattern);
     }
+
+    /// <summary>
+    /// 通过 PanGu 进行分词
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    public static IList<string> GetSegment(this string value)
+    {
+        return new Segment()
+            .DoSegment(value)
+            .Where(word => word.WordType == WordType.SimplifiedChinese && word.Word.Length > 1)
+            .Select(word => word.Word)
+            .ToList();
+    }
+
+        public static IList<string> SplitKeywords(this string value)
+    {
+        var regex = new Regex(@"[ ,,]+");
+
+        return regex.Split(value)
+                    .Where(s => !string.IsNullOrWhiteSpace(s))
+                    .Select(s => s.Trim())
+                    .ToList();
+    }
 }

+ 12 - 0
src/Hotline.XingTang/CallTelClient.cs

@@ -0,0 +1,12 @@
+using Hotline.CallCenter.Tels;
+using Hotline.CallCenter.Tels.CallTelDomain;
+
+namespace Hotline.XingTang;
+
+internal class CallTelClient : ICallTelClient
+{
+    public async Task<List<QueryTelResponse>> QueryTelsAsync(QueryTelRequest request, CancellationToken cancellationToken)
+    {
+        return new List<QueryTelResponse>();
+    }
+}

+ 17 - 0
src/Hotline.XingTang/ServiceCollectionExtensions.cs

@@ -0,0 +1,17 @@
+using Hotline.CallCenter.Tels;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.XingTang;
+public static class ServiceCollectionExtensions
+{
+    public static IServiceCollection AddXingTangSDK(this IServiceCollection services)
+    {
+        services.AddScoped<ICallTelClient, CallTelClient>();
+        return services;
+    }
+}

+ 5 - 0
src/Hotline/Authentications/SessionContextProvider.cs

@@ -20,4 +20,9 @@ public class SessionContextProvider : ISessionContextProvider, IScopeDependency
     {
         SessionContext = _serviceProvider.GetRequiredKeyedService<ISessionContext>(key);
     }
+
+    public void SetContext(ISessionContext sessionContext)
+    {
+        SessionContext = sessionContext;
+    }
 }

+ 1 - 0
src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs

@@ -13,5 +13,6 @@ namespace Hotline.Caching.Interfaces
         IReadOnlyList<SystemDicData> GetVisitSatisfaction();
         void RemoveSysDicDataCache(string code);
         IReadOnlyList<SystemDicData> AcceptType { get; }
+        IReadOnlyList<SystemDicData> LeaderSMS { get; }
     }
 }

+ 5 - 0
src/Hotline/Caching/Interfaces/ISystemSettingCacheManager.cs

@@ -13,6 +13,11 @@ namespace Hotline.Caching.Interfaces
         int SeatChaoTime { get; }
         int RingTimes { get; }
         string RecordPrefix { get; }
+
+        /// <summary>
+        /// 中心直办件默认回访人Id
+        /// 根据禅道 自贡需求 Id_361, 第一条, 3小条需求;
+        /// </summary>
         string DefaultVisitEmployeeId { get; }
         int FixedQueryCount { get; }
     }

+ 2 - 0
src/Hotline/Caching/Services/SysDicDataCacheManager.cs

@@ -32,6 +32,8 @@ namespace Hotline.Caching.Services
 
         public IReadOnlyList<SystemDicData> AcceptType => GetSysDicDataCache(SysDicTypeConsts.AcceptType);
 
+        public IReadOnlyList<SystemDicData> LeaderSMS => GetSysDicDataCache(SysDicTypeConsts.LeaderSMS);
+
         public void RemoveSysDicDataCache(string code)
         {
             _cacheSysDicData.Remove(code);

+ 1 - 1
src/Hotline/Caching/Services/SystemSettingCacheManager.cs

@@ -25,7 +25,7 @@ namespace Hotline.Caching.Services
             {
                 var dbsetting = _systemSettingRepository.GetAsync(d => d.Code == code).GetAwaiter().GetResult();
                 if (dbsetting == null)
-                    throw new UserFriendlyException("无效系统设置");
+                    throw new UserFriendlyException($"{code} 无效系统设置");
                 return dbsetting;
             });
             return setting;

+ 17 - 0
src/Hotline/CallCenter/Tels/CallTelDomain/QueryTelRequest.cs

@@ -0,0 +1,17 @@
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.CallCenter.Tels.CallTelDomain;
+public class QueryTelRequest
+{
+    /// <summary>
+    /// 分机号
+    /// </summary>
+    [RequestProperty(Name = "extn")]
+    public string? TelNo { get; set; }
+
+}

+ 23 - 0
src/Hotline/CallCenter/Tels/CallTelDomain/QueryTelResponse.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Hotline.CallCenter.Tels.CallTelDomain;
+public class QueryTelResponse
+{
+    [JsonPropertyName("uuid")]
+    public string Id { get; set; }
+    public string Name { get; set; }
+    [JsonPropertyName("nbr")]
+    public string TelNo { get; set; }
+    public string Description { get; set; }
+
+    public string Password { get; set; }
+
+    [JsonPropertyName("queue_id")]
+    public string QueueId { get; set; }
+
+}

+ 12 - 0
src/Hotline/CallCenter/Tels/ICallTelClient.cs

@@ -0,0 +1,12 @@
+using Hotline.CallCenter.Tels.CallTelDomain;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.CallCenter.Tels;
+public interface ICallTelClient
+{
+    Task<List<QueryTelResponse>> QueryTelsAsync(QueryTelRequest request, CancellationToken cancellationToken);
+}

+ 4 - 0
src/Hotline/Configurations/AppConfiguration.cs

@@ -49,6 +49,10 @@ namespace Hotline.Configurations
 
     public abstract class DefaultAppScopeConfiguration
     {
+        /// <summary>
+        /// 行政区划编码
+        /// </summary>
+        public string AreaCode { get; set; }
         public string CallCenterType { get; set; }
     }
 }

+ 1 - 1
src/Hotline/File/File.cs

@@ -91,7 +91,7 @@ namespace Hotline.File
 		/// <summary>
 		/// 完整附件路径
 		/// </summary>
-		[SugarColumn(ColumnDescription = "完整附件路径")]
+		[SugarColumn(ColumnDescription = "完整附件路径", ColumnDataType = "varchar(500)")]
 		public string? AllPath { get; set; }
 	}
 }

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

@@ -95,7 +95,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 特提至中心(优先派单组其次坐席)
         /// </summary>
         /// <returns></returns>
-        Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime,
+        Task<bool> RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled, DateTime? expiredTime,
             CancellationToken cancellationToken);
 
         ///// <summary>

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

@@ -438,7 +438,7 @@ public partial class Workflow
     /// </summary>
     public void UpdateActualOption()
     {
-        //if (FlowType is EFlowType.Review) return;
+        if (FlowType is EFlowType.Review) return;
 
         WorkflowStep? step;
         if (Steps.Count == 2

+ 131 - 124
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -37,6 +37,7 @@ namespace Hotline.FlowEngine.Workflows
         private readonly IRepository<User> _userRepository;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
         private readonly IWfModuleCacheManager _wfModuleCacheManager;
+        private readonly ISessionContextProvider _sessionContextProvider;
 
         public WorkflowDomainService(
             IWorkflowRepository workflowRepository,
@@ -64,6 +65,7 @@ namespace Hotline.FlowEngine.Workflows
             _fileRepository = fileRepository;
             _systemSettingCacheManager = systemSettingCacheManager;
             _wfModuleCacheManager = wfModuleCacheManager;
+            _sessionContextProvider = sessionContextProvider;
         }
 
         public async Task<Workflow> CreateWorkflowAsync(WorkflowModule wfModule, string title, string userId,
@@ -109,9 +111,9 @@ namespace Hotline.FlowEngine.Workflows
                     PublishStrategy.ParallelWhenAll, cancellationToken);
 
                 //firstStep是否为end,t: 实际办理节点为startStep, 并且handlerId赋值 f: 实际办理节点为firstStep, handlerId未赋值
-                workflow.UpdateActualStepWhenHandle(startStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+                workflow.UpdateActualStepWhenHandle(startStep, _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName, _sessionContextProvider.SessionContext.OrgLevel);
 
-                workflow.UpdateCurrentStepWhenHandle(startStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+                workflow.UpdateCurrentStepWhenHandle(startStep, _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName, _sessionContextProvider.SessionContext.OrgLevel);
 
                 var endTrace = await EndAsync(workflow, dto, firstStepDefine, startStep, expiredTime, cancellationToken);
                 return;
@@ -196,6 +198,8 @@ namespace Hotline.FlowEngine.Workflows
                 _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
                 externalId, cancellationToken);
 
+            //todo 重构为流程开放策略参数,业务决定
+            var defineHandler = startStepDefine.HandlerTypeItems.First();
             var startStep = CreateStartStep(workflow, startStepDefine, dto,
                 new FlowStepHandler
                 {
@@ -205,8 +209,8 @@ namespace Hotline.FlowEngine.Workflows
                     Username = _sessionContext.UserName,
                     OrgId = _sessionContext.RequiredOrgId,
                     OrgName = _sessionContext.OrgName,
-                    // RoleId = defineHandler.Key,
-                    // RoleName = defineHandler.Value,
+                    RoleId = defineHandler.Key,
+                    RoleName = defineHandler.Value,
                 }, expiredTime);
 
             if (dto.Files.Any())
@@ -343,23 +347,23 @@ namespace Hotline.FlowEngine.Workflows
 
                 if (countersignStartStep.IsStartCountersign)
                 {
-                    //var currentCountersign =
-                    //    workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId);
-                    //if (currentCountersign is null)
-                    //    throw new UserFriendlyException(
-                    //        $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}",
-                    //        "无效会签编号");
+                    var currentCountersign =
+                        workflow.Countersigns.FirstOrDefault(d => d.Id == countersignStartStep.StartCountersignId);
+                    if (currentCountersign is null)
+                        throw new UserFriendlyException(
+                            $"未查询到对应会签信息,workflowId:{workflow.Id}, countersignId:{currentStep.CountersignId}",
+                            "无效会签编号");
 
                     //结束step会签信息
                     countersignStartStep.CountersignEnd();
                     updateSteps.Add(countersignStartStep);
 
-                    ////结束会签
-                    //currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType,
-                    //    current.UserId, current.UserName,
-                    //    current.OrgId, current.OrgName,
-                    //    current.OrgAreaCode, current.OrgAreaName);
-                    //await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
+                    //结束会签
+                    currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType,
+                        current.UserId, current.UserName,
+                        current.OrgId, current.OrgName,
+                        current.OrgAreaCode, current.OrgAreaName);
+                    await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
                 }
             }
 
@@ -368,12 +372,12 @@ namespace Hotline.FlowEngine.Workflows
             //创建会签数据
             if (isStartCountersign)
             {
-                //var exists = workflow.Countersigns.Any(d =>
-                //    !d.IsCompleted() && d.StarterId == current.UserId);
-                //if (exists)
-                //    throw new UserFriendlyException("该用户在当前流程存在未结束会签");
-                //await StartCountersignAsync(current, workflow, currentStep, dto, flowAssignInfo.FlowAssignType,
-                //    counterSignType, expiredTime, cancellationToken);
+                var exists = workflow.Countersigns.Any(d =>
+                    !d.IsCompleted() && !string.IsNullOrEmpty(current.UserId) && d.StarterId == current.UserId);
+                if (exists)
+                    throw new UserFriendlyException("该用户在当前流程存在未结束会签");
+                await StartCountersignAsync(current, workflow, currentStep, dto, flowAssignInfo.FlowAssignType,
+                    counterSignType, expiredTime, cancellationToken);
             }
 
             currentStep.IsActualHandled = CheckIsActualHandle(workflow, currentStep, nextStepDefine, dto);
@@ -454,16 +458,6 @@ namespace Hotline.FlowEngine.Workflows
                 nextStepDefine, isNextDynamic, flowAssignInfo.FlowAssignType, expiredTime, isStartCountersign,
                 cancellationToken);
 
-            ////赋值当前节点的下级办理节点
-            //if (dto.IsStartCountersign
-            //   //|| (currentStep.IsInCountersign() &&
-            //   //    !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
-            //   )
-            //{
-            //    currentStep.CreateCountersignSteps(nextSteps);
-            //    await _workflowStepRepository.UpdateAsync(currentStep, cancellationToken);
-            //}
-
             // //更新办理对象(nextSteps无元素表示当前节点为会签办理节点且当前会签没有全部办理完成)
             // workflow.UpdateHandlers(current.RequiredUserId, current.RequiredOrgId,
             //     flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects, nextSteps.Any());
@@ -586,9 +580,7 @@ namespace Hotline.FlowEngine.Workflows
 
             var unhandlePreviousTrace = workflow.Traces.FirstOrDefault(d =>
                     d.Status is not EWorkflowStepStatus.Handled
-            //&& d.TraceType is EWorkflowTraceType.Previous
             );
-            //var previousOpinion = unhandlePreviousTrace?.Opinion ?? null;
 
             var unCompletedCountersign = workflow.Countersigns
                 .FirstOrDefault(d => !d.IsCompleted() && d.StarterOrgId == orgId);
@@ -611,13 +603,13 @@ namespace Hotline.FlowEngine.Workflows
             string? orgAreaCode, string? orgAreaName,
             CancellationToken cancellationToken)
         {
-            if (!workflow.IsCanHandle(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
-                    _sessionContext.Roles)) return;
+            if (!workflow.IsCanHandle(_sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.RequiredOrgId,
+                    _sessionContextProvider.SessionContext.Roles)) return;
             //工单完成以后查看的场景
             if (workflow.Status != EWorkflowStatus.Runnable) return;
 
-            var currentStep = GetUnHandleStep(workflow.Steps, _sessionContext.RequiredOrgId,
-                _sessionContext.RequiredUserId, _sessionContext.Roles);
+            var currentStep = GetUnHandleStep(workflow.Steps, _sessionContextProvider.SessionContext.RequiredOrgId,
+                _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.Roles);
             if (currentStep.Status is not EWorkflowStepStatus.WaitForAccept) return;
 
             if (currentStep.Handlers.All(d => d.Key != orgId && d.Key != userId)) return;
@@ -699,9 +691,9 @@ namespace Hotline.FlowEngine.Workflows
 
                     //结束会签
                     currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType,
-                        _sessionContext.RequiredUserId, _sessionContext.UserName,
-                        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                        _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                        _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                        _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName);
                     await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
                 }
             }
@@ -712,10 +704,10 @@ namespace Hotline.FlowEngine.Workflows
             if (isStartCountersign)
             {
                 var exists = workflow.Countersigns.Any(d =>
-                    !d.IsCompleted() && d.StarterId == _sessionContext.RequiredUserId);
+                    !d.IsCompleted() && d.StarterId == _sessionContextProvider.SessionContext.RequiredUserId);
                 if (exists)
                     throw new UserFriendlyException("该用户在当前流程存在未结束会签");
-                await StartCountersignAsync(_sessionContext, workflow, currentStep, dto, flowAssignInfo.FlowAssignType,
+                await StartCountersignAsync(_sessionContextProvider.SessionContext, workflow, currentStep, dto, flowAssignInfo.FlowAssignType,
                     counterSignType, expiredTime, cancellationToken);
             }
 
@@ -758,7 +750,7 @@ namespace Hotline.FlowEngine.Workflows
                         //throw new UserFriendlyException(
                         //    $"会签数据异常, workflowId: {currentStep.WorkflowId}, countersignId: {currentStep.CountersignId}",
                         //    "会签数据异常");
-                        countersign.MemberHandled(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
+                        countersign.MemberHandled(_sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.RequiredOrgId);
                         //update cs
                         await _workflowCountersignRepository.UpdateNav(countersign)
                             .Include(d => d.Members)
@@ -801,12 +793,12 @@ namespace Hotline.FlowEngine.Workflows
             if (workflow.ActualHandleStepId == currentStep.Id)
             {
                 //更新实际办理节点信息
-                workflow.UpdateActualStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+                workflow.UpdateActualStepWhenHandle(currentStep, _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName, _sessionContextProvider.SessionContext.OrgLevel);
             }
 
             if (workflow.CurrentStepId == currentStep.Id)
             {
-                workflow.UpdateCurrentStepWhenHandle(currentStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+                workflow.UpdateCurrentStepWhenHandle(currentStep, _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName, _sessionContextProvider.SessionContext.OrgLevel);
             }
 
             //检查是否流转到流程终点
@@ -852,7 +844,7 @@ namespace Hotline.FlowEngine.Workflows
 
             //更新会签实际办理对象信息
             if (currentStep.IsActualHandled)
-                workflow.AddCsActualHandler(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
+                workflow.AddCsActualHandler(_sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.RequiredOrgId);
 
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
@@ -867,7 +859,7 @@ namespace Hotline.FlowEngine.Workflows
             var currentTrace = workflow.Traces.First(d => d.Id == currentStep.Id);
             await _publisher.PublishAsync(
                 new NextStepNotify(workflow, dto, flowAssignInfo, currentTrace, nextStepDefine,
-                    _sessionContext.RequiredOrgId, expiredTime.HasValue), PublishStrategy.ParallelWhenAll,
+                    _sessionContextProvider.SessionContext.RequiredOrgId, expiredTime.HasValue), PublishStrategy.ParallelWhenAll,
                 cancellationToken);
         }
 
@@ -1148,9 +1140,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 批量修改工单办理对象
         /// </summary>
         public async Task ChangeHandlerBatchAsync(
-            IReadOnlyList<(string userId, string username, string orgId, string orgName, string? roleId, string? roleName, ICollection<WorkflowStep>
-                steps
-                )> handlers,
+            IReadOnlyList<(string userId, string username, string orgId, string orgName, string? roleId, string? roleName, ICollection<WorkflowStep> steps)> handlers,
             CancellationToken cancellationToken)
         {
             foreach (var handler in handlers)
@@ -1318,7 +1308,7 @@ namespace Hotline.FlowEngine.Workflows
         {
             var workflow = await GetWorkflowAsync(workflowId, withSteps: true, withTraces: true,
                 cancellationToken: cancellationToken);
-            // workflow.Assign(EFlowAssignType.User, _sessionContext.RequiredUserId);
+            // workflow.Assign(EFlowAssignType.User, _sessionContextProvider.SessionContext.RequiredUserId);
             //
             // workflow.HandlerOrgs = new();
             // workflow.HandlerUsers = new List<HandlerGroupItem>
@@ -1542,10 +1532,11 @@ namespace Hotline.FlowEngine.Workflows
         /// <summary>
         /// 特提至中心(优先派单组其次坐席)
         /// </summary>
-        /// <returns></returns>
-        public async Task RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled,
+        /// <returns>true 派单组  false 话务部</returns>
+        public async Task<bool> RecallToCenterFirstToSendAsync(string workflowId, string opinion, bool isOrderFiled,
             DateTime? expiredTime, CancellationToken cancellationToken)
         {
+            bool isPaiDan = true;
             var workflow = await GetWorkflowAsync(workflowId, withDefine: true, withSteps: true, withTraces: true, withCountersigns: true,
                 cancellationToken: cancellationToken);
             var sendStep = workflow.Steps.FirstOrDefault(d => d.BusinessType == EBusinessType.Send);
@@ -1617,7 +1608,9 @@ namespace Hotline.FlowEngine.Workflows
                 //flowAssignInfo.FlowAssignType = EFlowAssignType.Role;
                 await RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, EWorkflowTraceType.Recall, expiredTime, isOrderFiled,
                     cancellationToken);
+                isPaiDan = false;
             }
+            return isPaiDan;
         }
 
         //private async Task RecallToTargetStepAsync(Workflow workflow, WorkflowStep targetStep, string opinion, ISessionContext current,
@@ -1753,7 +1746,7 @@ namespace Hotline.FlowEngine.Workflows
                 case EPathPolicy.DirectUpper:
                     break;
                 case EPathPolicy.DirectUpperCenterIsTop:
-                    var currentOrgLevel = _sessionContext.RequiredOrgId.CalcOrgLevel();
+                    var currentOrgLevel = _sessionContextProvider.SessionContext.RequiredOrgId.CalcOrgLevel();
                     if (currentOrgLevel == 1)
                     {
                         nextStepDefines = nextStepDefines.Where(d => d.IsCenter()).ToList();
@@ -1784,9 +1777,9 @@ namespace Hotline.FlowEngine.Workflows
             var workflow = await GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
                 cancellationToken: cancellationToken);
 
-            var currentStep = GetUnHandleStep(workflow.Steps, _sessionContext.RequiredOrgId,
-                _sessionContext.RequiredUserId, _sessionContext.Roles);
-            //var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.Steps, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
+            var currentStep = GetUnHandleStep(workflow.Steps, _sessionContextProvider.SessionContext.RequiredOrgId,
+                _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.Roles);
+            //var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.Steps, _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.RequiredUserId);
 
             var endStepDefine = workflow.WorkflowDefinition.FindEndStepDefine();
 
@@ -1835,6 +1828,7 @@ namespace Hotline.FlowEngine.Workflows
             startStep.IsMain = true;
             startStep.IsOrigin = true;
             startStep.Status = EWorkflowStepStatus.WaitForAccept;
+            startStep.FlowDirection = dto.FlowDirection;
             startStep.PrevChosenStepCode = null;
             if (expiredTime.HasValue)
                 startStep.StepExpiredTime = expiredTime;
@@ -1861,7 +1855,7 @@ namespace Hotline.FlowEngine.Workflows
             //};
 
             //create endStep
-            var endStep = await CreateEndStepAsync(workflow, endStepDefine, currentStep, expiredTime,
+            var endStep = await CreateEndStepAsync(workflow, endStepDefine, currentStep, dto, expiredTime,
                 cancellationToken);
             //workflow.Steps.Add(endStep);
 
@@ -1871,7 +1865,7 @@ namespace Hotline.FlowEngine.Workflows
             workflow.Complete(endStep, dto.ReviewResult);
 
             //需求调整:归档时当前节点显示为归档节点
-            workflow.UpdateCurrentStepWhenHandle(endStep, _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName, _sessionContext.OrgLevel);
+            workflow.UpdateCurrentStepWhenHandle(endStep, _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName, _sessionContextProvider.SessionContext.OrgLevel);
             workflow.UpdateCurrentStepAcceptTime(endStep.AcceptTime.Value);
 
             //workflow.UpdateActualStepWhenHandle(endStep, current.OrgAreaCode, current.OrgAreaName, current.OrgLevel);
@@ -1907,7 +1901,7 @@ namespace Hotline.FlowEngine.Workflows
         public ECounterSignType? GetCounterSignType(bool isStartCountersign)
         {
             if (!isStartCountersign) return null;
-            return _sessionContext.OrgIsCenter ? ECounterSignType.Center : ECounterSignType.Department;
+            return _sessionContextProvider.SessionContext.OrgIsCenter ? ECounterSignType.Center : ECounterSignType.Department;
         }
 
         /// <summary>
@@ -1948,20 +1942,23 @@ namespace Hotline.FlowEngine.Workflows
             if (dto.IsStartCountersign) return;
             if (workflow.IsInCountersign) return;
 
-            if (nextStepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send)
+            if (workflow.FlowType is EFlowType.Handle)
             {
-                //坐席->派单不选办理对象时
-                workflow.UpdateCurrentStepWhenAssign(nextSteps.First(),
-                    new FlowStepHandler
-                    {
-                        OrgId = OrgSeedData.CenterId,
-                        OrgName = OrgSeedData.CenterName
-                    });
-            }
-            else
-            {
-                var nextHandler = dto.NextHandlers.First();
-                workflow.UpdateCurrentStepWhenAssign(nextSteps.First(), nextHandler);
+                if (nextStepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send)
+                {
+                    //坐席->派单不选办理对象时
+                    workflow.UpdateCurrentStepWhenAssign(nextSteps.First(),
+                        new FlowStepHandler
+                        {
+                            OrgId = OrgSeedData.CenterId,
+                            OrgName = OrgSeedData.CenterName
+                        });
+                }
+                else
+                {
+                    var nextHandler = dto.NextHandlers.First();
+                    workflow.UpdateCurrentStepWhenAssign(nextSteps.First(), nextHandler);
+                }
             }
         }
 
@@ -1972,20 +1969,23 @@ namespace Hotline.FlowEngine.Workflows
             if (workflow.IsInCountersign) return;
             if (nextStepDefine.StepType is EStepType.Summary or EStepType.End) return;
 
-            if (nextStepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send)
-            {
-                //坐席->派单不选办理对象时
-                workflow.UpdateActualStepWhenAssign(nextSteps.First(),
-                    new FlowStepHandler
-                    {
-                        OrgId = OrgSeedData.CenterId,
-                        OrgName = OrgSeedData.CenterName
-                    });
-            }
-            else
+            if (workflow.FlowType is EFlowType.Handle)
             {
-                var nextHandler = dto.NextHandlers.First();
-                workflow.UpdateActualStepWhenAssign(nextSteps.First(), nextHandler);
+                if (nextStepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send)
+                {
+                    //坐席->派单不选办理对象时
+                    workflow.UpdateActualStepWhenAssign(nextSteps.First(),
+                        new FlowStepHandler
+                        {
+                            OrgId = OrgSeedData.CenterId,
+                            OrgName = OrgSeedData.CenterName
+                        });
+                }
+                else
+                {
+                    var nextHandler = dto.NextHandlers.First();
+                    workflow.UpdateActualStepWhenAssign(nextSteps.First(), nextHandler);
+                }
             }
 
             //if ( /*workflow.FlowType is EFlowType.Handle &&*/
@@ -2284,10 +2284,10 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         private void HandleStep(WorkflowStep step, string opinion, string nextStepCode)
         {
-            step.Handle(_sessionContext.RequiredUserId, _sessionContext.UserName,
-                _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-                _sessionContext.OrgIsCenter, opinion, nextStepCode);
+            step.Handle(_sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName,
+                _sessionContextProvider.SessionContext.OrgIsCenter, opinion, nextStepCode);
 
             //var handler = step.FindActualHandler(current.Roles, current.RequiredUserId, current.RequiredOrgId);
             //if (handler is not null)
@@ -2393,7 +2393,7 @@ namespace Hotline.FlowEngine.Workflows
             {
                 //newStep.FlowAssignType = EFlowAssignType.User;
                 // 是否中心  临时紧急修改 后续在流程模版定义是否原办理人退回类型 来实现流程 禅道200
-                newStep.FlowAssignType = step.HandlerOrgIsCenter!.Value
+                newStep.FlowAssignType = step.BusinessType is EBusinessType.Seat or EBusinessType.Send
                     ? step.BusinessType is EBusinessType.Send ? EFlowAssignType.User : EFlowAssignType.Role
                     : EFlowAssignType.Org;
                 //if (newStep is { FlowAssignType: EFlowAssignType.Role, BusinessType: EBusinessType.Send })
@@ -2614,10 +2614,10 @@ namespace Hotline.FlowEngine.Workflows
                 foreach (var trace in uncompleteTraces)
                 {
                     trace.Handle(
-                        _sessionContext.RequiredUserId, _sessionContext.UserName,
-                        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-                        _sessionContext.OrgIsCenter, dto.Opinion);
+                        _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                        _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                        _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName,
+                        _sessionContextProvider.SessionContext.OrgIsCenter, dto.Opinion);
                 }
 
                 //await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
@@ -2672,9 +2672,9 @@ namespace Hotline.FlowEngine.Workflows
                 foreach (var unCompleteCountersign in unCompleteCountersigns)
                 {
                     unCompleteCountersign.End(null, null, EBusinessType.File,
-                        _sessionContext.RequiredUserId, _sessionContext.UserName,
-                        _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                        _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                        _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                        _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                        _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName);
                 }
 
                 await _workflowCountersignRepository.UpdateRangeAsync(unCompleteCountersigns, cancellationToken);
@@ -2757,6 +2757,7 @@ namespace Hotline.FlowEngine.Workflows
             Workflow workflow,
             StepDefine endStepDefine,
             WorkflowStep prevStep,
+            BasicWorkflowDto dto,
             DateTime? expiredTime,
             CancellationToken cancellationToken)
         {
@@ -2765,21 +2766,21 @@ namespace Hotline.FlowEngine.Workflows
 
             var handler = new FlowStepHandler
             {
-                Key = _sessionContext.UserId,
-                Value = _sessionContext.UserName,
-                UserId = _sessionContext.RequiredUserId,
-                Username = _sessionContext.UserName,
-                OrgId = _sessionContext.OrgId,
-                OrgName = _sessionContext.OrgName,
+                Key = _sessionContextProvider.SessionContext.UserId,
+                Value = _sessionContextProvider.SessionContext.UserName,
+                UserId = _sessionContextProvider.SessionContext.RequiredUserId,
+                Username = _sessionContextProvider.SessionContext.UserName,
+                OrgId = _sessionContextProvider.SessionContext.OrgId,
+                OrgName = _sessionContextProvider.SessionContext.OrgName,
             };
 
             var step = CreateStep(workflow, endStepDefine, prevStep, EFlowAssignType.User, handler,
                 null, null, EWorkflowStepStatus.WaitForAccept,
-                ECountersignPosition.None, expiredTime, endStepDefine.Name, true, businessType: EBusinessType.File);
+                ECountersignPosition.None, expiredTime, endStepDefine.Name, true, businessType: EBusinessType.File, flowDirection: dto.FlowDirection);
 
-            //step.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
-            //    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-            //    _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+            //step.Accept(_sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+            //    _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+            //    _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName);
 
             HandleStep(step, "流程归档", string.Empty);
 
@@ -2848,7 +2849,11 @@ namespace Hotline.FlowEngine.Workflows
                 var isMain = handlers.Count == 1 || (handlers.Count > 1 && handler.Key == dto.NextMainHandler);
                 var step = CreateStep(workflow, stepDefine, prevStep, flowAssignType,
                     handler, dto.NextStepCode, countersignId, stepStatus, csPosition, expiredTime,
-                    dto.NextStepName, isOrigin, isMain, handlerType, dto.BusinessType);
+                    dto.NextStepName, isOrigin, isMain, handlerType, dto.BusinessType, dto.FlowDirection);
+
+                //var stepHandler = stepHandlers.First(d => d.GetHandler().Key == handler.Key);
+                //step.StepHandlers = new List<WorkflowStepHandler> { stepHandler };
+
                 steps.Add(step);
             }
 
@@ -2920,8 +2925,8 @@ namespace Hotline.FlowEngine.Workflows
                         throw new UserFriendlyException(
                             $"TerminalDynamicMark parse to int failed, tMark: {currentStepDefine.TerminalDynamicMark}");
                     var leadRoleCode = _systemSettingCacheManager.GetSetting(SettingConstants.RoleLingDao)?.SettingValue[0];
-                    var isLead = _sessionContext.Roles.Any(x => x == leadRoleCode);
-                    return (currentOrgLevel <= tMark2) && isLead;
+                    var isLead = _sessionContextProvider.SessionContext.Roles.Any(x => x == leadRoleCode);
+                    return (currentOrgLevel <= tMark2) && (isLead || _sessionContextProvider.SessionContext.OrgIsCenter);
                 case EDynamicPolicy.OrgDownCenterTop:
                 case EDynamicPolicy.OrgDown:
                     if (!int.TryParse(currentStepDefine.TerminalDynamicMark, out var tMark1))
@@ -3055,16 +3060,16 @@ namespace Hotline.FlowEngine.Workflows
             ref List<WorkflowStep> updateSteps, ref List<WorkflowTrace> updateTraces)
         {
             var isHandled = step.Status is EWorkflowStepStatus.Handled;
-            var opinion = $"会签未办理完成,由 {_sessionContext.OrgName} 的 {_sessionContext.UserName} 终止办理";
+            var opinion = $"会签未办理完成,由 {_sessionContextProvider.SessionContext.OrgName} 的 {_sessionContextProvider.SessionContext.UserName} 终止办理";
             if (step.IsStartCountersign)
                 step.CountersignEnd();
 
             if (step.Status is not EWorkflowStepStatus.Handled)
             {
-                step.Handle(_sessionContext.RequiredUserId, _sessionContext.UserName,
-                    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                    _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-                    _sessionContext.OrgIsCenter, opinion);
+                step.Handle(_sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                    _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                    _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName,
+                    _sessionContextProvider.SessionContext.OrgIsCenter, opinion);
             }
 
             updateSteps.Add(step);
@@ -3107,9 +3112,9 @@ namespace Hotline.FlowEngine.Workflows
             //todo 1. trace? 先确定展现形式 2. end cs
 
             countersign.End(null, null, businessType,
-                _sessionContext.RequiredUserId, _sessionContext.UserName,
-                _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName);
 
             /*
             * //结束step会签信息
@@ -3118,9 +3123,9 @@ namespace Hotline.FlowEngine.Workflows
 
                    //结束会签
                    currentCountersign.End(currentStep.Id, currentStep.Code, currentStep.BusinessType,
-                       _sessionContext.RequiredUserId, _sessionContext.UserName,
-                       _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-                       _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName);
+                       _sessionContextProvider.SessionContext.RequiredUserId, _sessionContextProvider.SessionContext.UserName,
+                       _sessionContextProvider.SessionContext.RequiredOrgId, _sessionContextProvider.SessionContext.OrgName,
+                       _sessionContextProvider.SessionContext.OrgAreaCode, _sessionContextProvider.SessionContext.OrgAreaName);
                    await _workflowCountersignRepository.UpdateAsync(currentCountersign, cancellationToken);
             */
 
@@ -3147,7 +3152,8 @@ namespace Hotline.FlowEngine.Workflows
             bool isOrigin,
             bool isMainHandler = false,
             EHandlerType? handlerType = null, //动态节点依据动态策略判断
-            EBusinessType? businessType = null
+            EBusinessType? businessType = null,
+            EFlowDirection? flowDirection = null
         )
         {
             //if (!handlers.Any())
@@ -3164,6 +3170,7 @@ namespace Hotline.FlowEngine.Workflows
             step.CountersignId = countersignId;
             step.Status = stepStatus;
             step.CountersignPosition = countersignPosition;
+            step.FlowDirection = flowDirection;
             if (expiredTime.HasValue)
                 step.StepExpiredTime = expiredTime;
             //step.TimeLimit = GetTimeLimit("");

+ 6 - 6
src/Hotline/Orders/OrderDomainService.cs

@@ -346,8 +346,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
                 //投诉举报
                 case "30":
                 case "35":
-                    valid.Validation = dto.Title.Contains("意见") || dto.Title.Contains("建议") || dto.Title.Contains("信息") || dto.Title.Contains("咨询");
-                    valid.Validation = dto.Content.Contains("意见") || dto.Content.Contains("建议") || dto.Content.Contains("信息") || dto.Content.Contains("咨询");
+                    valid.Validation = dto.Title.Contains("意见") || dto.Title.Contains("建议")  || dto.Title.Contains("咨询");
+                    valid.Validation = dto.Content.Contains("意见") || dto.Content.Contains("建议")  || dto.Content.Contains("咨询");
                     if (dto.Content.Length < 25)
                     {
                         valid.Validation = true;
@@ -356,8 +356,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
                     break;
                 // 意见
                 case "1":
-                    valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("信息") || dto.Title.Contains("咨询");
-                    valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("信息") || dto.Content.Contains("咨询");
+                    valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报")|| dto.Title.Contains("咨询");
+                    valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("咨询");
                     if (dto.Content.Length < 5)
                     {
                         valid.Validation = true;
@@ -367,8 +367,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
                 //建议求助
                 case "15":
                 case "20":
-                    valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报") || dto.Title.Contains("信息") || dto.Title.Contains("咨询");
-                    valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("信息") || dto.Content.Contains("咨询");
+                    valid.Validation = dto.Title.Contains("投诉") || dto.Title.Contains("举报")  || dto.Title.Contains("咨询");
+                    valid.Validation = dto.Content.Contains("投诉") || dto.Content.Contains("举报") || dto.Content.Contains("咨询");
                     if (dto.Content.Length < 25)
                     {
                         valid.Validation = true;

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

@@ -47,7 +47,7 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     public async Task UpdateSmsReplyAsync(MessageDto data)
     {
         _logger.LogInformation($"UpdateSmsReplyAsync 收到通知: {data.ToJson()}");
-        if (data.IsSmsReply == false || data.SmsReplyContent.IsNullOrEmpty() || data.ExternalId.IsNullOrEmpty()) return;
+        if (data.IsSmsReply == false || data.SmsReplyContent.IsNullOrEmpty()) return;
 
         var orderVisits = await _orderVisitRepository.Queryable()
             .Includes(m => m.Order)

+ 5 - 0
src/Hotline/Settings/SettingConstants.cs

@@ -560,5 +560,10 @@ namespace Hotline.Settings
         /// 是否重置中心会签类型
         /// </summary>
         public const string IsResetCenterCountersignType = "IsResetCenterCountersignType";
+
+        /// <summary>
+        /// 上传文件格式显示
+        /// </summary>
+        public const string FileExt = "FileExt";
     }
 }

+ 17 - 6
src/Hotline/Settings/TimeLimitDomain/ExpireTimeLimitBase.cs

@@ -121,7 +121,7 @@ public abstract class ExpireTimeLimitBase
     /// </summary>
     /// <param name="code"></param>
     /// <returns></returns>
-    public virtual TimeConfig GetOrderTimeLimitConfig(string? code = null)
+    public TimeConfig GetOrderTimeLimitConfigBase(string? code = null)
     {
         if (string.IsNullOrEmpty(code))
         {
@@ -135,6 +135,17 @@ public abstract class ExpireTimeLimitBase
         }
     }
 
+    /// <summary>
+    /// 获取办理时限配置
+    /// </summary>
+    /// <param name="code"></param>
+    /// <returns></returns>
+    public virtual TimeConfig GetOrderTimeLimitConfig(string? code = null)
+    {
+        return GetOrderTimeLimitConfigBase(code);
+    }
+
+
     /// <summary>
     /// 计算期满时间
     /// </summary>
@@ -149,11 +160,11 @@ public abstract class ExpireTimeLimitBase
             throw new UserFriendlyException("中心派至部门的工单期满时间需受理类型参数");
         var timeConfig = flowDirection switch
         {
-            EFlowDirection.CenterToOrg => GetOrderTimeLimitConfig(order.AcceptTypeCode),
-            EFlowDirection.OrgToCenter => GetOrderTimeLimitConfig(),
-            EFlowDirection.FiledToCenter => GetOrderTimeLimitConfig(),
-			EFlowDirection.CenterToCenter => GetOrderTimeLimitConfig(order.AcceptTypeCode),
-            EFlowDirection.FiledToOrg => GetOrderTimeLimitConfig(order.AcceptTypeCode),
+            EFlowDirection.CenterToOrg => GetOrderTimeLimitConfigBase(order.AcceptTypeCode),
+            EFlowDirection.OrgToCenter => GetOrderTimeLimitConfigBase(),
+            EFlowDirection.FiledToCenter => GetOrderTimeLimitConfigBase(),
+			EFlowDirection.CenterToCenter => GetOrderTimeLimitConfigBase(order.AcceptTypeCode),
+            EFlowDirection.FiledToOrg => GetOrderTimeLimitConfigBase(order.AcceptTypeCode),
 			_ => throw new ArgumentOutOfRangeException(nameof(flowDirection), flowDirection, null)
         };
 

+ 1 - 1
src/Hotline/Settings/TimeLimitDomain/ZiGongExpireTimeLimit.cs

@@ -114,7 +114,7 @@ public class ZiGongExpireTimeLimit : ExpireTimeLimitBase, ICalcExpireTime, IScop
                 return false;
             return true;
         });
-        if (busCode is null && code.IsNullOrEmpty()) base.GetOrderTimeLimitConfig();
+        if (busCode.IsNullOrEmpty() && code.IsNullOrEmpty()) return GetOrderTimeLimitConfigBase();
         var inventory = await _timeLimitSettingInventoryRepository.GetByCode(code);
         if (inventory is not null) return inventory.Adapt<TimeConfig>();
         var timeConfig = await _timeLimitSettingRepository.GetByBusCode(busCode)

+ 38 - 0
src/Hotline/Tools/ObjectExtensions.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Exceptions;
+
+namespace Hotline.Tools;
+public static class ObjectExtensions
+{
+    /// <summary>
+    /// 扩展方法用于手动验证对象,并返回验证结果。
+    /// </summary>
+    /// <param name="obj">需要验证的对象</param>
+    /// <returns>如果验证成功返回 string.Empty,否则返回错误信息</returns>
+    public static string ValidateObject(this object obj, bool throwError = true)
+    {
+        var validationResults = new List<ValidationResult>();
+        var validationContext = new ValidationContext(obj);
+
+        // 验证对象
+        bool isValid = Validator.TryValidateObject(obj, validationContext, validationResults, true);
+
+        if (isValid)
+        {
+            return string.Empty;
+        }
+        else
+        {
+            var msg = string.Join("; ", validationResults.Select(result => result.ErrorMessage));
+            if (throwError)
+                throw UserFriendlyException.SameMessage(msg);
+            // 如果验证失败,返回所有错误信息的拼接
+            return msg;
+        }
+    }
+}

+ 25 - 0
src/Tr.Sdk/CallTelClient.cs

@@ -0,0 +1,25 @@
+using Hotline.CallCenter.Tels;
+using Hotline.CallCenter.Tels.CallTelDomain;
+using Mapster;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tr.Sdk;
+public class CallTelClient : ICallTelClient
+{
+    private readonly ITrClient _trClient;
+
+    public CallTelClient(ITrClient trClient)
+    {
+        _trClient = trClient;
+    }
+
+    public async Task<List<QueryTelResponse>> QueryTelsAsync(QueryTelRequest request, CancellationToken cancellationToken)
+    {
+        var result = await _trClient.QueryTelsAsync(request.Adapt<Tels.QueryTelRequest>(), cancellationToken);
+        return result.Adapt<List<QueryTelResponse>>();
+    }
+}

+ 5 - 1
src/Tr.Sdk/Tr.Sdk.csproj

@@ -8,8 +8,12 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
-    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
     <PackageReference Include="RestSharp" Version="110.2.0" />
   </ItemGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\Hotline\Hotline.csproj" />
+  </ItemGroup>
+
 </Project>

+ 3 - 1
src/Tr.Sdk/TrSdkStartupExtensions.cs

@@ -1,4 +1,5 @@
-using Microsoft.Extensions.DependencyInjection;
+using Hotline.CallCenter.Tels;
+using Microsoft.Extensions.DependencyInjection;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -12,6 +13,7 @@ namespace Tr.Sdk
         public static IServiceCollection AddTrSdk(this IServiceCollection services, string baseUrl, string apiKey, string apiSecret)
         {
             services.AddSingleton<ITrClient, TrClient>(_ => new TrClient(baseUrl, apiKey, apiSecret));
+            services.AddScoped<ICallTelClient, CallTelClient>();
             return services;
         }
     }

+ 2 - 0
src/XF.Domain/Authentications/ISessionContextProvider.cs

@@ -11,5 +11,7 @@ namespace XF.Domain.Authentications
         public ISessionContext SessionContext { get; set; }
 
         public void SetContext(string key);
+
+        public void SetContext(ISessionContext sessionContext);
     }
 }

+ 23 - 2
src/XF.Domain/Extensions/StringExtensions.cs

@@ -32,14 +32,14 @@ namespace XF.Domain.Extensions
 
         public static string UpperFirstChar(this string str)
         {
-            if(string.IsNullOrEmpty(str))
+            if (string.IsNullOrEmpty(str))
                 throw new ArgumentNullException(nameof(str));
             var firstChar = str[0];
             if (char.IsUpper(firstChar))
                 return str;
             firstChar = char.ToUpper(firstChar);
             str = str.Remove(0, 1);
-            return str.Insert(0,firstChar.ToString());
+            return str.Insert(0, firstChar.ToString());
         }
 
         public static string ToPascalCase(this string str)
@@ -54,5 +54,26 @@ namespace XF.Domain.Extensions
             }
             return sb.ToString();
         }
+
+        public static int GetChineseCharLength(this string str)
+        {
+            if (string.IsNullOrEmpty(str) || str.Length == 0)
+                return 0;
+            ASCIIEncoding ascii = new ASCIIEncoding();
+            int tempLen = 0;
+            byte[] s = ascii.GetBytes(str);
+            for (int i = 0; i < s.Length; i++)
+            {
+                if ((int)s[i] == 63)
+                {
+                    tempLen += 2;
+                }
+                else
+                {
+                    tempLen += 1;
+                }
+            }
+            return tempLen;
+        }
     }
 }

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است