Bladeren bron

Merge branch 'fix/bug_0115' into feature/snapshot

qinchaoyue 3 maanden geleden
bovenliggende
commit
c4f87e26a4
100 gewijzigde bestanden met toevoegingen van 10149 en 674 verwijderingen
  1. 7 0
      Hotline.sln
  2. 3 1
      src/Hotline.Ai.Jths/AiQualityService.cs
  3. 104 0
      src/Hotline.Ai.XingTang/AiQualityService.cs
  4. 15 0
      src/Hotline.Ai.XingTang/AiXingTangStartupExtensions.cs
  5. 20 0
      src/Hotline.Ai.XingTang/Hotline.Ai.XingTang.csproj
  6. 102 2
      src/Hotline.Api/Controllers/Bi/BiCallController.cs
  7. 674 44
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  8. 130 0
      src/Hotline.Api/Controllers/Bi/BiQualityController.cs
  9. 82 2
      src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs
  10. 70 39
      src/Hotline.Api/Controllers/CallController.cs
  11. 590 0
      src/Hotline.Api/Controllers/CaseController.cs
  12. 10 10
      src/Hotline.Api/Controllers/CommonPController.cs
  13. 84 4
      src/Hotline.Api/Controllers/ExportWordController.cs
  14. 134 6
      src/Hotline.Api/Controllers/FileController.cs
  15. 162 20
      src/Hotline.Api/Controllers/OrderController.cs
  16. 1 0
      src/Hotline.Api/Controllers/OrderTerminateController.cs
  17. 561 0
      src/Hotline.Api/Controllers/PlanController.cs
  18. 69 9
      src/Hotline.Api/Controllers/QualityController.cs
  19. 2 0
      src/Hotline.Api/Controllers/Snapshot/SnapshotOrderController.cs
  20. 128 8
      src/Hotline.Api/Controllers/TestController.cs
  21. 28 25
      src/Hotline.Api/Controllers/WebPortalController.cs
  22. 261 0
      src/Hotline.Api/Controllers/XthxController.cs
  23. 3 0
      src/Hotline.Api/Hotline.Api.csproj
  24. 1 0
      src/Hotline.Api/Realtimes/RealtimeMethods.cs
  25. 8 0
      src/Hotline.Api/Realtimes/RealtimeService.cs
  26. 4 5
      src/Hotline.Api/StartupExtensions.cs
  27. BIN
      src/Hotline.Api/Template/QualityCertificate.doc
  28. 7 2
      src/Hotline.Api/config/appsettings.Development.json
  29. 2 2
      src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs
  30. 64 1
      src/Hotline.Application.Tests/Controller/OrderControllerTest.cs
  31. 19 4
      src/Hotline.Application/Bigscreen/DataScreenRefreshService.cs
  32. 35 23
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  33. 1 1
      src/Hotline.Application/CallCenter/ICallApplication.cs
  34. 2 2
      src/Hotline.Application/CallCenter/TianRunCallApplication.cs
  35. 690 0
      src/Hotline.Application/Caselibrary/CaseApplication.cs
  36. 90 0
      src/Hotline.Application/Caselibrary/ICaseApplication.cs
  37. 3 1
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  38. 4 1
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  39. 45 21
      src/Hotline.Application/Handlers/FlowEngine/WorkflowPreviousHandler.cs
  40. 1 0
      src/Hotline.Application/Hotline.Application.csproj
  41. 1 0
      src/Hotline.Application/Knowledge/KnowApplication.cs
  42. 4 0
      src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs
  43. 35 10
      src/Hotline.Application/Orders/IOrderApplication.cs
  44. 207 32
      src/Hotline.Application/Orders/OrderApplication.cs
  45. 0 106
      src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs
  46. 13 10
      src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs
  47. 34 30
      src/Hotline.Application/Orders/OrderSendBackAuditApplication.cs
  48. 90 0
      src/Hotline.Application/Planlibrary/IPlanApplication.cs
  49. 674 0
      src/Hotline.Application/Planlibrary/PlanApplication.cs
  50. 7 0
      src/Hotline.Application/Quality/IQualityApplication.cs
  51. 113 6
      src/Hotline.Application/Quality/QualityApplication.cs
  52. 5 2
      src/Hotline.Application/Snapshot/Notifications/SnapshotHandler.cs
  53. 3 0
      src/Hotline.Application/Snapshot/SnapshotApplicationBase.cs
  54. 10 0
      src/Hotline.Application/StatisticalReport/CallReport/CallReportApplicationBase.cs
  55. 77 0
      src/Hotline.Application/StatisticalReport/CallReport/YiBinCallReportApplication.cs
  56. 15 0
      src/Hotline.Application/StatisticalReport/ICallReportApplication.cs
  57. 36 1
      src/Hotline.Application/StatisticalReport/IOrderReportApplication.cs
  58. 68 16
      src/Hotline.Application/StatisticalReport/OrderReportApplication.cs
  59. 34 0
      src/Hotline.Application/Xthx/IXthxApplication.cs
  60. 122 0
      src/Hotline.Application/Xthx/XthxApplication.cs
  61. 4 3
      src/Hotline.Repository.SqlSugar/CallCenter/TrCallRecordRepository.cs
  62. 542 139
      src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs
  63. 5 0
      src/Hotline.Repository.SqlSugar/Snapshot/OrderSnapshotRepository.cs
  64. 10 1
      src/Hotline.Share/Dtos/Bi/BiOrderDto.cs
  65. 57 7
      src/Hotline.Share/Dtos/Bigscreen/BigscreenDto.cs
  66. 173 3
      src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs
  67. 13 0
      src/Hotline.Share/Dtos/CallCenter/CallDto.cs
  68. 634 46
      src/Hotline.Share/Dtos/CallCenter/CenterReportStatisticsDto.cs
  69. 10 0
      src/Hotline.Share/Dtos/CallCenter/QueryTelOperationsFixedDto.cs
  70. 48 0
      src/Hotline.Share/Dtos/CallCenter/TelOperationXthxDto.cs
  71. 257 0
      src/Hotline.Share/Dtos/Caselibrary/CaseDataDto.cs
  72. 511 0
      src/Hotline.Share/Dtos/Caselibrary/CaseListDto.cs
  73. 189 0
      src/Hotline.Share/Dtos/Caselibrary/CaseTypeDto.cs
  74. 51 0
      src/Hotline.Share/Dtos/Caselibrary/CaseTypeOrgDto.cs
  75. 42 0
      src/Hotline.Share/Dtos/File/UploadAudioFilesRequestDto.cs
  76. 5 0
      src/Hotline.Share/Dtos/FlowEngine/NextStepsDto.cs
  77. 25 0
      src/Hotline.Share/Dtos/Knowledge/KnowledgeDataDto.cs
  78. 10 0
      src/Hotline.Share/Dtos/Knowledge/KnowledgeDto.cs
  79. 1 1
      src/Hotline.Share/Dtos/Order/Detail/OrderFlowTraceDto.cs
  80. 99 2
      src/Hotline.Share/Dtos/Order/OrderBiDto.cs
  81. 58 0
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  82. 12 0
      src/Hotline.Share/Dtos/Order/OrderStartFlowDto.cs
  83. 12 0
      src/Hotline.Share/Dtos/Order/OrderTsDetailsDto.cs
  84. 5 0
      src/Hotline.Share/Dtos/Order/Publish/QueryOrderPublishDto.cs
  85. 6 1
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  86. 12 1
      src/Hotline.Share/Dtos/Order/SendBackDto.cs
  87. 240 0
      src/Hotline.Share/Dtos/Planlibrary/PlanDataDto.cs
  88. 469 0
      src/Hotline.Share/Dtos/Planlibrary/PlanListDto.cs
  89. 189 0
      src/Hotline.Share/Dtos/Planlibrary/PlanTypeDto.cs
  90. 51 0
      src/Hotline.Share/Dtos/Planlibrary/PlanTypeOrgDto.cs
  91. 90 0
      src/Hotline.Share/Dtos/Quality/BiQualityDto.cs
  92. 17 0
      src/Hotline.Share/Dtos/Quality/QualityDto.cs
  93. 145 0
      src/Hotline.Share/Dtos/QualityExportWord/QualityCertificate.cs
  94. 2 1
      src/Hotline.Share/Dtos/Snapshot/RedPackDto.cs
  95. 115 22
      src/Hotline.Share/Dtos/TrCallCenter/TrTelDao.cs
  96. 55 0
      src/Hotline.Share/Dtos/Transfer.cs
  97. 1 1
      src/Hotline.Share/Dtos/WebPortal/WebFlowAcceptDto.cs
  98. 123 0
      src/Hotline.Share/Enums/CallCenter/EOperationStatus.cs
  99. 33 0
      src/Hotline.Share/Enums/Caselibrary/ECaseApplyStatus.cs
  100. 64 0
      src/Hotline.Share/Enums/Caselibrary/ECaseStatus.cs

+ 7 - 0
Hotline.sln

@@ -61,6 +61,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.WeChat", "src\Hotli
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TianQue.Sdk", "src\TianQue.Sdk\TianQue.Sdk.csproj", "{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hotline.Ai.XingTang", "src\Hotline.Ai.XingTang\Hotline.Ai.XingTang.csproj", "{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -159,6 +161,10 @@ Global
 		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -190,6 +196,7 @@ Global
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{75215667-65AF-4B7B-85E7-3140239B30CC} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{8E4F64EF-314A-45BA-8BB2-46FF5B06F7D5} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

+ 3 - 1
src/Hotline.Ai.Jths/AiQualityService.cs

@@ -21,8 +21,10 @@ namespace Hotline.Ai.Jths
             _client = new RestClient();
             _baseUrl = baseUrl;
         }
+        public async Task<string> CreateAiOrderQualityTask(string filename, CancellationToken cancellationToken) { return ""; }
 
-        public async Task CreateAiOrderQualityTask(
+
+		public async Task CreateAiOrderQualityTask(
              string id,
              string audioFile,
              string fromNo,

+ 104 - 0
src/Hotline.Ai.XingTang/AiQualityService.cs

@@ -0,0 +1,104 @@
+using RestSharp;
+using Fw.Utility.UnifyResponse;
+using System.Text;
+using System.Security.Cryptography;
+using Hotline.Share.Dtos.Quality;
+using Hotline.Ai.Quality;
+
+namespace Hotline.Ai.XingTang
+{
+    public class AiQualityService : IAiQualityService
+    {
+        private readonly RestClient _client;
+        private readonly string _baseUrl;
+
+        public AiQualityService(string baseUrl)
+        {
+            _client = new RestClient();
+            _baseUrl = baseUrl;
+        }
+
+        public async Task CreateAiOrderQualityTask(
+            string id,
+            string audioFile,
+            string fromNo,
+            DateTime? callStartTime,
+            string viteRecordPrefix,
+            string ywlxString,
+            CancellationToken cancellationToken)
+        { 
+        }
+
+
+		public async Task<string> CreateAiOrderQualityTask(string filename, CancellationToken cancellationToken)
+        {
+            var url = _baseUrl + "/offlinerecog?filename=" + filename;
+			var baseUrl = new Uri(url);
+            return  await ExecuteAsync(baseUrl.ToString(), Method.Get, "", cancellationToken);
+        }
+
+        public async Task<ApiResponse<TResponse>> ExecuteAsync<TRequest, TResponse>(string path, Method httpMethod,
+            TRequest request, CancellationToken cancellationToken)
+            where TRequest : class
+        {
+            var req = new RestRequest(path, httpMethod);
+            if (httpMethod is Method.Get)
+            {
+                req.AddObject(request);
+            }
+            else
+            {
+                req.AddJsonBody(request);
+            }
+
+            try
+            {
+                var response = await _client.ExecuteAsync<ApiResponse<TResponse>>(req, cancellationToken);
+                return response.Data;
+            }
+            catch (Exception e)
+            {
+                throw new HttpRequestException($"智能质检平台错误,Error: {e.Message}");
+            }
+        }
+
+        public async Task<string> ExecuteAsync<TRequest>(string path, Method httpMethod, TRequest request,
+            CancellationToken cancellationToken)
+            where TRequest : class
+        {
+            var req = new RestRequest(path, httpMethod);
+            req.Timeout = new TimeSpan(0,30,0);
+
+            try
+            {
+                var response = await _client.ExecuteAsync<ApiResponse>(req, cancellationToken);
+                return response.Content;
+            }
+            catch (Exception e)
+            {
+                throw new HttpRequestException($"智能质检平台错误,Error: {e.Message}");
+            }
+        }
+
+        /// <summary>
+        /// MD5加密
+        /// </summary>
+        /// <param name="input">需要加密的字符串</param>
+        /// <returns></returns>
+        private static string MD5Encrypt(string? input)
+        {
+            using var md5 = MD5.Create();
+            var t = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
+            var sb = new StringBuilder(32);
+            for (var i = 0; i < t.Length; i++)
+                sb.Append(t[i].ToString("x").PadLeft(2, '0'));
+            return sb.ToString();
+        }
+
+        private static string Base64En(string? model)
+        {
+            var bytes = Encoding.UTF8.GetBytes(model);
+            return Convert.ToBase64String(bytes);
+        }
+    }
+}

+ 15 - 0
src/Hotline.Ai.XingTang/AiXingTangStartupExtensions.cs

@@ -0,0 +1,15 @@
+using Hotline.Ai.Quality;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Hotline.Ai.XingTang
+{
+	public static class AiXingTangStartupExtensions
+	{
+		public  static IServiceCollection AddAiXingTang(this IServiceCollection services, string baseUrl)
+		{
+			services.AddSingleton<IAiQualityService, AiQualityService>(_ => new AiQualityService(baseUrl));
+
+			return services;
+		}
+	}
+}

+ 20 - 0
src/Hotline.Ai.XingTang/Hotline.Ai.XingTang.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="Fw.Utility.UnifyResponse" Version="1.0.0" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
+    <PackageReference Include="RestSharp" Version="112.1.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Hotline.Share\Hotline.Share.csproj" />
+    <ProjectReference Include="..\Hotline\Hotline.csproj" />
+  </ItemGroup>
+
+</Project>

+ 102 - 2
src/Hotline.Api/Controllers/Bi/BiCallController.cs

@@ -22,6 +22,7 @@ using XF.Domain.Repository;
 using Hotline.Settings;
 using XF.Utility.EnumExtensions;
 using Hotline.Share.Enums.CallCenter;
+using Hotline.Repository.SqlSugar.Extensions;
 
 namespace Hotline.Api.Controllers.Bi;
 
@@ -575,7 +576,7 @@ public class BiCallController : BaseController
             Date = "合计",
             PersonCallInCount = list.Sum(x=>x.PersonCallInCount),
             PersonCallInPutthroughCount = list.Sum(x=>x.PersonCallInPutthroughCount),
-            PersonRingOffCount = list.Sum(x=>x.PersonRingOffCount),//个人服务挂断
+            //PersonRingOffCount = list.Sum(x=>x.PersonRingOffCount),//个人服务挂断
             PersonQueueOffCount = list.Sum(x=>x.PersonQueueOffCount),//个人服务队列挂断
             PersonWaitOffCount = list.Sum(x=>x.PersonWaitOffCount) //个人服务等待挂断
         };
@@ -608,7 +609,7 @@ public class BiCallController : BaseController
             Date = "合计",
             EnterpriseCallInCount = list.Sum(x=>x.EnterpriseCallInCount),
             EnterpriseCallInPutthroughCount = list.Sum(x=>x.EnterpriseCallInPutthroughCount),
-            EnterpriseRingOffCount = list.Sum(x=>x.EnterpriseRingOffCount), //企业挂断
+            //EnterpriseRingOffCount = list.Sum(x=>x.EnterpriseRingOffCount), //企业挂断
             EnterpriseQueueOffCount = list.Sum(x=>x.EnterpriseQueueOffCount),//个人服务队列挂断
             EnterpriseWaitOffCount = list.Sum(x=>x.EnterpriseWaitOffCount) //个人服务等待挂断
         };
@@ -671,5 +672,104 @@ public class BiCallController : BaseController
                 await _callReportApplication.QueryCallOutDateStatisticsDetail(dto.QueryDto,enterpriseTels)
             , "呼出话务统计明细", "Date");
     }
+
+    /// <summary>
+    /// 坐席月接通率统计
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("query-seat-monthcall")]
+    public async Task<object> QuerySeatMonthCall([FromQuery]QuerySeatMonthCallRequest dto)
+    {
+        var list =  await _callReportApplication.QuerySeatMonthCall(dto);
+        var total = new QuerySeatMonthCallResp()
+        {
+            Name = "合计",
+            InAnswered = list.Sum(x => x.InAnswered),
+            InAvailableAnswer = list.Sum(x => x.InAvailableAnswer),
+            InHangupImmediateWhenAnswered = list.Sum(x => x.InHangupImmediateWhenAnswered),
+            OverTimeImmediate = list.Sum(x => x.OverTimeImmediate),
+            InTimeImmediate = list.Sum(x => x.InTimeImmediate),
+            InHanguped = list.Sum(x => x.InHanguped),
+            InHangupImmediate = list.Sum(x => x.InHangupImmediate),
+            OverTimeInHanguped = list.Sum(x => x.OverTimeInHanguped),
+        };
+
+        return new { List = list, Total = total };
+    }
+
+    /// <summary>
+    /// 坐席月接通率统计基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("query-seat-monthcall-basedata")]
+    public async Task<object> QuerySeatMonthCallBaseData()
+    {
+        return new
+        {
+            SeatUser =await _userRepository.Queryable().Where(x => x.UserType == Share.Enums.User.EUserType.Seat).ToListAsync()            
+        };
+    }
+
+    /// <summary>
+    /// 坐席月接通率统计导出
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("query-seat-monthcall/export")]
+    public async Task<FileStreamResult> QuerySeatMonthCallExport([FromBody] ExportExcelDto<QuerySeatMonthCallRequest> dto)
+    =>  _exportApplication.GetExcelFile(
+            dto,              
+            await _callReportApplication.QuerySeatMonthCall(dto.QueryDto)
+            , "坐席月接通率统计", "Name");
+
+
+    /// <summary>
+    /// 坐席月接通明细
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("query-seat-monthcall-detail")]
+    public async Task<PagedDto<QuerySeatMonthCallDetailResp>> QuerySeatMonthCallDetail([FromQuery] QuerySeatMonthCallDetailRequest dto)
+    {
+        var query = _callReportApplication.QuerySeatMonthCallDetail(dto);
+        var(total, items) =  await query.ToPagedListAsync(dto.PageIndex, dto.PageSize);
+        return new PagedDto<QuerySeatMonthCallDetailResp>(total, items);
+    }
+
+    /// <summary>
+    /// 坐席月接通明细导出
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("query-seat-monthcall-detail/export")]
+    public async Task<FileStreamResult> QuerySeatMonthCallDetailExport([FromBody] ExportExcelDto<QuerySeatMonthCallDetailRequest> dto)
+    {
+        var query = _callReportApplication.QuerySeatMonthCallDetail(dto.QueryDto);
+        List<QuerySeatMonthCallDetailResp> 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<QuerySeatMonthCallDetailResp>>(data);
+
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+        var dtos = dataDtos
+            .Select(stu => _mapper.Map(stu, typeof(QuerySeatMonthCallDetailResp), dynamicClass))
+            .Cast<object>()
+            .ToList();
+
+        var stream = ExcelHelper.CreateStream(dtos);
+
+        return ExcelStreamResult(stream, "坐席月接通明细");
+    }
+
     #endregion
 }

File diff suppressed because it is too large
+ 674 - 44
src/Hotline.Api/Controllers/Bi/BiOrderController.cs


+ 130 - 0
src/Hotline.Api/Controllers/Bi/BiQualityController.cs

@@ -0,0 +1,130 @@
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos;
+using Hotline.Share.Requests;
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using Hotline.Application.Quality;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Dtos.Quality;
+using Hotline.Tools;
+using Hotline.Quality;
+using Hotline.Share.Enums.Quality;
+
+namespace Hotline.Api.Controllers.Bi
+{
+	public class BiQualityController : BaseController
+	{
+		private readonly IMapper _mapper;
+		private readonly IQualityApplication _qualityApplication;
+		private readonly IQualityRepository _qualityRepository;
+
+
+		public BiQualityController(
+			IMapper mapper,
+			IQualityApplication qualityApplication,
+			IQualityRepository qualityRepository
+			)
+		{
+			_mapper = mapper;
+			_qualityApplication = qualityApplication;
+			_qualityRepository = qualityRepository;
+		}
+
+
+		/// <summary>
+		/// 坐席质检分析
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpGet("seats_quality_analyse")]
+		public async Task<PagedDto<SeatsQualityAnalyseDto>> SeatsQualityAnalyse([FromQuery] PagedKeywordRequest dto)
+		{
+			var (total, items) = await _qualityApplication.SeatsQualityAnalyse(dto, HttpContext.RequestAborted).ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+			return new PagedDto<SeatsQualityAnalyseDto>(total, _mapper.Map<IReadOnlyList<SeatsQualityAnalyseDto>>(items));
+		}
+
+		/// <summary>
+		/// 坐席质检分析导出
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpPost("seats_quality_analyse/export")]
+		public async Task<FileStreamResult> SeatsQualityAnalyseExport([FromBody] ExportExcelDto<PagedKeywordRequest> dto)
+		{
+			var query = _qualityApplication.SeatsQualityAnalyse(dto.QueryDto, HttpContext.RequestAborted);
+
+			List<Hotline.Quality.Quality> lists;
+			if (dto.IsExportAll)
+			{
+				lists = await query.ToListAsync(HttpContext.RequestAborted);
+			}
+			else
+			{
+				var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+				lists = items;
+			}
+
+			var listDtos = _mapper.Map<ICollection<SeatsQualityAnalyseDto>>(lists);
+
+			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+			var dtos = listDtos
+				.Select(stu => _mapper.Map(stu, typeof(SeatsQualityAnalyseDto), dynamicClass))
+				.Cast<object>()
+				.ToList();
+
+			var stream = ExcelHelper.CreateStream(dtos);
+
+			return ExcelStreamResult(stream, "坐席质检分析");
+
+		}
+
+		#region 质检工作分析
+
+		/// <summary>
+		/// 质检工单概览
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpGet("quality_order_overview")]
+		public async Task<List<QualityOrderOverviewDto>> QualityOrderOverview([FromQuery] QualityWorkAnalysisRequest dto)
+		{
+			var allOrderNum =  await _qualityRepository.Queryable().Where(x => x.Source == EQualitySource.Accepted && x.QualityTime >= dto.StartTime && x.QualityTime <= dto.EndTime && x.State == EQualityState.End).CountAsync();
+			var items = await _qualityApplication.QualityOrderOverview(dto,allOrderNum, HttpContext.RequestAborted).ToListAsync(HttpContext.RequestAborted);
+			return items;
+		}
+
+		/// <summary>
+		/// 质检工单概览导出
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpPost("quality_order_overview/export")]
+		public async Task<FileStreamResult> QualityOrderOverviewExport([FromBody] ExportExcelDto<QualityWorkAnalysisRequest> dto)
+		{
+			var allOrderNum = await _qualityRepository.Queryable().Where(x => x.Source == EQualitySource.Accepted && x.QualityTime >= dto.QueryDto.StartTime && x.QualityTime <= dto.QueryDto.EndTime && x.State == EQualityState.End).CountAsync();
+			var query = _qualityApplication.QualityOrderOverview(dto.QueryDto, allOrderNum, HttpContext.RequestAborted);
+
+			List<QualityOrderOverviewDto> listDtos;
+
+			listDtos = await query.ToListAsync(HttpContext.RequestAborted);
+
+			//var listDtos = _mapper.Map<ICollection<SeatsQualityAnalyseDto>>(lists);
+
+			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+			var dtos = listDtos
+				.Select(stu => _mapper.Map(stu, typeof(SeatsQualityAnalyseDto), dynamicClass))
+				.Cast<object>()
+				.ToList();
+
+			var stream = ExcelHelper.CreateStream(dtos);
+
+			return ExcelStreamResult(stream, "质检工单概览");
+
+		}
+
+		#endregion
+	}
+}

+ 82 - 2
src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs

@@ -1,18 +1,22 @@
-using Hotline.Configurations;
+using DocumentFormat.OpenXml.Drawing;
+using Hotline.Configurations;
 using Hotline.KnowledgeBase;
 using Hotline.Orders;
 using Hotline.Repository.SqlSugar.Orders;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Bigscreen;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.KnowledgeBase;
 using Hotline.Share.Enums.Order;
+using JiebaNet.Segmenter.Common;
 using MapsterMapper;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using SqlSugar;
+using System;
 using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers.Bigscreen
@@ -28,11 +32,13 @@ namespace Hotline.Api.Controllers.Bigscreen
         private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
         private readonly IRepository<SystemArea> _systemAreaRepository;
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        private readonly IRepository<OrderSecondaryHandling> _orderSecondaryHandlingRepository;
 
         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)
+            IOptionsSnapshot<AppConfiguration> appOptions,
+            IRepository<OrderSecondaryHandling> orderSecondaryHandlingRepository)
         {
             _orderRepository = orderRepository;
             _orderVisitRepository = orderVisitRepository;
@@ -43,6 +49,7 @@ namespace Hotline.Api.Controllers.Bigscreen
             _orderVisitDetailRepository = orderVisitDetailRepository;
             _systemAreaRepository = systemAreaRepository;
             _appOptions = appOptions;
+            _orderSecondaryHandlingRepository = orderSecondaryHandlingRepository;
         }
 
         /// <summary>
@@ -590,5 +597,78 @@ namespace Hotline.Api.Controllers.Bigscreen
                 return list;
             }
         }
+
+        /// <summary>
+        /// 二次办理统计
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-secondary-statistics")]
+        public async Task<SecondaryProcessingOrderStatisticsDto> OrderSecondaryStatistics(DateTime StartTime, DateTime EndTime)
+        {
+            DateTime? dateTime = DateTime.Now;
+            EndTime = EndTime.AddDays(1).AddSeconds(-1);
+            var data = new SecondaryProcessingOrderStatisticsDto
+            {
+                OrderCount = await _orderSecondaryHandlingRepository.Queryable()
+                 .Where(x => x.AuditTime >= StartTime && x.AuditTime <= EndTime && x.State != ESecondaryHandlingState.NotApply
+                 && x.State != ESecondaryHandlingState.Apply && x.State != ESecondaryHandlingState.Refuse).CountAsync(),
+
+                OrderOverdueCount = await _orderSecondaryHandlingRepository.Queryable()
+                .Includes(x => x.Order)
+                 //.Where(x => x.Order.Status < EOrderStatus.Filed)
+                 .Where(x => x.Order.ExpiredTime != null &&
+                        (((x.Order.Status == EOrderStatus.Filed || x.Order.Status == EOrderStatus.Published || x.Order.Status == EOrderStatus.Visited) &&
+                           x.Order.FiledTime >= x.Order.ExpiredTime) ||
+                         ((x.Order.Status != EOrderStatus.Filed && x.Order.Status != EOrderStatus.Published && x.Order.Status != EOrderStatus.Visited) &&
+                          dateTime >= x.Order.ExpiredTime.Value)))
+                 .Where(x => x.AuditTime >= StartTime && x.AuditTime <= EndTime
+                 && x.State != ESecondaryHandlingState.NotApply
+                 && x.State != ESecondaryHandlingState.Apply && x.State != ESecondaryHandlingState.Refuse)
+                 .CountAsync(),
+
+                OrderSoonOverdueCount = await _orderSecondaryHandlingRepository.Queryable()
+                .Includes(x => x.Order)
+                 .Where(x => x.Order.Status < EOrderStatus.Filed && dateTime > x.Order.NearlyExpiredTime && dateTime < x.Order.ExpiredTime)
+                 .Where(x => x.AuditTime >= StartTime && x.AuditTime <= EndTime
+                 && x.State != ESecondaryHandlingState.NotApply
+                 && x.State != ESecondaryHandlingState.Apply && x.State != ESecondaryHandlingState.Refuse)
+                 .CountAsync()
+            };
+            var da = await _orderSecondaryHandlingRepository.Queryable()
+                .LeftJoin<OrderVisit>((os, ov) => os.OrderId == ov.OrderId)
+                    .LeftJoin<OrderVisitDetail>((os, ov, od) => ov.Id == od.VisitId)
+                    .Where((os, ov, od) => ov.VisitState == EVisitState.Visited && od.VisitTarget == EVisitTarget.Org && ov.VisitTime >= StartTime && ov.VisitTime <= EndTime
+                    && os.State != ESecondaryHandlingState.NotApply
+                    && os.State != ESecondaryHandlingState.Apply && os.State != ESecondaryHandlingState.Refuse)
+                    .Select((os, ov, od) => new SecondarySatisfactionDto()
+                    {
+                        Count = SqlFunc.AggregateCount(os.Id),
+                        NoSatisfiedCount = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(od.OrgProcessingResults, "Key") == "1"
+                        || SqlFunc.JsonField(od.OrgProcessingResults, "Key") == "2", 1, 0)),//不满意数
+                    }).FirstAsync();
+            if (da != null)
+                data.SatisfactionRate = da.SatisfiedRate;
+            return data;
+        }
+
+        /// <summary>
+        /// 二次办理中工单概览
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-secondary-handling-query")]
+        public async Task<List<OrderSecondaryHandlingDto>> OrderSecondaryHandlingDetailQuery()
+        {
+            var quer = await _orderSecondaryHandlingRepository.Queryable()
+                 .Includes(x => x.Order)
+                  .Where(x => x.CreationTime.Date == DateTime.Now.Date)
+                  .OrderByDescending(x => x.CreationTime)
+                  .Take(50)
+                 .ToListAsync();
+            return _mapper.Map<List<OrderSecondaryHandlingDto>>(quer);
+        }
     }
 }

+ 70 - 39
src/Hotline.Api/Controllers/CallController.cs

@@ -13,11 +13,15 @@ using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Enums.CallCenter;
+using Hotline.Repository.SqlSugar.Extensions;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using System.Threading;
 using XF.Domain.Exceptions;
 using XF.Utility.EnumExtensions;
+using XF.Domain.Repository;
+using MapsterMapper;
+using SqlSugar;
 
 namespace Hotline.Api.Controllers
 {
@@ -25,33 +29,39 @@ namespace Hotline.Api.Controllers
     {
         private readonly ICallApplication _callApplication;
         private readonly Publisher _publisher;
+        private readonly IMapper _mapper;
         private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IRepository<TelOperationXthx> _telOperationXthxRepository;
 
-		public CallController(
+        public CallController(
             ICallApplication callApplication,
             Publisher publisher,
+            IMapper mapper,
             IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
-            ISystemSettingCacheManager systemSettingCacheManager)
+            ISystemSettingCacheManager systemSettingCacheManager,
+            IRepository<TelOperationXthx> telOperationXthxRepository)
         {
             _callApplication = callApplication;
             _publisher = publisher;
+            _mapper = mapper;
             _callcenterOptions = callcenterOptions;
             _systemSettingCacheManager = systemSettingCacheManager;
-		}
+            _telOperationXthxRepository = telOperationXthxRepository;
+        }
 
         /// <summary>
         /// 查询分机
         /// </summary>
         [HttpGet("tels")]
         public async Task<IList<TelDto>> QueryTels()
-            => (await _callApplication.QueryTelsAsync(new QueryTelsInDto(null) { PageSize = 99999}, HttpContext.RequestAborted)).Item2;
+            => (await _callApplication.QueryTelsAsync(new QueryTelsInDto(null) { PageSize = 99999 }, HttpContext.RequestAborted)).Item2;
 
         /// <summary>
         /// 查询分机
         /// </summary>
         [HttpGet("tels-paged")]
-        public async Task<PagedDto<TelDto>> QueryTelsAsync([FromQuery]QueryTelsInDto dto)
+        public async Task<PagedDto<TelDto>> QueryTelsAsync([FromQuery] QueryTelsInDto dto)
             => (await _callApplication.QueryTelsAsync(dto, HttpContext.RequestAborted)).ToPaged();
 
         /// <summary>
@@ -140,7 +150,7 @@ namespace Hotline.Api.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpGet("tel-operations-fixed")]
-        public Task<IReadOnlyList<TelOperation>> QueryTelOperationsFixed([FromQuery] QueryTelOperationsFixedDto dto) => 
+        public Task<IReadOnlyList<TelOperation>> QueryTelOperationsFixed([FromQuery] QueryTelOperationsFixedDto dto) =>
             _callApplication.QueryTelOperationsAsync(dto, HttpContext.RequestAborted);
 
         /// <summary>
@@ -155,8 +165,6 @@ namespace Hotline.Api.Controllers
             };
         }
 
-
-
         /// <summary>
         /// 通话转写
         /// </summary>
@@ -165,36 +173,59 @@ namespace Hotline.Api.Controllers
         [HttpPost("calls/transliteration")]
         public async Task CallTransliteration([FromBody] CallTransliteration dto)
         {
-	        foreach (var id in dto.Ids)
-	        {
-				var call = await _callApplication.GetTianrunCallAsync(id, HttpContext.RequestAborted);
-				if (call is null)
-					throw UserFriendlyException.SameMessage("通话信息错误");
-				if (call.TransliterationState == ECallTransliterationState.Underway)
-					throw UserFriendlyException.SameMessage("正在转写中,请勿重复点击,请稍作等待");
-				if (call.TransliterationState == ECallTransliterationState.Succeed)
-					throw UserFriendlyException.SameMessage("转写成功,不能重新转写");
-				call.InitTransliterationId();
-				await _callApplication.EditTransliterationStateAsync(call.Id, ECallTransliterationState.Underway, call.TransliterationId, HttpContext.RequestAborted);
-				var audioFile = call.RecordingAbsolutePath;
-				var fromNo = call.CPN;
-				var callStartTime = call.CreatedTime;
-				var Id = call.TransliterationId;
-				var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
-
-
-
-				var handler = new AiQualityHandler()
-				{
-					Id = Id,
-					Source = "AiAnswered",
-					AudioFile = audioFile,
-					FromNo = fromNo,
-					CallStartTime = callStartTime,
-					ViteRecordPrefix = setting?.SettingValue[0],
-				};
-				await _publisher.PublishAsync(new AiOrderQualityNotify(handler), PublishStrategy.ParallelNoWait, HttpContext.RequestAborted);
-			}
+            foreach (var id in dto.Ids)
+            {
+                var call = await _callApplication.GetTianrunCallAsync(id, HttpContext.RequestAborted);
+                if (call is null)
+                    throw UserFriendlyException.SameMessage("通话信息错误");
+                if (call.TransliterationState == ECallTransliterationState.Underway)
+                    throw UserFriendlyException.SameMessage("正在转写中,请勿重复点击,请稍作等待");
+                if (call.TransliterationState == ECallTransliterationState.Succeed)
+                    throw UserFriendlyException.SameMessage("转写成功,不能重新转写");
+                call.InitTransliterationId();
+                await _callApplication.EditTransliterationStateAsync(call.Id, ECallTransliterationState.Underway, call.TransliterationId, HttpContext.RequestAborted);
+                var audioFile = call.RecordingAbsolutePath;
+                var fromNo = call.CPN;
+                var callStartTime = call.CreatedTime;
+                var Id = call.TransliterationId;
+                var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
+
+
+
+                var handler = new AiQualityHandler()
+                {
+                    Id = Id,
+                    Source = "AiAnswered",
+                    AudioFile = audioFile,
+                    FromNo = fromNo,
+                    CallStartTime = callStartTime,
+                    ViteRecordPrefix = setting?.SettingValue[0],
+                };
+                await _publisher.PublishAsync(new AiOrderQualityNotify(handler), PublishStrategy.ParallelNoWait, HttpContext.RequestAborted);
+            }
+        }
+
+        #region 坐席动作类型统计
+
+        /// <summary>
+        /// 坐席动作类型统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("telaction/list")]
+        public async Task<PagedDto<TelActionListXthx>> TelActionList([FromQuery] TelActionXthxDto dto)
+        {
+            var (total, items) = await _telOperationXthxRepository.Queryable()
+                .WhereIF(string.IsNullOrEmpty(dto.TelNo) == false, x => x.TelNo.Contains(dto.TelNo))
+                .WhereIF(dto.OperationStatus != null, x => x.OperationStatus == dto.OperationStatus)
+                .WhereIF(string.IsNullOrEmpty(dto.UserName) == false, x => x.UserName.Contains(dto.UserName))
+                .WhereIF(dto.StartTime.HasValue, x => x.StartTime >= dto.StartTime)
+                .WhereIF(dto.EndTime.HasValue, x => x.StartTime <= dto.EndTime)
+                .OrderByDescending(x => x.StartTime)
+                .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+            return new PagedDto<TelActionListXthx>(total, _mapper.Map<IReadOnlyList<TelActionListXthx>>(items));
         }
-	}
+
+        #endregion
+    }
 }

+ 590 - 0
src/Hotline.Api/Controllers/CaseController.cs

@@ -0,0 +1,590 @@
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using XF.Domain.Repository;
+using Hotline.Application.Caselibrary;
+using Hotline.Share.Dtos.Caselibrary;
+using SqlSugar;
+using Hotline.CaseLibrary;
+using XF.Domain.Exceptions;
+using Hotline.Share.Dtos;
+using Hotline.Share.Tools;
+using Hotline.Share.Enums.Caselibrary;
+using Hotline.Application.ExportWord;
+using Hotline.Application.Tools;
+using Hotline.Share.Enums.Article;
+using XF.Utility.EnumExtensions;
+using Hotline.Share.Dtos.Order;
+using Hotline.Application.ExportExcel;
+
+namespace Hotline.Api.Controllers
+{
+    /// <summary>
+    /// 案例库
+    /// </summary>
+    public class CaseController : BaseController
+    {
+
+        #region 注入
+
+        private readonly IMapper _mapper;
+        private readonly ISessionContext _sessionContext;
+        private readonly ICaseApplication _caseApplication;
+        private readonly IRepository<CaseType> _caseTypeRepository;
+        private readonly IRepository<CaseList> _caseListRepository;
+        private readonly IRepository<CaseCollect> _caseCollectRepository;
+        private readonly IWordHelperService _wordHelperService;
+        private readonly IExportApplication _exportApplication;
+
+
+        public CaseController(
+           IMapper mapper,
+           ISessionContext sessionContext,
+           ICaseApplication CaseApplication,
+           IRepository<CaseType> CaseTypeRepository,
+           IRepository<CaseList> CaseListRepository,
+           IRepository<CaseCollect> CaseCollectRepository,
+           IWordHelperService wordHelperService,
+           IExportApplication exportApplication)
+        {
+            _mapper = mapper;
+            _sessionContext = sessionContext;
+            _caseApplication = CaseApplication;
+            _caseTypeRepository = CaseTypeRepository;
+            _caseListRepository = CaseListRepository;
+            _caseCollectRepository = CaseCollectRepository;
+            _wordHelperService = wordHelperService;
+            _exportApplication = exportApplication;
+        }
+
+        #endregion
+
+        #region 案例库类型管理
+
+        /// <summary>
+        /// 获取列表层级分类
+        /// </summary>
+        /// <param name="IsEnable">是否启用</param>
+        /// <returns></returns>
+        [HttpGet("type/treelist")]
+        public async Task<List<CaseTypeDto>> QueryAllTreeList(bool? IsEnable)
+        {
+            return await _caseTypeRepository.Queryable()
+                .WhereIF(IsEnable.HasValue, x => x.IsEnable == IsEnable)
+                .Where(x => SqlFunc.Subqueryable<CaseTypeOrg>().Where(to => to.TypeId == x.Id).Any() ||
+                SqlFunc.Subqueryable<CaseTypeOrg>().Where(to => to.TypeId == x.Id).NotAny()
+                )
+                .Select(x => new CaseTypeDto()
+                {
+                    Id = x.Id.SelectAll()
+                }
+                )
+                .OrderBy(x => x.Sort).ToTreeAsync(it => it.children, it => it.ParentId, null, it => it.Id);
+        }
+
+        /// <summary>
+        /// 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("type/add")]
+        public async Task<string> AddType([FromBody] AddCaseTypeDto dto)
+        {
+            return await _caseApplication.AddTypeAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("type/update")]
+        public async Task UpdateType([FromBody] UpdateCaseTypeDto dto)
+        {
+            await _caseApplication.UpdateTypeAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 查询详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpGet("type/info/{Id}")]
+        public async Task<CaseType> GetType(string Id)
+        {
+            var types = await _caseTypeRepository.Queryable()
+                .Includes(x => x.CaseTypeOrgs)   // 填充子对象
+                .Where(x => x.Id == Id)
+                .FirstAsync(HttpContext.RequestAborted);
+            if (types is null)
+                throw UserFriendlyException.SameMessage("查询失败!");
+            return types;
+        }
+
+        /// <summary>
+        /// 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpDelete("type/remove/{Id}")]
+        public async Task RemoveType(string Id)
+        {
+            await _caseApplication.RemoveTypeAsync(Id, HttpContext.RequestAborted);
+        }
+
+        #endregion
+
+        #region 案例库管理
+
+        /// <summary>
+        /// 案例库分类列表
+        /// </summary>
+        /// <param name="IsEnable"></param>
+        /// <returns></returns>
+        [HttpGet("list/treelist")]
+        public async Task<List<CaseTypeDto>> QueryAllCaseTypeTreeList(bool? IsEnable)
+        {
+            return await _caseTypeRepository.Queryable()
+                .WhereIF(IsEnable.HasValue, x => x.IsEnable == IsEnable)
+                .Where(x => SqlFunc.Subqueryable<CaseTypeOrg>().Where(to => to.TypeId == x.Id).Any() ||
+                SqlFunc.Subqueryable<CaseTypeOrg>().Where(to => to.TypeId == x.Id).NotAny()
+                )
+                .Select(x => new CaseTypeDto()
+                {
+                    Id = x.Id.SelectAll(),
+                    CaseNum = SqlFunc.Subqueryable<CaseRelationType>().LeftJoin<CaseList>((kr, k) => kr.CaseId == k.Id)
+                         .Where((kr, k) => kr.CaseTypeId == x.Id)
+                         .DistinctCount(kr => kr.CaseId)
+                }
+                )
+                .OrderBy(x => x.Sort).ToTreeAsync(it => it.children, it => it.ParentId, null, it => it.Id);
+        }
+
+        /// <summary>
+        /// 案例库列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        [HttpGet("list")]
+        public async Task<PagedDto<CaseDataDto>> QueryAllCaseList([FromQuery] CaseListDto pagedDto)
+        {
+            return (await _caseApplication.QueryAllCaseListAsync(pagedDto, HttpContext.RequestAborted)).ToPaged();
+        }
+
+        /// <summary>
+        /// 案例库草稿
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/draft")]
+        public async Task<string> CaseDraft([FromBody] AddCaseListDto dto)
+        {
+            dto.Status = ECaseStatus.NewDrafts;
+            return await _caseApplication.AddCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库草稿修改
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/draftupdate")]
+        public async Task UpdateCaseDraft([FromBody] UpdateCaseListDto dto)
+        {
+            dto.Status = ECaseStatus.NewDrafts;
+            await _caseApplication.UpdateCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库草稿上架到审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/draftadd")]
+        public async Task AddCaseDraft([FromBody] UpdateCaseListDto dto)
+        {
+            dto.ApplyStatus = ECaseApplyStatus.Add;
+            dto.Status = ECaseStatus.Auditing;
+            await _caseApplication.UpdateCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库草稿删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpDelete("list/draftremove/{Id}")]
+        public async Task RemovePlanDraft(string Id)
+        {
+            UpdateCaseListDto dto = new UpdateCaseListDto();
+            dto.ApplyStatus = ECaseApplyStatus.Delete;
+            dto.Status = ECaseStatus.Auditing;
+            dto.Id = Id;
+            await _caseApplication.RemoveCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库删除
+        /// </summary>
+        /// <param name="dtoDel"></param>
+        /// <returns></returns>
+        [HttpDelete("list/remove")]
+        public async Task RemovePlan([FromBody] DelCaseListDto dtoDel)
+        {
+            UpdateCaseListDto dto = new UpdateCaseListDto();
+            dto.ApplyStatus = ECaseApplyStatus.Delete;
+            dto.Status = ECaseStatus.Auditing;
+            dto.Id = dtoDel.Id;
+            dto.ApplyReason = dtoDel.ApplyReason;
+            await _caseApplication.UpdateCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库新增到审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/add")]
+        public async Task<string> AddCase([FromBody] AddCaseListDto dto)
+        {
+            dto.ApplyStatus = ECaseApplyStatus.Add;
+            dto.Status = ECaseStatus.Auditing;
+            return await _caseApplication.AddCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库编辑提交到审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/update")]
+        public async Task UpdateCase([FromBody] UpdateCaseListDto dto)
+        {
+            dto.ApplyStatus = ECaseApplyStatus.Update;
+            dto.Status = ECaseStatus.Auditing;
+            await _caseApplication.UpdateCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库上架
+        /// </summary>
+        /// <param name="Id">案例库ID</param>
+        /// <returns></returns>
+        [HttpGet("list/onshelf/{Id}")]
+        public async Task OnshelfPlan(string Id)
+        {
+            UpdateCaseListDto dto = new UpdateCaseListDto();
+            dto.Id = Id;
+            dto.ApplyStatus = ECaseApplyStatus.Add;
+            dto.Status = ECaseStatus.OnShelf;
+            dto.OnShelfTime = DateTime.Now;
+            await _caseApplication.AuditCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库下架
+        /// </summary>
+        /// <param name="Id">案例库ID</param>
+        /// <returns></returns>
+        [HttpGet("list/offshelf/{Id}")]
+        public async Task OffshelfCase(string Id)
+        {
+            UpdateCaseListDto dto = new UpdateCaseListDto();
+            dto.Id = Id;
+            dto.ApplyStatus = ECaseApplyStatus.Offshelf;
+            dto.Status = ECaseStatus.Auditing;
+            dto.OffShelfTime = DateTime.Now;
+
+            await _caseApplication.AuditCaseAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 设置热门
+        /// </summary>
+        /// <param name="Ids"></param>
+        /// <param name="Popular"></param>
+        /// <returns></returns>
+        [HttpPost("list/popular")]
+        public async Task PopularCase([FromBody] CaseInfoPopularDto dto)
+        {
+            if (dto.Ids.Length > 0)
+            {
+                for (int i = 0; i < dto.Ids.Length; i++)
+                {
+                    var Case = await _caseListRepository.GetAsync(dto.Ids[i]);
+
+                    //if (Case == null)
+                    //    throw UserFriendlyException.SameMessage("案例库查询失败");
+                    if (Case != null)
+                    {
+                        Case.IsPopular = dto.Popular;
+                        await _caseListRepository.UpdateNullAsync(Case, HttpContext.RequestAborted);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// 案例库审核(新增、修改、删除)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/examin")]
+        public async Task ExaminCase([FromBody] AuditCaseListDto dto)
+        {
+            var Case = await _caseListRepository.GetAsync(dto.Id);
+            if (Case == null)
+                throw UserFriendlyException.SameMessage("案例库查询失败");
+
+            var CaseDto = _mapper.Map<UpdateCaseListDto>(Case);
+
+            if (dto.State == 0)
+            {//不同意
+                CaseDto.Status = ECaseStatus.Revert;
+            }
+            else if (dto.State == 1)
+            {//同意 
+                if (CaseDto.ApplyStatus == ECaseApplyStatus.Add)
+                {
+                    CaseDto.Status = ECaseStatus.OnShelf;
+                    CaseDto.OnShelfTime = DateTime.Now;
+                }
+                if (CaseDto.ApplyStatus == ECaseApplyStatus.Update)
+                {
+                    CaseDto.Status = ECaseStatus.OnShelf;
+                    CaseDto.OnShelfTime = DateTime.Now;
+                    CaseDto.UpdateTime = DateTime.Now;
+                }
+                if (CaseDto.ApplyStatus == ECaseApplyStatus.Offshelf)
+                {
+                    CaseDto.Status = ECaseStatus.OffShelf;
+                    CaseDto.OffShelfTime = DateTime.Now;
+                }
+                if (CaseDto.ApplyStatus == ECaseApplyStatus.Delete)
+                {
+                    CaseDto.Status = ECaseStatus.Drafts;
+                }
+            }
+            CaseDto.Id = dto.Id;
+            CaseDto.ExaminTime = DateTime.Now;
+            CaseDto.ExaminManId = _sessionContext.UserId;
+            CaseDto.ExaminOrganizeId = _sessionContext.OrgId;
+            CaseDto.ExaminOpinion = dto.ExaminOpinion;
+
+            await _caseApplication.AuditCaseAsync(CaseDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="IsAddPv"></param>
+        /// <returns></returns>
+        [HttpGet("list/info")]
+        public async Task<CaseInfoDto> GetCase(string Id, bool IsAddPv)
+        {
+            return await _caseApplication.GetCaseAsync(Id, IsAddPv, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 案例库申请理由
+        /// </summary>
+        /// <param name="Id">案例库ID</param>
+        /// <returns></returns>
+        [HttpGet("list/reason/{Id}")]
+        public async Task<CaseApplyReasonDto> ReasonPlan(string Id)
+        {
+            var reason = await _caseListRepository.GetAsync(x => x.Id == Id);
+            return _mapper.Map<CaseApplyReasonDto>(reason);
+        }
+
+        /// <summary>
+        /// 案例库评分
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/score")]
+        public async Task ScoreCase([FromBody] PvCaseListDto dto)
+        {
+            var collect = await _caseCollectRepository.GetAsync(x => x.CaseId == dto.Id && x.CreatorId == _sessionContext.UserId);
+            if (collect != null)
+            {
+                if (collect.Score > 0)
+                    throw UserFriendlyException.SameMessage("当前知识已经评分");
+
+                collect.Score = dto.Score;
+                await _caseCollectRepository.UpdateAsync(collect, HttpContext.RequestAborted);
+            }
+            else
+            {
+                collect = new CaseCollect();
+                collect.CaseId = dto.Id;
+                collect.Score = dto.Score;
+                await _caseCollectRepository.AddAsync(collect, HttpContext.RequestAborted);
+            }
+
+            //计算总分
+            var sugar = _caseCollectRepository.Queryable().Where(x => x.CaseId == dto.Id);
+            var count = await sugar.CountAsync();
+            var collects = await sugar.SumAsync(x => x.Score);
+            var scoreTemp = collects / count;
+            var Case = await _caseListRepository.GetAsync(x => x.Id == dto.Id);
+            if (Case != null)
+            {
+                Case.Score = decimal.Round(scoreTemp.Value, 1);
+                await _caseListRepository.UpdateAsync(Case, HttpContext.RequestAborted);
+            }
+        }
+
+        /// <summary>
+        /// 案例库查重
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/exist")]
+        public async Task<bool> ExistCase([FromBody] CaseExistDto dto)
+        {
+            var any = await _caseListRepository.Queryable()
+                .Where(x => x.Status == ECaseStatus.Auditing || x.Status >= ECaseStatus.OnShelf)
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Equals(dto.Title))
+                .WhereIF(!string.IsNullOrEmpty(dto.Abstract), x => x.Abstract.Equals(dto.Abstract))
+                .WhereIF(!string.IsNullOrEmpty(dto.Describe), x => x.Describe.Equals(dto.Describe))
+                .WhereIF(!string.IsNullOrEmpty(dto.Result), x => x.Result.Equals(dto.Result))
+                .WhereIF(!string.IsNullOrEmpty(dto.Reason), x => x.Reason.Equals(dto.Reason))
+                .WhereIF(!string.IsNullOrEmpty(dto.Id), x => x.Id != dto.Id)
+                .AnyAsync();
+            return any;
+        }
+
+        /// <summary>
+        /// 案例库详情导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/info/export")]
+        public async Task<IActionResult> CaseInfoExport([FromBody] CaseInfoExportDto dto)
+        {
+            if (dto.Ids.Length > 1)
+            {
+                var streams = await _caseApplication.CaseInfoListExportAsync(dto, HttpContext.RequestAborted);
+                byte[] fileBytes = _wordHelperService.ConvertZipStream(streams);
+                var name = DateTime.Now.ToString("yyyyMMddHHmmss");
+                return File(fileBytes, "application/octet-stream", $"{name}.zip");
+            }
+            var info = await _caseListRepository.GetAsync(dto.Ids[0]) ?? throw UserFriendlyException.SameMessage("案例不存在");
+            return info.Result.HtmlToStream(dto.FileType).GetFileStreamResult(dto.FileType, info.Title, false);
+        }
+
+        /// <summary>
+        /// 案例库列表导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("export")]
+        public async Task<IActionResult> GetPlanListExportAsync([FromBody] ExportExcelDto<CaseListDto> dto)
+        {
+            var items = (await _caseApplication.QueryAllCaseListAsync(dto.QueryDto, HttpContext.RequestAborted)).Item2;
+            return _exportApplication.GetExcelFile(dto, items, "案例明细导出");
+        }
+
+        /// <summary>
+        /// 案例库列表页面枚举值
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("list/status-data")]
+        public Dictionary<string, dynamic> PlanStatus()
+        {
+            var tabStatusName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(3, "已上架"),
+                new KeyValuePair<int, string>(4, "已下架"),
+                new KeyValuePair<int, string>(1, "审核中"),
+                new KeyValuePair<int, string>(7, "草稿"),
+                new KeyValuePair<int, string>(-1, "全部")
+            };
+
+            var tabExamineName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(-1, "全部"),
+                new KeyValuePair<int, string>(0, "新增审核"),
+                new KeyValuePair<int, string>(1, "修改审核"),
+                new KeyValuePair<int, string>(2, "删除审核")
+            };
+
+            var StatusName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(-1, "全部"),
+                new KeyValuePair<int, string>(0, "待提交"),
+                new KeyValuePair<int, string>(1, "审核中"),
+                new KeyValuePair<int, string>(3, "已上架"),
+                new KeyValuePair<int, string>(4, "已下架"),
+                new KeyValuePair<int, string>(5, "审核不通过"),
+                new KeyValuePair<int, string>(6, "已过期"),
+                new KeyValuePair<int, string>(7, "草稿")
+            };
+
+            var ApplyStatusName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(-1, "全部"),
+                new KeyValuePair<int, string>(0, "新增审核"),
+                new KeyValuePair<int, string>(1, "修改审核"),
+                new KeyValuePair<int, string>(2, "删除审核")
+            };
+
+            var ignoreFileType = EFileType.excel | EFileType.pdf;
+            var items = EnumExts.GetDescriptions<EFileType>();
+            var filteredDictionary = items
+                 .Where(kvp => (ignoreFileType & (EFileType)kvp.Key) == 0)
+                 .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
+                 .ToList();
+
+            return new Dictionary<string, dynamic>
+            {
+                { "fileType", filteredDictionary},
+                { "tabStatusName", tabStatusName },
+                { "tabExamineName", tabExamineName },
+                { "StatusName", StatusName },
+                { "ApplyStatusName", ApplyStatusName }
+            };
+        }
+
+        #endregion
+
+        #region 案例库检索
+
+        /// <summary>
+        /// 检索列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        [HttpGet("search")]
+        public async Task<PagedDto<CaseDataDto>> QueryOnShelfCaseList([FromQuery] CaseListDto pagedDto)
+        {
+            pagedDto.Status = ECaseStatus.OnShelf;
+            return (await _caseApplication.QueryAllCaseListAsync(pagedDto, HttpContext.RequestAborted)).ToPaged();
+        }
+
+        /// <summary>
+        /// 检索列表前10
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("search/top10")]
+        public async Task<List<CasePageViewDto>> QueryTop10CaseList()
+        {
+            return await _caseListRepository.Queryable()
+                .Take(10)
+                .Where(x => x.Status == ECaseStatus.OnShelf)
+                .Select(x => new CasePageViewDto
+                {
+                    Id = x.Id,
+                    Title = x.Title,
+                    PageView = x.PageView
+                })
+                .OrderBy(x => x.PageView, OrderByType.Desc)
+                .ToListAsync();
+        }
+
+        #endregion
+    }
+}
+

+ 10 - 10
src/Hotline.Api/Controllers/CommonPController.cs

@@ -432,20 +432,20 @@ namespace Hotline.Api.Controllers
 					.ToListAsync();
 				var waitedList = waitedDataList.Where(x => x.Status != EOrderStatus.Countersigning &&
 				 x.Time > DateTime.Now && x.Status < EOrderStatus.Filed && DateTime.Now < x.NearlyExpiredTime
-				).OrderBy(x=>x.Time).Take(40).ToList();
+				).OrderBy(x=>x.Time).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))
-														  .OrderBy(x=> x.Time).Take(40).ToList();
+														  .OrderBy(x=> x.Time).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.Status == EOrderStatus.Countersigning).OrderBy(x => x.Time).Take(40).ToList();
+				var signDataList = waitedDataList.Where(x => x.Status == EOrderStatus.Countersigning).OrderBy(x => x.Time).ToList();
 				signDataList = signDataList.Count > 0 ? signDataList.Copy() : signDataList;
 				signDataList.ForEach(x => x.Type = "Sign");
 				
@@ -467,7 +467,7 @@ namespace Hotline.Api.Controllers
 						Time = d.Order.ExpiredTime,
 						Status = d.Order.Status,
 						CounterSignType = d.Order.CounterSignType
-					}).Take(40)
+					})
 					.ToListAsync();
 				//allNum += screenDataList.Count;
 				//allList.AddRange(screenDataList);
@@ -487,7 +487,7 @@ namespace Hotline.Api.Controllers
 						Time = d.Order.ExpiredTime,
 						Status = d.Order.Status,
 						CounterSignType = d.Order.CounterSignType
-					}).Take(40)
+					})
 					.ToListAsync();
 				//allNum += delayDataList.Count;
 				//allList.AddRange(delayDataList);
@@ -518,7 +518,7 @@ namespace Hotline.Api.Controllers
 					Time = x.OrderVisit.Order.ExpiredTime,
 					Status = x.OrderVisit.Order.Status,
 					CounterSignType = x.OrderVisit.Order.CounterSignType
-				}).Take(40).ToListAsync();
+				}).ToListAsync();
 				//allNum += screenApplyDataList.Count;
 				//allList.AddRange(screenApplyDataList);
 				//退回待审批
@@ -538,7 +538,7 @@ namespace Hotline.Api.Controllers
 						Time = d.Order.ExpiredTime,
 						Status = d.Order.Status,
 						CounterSignType = d.Order.CounterSignType
-					}).Take(40)
+					})
 					.ToListAsync();
 				//allNum += sendBackAuditDataList.Count;
 				//allList.AddRange(sendBackAuditDataList);
@@ -564,14 +564,14 @@ namespace Hotline.Api.Controllers
 						Time = d.ExpiredTime,
 						Status = d.Status,
 						CounterSignType = d.CounterSignType
-					}).Take(40)
+					})
 					.ToListAsync();
 				return new
 				{
 					//AllNum = allNum,
 					//AllList = allList,
-					WaitedNum = waitedList.Count > 40 ? 40 : waitedList.Count,
-					WaitedList = waitedList.Take(40).ToList(),
+					WaitedNum = waitedList.Count,
+					WaitedList = waitedList.ToList(),
 					WaitedExpiredNum = waitedExpiredDataList.Count,
 					WaitedExpiredList = waitedExpiredDataList,
 					SignDataNum = signDataList.Count,

+ 84 - 4
src/Hotline.Api/Controllers/ExportWordController.cs

@@ -2,12 +2,15 @@
 using Hotline.Caching.Interfaces;
 using Hotline.Configurations;
 using Hotline.Orders;
+using Hotline.Quality;
 using Hotline.Settings;
 using Hotline.Share.Dtos.OrderExportWord;
+using Hotline.Share.Dtos.QualityExportWord;
 using Hotline.Share.Enums.Order;
 using MapsterMapper;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
+using MongoDB.Driver.Linq;
 using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers
@@ -22,15 +25,17 @@ namespace Hotline.Api.Controllers
         private readonly ILogger<ExportWordController> _logger;
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+		private readonly IQualityRepository _qualityRepository;
 
-        public ExportWordController(IOrderRepository orderRepository,
+		public ExportWordController(IOrderRepository orderRepository,
             IWordHelperService wordHelperService,
             IMapper mapper,
            IRepository<OrderVisit> orderVisitRepository,
            IRepository<OrderVisitDetail> orderVisitedDetailRepository,
            ILogger<ExportWordController> logger,
             IOptionsSnapshot<AppConfiguration> appOptions,
-            ISystemSettingCacheManager systemSettingCacheManager)
+            ISystemSettingCacheManager systemSettingCacheManager,
+			IQualityRepository qualityRepository)
         {
             _orderRepository = orderRepository;
             _wordHelperService = wordHelperService;
@@ -40,7 +45,9 @@ namespace Hotline.Api.Controllers
             _logger = logger;
             _appOptions = appOptions;
             _systemSettingCacheManager = systemSettingCacheManager;
-        }
+			_qualityRepository = qualityRepository;
+
+		}
 
         /// <summary>
         /// 工单交办单导出         
@@ -120,5 +127,78 @@ namespace Hotline.Api.Controllers
             return File(fileBytes, "application/octet-stream", $"{name}.zip");
         }
 
-    }
+
+        /// <summary>
+        /// 质检单导出
+        /// </summary>
+        /// <param name="Ids"></param>
+        /// <returns></returns>
+		[HttpPost("quality_certificate")]
+		public async Task<IActionResult> QualityCertificate([FromBody] List<string> Ids) {
+
+			var streams = new Dictionary<string, Stream>();
+			var path = $"{Directory.GetCurrentDirectory()}/Template/QualityCertificate.doc";
+			int num = 1;
+			foreach (var item in Ids)
+			{
+                var quality = await _qualityRepository.GetAsync(item, HttpContext.RequestAborted);
+				if (quality == null)
+					continue;
+				var order = await _orderRepository.GetAsync(quality.OrderId, HttpContext.RequestAborted);
+				if (order == null)
+					continue;
+
+				var exportTest = _mapper.Map<QualityCertificate>(order);
+
+				if (_appOptions.Value.IsZiGong)
+					exportTest.CityName = "自贡市";
+				else if (_appOptions.Value.IsYiBin)
+					exportTest.CityName = "宜宾市";
+				else if (_appOptions.Value.IsLuZhou)
+					exportTest.CityName = "泸州市";
+
+                //质检信息
+                exportTest.Grade = quality.Grade;
+                exportTest.QualityContent = quality.Content;
+				exportTest.UserName = quality.UserName;
+				exportTest.QualityTime = quality.QualityTime.HasValue? quality.QualityTime.Value.ToString("yyyy-MM-dd HH:mm:ss") :string.Empty;
+
+				//查询回访信息
+				var visitData = await _orderVisitRepository.GetAsync(p => p.OrderId == order.Id && p.VisitState == EVisitState.Visited, HttpContext.RequestAborted);
+				if (visitData != null)
+				{
+					//回访明细
+					var visitDetail = await _orderVisitedDetailRepository.Queryable().Where(p => p.VisitId == visitData.Id && p.VisitTarget == Share.Enums.Order.EVisitTarget.Org).ToListAsync();
+					string visit = "";
+					foreach (var itemVisit in visitDetail)
+					{
+						visit += "回访部门:" + itemVisit.VisitOrgName;
+						visit += " \n办件结果:" + itemVisit.OrgProcessingResults?.Value + "    办事态度:" + itemVisit.OrgHandledAttitude?.Value + "\n";
+
+						if (itemVisit.VisitOrgCode == order.ActualHandleOrgCode)
+							exportTest.VisitContent = "回访内容:" + itemVisit.VisitContent;
+					}
+					exportTest.VisitOrg = visit;
+				}
+				if (Ids.Count > 1)
+				{
+					streams.Add(order.No + "_" + num + path.Substring(path.LastIndexOf(".")), _wordHelperService.WordStream(path, exportTest));
+					num = num + 1;
+				}
+				else
+				{
+					var btyes = _wordHelperService.WordByte(path, exportTest);
+					HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
+					return File(btyes, "application/vnd.ms-word", order.No + path.Substring(path.LastIndexOf(".")));
+				}
+			}
+
+			//调用压缩方法 进行压缩 (接收byte[] 数据)
+			byte[] fileBytes = _wordHelperService.ConvertZipStream(streams);
+			HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
+			var name = DateTime.Now.ToString("yyyyMMddHHmmss");
+			return File(fileBytes, "application/octet-stream", $"{name}.zip");
+		}
+
+	}
 }

+ 134 - 6
src/Hotline.Api/Controllers/FileController.cs

@@ -9,6 +9,13 @@ using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using Hotline.Caching.Interfaces;
 using Microsoft.AspNetCore.Authorization;
+using DocumentFormat.OpenXml.Presentation;
+using Hotline.File;
+using NPOI.HPSF;
+using Hotline.Share.Requests;
+using SqlSugar;
+using Hotline.Tools;
+using Hotline.Share.Dtos.Order;
 
 namespace Hotline.Api.Controllers
 {
@@ -19,20 +26,24 @@ namespace Hotline.Api.Controllers
         private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
         private readonly IRepository<File.File> _fileRepository;
         private readonly ILogger<FileController> _logger;
+        private readonly IRepository<UploadAudioFiles> _uploadAudioFilesRepository;
 
         public FileController(
             ISessionContext sessionContext,
             IMapper mapper,
             ISystemDicDataCacheManager sysDicDataCacheManager,
             IRepository<File.File> fileRepository,
-            ILogger<FileController> logger)
+            ILogger<FileController> logger,
+            IRepository<UploadAudioFiles> uploadAudioFilesRepository)
         {
             _sessionContext = sessionContext;
             _mapper = mapper;
             _sysDicDataCacheManager = sysDicDataCacheManager;
             _fileRepository = fileRepository;
             _logger = logger;
+            _uploadAudioFilesRepository = uploadAudioFilesRepository;
         }
+
         #region 附件管理
         /// <summary>
         /// 新增附件
@@ -148,11 +159,128 @@ namespace Hotline.Api.Controllers
         [HttpGet("download-proxy")]
         public async Task<IActionResult> DownloadProxy([FromServices] IHttpClientFactory clientFactory, string path)
         {
-                using var client = clientFactory.CreateClient();
-                var responseMessage = await client.GetAsync(path, HttpContext.RequestAborted);
-                responseMessage.EnsureSuccessStatusCode();
-                var stream = await responseMessage.Content.ReadAsStreamAsync(HttpContext.RequestAborted);
-                return File(stream, responseMessage?.Content?.Headers?.ContentType?.MediaType);
+            using var client = clientFactory.CreateClient();
+            var responseMessage = await client.GetAsync(path, HttpContext.RequestAborted);
+            responseMessage.EnsureSuccessStatusCode();
+            var stream = await responseMessage.Content.ReadAsStreamAsync(HttpContext.RequestAborted);
+            return File(stream, responseMessage?.Content?.Headers?.ContentType?.MediaType);
+        }
+
+        #region 录音上传
+        /// <summary>
+        /// 上传录音保存
+        /// </summary>
+        /// <param name="dtos"></param>
+        /// <returns></returns>
+        [HttpPost("adduploadaudiofiles")]
+        public async Task<object> AddUploadAudioFiles([FromBody] List<FileDto> dtos)
+        {
+            if (dtos == null || dtos.Count == 0)
+                throw UserFriendlyException.SameMessage("请上传附件");
+            var successNum = 0;
+            var errorNum = 0;
+            foreach (var item in dtos)
+            {
+                var fileName = item.FileName;
+                string strSuffix = fileName.LastIndexOf(".") > 0 ? fileName.Substring(fileName.LastIndexOf(".") + 1) : "";
+                string strSubFileName = fileName.LastIndexOf(".") > 0 ? fileName.Substring(0, fileName.LastIndexOf(".")) : fileName;
+                UploadAudioFiles audioFiles = new UploadAudioFiles()
+                {
+                    FileId = item.Additions,
+                    FileName = item.FileName,
+                    Name = strSubFileName,
+                    Type = strSuffix,
+                    Path = item.Path,
+                    AllPath = item.AllPath
+                };
+                var id = await _uploadAudioFilesRepository.AddAsync(audioFiles, HttpContext.RequestAborted);
+                if (!string.IsNullOrEmpty(id))
+                    successNum++;
+                else
+                    errorNum++;
+
+            }
+            return new
+            {
+                successNum = successNum,
+                errorNum = errorNum
+            };
         }
+
+        /// <summary>
+        /// 删除
+        /// </summary>
+        /// <param name="Ids"></param>
+        /// <returns></returns>
+        [HttpGet("deleteuploadaudiofiles")]
+        public async Task DeleteUploadAudioFiles([FromQuery] List<string> Ids)
+        {
+            if (Ids == null || Ids.Count == 0)
+                throw UserFriendlyException.SameMessage("请选择需要删除的数据");
+            foreach (var item in Ids)
+            {
+                await _uploadAudioFilesRepository.RemoveAsync(p => p.Id == item, false, HttpContext.RequestAborted);
+            }
+        }
+
+        /// <summary>
+        /// 查询列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("getaudiofileslist")]
+        public async Task<PagedDto<UploadAudioFilesRequestDto>> GetAudioFilesList([FromQuery] PagedKeywordRequest dto)
+        {
+            var (total, items) = await _uploadAudioFilesRepository.Queryable()
+                .Where(p => p.CreationTime >= dto.StartTime && p.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.Keyword), p => p.Name.Contains(dto.Keyword))
+                .OrderByIF(dto is { SortField: "creationTime", SortRule: 0 }, d => d.CreationTime, OrderByType.Asc) //创建时间升序
+                .OrderByIF(dto is { SortField: "creationTime", SortRule: 1 }, d => d.CreationTime, OrderByType.Desc) //创建时间降序
+                 .ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<UploadAudioFilesRequestDto>(total, _mapper.Map<IReadOnlyList<UploadAudioFilesRequestDto>>(items));
+        }
+
+        /// <summary>
+        /// 查询列表导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getaudiofileslist/export")]
+        public async Task<FileStreamResult> ExportGetAudioFilesList([FromBody] ExportExcelDto<PagedKeywordRequest> dto)
+        {
+            var query = _uploadAudioFilesRepository.Queryable()
+                .Where(p => p.CreationTime >= dto.QueryDto.StartTime && p.CreationTime <= dto.QueryDto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.QueryDto.Keyword), p => p.Name.Contains(dto.QueryDto.Keyword))
+                .OrderByIF(dto.QueryDto is { SortField: "creationTime", SortRule: 0 }, d => d.CreationTime, OrderByType.Asc) //创建时间升序
+                .OrderByIF(dto.QueryDto is { SortField: "creationTime", SortRule: 1 }, d => d.CreationTime, OrderByType.Desc);//创建时间降序
+
+            List<UploadAudioFiles> lists;
+            if (dto.IsExportAll)
+            {
+                lists = await query.ToListAsync(HttpContext.RequestAborted);
+            }
+            else
+            {
+                var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+                lists = items;
+            }
+
+            var listDtos = _mapper.Map<ICollection<UploadAudioFilesRequestDto>>(lists);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+            var dtos = listDtos
+                .Select(stu => _mapper.Map(stu, typeof(UploadAudioFilesRequestDto), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "录音上传数据导出");
+
+        }
+
+        #endregion
     }
 }

+ 162 - 20
src/Hotline.Api/Controllers/OrderController.cs

@@ -1,12 +1,15 @@
 using DotNetCore.CAP;
 using Hotline.Api.Filter;
 using Hotline.Application.CallCenter;
+using Hotline.Application.Contracts.Validators.FlowEngine;
 using Hotline.Application.ExportExcel;
 using Hotline.Application.FlowEngine;
 using Hotline.Application.Orders;
 using Hotline.Application.Quality;
 using Hotline.Application.Systems;
+using Hotline.Authentications;
 using Hotline.Caching.Interfaces;
+using Hotline.CallCenter.Calls;
 using Hotline.Configurations;
 using Hotline.ContingencyManagement.Notifies;
 using Hotline.EventBus;
@@ -29,9 +32,12 @@ using Hotline.Settings.Hotspots;
 using Hotline.Settings.TimeLimitDomain;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Order.Detail;
+using Hotline.Share.Dtos.Order.Handle;
 using Hotline.Share.Dtos.Order.Migration;
 using Hotline.Share.Dtos.Order.Publish;
 using Hotline.Share.Dtos.Settings;
@@ -40,6 +46,7 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Enums.Settings;
+using Hotline.Share.Mq;
 using Hotline.Share.Requests;
 using Hotline.Share.Tools;
 using Hotline.Tools;
@@ -55,7 +62,6 @@ using MiniExcelLibs;
 using SqlSugar;
 using System.Text;
 using System.Text.Json;
-using Hotline.Share.Dtos.Order.Handle;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Entities;
@@ -427,14 +433,17 @@ public class OrderController : BaseController
     [HttpPost("batch-publish")]
     public async Task BatchPublishOrder([FromBody] BatchPublishOrderDto dto)
     {
+        //任务 127  市州通用-会签件需支持批量发布
+        //会签件也需支持批量发布;若会签件要批量发布,就默认不公开,只对实际办理部门进行回访即可
         var hasHuiQian = await _orderRepository.Queryable().AnyAsync(x => dto.Ids.Contains(x.Id) && x.CounterSignType != null);
+        //var hasHuiQian = await _orderRepository.Queryable().AnyAsync(x => dto.Ids.Contains(x.Id));
         if (hasHuiQian)
             throw UserFriendlyException.SameMessage("选择的工单中含有会签工单, 不能批量发布. 请排除会签工单.");
 
         var hasProvince = await _orderRepository.Queryable().AnyAsync(x => dto.Ids.Contains(x.Id) && x.Source == ESource.ProvinceStraight);
         //if (hasProvince)
         //    throw UserFriendlyException.SameMessage("选择的工单中含有省工单, 不能批量发布. 请排除省工单.");
-        if (_appOptions.Value.IsYiBin)
+        if (_appOptions.Value.IsYiBin || _appOptions.Value.IsZiGong)
         {
             hasProvince = await _orderRepository.Queryable().AnyAsync(x => dto.Ids.Contains(x.Id) && x.IsProvince);
         }
@@ -1106,17 +1115,36 @@ public class OrderController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpGet("visit/basedata")]
-    public Dictionary<string, dynamic> VisitBaseData() => new Dictionary<string, dynamic>
+    public Dictionary<string, dynamic> VisitBaseData()
+    {
+        var voiceEvaluate = EnumExts.GetDescriptions<EVoiceEvaluate>();
+        var seatEvaluate = EnumExts.GetDescriptions<ESeatEvaluate>();
+        if (_appOptions.Value.IsZiGong == true)
         {
-            { "seatEvaluate", EnumExts.GetDescriptions<ESeatEvaluate>() },
+            voiceEvaluate = EnumExtensions.GetEnumKeyValueList<EVoiceEvaluate>();
+            seatEvaluate = EnumExtensions.GetEnumKeyValueList<ESeatEvaluate>();
+        }
+        var visitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
+        var visitManner = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner);
+        if (_appOptions.Value.IsZiGong == false)
+        {
+            seatEvaluate = seatEvaluate.Where(m => new int[] { -1, 1, 3 }.Contains(m.Key) == false).ToList();
+            visitSatisfaction = visitSatisfaction.Where(x => x.DicDataValue != "-1").ToList();
+            visitManner = visitManner.Where(x => x.DicDataValue != "-1").ToList();
+        }
+
+        return new Dictionary<string, dynamic>
+        {
+            { "seatEvaluate", seatEvaluate },
             { "visitType", EnumExts.GetDescriptions<EVisitType>() },
-            { "voiceEvaluate", EnumExts.GetDescriptions<EVoiceEvaluate>() },
-            { "visitSatisfaction", _sysDicDataCacheManager.GetVisitSatisfaction().Where(m => m.DicDataValue != "-1").ToList() },
-            { "visitMananer", _sysDicDataCacheManager.VisitMananer.Where(x => x.DicDataValue != "-1").ToList() },
+            { "voiceEvaluate", voiceEvaluate},
+            { "visitSatisfaction", visitSatisfaction },
+            { "visitMananer", visitManner},
             { "visitStateQuery", EnumExts.GetDescriptions<EVisitStateQuery>() },
             { "sourceChannel", _sysDicDataCacheManager.SourceChannel },
             { "aiVisitResult", EnumExts.GetDescriptions<EAiVisitResult>() }
         };
+    }
 
     /// <summary>
     /// 回访详情
@@ -1144,14 +1172,20 @@ public class OrderController : BaseController
         //    x => x.OrderId == orderVisit.OrderId && x.AgainState == EAgainState.DoAgain, HttpContext.RequestAborted);
         var voiceEvaluate = EnumExts.GetDescriptions<EVoiceEvaluate>();
         var seatEvaluate = EnumExts.GetDescriptions<ESeatEvaluate>();
+        if (_appOptions.Value.IsZiGong==true)
+        {
+            voiceEvaluate = EnumExtensions.GetEnumKeyValueList<EVoiceEvaluate>();
+            seatEvaluate=EnumExtensions.GetEnumKeyValueList<ESeatEvaluate>();
+        }
+        var visitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
+        var visitManner = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner);
         if (_appOptions.Value.IsZiGong == false)
         {
-            seatEvaluate = seatEvaluate.Where(m => new int[] { 1, 3 }.Contains(m.Key) == false).ToList();
+            seatEvaluate = seatEvaluate.Where(m => new int[] { -1, 1, 3 }.Contains(m.Key) == false).ToList();
+            visitSatisfaction = visitSatisfaction.Where(x => x.DicDataValue != "-1").ToList();
+            visitManner = visitManner.Where(x => x.DicDataValue != "-1").ToList();
         }
-
-        var visitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction).Where(x => x.DicDataValue != "-1");
         var dissatisfiedReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
-        var visitManner = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner).Where(x => x.DicDataValue != "-1");
         //var callRecord = await _trCallRecordRepository.GetAsync(x => x.CallAccept == orderVisit.CallId); //由CallAccept改为OtherAccept
         //var callRecord = await _trCallRecordRepository.GetAsync(x => x.OtherAccept == orderVisit.CallId && string.IsNullOrEmpty(x.OtherAccept) == false, HttpContext.RequestAborted);
         var recordingFileUrl = "";
@@ -1284,7 +1318,7 @@ public class OrderController : BaseController
     public async Task Visit([FromBody] VisitDto dto)
     {
         // 发送延迟关联通话记录消息
-        await _callApplication.PublishVisitRelevanceCallIdAsync(dto.Adapt<OrderRelevanceCallIdDto>(), HttpContext.RequestAborted);
+        dto.CallId = await _callApplication.PublishVisitRelevanceCallIdAsync(dto.Adapt<OrderRelevanceCallIdDto>(), HttpContext.RequestAborted);
         dto.CallId = await _callApplication.GetOrSetCallIdAsync(dto.CallId, HttpContext.RequestAborted);
         await _orderApplication.SaveOrderVisit(dto, HttpContext.RequestAborted);
     }
@@ -1951,11 +1985,11 @@ public class OrderController : BaseController
         if (int.Parse(setting?.SettingValue[0]) != 0 && !_sessionContext.OrgIsCenter)
         {
             int count = await _orderDelayRepository.CountAsync(x =>
-                x.OrderId == delaydto.OrderId && x.ApplyOrgCode == _sessionContext.RequiredOrgId && x.DelayState == EDelayState.Pass);
+                x.OrderId == delaydto.OrderId && x.ApplyOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) == _sessionContext.RequiredOrgId.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) && x.DelayState == EDelayState.Pass);
             if (_appOptions.Value.IsZiGong)
             {
                 count = await _orderDelayRepository.CountAsync(x =>
-                    x.OrderId == delaydto.OrderId && x.ApplyOrgCode == _sessionContext.RequiredOrgId &&
+                    x.OrderId == delaydto.OrderId &&  x.ApplyOrgCode == _sessionContext.RequiredOrgId &&
                     (x.DelayState == EDelayState.Pass || x.DelayState == EDelayState.NoPass));
             }
 
@@ -2035,6 +2069,98 @@ public class OrderController : BaseController
         }
     }
 
+	/// <summary>
+	/// 批量审批延期
+	/// </summary>
+	[HttpPost("delay/batch_audit")]
+	[LogFilter("批量审批延期")]
+	public async Task<string> BatchAuditDelay([FromBody] BatchDelayNextFlowDto dto) {
+		var result = new StringBuilder();
+		var fail = 0;
+		var success = 0;
+		foreach (var item in dto.DelayId)
+		{
+			try
+			{
+                var workflow = dto.NextWorkflow;
+				var delay = await _orderDelayRepository.Queryable().Includes(x=>x.Order).Where(x=>x.Id == item).FirstAsync(HttpContext.RequestAborted);
+				workflow.WorkflowId = delay.WorkflowId;
+				var workflowEntuty = await _workflowDomainService.GetWorkflowAsync(workflow.WorkflowId, withDefine: true, withSteps: true,cancellationToken: HttpContext.RequestAborted);
+				var currentStep =
+					workflowEntuty.Steps.FirstOrDefault(d => d.Status == EWorkflowStepStatus.WaitForAccept || d.Status == EWorkflowStepStatus.WaitForHandle);
+
+				NextStepsWithOpinionDto<NextStepOption> next = null;
+
+				try
+				{
+					next = await _workflowApplication.GetNextStepsAsync(delay.WorkflowId, HttpContext.RequestAborted);
+				}
+				catch (UserFriendlyException e)
+				{
+					if (e.Message.Contains("未找到对应节点"))
+					{
+						result.Append("无权审核:" + delay.No);
+						fail++;
+					}
+					else
+					{
+						throw;
+					}
+				}
+				if (next == null) continue;
+
+				if (!delay.Order.IsProvince)
+				{
+					if (next.Steps.Any(x => x.Value == "省审批"))
+					{
+						next.Steps.Remove(next.Steps.First(x => x.Value == "省审批"));
+					}
+				}
+
+				if (!_sessionContext.OrgIsCenter && currentStep.Name != "中心初审")
+				{
+					if (next.Steps.Any(x => x.Value == "中心终审"))
+					{
+						next.Steps.Remove(next.Steps.First(x => x.Value == "中心终审"));
+					}
+				}
+
+				var isBatch = next.Steps.Where(x => x.Value == workflow.NextStepName).Any();
+                if (isBatch)
+                {
+					var step = next.Steps.Where(x => x.Value == workflow.NextStepName).FirstOrDefault();
+					workflow.NextStepCode = step.Key;
+					workflow.NextStepName = step.Value;
+				}
+                else {
+					result.Append("无权审核:" + delay.No);
+					fail++;
+                    continue;
+				}
+
+				workflow.StepId = next.StepId;
+                workflow.ReviewResult = dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed;
+
+				if (workflow.ReviewResult == EReviewResult.Approval) {
+					await _workflowDomainService.NextAsync(_sessionContext, workflow,cancellationToken: HttpContext.RequestAborted);
+				}
+				else
+				{
+					var reject = workflow.Adapt<RejectDto>();
+					await _workflowApplication.RejectAsync(reject, HttpContext.RequestAborted);
+				}
+				success++;
+			}
+			catch (UserFriendlyException e)
+			{
+				result.Append(e.Message);
+				fail++;
+			}
+		}
+		return $"总共: {dto.DelayId.Length}, 成功: {success}, 失败: {fail}, 失败原因: {result.ToString()}";
+
+	}
+
     /// <summary>
     ///  延期查询流程办理下一步可选节点
     /// </summary>
@@ -3394,6 +3520,10 @@ public class OrderController : BaseController
         var delayModel = order.OrderDelays.MaxBy(x => x.CreationTime);
         if (delayModel != null)
         {
+            if (delayModel.IsProDelay)
+            {
+                dto.ProvinceDelayString = "该工单已向省平台发送延期申请!延期状态:" + delayModel.DelayState.GetDescription();
+            }
             var workFlow = await _workflowRepository.GetAsync(delayModel.WorkflowId);
             switch (delayModel.DelayState)
             {
@@ -3416,6 +3546,7 @@ public class OrderController : BaseController
         else
         {
             dto.DelayString = "";
+            dto.ProvinceDelayString = "";
         }
 
         //dto.CanPrevious = canPrevious;
@@ -3524,6 +3655,14 @@ public class OrderController : BaseController
             dto.ProvinceRevokeString = "该工单已由省平台发送撤单!请直接归档办理!";
         }
 
+        //省甄别
+        var orderScreen = await _orderScreenRepository.Queryable().Where(x => x.OrderId == order.Id && x.IsProScreen == true).OrderByDescending(x => x.CreationTime)
+                 .FirstAsync();
+        if (orderScreen != null)
+        {
+            dto.ProvinceScreenString = "该工单已向省平台发送甄别申请!甄别状态:" + orderScreen.Status.GetDescription();
+        }
+
         //终止
         var orderTerminateList = await _orderTerminateRepository.Queryable().Where(x => x.OrderId == order.Id).ToListAsync();
         dto.OrderTerminateStatus = orderTerminateList.Any(x => x.Status == ETerminateStatus.End) ? "同意" :
@@ -3644,11 +3783,11 @@ public class OrderController : BaseController
             await _callApplication.RelateTianrunCallWithOrderAsync(order.CallId, order.Id, HttpContext.RequestAborted);
 
         //内容分词
-        await _orderApplication.OrderParticiple(dto.Content, order.Id, order.CreationTime, HttpContext.RequestAborted);
+        await _orderApplication.OrderParticiple(dto.Content, order.Id, order.No, order.Title, order.CreationTime, HttpContext.RequestAborted);
         //敏感分词
         await _orderApplication.OrderSensitiveParticiple(dto.Content, order.Id, HttpContext.RequestAborted);
-
-
+        //知识库引用
+        await _orderApplication.AddKnowledgeQuote(order.Id, order.Title, order.No, order.KnowledgeQuote, HttpContext.RequestAborted);
         ////sms
         //try
         //{
@@ -3785,7 +3924,7 @@ public class OrderController : BaseController
         await _orderCopyRepository.AddAsync(copy, HttpContext.RequestAborted);
 
         if (order.Content != dto.Content)
-            await _orderApplication.OrderParticiple(dto.Content, dto.Id, order.CreationTime, HttpContext.RequestAborted);
+            await _orderApplication.OrderParticiple(dto.Content, dto.Id, order.No, order.Title, order.CreationTime, HttpContext.RequestAborted);
         if (dto.RepeatableEventDetails?.Any() ?? false)
         {
             var reAdds = dto.RepeatableEventDetails.Where(x => string.IsNullOrEmpty(x.OrderId) && !x.IsDeleted)
@@ -3871,6 +4010,8 @@ public class OrderController : BaseController
 
         //敏感分词
         await _orderApplication.OrderSensitiveParticiple(dto.Content, order.Id, HttpContext.RequestAborted);
+        //知识库引用
+        await _orderApplication.AddKnowledgeQuote(order.Id, order.Title, order.No, order.KnowledgeQuote, HttpContext.RequestAborted);
 
         return new { Id = order.Id, No = order.No, Password = order.Password, CallId = order.CallId };
     }
@@ -3987,6 +4128,7 @@ public class OrderController : BaseController
         if (orderId.NotNullOrEmpty())
         {
             outDto.Opinion = await _typeCache.GetAsync($"tmp_opinion_{orderId}{_sessionContext.UserId}", HttpContext.RequestAborted);
+            outDto.Content = (await _orderRepository.GetAsync(orderId, HttpContext.RequestAborted))?.Content;
         }
 
         //随手拍
@@ -4055,10 +4197,8 @@ public class OrderController : BaseController
             var order = await _orderRepository.GetAsync(orderId, HttpContext.RequestAborted);
             if (order is null)
             {
-                stringBuilder.Append($"工单:{orderId} 不存在");
                 continue;
             }
-
             var snapshot = await _orderSnapshotApplication.UpdateSafetyAsync(orderId, dto.IsSafetyDepartment, dto.Remark);
             if (snapshot is null)
             {
@@ -4331,6 +4471,7 @@ public class OrderController : BaseController
             throw UserFriendlyException.SameMessage("该工单未开启流程");
         var dto = await _workflowApplication.GetNextStepsAsync(order.WorkflowId, HttpContext.RequestAborted);
         dto.ExpiredTime = order.ExpiredTime;
+        dto.Content = order.Content;
         var rsp = _mapper.Map<NextStepsWithOpinionDto<RecommendStepOption>>(dto);
         foreach (var step in rsp.Steps)
         {
@@ -4364,6 +4505,7 @@ public class OrderController : BaseController
     [HttpPost("steps/temp")]
     public async Task TempSaveAsync([FromBody] StepTempInDto dto)
     {
+        if (dto.OrderId.IsNullOrEmpty() || dto.Opinion.IsNullOrEmpty()) return;
         await _typeCache.SetAsync($"tmp_opinion_{dto.OrderId}{_sessionContext.UserId}", dto.Opinion, TimeSpan.FromDays(3),
             HttpContext.RequestAborted);
     }

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

@@ -199,6 +199,7 @@ namespace Hotline.Api.Controllers
 			else
 				model.FileJson = new List<Share.Dtos.File.FileJson>();
 
+			model.Status = ETerminateStatus.Approval;
 			model.Content = dto.Data.Content;
 			model.IsRecommit = true;
 			await _orderTerminateRepository.UpdateAsync(model, HttpContext.RequestAborted);

+ 561 - 0
src/Hotline.Api/Controllers/PlanController.cs

@@ -0,0 +1,561 @@
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using XF.Domain.Repository;
+using Hotline.Application.Planlibrary;
+using Hotline.Share.Dtos.Planlibrary;
+using SqlSugar;
+using Hotline.Planlibrary;
+using XF.Domain.Exceptions;
+using Hotline.Share.Dtos;
+using Hotline.Share.Tools;
+using Hotline.Share.Enums.Planlibrary;
+using Hotline.Application.ExportWord;
+using Hotline.Application.Tools;
+using Hotline.Share.Enums.Article;
+using XF.Utility.EnumExtensions;
+using Hotline.Share.Dtos.Order;
+using Hotline.Application.ExportExcel;
+
+namespace Hotline.Api.Controllers
+{
+    /// <summary>
+    /// 预案库
+    /// </summary>
+    public class PlanController : BaseController
+    {
+
+
+        #region 注入
+
+        private readonly IMapper _mapper;
+        private readonly ISessionContext _sessionContext;
+        private readonly IPlanApplication _planApplication;
+        private readonly IRepository<PlanType> _planTypeRepository;
+        private readonly IRepository<PlanList> _planListRepository;
+        private readonly IRepository<PlanCollect> _planCollectRepository;
+        private readonly IWordHelperService _wordHelperService;
+        private readonly IExportApplication _exportApplication;
+
+        public PlanController(
+           IMapper mapper,
+           ISessionContext sessionContext,
+           IPlanApplication planApplication,
+           IRepository<PlanType> planTypeRepository,
+           IRepository<PlanList> planListRepository,
+           IRepository<PlanCollect> planCollectRepository,
+           IWordHelperService wordHelperService,
+           IExportApplication exportApplication)
+        {
+            _mapper = mapper;
+            _sessionContext = sessionContext;
+            _planApplication = planApplication;
+            _planTypeRepository = planTypeRepository;
+            _planListRepository = planListRepository;
+            _planCollectRepository = planCollectRepository;
+            _wordHelperService = wordHelperService;
+            _exportApplication = exportApplication;
+        }
+
+        #endregion
+
+        #region 预案库类型管理
+
+        /// <summary>
+        /// 获取列表层级分类
+        /// </summary>
+        /// <param name="IsEnable">是否启用</param>
+        /// <returns></returns>
+        [HttpGet("type/treelist")]
+        public async Task<List<PlanTypeDto>> QueryAllTreeList(bool? IsEnable)
+        {
+            return await _planTypeRepository.Queryable()
+                .WhereIF(IsEnable.HasValue, x => x.IsEnable == IsEnable)
+                .Where(x => SqlFunc.Subqueryable<PlanTypeOrg>().Where(to => to.TypeId == x.Id).Any() ||
+                SqlFunc.Subqueryable<PlanTypeOrg>().Where(to => to.TypeId == x.Id).NotAny()
+                )
+                .Select(x => new PlanTypeDto()
+                {
+                    Id = x.Id.SelectAll()
+                }
+                )
+                .OrderBy(x => x.Sort).ToTreeAsync(it => it.children, it => it.ParentId, null, it => it.Id);
+        }
+
+        /// <summary>
+        /// 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("type/add")]
+        public async Task<string> AddType([FromBody] AddPlanTypeDto dto)
+        {
+            return await _planApplication.AddTypeAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("type/update")]
+        public async Task UpdateType([FromBody] UpdatePlanTypeDto dto)
+        {
+            await _planApplication.UpdateTypeAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 查询详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpGet("type/info/{Id}")]
+        public async Task<PlanType> GetType(string Id)
+        {
+            var types = await _planTypeRepository.Queryable()
+                .Includes(x => x.PlanTypeOrgs)   // 填充子对象
+                .Where(x => x.Id == Id)
+                .FirstAsync(HttpContext.RequestAborted);
+            if (types is null)
+                throw UserFriendlyException.SameMessage("查询失败!");
+            return types;
+        }
+
+        /// <summary>
+        /// 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpDelete("type/remove/{Id}")]
+        public async Task RemoveType(string Id)
+        {
+            await _planApplication.RemoveTypeAsync(Id, HttpContext.RequestAborted);
+        }
+
+        #endregion
+
+        #region 预案库管理
+
+        /// <summary>
+        /// 预案库分类列表
+        /// </summary>
+        /// <param name="IsEnable"></param>
+        /// <returns></returns>
+        [HttpGet("list/treelist")]
+        public async Task<List<PlanTypeDto>> QueryAllPlanTypeTreeList(bool? IsEnable)
+        {
+            return await _planTypeRepository.Queryable()
+                .WhereIF(IsEnable.HasValue, x => x.IsEnable == IsEnable)
+                .Where(x => SqlFunc.Subqueryable<PlanTypeOrg>().Where(to => to.TypeId == x.Id).Any() ||
+                            SqlFunc.Subqueryable<PlanTypeOrg>().Where(to => to.TypeId == x.Id).NotAny()
+                )
+                .Select(x => new PlanTypeDto()
+                {
+                    Id = x.Id.SelectAll(),
+                    PlanNum = SqlFunc.Subqueryable<PlanRelationType>().LeftJoin<PlanList>((kr, k) => kr.PlanId == k.Id)
+                         .Where((kr, k) => kr.PlanTypeId == x.Id)
+                         .DistinctCount(kr => kr.PlanId)
+                }
+                )
+                .OrderBy(x => x.Sort).ToTreeAsync(it => it.children, it => it.ParentId, null, it => it.Id);
+        }
+
+        /// <summary>
+        /// 预案库列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        [HttpGet("list")]
+        public async Task<PagedDto<PlanDataDto>> QueryAllPlanList([FromQuery] PlanListDto pagedDto)
+        {
+            return (await _planApplication.QueryAllPlanListAsync(pagedDto, HttpContext.RequestAborted)).ToPaged();
+        }
+
+        /// <summary>
+        /// 预案库草稿
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/draft")]
+        public async Task<string> PlanDraft([FromBody] AddPlanListDto dto)
+        {
+            dto.Status = EPlanStatus.NewDrafts;
+            return await _planApplication.AddPlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库草稿修改
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/draftupdate")]
+        public async Task UpdatePlanDraft([FromBody] UpdatePlanListDto dto)
+        {
+            dto.Status = EPlanStatus.NewDrafts;
+            await _planApplication.UpdatePlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库草稿到审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/draftadd")]
+        public async Task AddPlanDraft([FromBody] UpdatePlanListDto dto)
+        {
+            dto.ApplyStatus = EPlanApplyStatus.Add;
+            dto.Status = EPlanStatus.OnShelf;
+            await _planApplication.UpdatePlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库草稿删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        [HttpDelete("list/draftremove/{Id}")]
+        public async Task RemovePlanDraft(string Id)
+        {
+            UpdatePlanListDto dto = new UpdatePlanListDto();
+            dto.ApplyStatus = EPlanApplyStatus.Delete;
+            dto.Status = EPlanStatus.Auditing;
+            dto.Id = Id;
+            await _planApplication.RemovePlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库删除到审核
+        /// </summary>
+        /// <param name="dtoDel"></param>
+        /// <returns></returns>
+        [HttpDelete("list/remove")]
+        public async Task RemovePlan([FromBody] DelPlanListDto dtoDel)
+        {
+            UpdatePlanListDto dto = new UpdatePlanListDto();
+            dto.ApplyStatus = EPlanApplyStatus.Delete;
+            dto.Status = EPlanStatus.Auditing;
+            dto.Id = dtoDel.Id;
+            dto.ApplyReason = dtoDel.ApplyReason;
+            await _planApplication.UpdatePlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库新增到审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/add")]
+        public async Task<string> AddPlan([FromBody] AddPlanListDto dto)
+        {
+            dto.ApplyStatus = EPlanApplyStatus.Add;
+            dto.Status = EPlanStatus.Auditing;
+            return await _planApplication.AddPlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库编辑提交到审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/update")]
+        public async Task UpdatePlan([FromBody] UpdatePlanListDto dto)
+        {
+            dto.ApplyStatus = EPlanApplyStatus.Update;
+            dto.Status = EPlanStatus.Auditing;
+            await _planApplication.UpdatePlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库上架
+        /// </summary>
+        /// <param name="Id">预案库ID</param>
+        /// <returns></returns>
+        [HttpGet("list/onshelf/{Id}")]
+        public async Task OnshelfPlan(string Id)
+        {
+            UpdatePlanListDto dto = new UpdatePlanListDto();
+            dto.Id = Id;
+            dto.ApplyStatus = EPlanApplyStatus.Add;
+            dto.Status = EPlanStatus.OnShelf;
+            dto.OnShelfTime = DateTime.Now;
+            await _planApplication.AuditPlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库下架
+        /// </summary>
+        /// <param name="Id">预案库ID</param>
+        /// <returns></returns>
+        [HttpGet("list/offshelf/{Id}")]
+        public async Task OffshelfPlan(string Id)
+        {
+            UpdatePlanListDto dto = new UpdatePlanListDto();
+            dto.Id = Id;
+            dto.ApplyStatus = EPlanApplyStatus.Offshelf;
+            dto.Status = EPlanStatus.OffShelf;
+            dto.OffShelfTime = DateTime.Now;
+
+            await _planApplication.AuditPlanAsync(dto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库审核(新增、修改、删除)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/examin")]
+        public async Task ExaminPlan([FromBody] AuditPlanListDto dto)
+        {
+            var plan = await _planListRepository.GetAsync(dto.Id);
+            if (plan == null)
+                throw UserFriendlyException.SameMessage("预案库查询失败");
+
+            var planDto = _mapper.Map<UpdatePlanListDto>(plan);
+
+            if (dto.State == 0)
+            {//不同意
+                planDto.Status = EPlanStatus.Revert;
+            }
+            else if (dto.State == 1)
+            {//同意 
+                if (planDto.ApplyStatus == EPlanApplyStatus.Add)
+                {
+                    planDto.Status = EPlanStatus.OnShelf;
+                    planDto.OnShelfTime = DateTime.Now;
+                }
+                if (planDto.ApplyStatus == EPlanApplyStatus.Update)
+                {
+                    planDto.Status = EPlanStatus.OnShelf;
+                    planDto.OnShelfTime = DateTime.Now;
+                    planDto.UpdateTime = DateTime.Now;
+                }
+                if (planDto.ApplyStatus == EPlanApplyStatus.Offshelf)
+                {
+                    planDto.Status = EPlanStatus.OffShelf;
+                    planDto.OffShelfTime = DateTime.Now;
+                }
+                if (planDto.ApplyStatus == EPlanApplyStatus.Delete)
+                {
+                    planDto.Status = EPlanStatus.Drafts;
+                }
+            }
+            planDto.Id = dto.Id;
+            planDto.ExaminTime = DateTime.Now;
+            planDto.ExaminManId = _sessionContext.UserId;
+            planDto.ExaminOrganizeId = _sessionContext.OrgId;
+            planDto.ExaminOpinion = dto.ExaminOpinion;
+
+            await _planApplication.AuditPlanAsync(planDto, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="IsAddPv"></param>
+        /// <returns></returns>
+        [HttpGet("list/info")]
+        public async Task<PlanInfoDto> GetPlan(string Id, bool IsAddPv)
+        {
+            return await _planApplication.GetPlanAsync(Id, IsAddPv, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 预案库申请理由
+        /// </summary>
+        /// <param name="Id">预案库ID</param>
+        /// <returns></returns>
+        [HttpGet("list/reason/{Id}")]
+        public async Task<PlanApplyReasonDto> ReasonPlan(string Id)
+        {
+            var reason = await _planListRepository.GetAsync(x => x.Id == Id);
+            return _mapper.Map<PlanApplyReasonDto>(reason);
+        }
+
+        /// <summary>
+        /// 预案库评分
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("list/score")]
+        public async Task ScorePlan([FromBody] PvPlanListDto dto)
+        {
+            var collect = await _planCollectRepository.GetAsync(x => x.PlanId == dto.Id && x.CreatorId == _sessionContext.UserId);
+            if (collect != null)
+            {
+                if (collect.Score > 0)
+                    throw UserFriendlyException.SameMessage("当前知识已经评分");
+
+                collect.Score = dto.Score;
+                await _planCollectRepository.UpdateAsync(collect, HttpContext.RequestAborted);
+            }
+            else
+            {
+                collect = new PlanCollect();
+                collect.PlanId = dto.Id;
+                collect.Score = dto.Score;
+                await _planCollectRepository.AddAsync(collect, HttpContext.RequestAborted);
+            }
+
+            //计算总分
+            var sugar = _planCollectRepository.Queryable().Where(x => x.PlanId == dto.Id);
+            var count = await sugar.CountAsync();
+            var collects = await sugar.SumAsync(x => x.Score);
+            var scoreTemp = collects / count;
+            var plan = await _planListRepository.GetAsync(x => x.Id == dto.Id);
+            if (plan != null)
+            {
+                plan.Score = decimal.Round(scoreTemp.Value, 1);
+                await _planListRepository.UpdateAsync(plan, HttpContext.RequestAborted);
+            }
+        }
+
+        /// <summary>
+        /// 预案库查重
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/exist")]
+        public async Task<bool> ExistPlan([FromBody] PlanExistDto dto)
+        {
+            var any = await _planListRepository.Queryable()
+                .Where(x => x.Status == EPlanStatus.Auditing || x.Status >= EPlanStatus.OnShelf)
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Equals(dto.Title))
+                .WhereIF(!string.IsNullOrEmpty(dto.Content), x => x.Content.Equals(dto.Content))
+                .WhereIF(!string.IsNullOrEmpty(dto.Id), x => x.Id != dto.Id)
+                .AnyAsync();
+            return any;
+        }
+
+        /// <summary>
+        /// 预案库详情导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("list/info/export")]
+        public async Task<IActionResult> PlanInfoExport([FromBody] PlanInfoExportDto dto)
+        {
+            if (dto.Ids.Length > 1)
+            {
+                var streams = await _planApplication.PlanInfoListExportAsync(dto, HttpContext.RequestAborted);
+                byte[] fileBytes = _wordHelperService.ConvertZipStream(streams);
+                var name = DateTime.Now.ToString("yyyyMMddHHmmss");
+                return File(fileBytes, "application/octet-stream", $"{name}.zip");
+            }
+            var info = await _planListRepository.GetAsync(dto.Ids[0]) ?? throw UserFriendlyException.SameMessage("预案不存在");
+            return info.Content.HtmlToStream(dto.FileType).GetFileStreamResult(dto.FileType, info.Title, false);
+        }
+
+        /// <summary>
+        /// 预案库列表导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("export")]
+        public async Task<IActionResult> GetPlanListExportAsync([FromBody] ExportExcelDto<PlanListDto> dto)
+        {
+            var items = (await _planApplication.QueryAllPlanListAsync(dto.QueryDto, HttpContext.RequestAborted)).Item2;
+            return _exportApplication.GetExcelFile(dto, items, "预案明细导出");
+        }
+
+
+        /// <summary>
+        /// 预案库列表页面枚举值
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("list/status-data")]
+        public Dictionary<string, dynamic> PlanStatus()
+        {
+            var tabStatusName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(3, "已上架"),
+                new KeyValuePair<int, string>(4, "已下架"),
+                new KeyValuePair<int, string>(1, "审核中"),
+                new KeyValuePair<int, string>(7, "草稿"),
+                new KeyValuePair<int, string>(-1, "全部")
+            };
+
+            var tabExamineName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(-1, "全部"),
+                new KeyValuePair<int, string>(0, "新增审核"),
+                new KeyValuePair<int, string>(1, "修改审核"),
+                new KeyValuePair<int, string>(2, "删除审核")
+            };
+
+            var StatusName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(-1, "全部"),
+                new KeyValuePair<int, string>(0, "待提交"),
+                new KeyValuePair<int, string>(1, "审核中"),
+                new KeyValuePair<int, string>(3, "已上架"),
+                new KeyValuePair<int, string>(4, "已下架"),
+                new KeyValuePair<int, string>(5, "审核不通过"),
+                new KeyValuePair<int, string>(6, "已过期"),
+                new KeyValuePair<int, string>(7, "草稿")
+            };
+
+            var ApplyStatusName = new List<KeyValuePair<int, string>>
+            {
+                new KeyValuePair<int, string>(-1, "全部"),
+                new KeyValuePair<int, string>(0, "新增审核"),
+                new KeyValuePair<int, string>(1, "修改审核"),
+                new KeyValuePair<int, string>(2, "删除审核")
+            };
+
+            var ignoreFileType = EFileType.excel | EFileType.pdf;
+            var items = EnumExts.GetDescriptions<EFileType>();
+            var filteredDictionary = items
+                 .Where(kvp => (ignoreFileType & (EFileType)kvp.Key) == 0)
+                 .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
+                 .ToList();
+
+            return new Dictionary<string, dynamic>
+            {
+                { "fileType", filteredDictionary},
+                { "tabStatusName", tabStatusName },
+                { "tabExamineName", tabExamineName },
+                { "StatusName", StatusName },
+                { "ApplyStatusName", ApplyStatusName }
+            };
+        }
+
+        #endregion
+
+        #region 预案库检索
+
+        /// <summary>
+        /// 检索列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        [HttpGet("search")]
+        public async Task<PagedDto<PlanDataDto>> QueryOnShelfPlanList([FromQuery] PlanListDto pagedDto)
+        {
+            pagedDto.Status = EPlanStatus.OnShelf;
+            return (await _planApplication.QueryAllPlanListAsync(pagedDto, HttpContext.RequestAborted)).ToPaged();
+        }
+
+        /// <summary>
+        /// 检索列表前10
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("search/top10")]
+        public async Task<List<PlanPageViewDto>> QueryTop10PlanList()
+        {
+            return await _planListRepository.Queryable()
+                .Take(10)
+                .Where(x => x.Status == EPlanStatus.OnShelf)
+                .Select(x => new PlanPageViewDto
+                {
+                    Id = x.Id,
+                    Title = x.Title,
+                    PageView = x.PageView
+                })
+                .OrderBy(x => x.PageView, OrderByType.Desc)
+                .ToListAsync();
+        }
+
+        #endregion
+    }
+}

+ 69 - 9
src/Hotline.Api/Controllers/QualityController.cs

@@ -21,6 +21,9 @@ using Hotline.Application.CallCenter;
 using Hotline.CallCenter.Configs;
 using Microsoft.Extensions.Options;
 using Hotline.Configurations;
+using Hotline.CallCenter.Calls;
+using System.Linq;
+using Hotline.Repository.SqlSugar.Quality;
 
 namespace Hotline.Api.Controllers
 {
@@ -47,8 +50,9 @@ namespace Hotline.Api.Controllers
         private readonly ICallApplication _callApplication;
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
         private readonly IRepository<SystemLog> _logRepository;
+		private readonly IRepository<QualityTransferRecords> _qualityTransferRecordsRepository;
 
-        public QualityController(
+		public QualityController(
             ISessionContext sessionContext,
             IMapper mapper,
             IQualityRepository qualitey,
@@ -67,7 +71,8 @@ namespace Hotline.Api.Controllers
             ISystemSettingCacheManager systemSettingCacheManager,
             ICallApplication callApplication,
             IOptionsSnapshot<AppConfiguration> appOptions,
-            IRepository<SystemLog> logRepository)
+            IRepository<SystemLog> logRepository,
+			IRepository<QualityTransferRecords> qualityTransferRecordsRepository)
         {
             _sessionContext = sessionContext;
             _mapper = mapper;
@@ -88,7 +93,8 @@ namespace Hotline.Api.Controllers
             _callApplication = callApplication;
             _appOptions = appOptions;
             _logRepository = logRepository;
-        }
+            _qualityTransferRecordsRepository = qualityTransferRecordsRepository;
+		}
         #region 质检管理
         /// <summary>
         /// 删除质检
@@ -611,12 +617,66 @@ namespace Hotline.Api.Controllers
             return rsp;
         }
 
-        /// <summary>
-        /// 智能质检结果返回接收 
-        /// </summary>
-        /// <param name="dto"></param>
-        /// <returns></returns>
-        [AllowAnonymous]
+		/// <summary>
+		/// 智能质检转写_兴唐
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpPost("aitransfer_xt")]
+		[LogFilter("智能质检转写_兴唐")]
+		public async Task AiTransfer_XT([FromBody] List<AiQualityXTDto> dto)
+		{
+			var transfers = new List<QualityTransferRecords>();
+			foreach (var item in dto)
+			{
+                var records =  await _qualityTransferRecordsRepository.Queryable().Where(x=>x.QualityId == item.Id  &&  x.IsFinished == false).AnyAsync();
+                if (records)
+                    continue;
+
+				var transfer = new QualityTransferRecords();
+				transfer.QualityId = item.Id;
+				transfer.IsFinished = false;
+				transfer.TransferState = EQualityTransferState.NotStarted;
+				transfers.Add(transfer);
+				await _qualitey.Updateable().SetColumns(x => new Hotline.Quality.Quality { TransferState = EQualityTransferState.Translating }).Where(x => x.Id == item.Id).ExecuteCommandAsync();
+			}
+			await _qualityTransferRecordsRepository.AddRangeAsync(transfers);
+		}
+
+		/// <summary>
+		/// 智能质检转写
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[AllowAnonymous]
+		[HttpPost("transfer")]
+		[LogFilter("智能质检转写_兴唐_定时调用")]
+		public async Task AiTransfer_XT()
+		{
+            var translatings = await _qualityTransferRecordsRepository.Queryable().Where(x => x.IsFinished == false && x.TransferState == EQualityTransferState.Translating).OrderBy(x => x.TransferTime).ToListAsync();
+            if (translatings.Any())
+            {
+                var transfer = translatings.FirstOrDefault();
+                if ((DateTime.Now - transfer.TransferTime.Value).TotalMinutes >= 30)
+                {
+                    await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Lose }).Where(x => x.Id == transfer.Id).ExecuteCommandAsync();
+                }
+                return;
+            }
+            else {
+				var notStarted = await _qualityTransferRecordsRepository.Queryable().Where(x => x.IsFinished == false && x.TransferState == EQualityTransferState.NotStarted).OrderBy(x => x.TransferTime).FirstAsync();
+				await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Translating , TransferTime = DateTime.Now }).Where(x => x.Id == notStarted.Id).ExecuteCommandAsync();
+				_qualityApplication.Transfer_XT(notStarted.QualityId, HttpContext.RequestAborted);
+			}
+		}
+
+
+		/// <summary>
+		/// 智能质检结果返回接收 
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[AllowAnonymous]
         [HttpPost("AiResult")]
         [LogFilter("智能质检结果返回接收")]
         public async Task AiResult([FromBody] List<AiQualityResultDto> dto)

+ 2 - 0
src/Hotline.Api/Controllers/Snapshot/SnapshotOrderController.cs

@@ -142,6 +142,8 @@ public class SnapshotOrderController : BaseController
             .Where((order, snapshot) => order.Id == id)
             .Select((order, snapshot) => new LabelOrderSnapshotDetailOutDto { Id = order.Id, Title = order.Title, Lables =snapshot.Labels })
             .FirstAsync();
+        var snapshot = await _orderSnapshotRepository.GetAsync(id, HttpContext.RequestAborted);
+        order.Lables = snapshot.Labels;
         order.LabelsBaseData = _systemDicDataCacheManager.SnapshotOrderLabel;
         return order;
     }

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

@@ -78,6 +78,7 @@ using Google.Protobuf.WellKnownTypes;
 using Microsoft.AspNetCore.DataProtection;
 using Hotline.Share.Tools;
 using NETCore.Encrypt;
+using Hotline.Ai.Quality;
 
 namespace Hotline.Api.Controllers;
 
@@ -146,10 +147,13 @@ public class TestController : BaseController
     private readonly ICalcExpireTime _expireTime;
     private readonly ICallNativeRepository _callNativeRepository;
     private readonly IRepository<OldSendProData> _oldSendProDataRepository;
+	private readonly IOrderScreenRepository _orderScreenRepository;
+	private readonly IRepository<OrderVisit> _orderVisitRepository;
     private readonly IThirdIdentiyService _thirdIdentiyService;
+	private readonly IServiceProvider _serviceProvider;
 
 
-    public TestController(
+	public TestController(
         //INewRockClient client,
         ILogger<TestController> logger,
         //IAuthorizeGenerator authorizeGenerator,
@@ -202,7 +206,11 @@ ICallApplication callApplication,
 ,
         ICallNativeRepository callNativeRepository,
         IRepository<OldSendProData> oldSendProDataRepository,
-        IThirdIdentiyService thirdIdentiyService)
+        IThirdIdentiyService thirdIdentiyService,
+		IOrderScreenRepository orderScreenRepository,
+		IRepository<OrderVisit> orderVisitRepository,
+		IServiceProvider serviceProvider
+		)
     {
         _logger = logger;
         //_authorizeGenerator = authorizeGenerator;
@@ -253,7 +261,10 @@ ICallApplication callApplication,
         _callNativeRepository = callNativeRepository;
         _oldSendProDataRepository = oldSendProDataRepository;
         _thirdIdentiyService = thirdIdentiyService;
-    }
+        _orderScreenRepository = orderScreenRepository;
+        _orderVisitRepository = orderVisitRepository;
+        _serviceProvider = serviceProvider;
+	}
 
     /// <summary>
     /// 测试获取电话号码
@@ -545,10 +556,12 @@ ICallApplication callApplication,
         //var r = _timeLimitDomainService.CalcExpiredTime(DateTime.Now, EFlowDirection.CenterToCenter, batchId);
         //var r = _timeLimitDomainService.CalcEndTime(DateTime.Parse("2024-09-12 14:45:47"), Share.Enums.Settings.ETimeType.WorkDay, 2, 80, 50);
         //_capPublisher.PublishDelay((DateTime.Now.AddMinutes(2) - DateTime.Now), EventNames.OrderRelateCall, "123");
-        var times =await _expireTime.CalcExpiredTime(DateTime.Parse("2024-12-31 13:25:13.137977"), DateTime.Parse("2024-12-31 13:25:13.137977"), EFlowDirection.CenterToOrg,new OrderTimeClacInfo() { AcceptTypeCode= "10" });
+        //var times =await _expireTime.CalcExpiredTime(DateTime.Parse("2024-12-31 13:25:13.137977"), DateTime.Parse("2024-12-31 13:25:13.137977"), EFlowDirection.CenterToOrg,new OrderTimeClacInfo() { AcceptTypeCode= "10" });
+        //await _expireTime.CalcWorkTimeToDecimal(visit.VisitTime.Value, DateTime.Now, false);
         //var times = await _expireTime.CalcWorkTimeToDecimal(DateTime.Parse("2024-12-16 21:36:27"), DateTime.Parse("2024-12-17 12:47:05"), false);
+        var query = _userRepository.Queryable().Where(x => false);
         //await _capPublisher.PublishDelay(EventNames.OrderRelateCall, "123", cancellationToken: HttpContext.RequestAborted);
-        return OpenResponse.Ok(times);
+        return OpenResponse.Ok(query.ToSqlString());
     }
 
 
@@ -1013,12 +1026,36 @@ ICallApplication callApplication,
     }
 
     [HttpGet("t4")]
-    public async Task<string> Test4()
+	[AllowAnonymous]
+	public async Task<string> Test4()
     {
-        return DateTime.Now.ToString("O");
+        var a = await _expireTime.CalcWorkTimeToDecimal(DateTime.Parse("2024-12-17 22:33:22"), DateTime.Parse("2024-12-18 10:29:53"), false);
+		return DateTime.Now.ToString("O");
     }
 
-    [AllowAnonymous]
+
+    /// <summary>
+    /// 批量处理甄别耗时问题
+    /// </summary>
+    /// <returns></returns>
+	[HttpGet("order_screen_timeconsuming")]
+	[AllowAnonymous]
+	public async Task OrderScreenTimeConsuming()
+	{
+        var screens = await _orderScreenRepository.Queryable().ToListAsync();
+        foreach (var item in screens)
+        {
+            var visit = await _orderVisitRepository.Queryable().Where(x=>x.Id == item.VisitId).FirstAsync();
+            if (visit != null)
+            {
+				var time = await _expireTime.CalcWorkTimeToDecimal(visit.VisitTime.Value, item.CreationTime, false);
+                await _orderScreenRepository.Updateable().SetColumns(x => new OrderScreen { TimeConsuming = time }).Where(x => x.Id == item.Id).ExecuteCommandAsync();
+			}
+        }
+		
+	}
+
+	[AllowAnonymous]
     [HttpGet("t5")]
     public async Task<string> GetUserAllowAnonymous()
     {
@@ -1347,6 +1384,89 @@ ICallApplication callApplication,
         }
     }
 
+	/// <summary>
+	/// 老系统数据同步
+	/// </summary>
+	/// <returns></returns>
+	[HttpGet("old_data_synchronization")]
+	[AllowAnonymous]
+	public async Task oldDataSynchronization([FromQuery] string? no)
+	{
+		var orders = new List<Order>();
+		if (string.IsNullOrEmpty(no))
+		{
+			orders = await _orderRepository.Queryable().Where(x => x.No.Length == 12 && x.ActualHandleTime == null && x.CenterToOrgTime == null)
+                .Where(x=>x.CreationTime >= DateTime.Parse("2024-01-01") && x.CreationTime <  DateTime.Parse("2024-02-01")).ToListAsync();
+		}
+		else
+		{
+			orders = await _orderRepository.Queryable().Where(x => x.No == no).ToListAsync();
+		}
+		foreach (var order in orders)
+		{
+
+			var steps = await _workflowStepRepository.Queryable().Where(x => x.ExternalId == order.Id).ToListAsync();
+
+			var actualHandleStep = steps.Where(x => x.HandlerOrgId == order.ActualHandleOrgCode).OrderByDescending(x => x.CreationTime).FirstOrDefault();
+			var CenterToOrgStep = steps.Where(x => x.AssignerOrgId == "001" && x.HandlerOrgId != "001").OrderByDescending(x => x.CreationTime).FirstOrDefault();
+
+			order.ActualHandleTime = actualHandleStep.HandleTime;
+			order.CenterToOrgTime = CenterToOrgStep is null? null : CenterToOrgStep.CreationTime;
+
+			await _orderRepository.Updateable().SetColumns(x => new Order { ActualHandleTime = order.ActualHandleTime, CenterToOrgTime = order.CenterToOrgTime }).Where(x => x.Id == order.Id).ExecuteCommandAsync();
+
+
+			var now = order.FiledTime.Value;
+			var handleDuration = order.StartTime.HasValue
+				? //_timeLimitDomainService.CalcWorkTime(
+				await _expireTime.CalcWorkTime(
+					order.StartTime.Value,
+					now, order.ProcessType is EProcessType.Zhiban)
+				: 0;
+			var fileDuration = order.CenterToOrgTime.HasValue
+				? // _timeLimitDomainService.CalcWorkTime(
+				await _expireTime.CalcWorkTime(
+					order.CenterToOrgTime.Value,
+					now, order.ProcessType is EProcessType.Zhiban)
+				: 0;
+			var allDuration = order.StartTime.HasValue
+				?
+				//_timeLimitDomainService.CalcWorkTime(
+				await _expireTime.CalcWorkTime(
+					order.StartTime.Value, now,
+					order.ProcessType is EProcessType.Zhiban)
+				: 0;
+			var creationTimeHandleDurationWorkday = order.ActualHandleTime.HasValue
+				?
+				// _timeLimitDomainService.CalcWorkTime(
+				await _expireTime.CalcWorkTime(
+					order.CreationTime, now,
+					order.ProcessType is EProcessType.Zhiban)
+				: 0;
+			var centerToOrgHandleDurationWorkday = order.ActualHandleTime.HasValue && order.CenterToOrgTime.HasValue
+				?
+				// _timeLimitDomainService.CalcWorkTime(
+				await _expireTime.CalcWorkTime(
+					order.CenterToOrgTime.Value, now,
+					order.ProcessType is EProcessType.Zhiban)
+				: 0;
+			creationTimeHandleDurationWorkday = creationTimeHandleDurationWorkday <= 0 ? 10 : creationTimeHandleDurationWorkday;
+			centerToOrgHandleDurationWorkday = centerToOrgHandleDurationWorkday <= 0 ? 10 : centerToOrgHandleDurationWorkday;
+
+			order.File(now, handleDuration, fileDuration, allDuration, creationTimeHandleDurationWorkday, centerToOrgHandleDurationWorkday);
+
+		}
+
+	}
+
+	[HttpGet("aiXingTang")]
+	[AllowAnonymous]
+	public async Task aiXingTang() {
+		var aiQualityService = _serviceProvider.GetRequiredService<IAiQualityService>();
+        await aiQualityService.CreateAiOrderQualityTask("cs202501030002.mp3", HttpContext.RequestAborted);
+
+	}
+
     /// <summary>
     /// 加密验证
     /// </summary>

+ 28 - 25
src/Hotline.Api/Controllers/WebPortalController.cs

@@ -58,6 +58,7 @@ namespace Hotline.Api.Controllers
         private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
         private readonly ITypedCache<string> _getVailData;
+        private readonly IRepository<KnowledgeWord> _knowledgeWordRepository;
 
         public WebPortalController(IMapper mapper,
             IMediator mediator,
@@ -81,7 +82,8 @@ namespace Hotline.Api.Controllers
             IRepository<Knowledge> knowledgeRepository,
             ISystemDicDataCacheManager systemDicDataCacheManager,
             IOptionsSnapshot<AppConfiguration> appOptions,
-            ITypedCache<string> getVailData
+            ITypedCache<string> getVailData,
+            IRepository<KnowledgeWord> knowledgeWordRepository
             )
         {
             _mapper = mapper;
@@ -107,6 +109,7 @@ namespace Hotline.Api.Controllers
             _systemDicDataCacheManager = systemDicDataCacheManager;
             _appOptions = appOptions;
             _getVailData = getVailData;
+            _knowledgeWordRepository = knowledgeWordRepository;
         }
 
         #region 通知
@@ -761,7 +764,7 @@ namespace Hotline.Api.Controllers
                            ConTypeName = p.HotspotName,
                            FlowAddDate = p.AcceptTime,
                            RSFlagName = p.State == "1" ? "办理完成" : "办理中",
-                           PubDate = p.PubDate
+                           PubDate = p.CreationTime
                        });
 
             var items = await _orderRepository.UnionAll(queryNew, queryold)
@@ -780,9 +783,6 @@ namespace Hotline.Api.Controllers
             return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(returnDto, "成功"));
         }
 
-        /// <summary>
-
-
         /// <summary>
         /// 查询工单发布后公开的数据
         /// </summary>
@@ -1038,26 +1038,29 @@ namespace Hotline.Api.Controllers
 
             var data = _mapper.Map<Hotline.Share.Dtos.Order.AddOrderDto>(dto);
             data.Source = ESource.WebPortal;
-            if (string.IsNullOrEmpty(data.SourceChannelCode))
-            {
-                data.SourceChannel = "因特网";
-                data.SourceChannelCode = "YTW";
-            }
+            data.SourceChannel = "因特网";
+            data.SourceChannelCode = "YTW";
 
-            if (dto.FromID == "2")
+            switch (dto.FromID)
             {
-                data.SourceChannel = "APP";
-                data.SourceChannelCode = "AP";
-                data.Source = ESource.APP;
+                case "2":
+                    data.SourceChannel = "APP";
+                    data.SourceChannelCode = "AP";
+                    data.Source = ESource.APP;
+                    break;
+                case "3":
+                    data.SourceChannel = "微信";
+                    data.SourceChannelCode = "WX";
+                    data.Source = ESource.WeChat;
+                    break;
+                case "9"://宜宾人社专用
+                    data.SourceChannel = "人社APP";
+                    data.SourceChannelCode = "RSAPP";
+                    data.Source = ESource.YBHumanSocietyAPP;
+                    break;
+                default:
+                    break;
             }
-
-            if (dto.FromID == "3")
-            {
-                data.SourceChannel = "微信";
-                data.SourceChannelCode = "WX";
-                data.Source = ESource.WeChat;
-            }
-
             if (!string.IsNullOrEmpty(data.LicenceNo))
             {
                 data.LicenceTypeCode = "10";
@@ -1360,14 +1363,14 @@ namespace Hotline.Api.Controllers
             var typeSpliceNameTags = string.Empty;
             if (!string.IsNullOrEmpty(dto.KnowledgeBaseTags))
             {
-                var type = await _knowledgeTypeRepository.GetAsync(x => x.Name == dto.KnowledgeBaseTags);
-                typeSpliceNameTags = type?.SpliceName;
+                var type = await _knowledgeWordRepository.GetAsync(x => x.Tag == dto.KnowledgeBaseTags && x.IsEnable == 0);
+                typeSpliceNameTags = type?.Id;
             }
 
             var (total, items) = await _knowledgeRepository.Queryable()
                 .Where(p => p.IsPublic == true && p.Status == EKnowledgeStatus.OnShelf)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Title.Contains(dto.Title))
-                .WhereIF(!string.IsNullOrEmpty(typeSpliceNameTags), p => p.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith(typeSpliceNameTags)))
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceNameTags), p => SqlFunc.JsonArrayAny(p.Keywords, typeSpliceNameTags) == true)
                 .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith(typeSpliceName)))
                 .OrderByDescending(p => p.CreationTime)
                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);

+ 261 - 0
src/Hotline.Api/Controllers/XthxController.cs

@@ -0,0 +1,261 @@
+using Hotline.Application.Xthx;
+using Hotline.Caching.Interfaces;
+using Hotline.CallCenter.Tels;
+using Hotline.Settings;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Enums.CallCenter;
+using MapsterMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using System.Text.Json;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Api.Controllers
+{
+    /// <summary>
+    /// 兴唐恒信
+    /// </summary>
+    public class XthxController : BaseController
+    {
+
+        #region 注入
+
+        private readonly IMapper _mapper;
+        private readonly ISessionContext _sessionContext;
+        private readonly IXthxApplication _xthxApplication;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;                //系统参数
+        private readonly IRepository<TelOperationXthx> _telOperationXthxRepository;            //坐席动作类型
+        private readonly IRepository<TelOperationXthxLog> _telOperationXthxLogRepository;      //坐席动作类型
+
+        public XthxController(
+           IMapper mapper,
+           ISessionContext sessionContext,
+           IXthxApplication xthxApplication,
+           ISystemSettingCacheManager systemSettingCacheManager,
+           IRepository<TelOperationXthx> telOperationXthxRepository,
+            IRepository<TelOperationXthxLog> telOperationXthxLogRepository)
+        {
+            _mapper = mapper;
+            _sessionContext = sessionContext;
+            _xthxApplication = xthxApplication;
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _telOperationXthxRepository = telOperationXthxRepository;
+            _telOperationXthxLogRepository = telOperationXthxLogRepository;
+        }
+
+        #endregion
+
+        #region 分机状态变更接收
+
+        /// <summary>
+        /// 分机状态变更接收
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("status/add")]
+        [AllowAnonymous]
+        public async Task<string> AddOperationAsync([FromBody] TelOperationXthxDto dto)
+        {
+            #region 描述
+
+            /*
+                1、根据分机号(telNo)和员工号(staffNo)确定唯一关系。
+
+                2、设计话机动作状态【签入、示忙、小休】
+
+                3、当【签入】时记录签入状态、开始时间,当【签出】时找到未结束的签入记录更新结束时间。
+                   先结束之前的【签入】,再重新【签入】
+
+                4、当【示忙】时记录示忙状态、开始时间,当传入非【示忙】状态,并且有未结束的【示忙】状态时更新结束时间。
+                   先结束之前的【示忙】,再重新【示忙】
+
+                5、当【小休】时记录小休状态、开始时间,当传入非【小休】状态,并且有未结束的【小休】状态时更新结束时间。
+                   先结束之前的【小休】,再重新【小休】
+
+                6、当【空闲】时,需要检查示忙、小休是否结束,未结束需要结束
+
+                7、当【签出】时,需要检查示忙、小休是否结束,未结束需要结束
+
+                8、除了【签入签出】状态,其他状态都要验证当前是否有【签入】,没有则新增签入
+             */
+
+            #endregion
+
+            #region 鉴权
+
+            var settingBase = _systemSettingCacheManager.GetSetting(SettingConstants.XthxVerificationStatusAdd)?.SettingValue[0];
+            if (settingBase != null)
+            {
+                TelAppXthxDto telAppDto = JsonSerializer.Deserialize<TelAppXthxDto>(settingBase);
+                if (dto.appId != telAppDto.appId || dto.appSecret != telAppDto.appSecret)
+                    throw UserFriendlyException.SameMessage("appId或者appSecret错误");
+            }
+
+            #endregion
+
+            // 查询日期间隔天数
+            var day = 3;
+
+            // 增加动作日志
+            var id = await _xthxApplication.AddOperationLogAsync(dto, HttpContext.RequestAborted);
+            if (!string.IsNullOrEmpty(id))
+            {
+                if (dto.Status == EOperationStatus.SignIn)
+                {
+                    #region 签入 100
+
+                    var xthxList = await _telOperationXthxRepository.Queryable()
+                        .Where(x => x.TelNo == dto.TelNo &&
+                                    x.StaffNo == dto.StaffNo &&
+                                    x.OperationStatus == EOperationStatus.SignIn &&
+                                    SqlFunc.DateDiff(DateType.Day, x.CreationTime, DateTime.Now) < day).ToListAsync();
+                    if (xthxList != null && xthxList.Count > 0)
+                    {
+                        // 判断是否存在数据,存在数据需要判断结束时间是否为空
+                        var rowSign = xthxList.Where(x => x.EndTime == null).ToList();//.Select("EndTime is null");
+                        if (rowSign != null && rowSign.Count > 0)
+                        {// 如果结束时间为空 结束之前的(签收、示忙、小休)
+                            foreach (var item in rowSign)
+                            {
+                                await _xthxApplication.UpdateOperationAsync(id, item.Id, HttpContext.RequestAborted);
+                            }
+                        }
+                    }
+                    await _xthxApplication.AddOperationAsync(dto, id, HttpContext.RequestAborted);
+
+                    #endregion
+                }
+                if (dto.Status == EOperationStatus.ShowBusy)
+                {
+                    #region 示忙 202
+
+                    var xthxList = await _telOperationXthxRepository.Queryable()
+                        .Where(x => x.TelNo == dto.TelNo &&
+                                    x.StaffNo == dto.StaffNo &&
+                                    x.OperationStatus == EOperationStatus.ShowBusy &&
+                                    SqlFunc.DateDiff(DateType.Day, x.CreationTime, DateTime.Now) < day).ToListAsync();
+                    if (xthxList != null && xthxList.Count > 0)
+                    {
+                        // 判断是否存在数据,存在数据需要判断结束时间是否为空
+                        var rowSign = xthxList.Where(x => x.EndTime == null).ToList();//.Select("EndTime is null");
+                        if (rowSign != null && rowSign.Count > 0)
+                        {// 如果结束时间为空 结束之前的(签收、示忙、小休)
+                            foreach (var item in rowSign)
+                            {
+                                await _xthxApplication.UpdateOperationAsync(id, item.Id, HttpContext.RequestAborted);
+                            }
+                        }
+                    }
+                    await _xthxApplication.AddOperationAsync(dto, id, HttpContext.RequestAborted);
+
+                    #endregion
+                }
+                if (dto.Status == EOperationStatus.ShortBreak)
+                {
+                    #region 小休 201
+
+                    var xthxList = await _telOperationXthxRepository.Queryable()
+                        .Where(x => x.TelNo == dto.TelNo &&
+                                    x.StaffNo == dto.StaffNo &&
+                                    x.OperationStatus == EOperationStatus.ShortBreak &&
+                                    SqlFunc.DateDiff(DateType.Day, x.CreationTime, DateTime.Now) < day).ToListAsync();
+                    if (xthxList != null && xthxList.Count > 0)
+                    {
+
+                        // 判断是否存在数据,存在数据需要判断结束时间是否为空
+                        var rowSign = xthxList.Where(x => x.EndTime == null).ToList();//.Select("EndTime is null");
+                        if (rowSign != null && rowSign.Count > 0)
+                        {// 如果结束时间为空 结束之前的(签收、示忙、小休)
+                            foreach (var item in rowSign)
+                            {
+                                await _xthxApplication.UpdateOperationAsync(id, item.Id, HttpContext.RequestAborted);
+                            }
+                        }
+                    }
+                    await _xthxApplication.AddOperationAsync(dto, id, HttpContext.RequestAborted);
+
+                    #endregion
+                }
+                else if (dto.Status == EOperationStatus.FreeTime)
+                {
+                    #region 空闲 200
+
+                    // 当【空闲】时,需要检查示忙、小休是否结束,未结束需要结束
+
+                    var sign = await _telOperationXthxRepository.Queryable()
+                        .Where(x => x.TelNo == dto.TelNo &&
+                                    x.StaffNo == dto.StaffNo &&
+                                   (x.OperationStatus == EOperationStatus.ShowBusy ||
+                                    x.OperationStatus == EOperationStatus.ShortBreak) &&
+                                    x.StartTime != null &&
+                                    x.EndTime == null)
+                        .ToListAsync();
+                    if (sign != null)
+                    {
+                        // 判断是否有签入未结束的动作
+                        foreach (var item in sign)
+                        {
+                            await _xthxApplication.UpdateOperationAsync(id, item.Id!, HttpContext.RequestAborted);
+                        }
+                    }
+
+                    #endregion
+                }
+                else if (dto.Status == EOperationStatus.SignOut)
+                {
+                    #region 签出 0
+
+                    // 当【签出】时,需要检查示忙、小休是否结束,未结束需要结束
+
+                    var sign = await _telOperationXthxRepository.Queryable()
+                        .Where(x => x.TelNo == dto.TelNo &&
+                                    x.StaffNo == dto.StaffNo &&
+                                   (x.OperationStatus == EOperationStatus.SignIn ||
+                                    x.OperationStatus == EOperationStatus.ShowBusy ||
+                                    x.OperationStatus == EOperationStatus.ShortBreak) &&
+                                    x.StartTime != null &&
+                                    x.EndTime == null)
+                        .ToListAsync();
+                    if (sign != null)
+                    {
+                        // 判断是否有签入未结束的动作
+                        foreach (var item in sign)
+                        {
+                            await _xthxApplication.UpdateOperationAsync(id, item.Id!, HttpContext.RequestAborted);
+                        }
+                    }
+
+                    #endregion
+                }
+                else
+                {
+                    #region 其他动作
+
+                    // 除了【签入签出】状态,其他状态都要验证当前是否有【签入】,没有则新增
+                    var sign = await _telOperationXthxRepository.Queryable()
+                        .Where(x => x.TelNo == dto.TelNo &&
+                                    x.StaffNo == dto.StaffNo &&
+                                    x.OperationStatus == EOperationStatus.SignIn &&
+                                    x.StartTime != null &&
+                                    x.EndTime == null)
+                        .ToListAsync();
+                    if (sign.Count == 0)
+                    {
+                        // 未找到数据 新增
+                        dto.Status = EOperationStatus.SignIn;
+                        await _xthxApplication.AddOperationAsync(dto, id, HttpContext.RequestAborted);
+                    }
+
+                    #endregion
+                }
+            }
+            return id;
+        }
+
+        #endregion
+
+    }
+}

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

@@ -36,6 +36,9 @@
     <None Update="Template\AssignmentForm.doc">
       <CopyToOutputDirectory>Always</CopyToOutputDirectory>
     </None>
+    <None Update="Template\QualityCertificate.doc">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+    </None>
     <None Remove="logs\**" />
   </ItemGroup>
 

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

@@ -70,6 +70,7 @@
 
         public static string OrderHandlingDetail = "OrderHandlingDetail";
 
+        public static string OrderSecondaryHandlingDetail = "OrderSecondaryHandlingDetail";
         #endregion
 
         #region 司法大屏

+ 8 - 0
src/Hotline.Api/Realtimes/RealtimeService.cs

@@ -219,6 +219,14 @@ public class RealtimeService : IRealtimeService, IScopeDependency
     public Task OrderHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigDataScreen, RealtimeMethods.OrderHandlingDetail,obj, cancellationToken);
 
+    /// <summary>
+    /// 推送二次办理中工单概览
+    /// </summary>
+    /// <param name="obj"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public Task OrderSecondaryHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.BigDataScreen, RealtimeMethods.OrderSecondaryHandlingDetail, obj, cancellationToken);
     #endregion
 
     /// <summary>

+ 4 - 5
src/Hotline.Api/StartupExtensions.cs

@@ -46,6 +46,7 @@ using XF.Domain.Repository.Events;
 using Hotline.Orders.DatabaseEventHandler;
 using Hotline.Snapshot;
 using Hotline.WeChat;
+using Hotline.Ai.XingTang;
 
 
 namespace Hotline.Api;
@@ -150,13 +151,11 @@ internal static class StartupExtensions
                 services.AddYbEnterpriseSdk(appConfiguration.YiBin.Enterprise.AddressUrl)
                     .AddKeyedScoped<ISessionContext, YbEnterpriseSessionContext>(YbEnterpriseSessionContext.Key)
                     .AddKeyedScoped<ISessionContext, ZzptSessionContext>(ZzptSessionContext.Key);
-                //services.AddProxiedScoped<ISnapshotApplication, DefaultSnapshotApplication>();
                 break;
             case AppDefaults.AppScope.ZiGong:
-                //services.AddProxiedScoped<ISnapshotApplication, ZiGongSnapshotApplication>();
+				services.AddAiXingTang(appConfiguration.ZiGong.AiQuality.Url);
                 break;
             case AppDefaults.AppScope.LuZhou:
-                //services.AddProxiedScoped<ISnapshotApplication, DefaultSnapshotApplication>();
                 break;
         }
 
@@ -243,10 +242,10 @@ internal static class StartupExtensions
         if (swaggerEnable)
         {
             app.UseSwagger();
-            app.UseSwaggerUI(options => 
+            app.UseSwaggerUI(options =>
             {
                 options.DefaultModelsExpandDepth(1);
-                options.DefaultModelExpandDepth(4);
+                options.DefaultModelExpandDepth(5);
             });
             //app.UseSwaggerUI(c =>
             //{

BIN
src/Hotline.Api/Template/QualityCertificate.doc


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

@@ -25,6 +25,11 @@
       }
     },
     "ZiGong": {
+      //智能质检
+      "AiQuality": {
+        "Url": "http://175.10.86.234:10095" // 正式
+        //"Url": "http://118.122.73.80:19072/", // 测试
+      },
       "AreaCode": "510300",
       "CallCenterType": "XingTang"
     },
@@ -62,13 +67,13 @@
     }
   },
   "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 //test:3, dev:5
+    "Database": 3 //test:3, dev:5
   },
   "Swagger": true,
   "AccLog":  false,

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

@@ -102,9 +102,9 @@ public class AddOrderDtoValidator : AbstractValidator<AddOrderDto>
         #endregion
 
         #region 投诉对象信息
-        RuleFor(d => d.OrderExtension.EnterpriseName).MaxLengthWithChineseChar(100).When(d => d.OrderExtension != null).WithMessage("企业名称最多100字符");
+        RuleFor(d => d.OrderExtension.EnterpriseName).MaxLengthWithChineseChar(80).When(d => d.OrderExtension != null).WithMessage("企业名称最多80字符");
         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.RegisterAddress).MaxLengthWithChineseChar(400).When(d => d.OrderExtension != null).WithMessage("企业注册地址最多400字符");
         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字符");

+ 64 - 1
src/Hotline.Application.Tests/Controller/OrderControllerTest.cs

@@ -18,6 +18,8 @@ using Hotline.Repository.SqlSugar;
 using Hotline.Repository.SqlSugar.Snapshot;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
@@ -32,6 +34,7 @@ using Hotline.Share.Enums.Settings;
 using Hotline.Share.Tools;
 using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
+using Mapster;
 using MediatR;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
@@ -60,6 +63,7 @@ public class OrderControllerTest : TestBase
     private readonly IRepository<OrderPublish> _orderPublishRepository;
     private readonly INotificationHandler<EndWorkflowNotify> _orderPublishEndWorkflowHandler;
     private readonly IOrderVisitRepository _orderVisitRepository;
+    private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
     private readonly IRepository<SystemSetting> _systemSettingRepository;
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
     private readonly IRepository<CallNative> _callNativeRepository;
@@ -68,8 +72,21 @@ public class OrderControllerTest : TestBase
     private readonly ISqlSugarClient _capSqlClient;
     private readonly IIndustryRepository _industryRepository;
     private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly ISystemLogRepository _systemLogRepository;
+    private readonly IOrderVisitDomainService _orderVisitDomainService;
 
-    public OrderControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IRepository<Hotspot> hotspotRepository, OrderController orderController, IOrderRepository orderRepository, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, OrderServiceMock orderServiceMock, IRepository<OrderPublish> orderPublishRepository, INotificationHandler<EndWorkflowNotify> orderPublishEndWorkflowHandler, IOrderVisitRepository orderVisitRepository, IRepository<SystemSetting> systemSettingRepository, ISystemSettingCacheManager systemSettingCacheManager, IRepository<CallNative> callNativeRepository, IRepository<CallidRelation> callIdRelationRepository, XingTangCallApplication defaultCallApplication, ISugarUnitOfWork<CapDbContext> capDbContext, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, IIndustryRepository industryRepository, IOrderSnapshotRepository orderSnapshotRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
+    public OrderControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository,
+        UserController userController, IRepository<Hotspot> hotspotRepository, OrderController orderController, 
+        IOrderRepository orderRepository, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, 
+        OrderServiceMock orderServiceMock, IRepository<OrderPublish> orderPublishRepository,
+        INotificationHandler<EndWorkflowNotify> orderPublishEndWorkflowHandler,
+        IOrderVisitRepository orderVisitRepository, IRepository<SystemSetting> systemSettingRepository,
+        ISystemSettingCacheManager systemSettingCacheManager, IRepository<CallNative> callNativeRepository,
+        IRepository<CallidRelation> callIdRelationRepository, XingTangCallApplication defaultCallApplication,
+        ISugarUnitOfWork<CapDbContext> capDbContext, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService,
+        IThirdAccountRepository thirdAccount, IIndustryRepository industryRepository, IOrderSnapshotRepository orderSnapshotRepository,
+        ISystemLogRepository systemLogRepository, IOrderVisitDomainService orderVisitDomainService, IRepository<OrderVisitDetail> orderVisitDetailRepository)
+        : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _hotspotRepository = hotspotRepository;
         _orderController = orderController;
@@ -90,6 +107,9 @@ public class OrderControllerTest : TestBase
         _capSqlClient = capDbContext.Db;
         _industryRepository = industryRepository;
         _orderSnapshotRepository = orderSnapshotRepository;
+        _systemLogRepository = systemLogRepository;
+        _orderVisitDomainService = orderVisitDomainService;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
     }
 
     /// <summary>
@@ -159,6 +179,49 @@ public class OrderControllerTest : TestBase
         }
     }
 
+    [Fact]
+    public async Task Visit_Test()
+    {
+        SetZuoXi();
+        var order = _orderServiceMock.CreateOrder()
+            .办理到一级部门()
+            .办理到二级部门(Set一级部门)
+            .办理一级部门汇总(Set二级部门)
+            .办理到归档(Set一级部门)
+            .发布工单(SetZuoXi)
+            .GetCreateResult();
+
+        var visit = await _orderVisitRepository.Queryable()
+            .Where(m => m.OrderId == order.Id)
+            .FirstAsync();
+        var visitDetaila = await _orderVisitDetailRepository.Queryable().Where(m => m.VisitId == visit.Id).ToListAsync();
+        var visitDetail = visitDetaila.Adapt<List<VisitDetailDto>>();
+        visitDetail.ForEach(m => { 
+            m.SeatEvaluate = ESeatEvaluate.DefaultSatisfied;
+            m.OrgProcessingResults = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>("0");
+            m.OrgHandledAttitude = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>("0");
+        });
+
+        var callNo = DateTime.Now.ToString("yyyyMMddhhmmss") + "Flow";
+        var inDto = _fixture.Build<CallNative>()
+            .With(m => m.Id, Ulid.NewUlid().ToString())
+            .With(m => m.CallNo, callNo)
+            .With(m => m.Direction, ECallDirection.In)
+            .With(m => m.IsDeleted, false)
+            .Create();
+        await _callNativeRepository.AddAsync(inDto);
+
+        _systemLogRepository.Add("回访外呼已经接通", "", new CallRemarkDto { CallId = callNo,  CallNumber = inDto.ToNo }.ToJson(), "", 1, visit.Id);
+        var visitDto = new VisitDto
+        { 
+            Id = visit.Id,
+            IsPutThrough = true,
+            VisitDetails = visitDetail
+        };
+
+        await _orderController.Visit(visitDto);
+    }
+
     /// <summary>
     /// 创建工单并派送给派单员
     /// </summary>

+ 19 - 4
src/Hotline.Application/Bigscreen/DataScreenRefreshService.cs

@@ -14,10 +14,12 @@ using System.Text;
 using System.Threading.Tasks;
 using Hotline.Settings;
 using XF.Domain.Constants;
+using XF.Domain.Repository;
+using Hotline.JudicialManagement;
 
 namespace Hotline.Application.Bigscreen
 {
-    public class DataScreenRefreshService: BackgroundService
+    public class DataScreenRefreshService : BackgroundService
     {
         private readonly IServiceScopeFactory _serviceScopeFactory;
 
@@ -33,7 +35,7 @@ namespace Hotline.Application.Bigscreen
             var _orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
             var _mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
             var systemSettingCacheManager = scope.ServiceProvider.GetRequiredService<ISystemSettingCacheManager>();
-           
+            var _orderSecondaryHandlingRepository = scope.ServiceProvider.GetRequiredService<IRepository<OrderSecondaryHandling>>();
             int times = int.Parse(systemSettingCacheManager.GetSetting(SettingConstants.BsDataStateChangedTimes)?.SettingValue[0]);
             while (!stoppingToken.IsCancellationRequested)
             {
@@ -95,7 +97,7 @@ namespace Hotline.Application.Bigscreen
 
                     await realtimeService.OrderCountStatisticsAsync(dto, stoppingToken);
                 }
-                catch{}
+                catch { }
 
                 try
                 {
@@ -110,7 +112,20 @@ namespace Hotline.Application.Bigscreen
 
                     await realtimeService.OrderHandlingDetailAsync(orderlist, stoppingToken);
                 }
-                catch{}
+                catch { }
+
+                try
+                {
+                    var list = await _orderSecondaryHandlingRepository.Queryable().Includes(x => x.Order)
+                              .Where(x => x.CreationTime.Date == DateTime.Now.Date)
+                              .OrderByDescending(x => x.CreationTime)
+                              .Take(50)
+                             .ToListAsync();
+                    var orderlist = _mapper.Map<List<OrderSecondaryHandlingDto>>(list);
+
+                    await realtimeService.OrderSecondaryHandlingDetailAsync(orderlist, stoppingToken);
+                }
+                catch { }
 
                 await Task.Delay(times);
             }

+ 35 - 23
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -299,6 +299,8 @@ public abstract class DefaultCallApplication : ICallApplication
             .WhereIF(!string.IsNullOrEmpty(dto.StaffNo), d => d.StaffNo == dto.StaffNo)
             .WhereIF(!string.IsNullOrEmpty(dto.GroupId), d => d.GroupId == dto.GroupId)
             .WhereIF(dto.OperateState != null, d => d.OperateState == dto.OperateState)
+            .WhereIF(dto.OperateTimeStart.HasValue, x => x.OperateTime >= dto.OperateTimeStart)
+            .WhereIF(dto.OperateTimeEnd.HasValue, x => x.OperateTime <= dto.OperateTimeEnd)
             .OrderByDescending(d => d.Id)
             .ToFixedListAsync(dto, cancellationToken);
     }
@@ -331,28 +333,6 @@ public abstract class DefaultCallApplication : ICallApplication
         return callOrder.CallId;
     }
 
-    /// <summary>
-    /// 发送延迟消息让回访去关联通话记录
-    /// </summary>
-    /// <param name="cancellationToken"></param>
-    /// <returns></returns>
-    public virtual async Task PublishVisitRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken)
-    {
-        if (dto.CallId.IsNullOrEmpty()) return;
-
-        var seconds = _systemSettingCacheManager.VisitCallDelaySecond;
-        await _capPublisher.PublishDelayAsync(TimeSpan.FromSeconds(seconds), EventNames.VisitCallDelay, dto, cancellationToken: cancellationToken);
-    }
-
-    public virtual async Task PublishOrderRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken)
-    {
-        if (dto.CallId.IsNullOrEmpty()) return;
-
-        var seconds = _systemSettingCacheManager.VisitCallDelaySecond;
-        await _capPublisher.PublishDelayAsync(TimeSpan.FromSeconds(seconds), EventNames.VisitCallDelay, dto, cancellationToken: cancellationToken);
-    }
-
-
     public async Task<CallidRelation> GetRelationAsync(string callNo, CancellationToken cancellation)
     {
         return await _callIdRelationRepository.GetAsync(callNo, cancellation);
@@ -466,6 +446,38 @@ public abstract class DefaultCallApplication : ICallApplication
     }
 
     /// <summary>
+    /// 发送延迟消息让回访去关联通话记录
+    /// 如果前端入参中的CallId为空, 就根据回访Id在systemLog中查询.前端在回访界面点击"回访"按钮拨号时会在systemLog中保存回访的号码,callNo,回访Id;
+    /// 通过systemLog修复前端没有传callId这种情况;
+    /// </summary>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public virtual async Task<string> PublishVisitRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken)
+    {
+        if (dto.CallId.IsNullOrEmpty())
+        {
+            try
+            {
+                var log = await _systemLogRepository.Queryable()
+                    .Where(m => m.IpUrl == dto.Id && m.Name == "回访外呼已经接通")
+                    .FirstAsync(cancellationToken);
+                if (log is null || log.Remark.IsNullOrEmpty()) return null;
+                var callRemark = log.Remark.FromJson<CallRemarkDto>();
+                dto.CallId = callRemark.CallId;
+            }
+            catch (Exception e)
+            { 
+                _logger.LogError($"PublishVisitRelevanceCallIdAsync: {e.ToJson()}");
+            }
+        }
+
+        var seconds = _systemSettingCacheManager.VisitCallDelaySecond;
+        await _capPublisher.PublishDelayAsync(TimeSpan.FromSeconds(seconds), EventNames.VisitCallDelay, dto, cancellationToken: cancellationToken);
+        return dto.CallId;
+    }
+
+    /// <summary>
+    /// 处理: EventNames.VisitCallDelay 消息
     /// 保存回访详情时发送延迟消息同步通话记录
     /// 如果回访通话记录有多条, 需要关联通话时长最长的那条
     /// </summary>
@@ -597,7 +609,7 @@ public abstract class DefaultCallApplication : ICallApplication
             TrCallRecordDto = call.Adapt<TrCallDto>()
         }, cancellationToken: cancellationToken);
         var msg = $"原CallId: {orderCall.CallId}, 更新CallId: {call.Id}";
-        _systemLogRepository.Add("延迟更新工单通话", orderId, msg,status:1, ipUrl: orderCall.CallId);
+        _systemLogRepository.Add("延迟更新工单通话", orderId, msg, status: 1, ipUrl: orderCall.CallId);
         return msg + "(完成推省上)";
     }
 

+ 1 - 1
src/Hotline.Application/CallCenter/ICallApplication.cs

@@ -84,7 +84,7 @@ namespace Hotline.Application.CallCenter
         /// </summary>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        Task PublishVisitRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken);
+        Task<string> PublishVisitRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken);
 
         Task<CallidRelation> GetRelationAsync(string callNo, CancellationToken cancellation);
 

+ 2 - 2
src/Hotline.Application/CallCenter/TianRunCallApplication.cs

@@ -109,9 +109,9 @@ namespace Hotline.Application.CallCenter
             return await Task.FromResult(callNo);
         }
 
-        public override async Task PublishVisitRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken)
+        public override async Task<string> PublishVisitRelevanceCallIdAsync(OrderRelevanceCallIdDto dto, CancellationToken cancellationToken)
         {
-            return;
+            return await Task.FromResult(dto.CallId);
         }
 
         public override async Task OrderVisitRelevanceCallIdAsync(VisitDto dto, CancellationToken cancellationToken)

+ 690 - 0
src/Hotline.Application/Caselibrary/CaseApplication.cs

@@ -0,0 +1,690 @@
+using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos.Knowledge;
+using Hotline.Share.Dtos.Caselibrary;
+using Hotline.Share.Enums.Caselibrary;
+using MapsterMapper;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.SeedData;
+using Hotline.File;
+using Hotline.Application.Bulletin;
+using Hotline.Share.Tools;
+using Hotline.Application.Tools;
+using SqlSugar;
+using Hotline.CaseLibrary;
+using Hotline.Orders;
+using Hotline.KnowledgeBase;
+using Hotline.Share.Enums.Planlibrary;
+using System.Numerics;
+using Hotline.Share.Dtos.Planlibrary;
+using Hotline.Planlibrary;
+
+namespace Hotline.Application.Caselibrary
+{
+    /// <summary>
+    /// 案例库处理
+    /// </summary>
+    public class CaseApplication : ICaseApplication, IScopeDependency
+    {
+
+        #region 注册
+
+        private readonly IRepository<CaseList> _caseListRepository;                           //案例库列表
+        private readonly IRepository<CaseType> _caseTypeRepository;                           //案例库分类管理
+        private readonly IRepository<CaseTypeOrg> _caseTypeOrgRepository;                     //案例库分类关联机构
+        private readonly IRepository<CaseCollect> _caseCollectRepository;                     //案例库收藏评分
+        private readonly IRepository<Order> _orderRepository;                                 //工单
+        private readonly IRepository<Hotline.KnowledgeBase.Knowledge> _knowledgeRepository;   //知识库
+        private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
+        private readonly IRepository<Hotspot> _hotspotTypeRepository;
+        private readonly IFileRepository _fileRepository;
+        private readonly IBulletinApplication _bulletinApplication;
+
+        public CaseApplication(
+            IRepository<CaseList> caseListRepository,
+            IRepository<CaseType> caseTypeRepository,
+            IRepository<CaseTypeOrg> caseTypeOrgRepository,
+            IRepository<CaseCollect> caseCollectRepository,
+            IRepository<Order> orderRepository,
+            IRepository<Hotline.KnowledgeBase.Knowledge> knowledgeRepository,
+            ISessionContext sessionContext,
+            IMapper mapper,
+            IRepository<Hotspot> hotspotTypeRepository,
+            IFileRepository fileRepository,
+            IBulletinApplication bulletinApplication)
+        {
+            _caseListRepository = caseListRepository;
+            _caseTypeRepository = caseTypeRepository;
+            _caseTypeOrgRepository = caseTypeOrgRepository;
+            _caseCollectRepository = caseCollectRepository;
+            _orderRepository = orderRepository;
+            _knowledgeRepository = knowledgeRepository;
+            _sessionContext = sessionContext;
+            _mapper = mapper;
+            _hotspotTypeRepository = hotspotTypeRepository;
+            _fileRepository = fileRepository;
+            _bulletinApplication = bulletinApplication;
+        }
+
+        #endregion
+
+        #region 案例库类型管理
+
+        #region 案例库类型 - 新增
+
+        /// <summary>
+        /// 案例库类型 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<string> AddTypeAsync(AddCaseTypeDto dto, CancellationToken cancellationToken)
+        {
+            var sandard = await _caseTypeRepository.GetAsync(p => p.ParentId == dto.ParentId && p.Name == dto.Name && p.IsDeleted == false, cancellationToken);
+            if (sandard is not null)
+                throw UserFriendlyException.SameMessage("当前层级已存在相同名称的分类!");
+
+            var type = _mapper.Map<CaseType>(dto);
+            type.InitId();
+            type.IsEnable = true;
+            //获取分类名称全称
+            string FullName = await GetFullName(type.ParentId);
+            //处理全称,如果为第一级直接用全称,否则获取全称后拼接名称
+            type.SpliceName = string.IsNullOrEmpty(FullName) ? dto.Name : FullName + "-" + dto.Name;
+            var id = await _caseTypeRepository.AddAsync(type, cancellationToken);
+            if (dto.TypeOrgDtos != null && dto.TypeOrgDtos.Any())
+            {
+                List<CaseTypeOrg> orgs = _mapper.Map<List<CaseTypeOrg>>(dto.TypeOrgDtos);
+                orgs.ForEach(x => x.TypeId = type.Id);
+                await _caseTypeOrgRepository.AddRangeAsync(orgs, cancellationToken);
+            }
+            return id;
+        }
+
+        #endregion
+
+        #region 案例库类型 - 编辑
+
+        /// <summary>
+        /// 案例库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task UpdateTypeAsync(UpdateCaseTypeDto dto, CancellationToken cancellationToken)
+        {
+            //查询原有数据
+            var type = await _caseTypeRepository.GetAsync(dto.Id, cancellationToken);
+            if (type is null)
+                throw UserFriendlyException.SameMessage("编辑失败!");
+            bool result = type.Name != dto.Name || type.ParentId != dto.ParentId;
+            //是否更改分类名称或者层级
+
+            //转换
+            _mapper.Map(dto, type);
+            //如果更改了名称或者修改了层级,则修改全称,未更改不修改
+            if (result)
+            {
+                string FullName = await GetFullName(type.ParentId);//获取分类名称全称
+                type.SpliceName = string.IsNullOrEmpty(FullName) ? dto.Name : FullName + "-" + dto.Name;//处理全称,如果为第一级直接用全称,否则获取全称后拼接名称
+            }
+
+            //修改数据
+            await _caseTypeRepository.UpdateAsync(type, cancellationToken);
+
+            //如果修改了名称,对应修改子分类全称
+            if (result)
+                await UpdateChildNode(type.Id);
+
+            // 修改关联机构
+            await _caseTypeOrgRepository.RemoveAsync(x => x.TypeId == type.Id, false, cancellationToken);
+            if (dto.TypeOrgDtos != null && dto.TypeOrgDtos.Any())
+            {
+                List<CaseTypeOrg> orgs = _mapper.Map<List<CaseTypeOrg>>(dto.TypeOrgDtos);
+                orgs.ForEach(x => x.TypeId = type.Id);
+                await _caseTypeOrgRepository.AddRangeAsync(orgs, cancellationToken);
+            }
+        }
+
+        #endregion
+
+        #region 案例库类型 - 删除
+
+        /// <summary>
+        /// 案例库类型 - 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task RemoveTypeAsync(string Id, CancellationToken cancellationToken)
+        {
+            //查询数据是否存在
+            var sandard = await _caseTypeRepository.GetAsync(p => p.Id == Id && p.IsDeleted == false, cancellationToken);
+            if (sandard is null)
+                throw UserFriendlyException.SameMessage("分类不存在!");
+
+            //查询是否有子级分类
+            var checkChild = await _caseTypeRepository.CountAsync(p => p.ParentId == Id && p.IsDeleted == false, cancellationToken);
+            if (checkChild > 0)
+                throw UserFriendlyException.SameMessage("存在子级分类!");
+
+            //查询是否有案例分类
+            var checkKnowledge = await _caseListRepository.CountAsync(p => p.CaseTypes.Any(t => t.Id == Id), cancellationToken);
+            if (checkKnowledge > 0)
+                throw UserFriendlyException.SameMessage("分类存在案例!");
+
+            //删除操作
+            await _caseTypeRepository.RemoveAsync(sandard, true, cancellationToken);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region 案例库管理
+
+        #region 案例库 - 列表
+
+        /// <summary>
+        /// 案例库 - 列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        public async Task<(int, IList<CaseDataDto>)> QueryAllCaseListAsync(CaseListDto pagedDto, CancellationToken cancellationToken)
+        {
+            var typeSpliceName = string.Empty;
+
+            if (!string.IsNullOrEmpty(pagedDto.CaseTypeID))
+            {
+                var type = await _caseTypeRepository.GetAsync(x => x.Id == pagedDto.CaseTypeID);
+                typeSpliceName = type?.SpliceName;
+            }
+
+            //单表分页
+            var (total, temp) = await _caseListRepository.Queryable()
+                .Includes(x => x.CaseTypes)
+                .Includes(x => x.ExaminMan)
+                .Includes(x => x.Order)
+                .Includes(x => x.Knowledge)
+                .Where(x => x.IsDeleted == false)
+
+                .WhereIF(pagedDto.IsPopular == true, x => x.IsPopular == true)
+
+                .WhereIF(OrgSeedData.CenterId != pagedDto.CreateOrgId && !string.IsNullOrEmpty(pagedDto.CreateOrgId), x => x.CreatorOrgId != null && x.CreatorOrgId.StartsWith(pagedDto.CreateOrgId!))
+
+                .WhereIF(!string.IsNullOrEmpty(pagedDto.Title) &&
+                          string.IsNullOrEmpty(pagedDto.Keywords) &&
+                          string.IsNullOrEmpty(pagedDto.Abstract), x => x.Title.Contains(pagedDto.Title!))
+
+                .WhereIF(string.IsNullOrEmpty(pagedDto.Title) &&
+                        !string.IsNullOrEmpty(pagedDto.Keywords) &&
+                         string.IsNullOrEmpty(pagedDto.Abstract), x => x.Keywords.Contains(pagedDto.Keywords!))
+
+                .WhereIF(string.IsNullOrEmpty(pagedDto.Title) &&
+                         string.IsNullOrEmpty(pagedDto.Keywords) &&
+                        !string.IsNullOrEmpty(pagedDto.Abstract), x => x.Abstract.Contains(pagedDto.Abstract!))
+
+                .WhereIF(!string.IsNullOrEmpty(pagedDto.Title) &&
+                         !string.IsNullOrEmpty(pagedDto.Keywords) &&
+                         !string.IsNullOrEmpty(pagedDto.Abstract)
+                                                                , x => x.Title.Contains(pagedDto.Title!) ||
+                                                                  x.Keywords!.Contains(pagedDto.Keywords!) ||
+                                                                  x.Abstract!.Contains(pagedDto.Abstract!))
+
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == ECaseStatus.OnShelf, x => x.Status == ECaseStatus.OnShelf)
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == ECaseStatus.OffShelf, x => x.Status == ECaseStatus.OffShelf)
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == ECaseStatus.Auditing, x => x.Status == ECaseStatus.Auditing)
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == ECaseStatus.NewDrafts, x =>
+                                                                                               (x.Status == ECaseStatus.Drafts && x.CreatorId == _sessionContext.UserId) ||
+                                                                                               (x.Status == ECaseStatus.Revert && x.CreatorId == _sessionContext.UserId) ||
+                                                                                               (x.Status == ECaseStatus.NewDrafts && x.CreatorId == _sessionContext.UserId))
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == ECaseStatus.All, x => (x.Status == ECaseStatus.OnShelf ||
+                                                                                               x.Status == ECaseStatus.OffShelf ||
+                                                                                               x.Status == ECaseStatus.Auditing) ||
+                                                                                              (x.Status == ECaseStatus.Drafts && x.CreatorId == _sessionContext.UserId) ||
+                                                                                              (x.Status == ECaseStatus.Revert && x.CreatorId == _sessionContext.UserId) ||
+                                                                                              (x.Status == ECaseStatus.NewDrafts && x.CreatorId == _sessionContext.UserId))
+
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.CaseTypes.Any(t => t.SpliceName.StartsWith(typeSpliceName)))
+
+                .WhereIF(pagedDto.CreationTimeStart.HasValue, x => x.CreationTime >= pagedDto.CreationTimeStart)
+                .WhereIF(pagedDto.CreationTimeEnd.HasValue, x => x.CreationTime <= pagedDto.CreationTimeEnd)
+
+                .WhereIF(pagedDto.OnShelfTimeStart.HasValue, x => x.OnShelfTime >= pagedDto.OnShelfTimeStart)
+                .WhereIF(pagedDto.OnShelfTimeEnd.HasValue, x => x.OnShelfTime <= pagedDto.OnShelfTimeEnd)
+
+                .WhereIF(pagedDto.OffShelfTimeStart.HasValue, x => x.OffShelfTime >= pagedDto.OffShelfTimeStart)
+                .WhereIF(pagedDto.OffShelfTimeEnd.HasValue, x => x.OffShelfTime <= pagedDto.OffShelfTimeEnd)
+
+                .WhereIF(pagedDto.UpdateTimeStart.HasValue, x => x.UpdateTime >= pagedDto.UpdateTimeStart)
+                .WhereIF(pagedDto.UpdateTimeEnd.HasValue, x => x.UpdateTime <= pagedDto.UpdateTimeEnd)
+
+                .WhereIF(pagedDto.ExaminTimeStart.HasValue, x => x.ExaminTime >= pagedDto.ExaminTimeStart)
+                .WhereIF(pagedDto.ExaminTimeEnd.HasValue, x => x.ExaminTime <= pagedDto.ExaminTimeEnd)
+
+                .OrderByIF(string.IsNullOrEmpty(pagedDto.SortField), x => x.CreationTime, OrderByType.Desc)
+                .OrderByIF(pagedDto is { SortField: "pageView" }, x => x.PageView, OrderByType.Desc)         //阅读量
+                .OrderByIF(pagedDto is { SortField: "score" }, x => x.Score, OrderByType.Desc)               //评分
+                .OrderByIF(pagedDto is { SortField: "creationTime" }, x => x.CreationTime, OrderByType.Desc) //创建时间
+                                                                                                             //.Select(x => new CaseDataDto
+                                                                                                             //{
+                                                                                                             //    Id = x.Id.SelectAll(),
+                                                                                                             //    OrderTitle = x.Order.Title,
+                                                                                                             //    KnowledgeTitle = x.Knowledge.Title
+                                                                                                             //})
+                .ToPagedListAsync(pagedDto.PageIndex, pagedDto.PageSize, cancellationToken);
+
+            return (total, _mapper.Map<IList<CaseDataDto>>(temp));
+            //return (total, temp);
+        }
+
+        #endregion
+
+        #region 案例库 - 新增
+
+        /// <summary>
+        /// 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<string> AddCaseAsync(AddCaseListDto dto, CancellationToken cancellationToken)
+        {
+            var cList = _mapper.Map<CaseList>(dto);
+
+            var any = await _caseListRepository.Queryable().Where(x => x.Status == ECaseStatus.OnShelf && x.Title == dto.Title).AnyAsync();
+            if (any)
+                throw UserFriendlyException.SameMessage("当前案例标题存在重复标题!");
+
+            cList.InitId();
+
+            if (dto.Files != null && dto.Files.Count > 0)
+                cList.FileJson = await _fileRepository.AddFileAsync(dto.Files, cList.Id, "", cancellationToken);
+
+            if (dto.CaseTypes.Any())
+            {
+                cList.CaseTypes = dto.CaseTypes.Select(d => new CaseType
+                {
+                    Id = d.Id,
+                    Name = d.Name,
+                    SpliceName = d.SpliceName
+                }).ToList();
+            }
+
+            await _caseListRepository.AddNav(cList)
+                                     .Include(d => d.CaseTypes)
+                                     .ExecuteCommandAsync();
+
+            return cList.Id;
+        }
+
+        #endregion
+
+        #region 案例库 - 修改
+
+        /// <summary>
+        /// 修改
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task UpdateCaseAsync(UpdateCaseListDto dto, CancellationToken cancellationToken)
+        {
+            var Case = await _caseListRepository.GetAsync(dto.Id);
+
+            if (Case == null)
+                throw UserFriendlyException.SameMessage("案例库查询失败");
+
+            if ((Case.Status == ECaseStatus.OnShelf || Case.Status == ECaseStatus.Auditing) && (Case.ExpiredTime.HasValue && Case.ExpiredTime.Value > DateTime.Now))
+                throw UserFriendlyException.SameMessage("案例库数据不可修改");
+
+            var any = await _caseListRepository.Queryable().Where(x => x.Status == ECaseStatus.OnShelf && x.Title == dto.Title && x.Id != dto.Id).AnyAsync();
+            if (any)
+                throw UserFriendlyException.SameMessage("当前案例标题存在重复标题!");
+
+            if (dto.ApplyStatus == ECaseApplyStatus.Delete)
+            {
+                Case.Status = (ECaseStatus)dto.Status;
+                Case.ApplyStatus = (ECaseApplyStatus)dto.ApplyStatus;
+                Case.Id = dto.Id;
+                Case.ApplyReason = dto.ApplyReason;
+            }
+            else
+            {
+                _mapper.Map(dto, Case);
+
+                if (dto.Files != null && dto.Files.Count > 0)
+                    Case.FileJson = await _fileRepository.AddFileAsync(dto.Files, Case.Id, "", cancellationToken);
+                else
+                    Case.FileJson = new List<Share.Dtos.File.FileJson>();
+            }
+
+
+            if (dto.ApplyStatus != ECaseApplyStatus.Delete)
+            {
+                if (dto.CaseTypes.Any())
+                {
+                    Case.CaseTypes = dto.CaseTypes.Select(d => new CaseType
+                    {
+                        Id = d.Id
+                    }).ToList();
+
+                    await _caseListRepository.UpdateNav(Case)
+                        .Include(d => d.CaseTypes, new UpdateNavOptions
+                        {
+                            ManyToManyIsUpdateA = true
+                        })
+                        .ExecuteCommandAsync();
+                }
+                else
+                {
+                    await _caseListRepository.UpdateAsync(Case, cancellationToken);
+                }
+            }
+            else
+            {
+                await _caseListRepository.UpdateAsync(Case, cancellationToken);
+            }
+        }
+
+        #endregion
+
+        #region 案例库 - 删除
+
+        /// <summary>
+        /// 删除
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task RemoveCaseAsync(UpdateCaseListDto dto, CancellationToken cancellationToken)
+        {
+            var Case = await _caseListRepository.GetAsync(dto.Id);
+
+            if (Case == null)
+                throw UserFriendlyException.SameMessage("案例库查询失败");
+
+            _mapper.Map(dto, Case);
+
+            Case.IsDeleted = true;
+
+            await _caseListRepository.UpdateNullAsync(Case, cancellationToken);
+        }
+
+        #endregion
+
+        #region 案例库 - 审核
+
+        /// <summary>
+        /// 审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task AuditCaseAsync(UpdateCaseListDto dto, CancellationToken cancellationToken)
+        {
+            var Case = await _caseListRepository.GetAsync(dto.Id);
+
+            if (Case == null)
+                throw UserFriendlyException.SameMessage("案例库查询失败");
+
+            Case.Status = (ECaseStatus)dto.Status!;
+            Case.ApplyStatus = (ECaseApplyStatus)dto.ApplyStatus!;
+            Case.ExaminTime = dto.ExaminTime;
+            Case.ExaminManId = dto.ExaminManId;
+            Case.ExaminOrganizeId = dto.ExaminOrganizeId;
+            Case.ExaminOpinion = dto.ExaminOpinion;
+            Case.UpdateTime = dto.UpdateTime;
+            Case.OnShelfTime = dto.OnShelfTime;
+            Case.OffShelfTime = dto.OffShelfTime;
+
+            if (Case.ApplyStatus == ECaseApplyStatus.Delete)
+            {
+                Case.IsDeleted = true;
+            }
+
+            await _caseListRepository.UpdateNullAsync(Case, cancellationToken);
+        }
+
+        #endregion
+
+        #region 案例库 - 详情
+
+        /// <summary>
+        /// 详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="IsAddPv">默认不增加,false不增加,true增加浏览量</param>
+        /// <returns></returns>
+        public async Task<CaseInfoDto> GetCaseAsync(string Id, bool? IsAddPv, CancellationToken cancellationToken)
+        {
+            var Case = await _caseListRepository.Queryable()
+                .Includes(x => x.CaseTypes)
+                .Includes(x => x.Order)
+                .Includes(x => x.Knowledge)
+                .Where(x => x.Id == Id).FirstAsync();
+            if (Case == null)
+                throw UserFriendlyException.SameMessage("案例库查询失败");
+            ;
+            //转化
+            var caseInfoDto = _mapper.Map<CaseInfoDto>(Case);
+
+            if (Case != null)
+            {
+                if (!string.IsNullOrEmpty(Case.Abstract))
+                    caseInfoDto.Abstract = _bulletinApplication.GetSiteUrls(Case.Abstract);
+                if (!string.IsNullOrEmpty(Case.Describe))
+                    caseInfoDto.Describe = _bulletinApplication.GetSiteUrls(Case.Describe);
+                if (!string.IsNullOrEmpty(Case.Result))
+                    caseInfoDto.Result = _bulletinApplication.GetSiteUrls(Case.Result);
+                if (!string.IsNullOrEmpty(Case.Reason))
+                    caseInfoDto.Reason = _bulletinApplication.GetSiteUrls(Case.Reason);
+            }
+
+            // 分类
+            //var relationType = await _caseRelationTypeRepository.QueryAsync(x => x.CaseId == Id && x.CreatorId == _sessionContext.UserId);
+            //if (relationType != null)
+            //{
+            //    caseInfoDto.CaseTypes = _mapper.Map<List<CaseRelationTypeDto>>(relationType);
+            //}
+
+            // 收藏
+            var collect = await _caseCollectRepository.GetAsync(x => x.CaseId == Id && x.CreatorId == _sessionContext.UserId);
+            if (collect != null)
+                caseInfoDto.Collect = _mapper.Map<CaseCollectDto>(collect);
+
+            // 附件
+            if (caseInfoDto.FileJson != null && caseInfoDto.FileJson.Any())
+            {
+                var ids = caseInfoDto.FileJson.Select(x => x.Id).ToList();
+                caseInfoDto.Files = await _fileRepository.GetFilesAsync(ids, cancellationToken);
+            }
+
+            // 更新浏览量
+            if (IsAddPv == true)
+            {
+                //修改浏览量
+                Case.PageView++;
+                //修改点击量
+                await _caseListRepository.UpdateAsync(Case, cancellationToken);
+            }
+            return caseInfoDto;
+        }
+
+        #endregion
+
+        #region 案例库 - 批量导出
+
+        /// <summary>
+        /// 案例库 - 批量导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<Dictionary<string, Stream>> CaseInfoListExportAsync(CaseInfoExportDto dto, CancellationToken cancellationToken)
+        {
+            var streamList = new Dictionary<string, Stream>();
+            var knowList = await _caseListRepository.Queryable()
+                .Where(m => dto.Ids.Contains(m.Id))
+                .Select(m => new { m.Title, m.Result })
+                .ToListAsync(cancellationToken);
+
+            var tasks = knowList.Select(async item =>
+            {
+                var stream = await Task.Run(() => item.Result.HtmlToStream(dto.FileType), cancellationToken);
+                return new KeyValuePair<string, Stream>(
+                    item.Title + dto.FileType.GetFileExtension(),
+                    stream
+                );
+            });
+
+            var results = await Task.WhenAll(tasks);
+
+            foreach (var kvp in results)
+            {
+                if (!streamList.ContainsKey(kvp.Key))
+                {
+                    streamList.Add(kvp.Key, kvp.Value);
+                }
+            }
+
+            return streamList;
+        }
+
+        #endregion
+
+        #endregion
+
+        #region 私有方法
+
+        #region 查询所有子级
+
+        /// <summary>
+        /// 查询所有子级
+        /// </summary>
+        /// <param name="treeDatas">分类数据</param>
+        /// <param name="ID">需要查询哪级下面的分类</param>
+        /// <param name="checkId">选中的数据ID</param>
+        /// <returns></returns>
+        private List<TreeListDto> GetChildren(List<CaseType> treeDatas, string ID, string? checkId)
+        {
+            List<TreeListDto> nodeList = new();
+            //根据ID查询子级
+            var children = treeDatas.Where(q => q.ParentId == ID);
+            foreach (var dr in children)
+            {
+                //组装数据
+                TreeListDto node = new()
+                {
+                    name = dr.Name,
+                    ParentID = dr.ParentId,
+                    value = dr.Id,
+                    IsEnable = dr.IsEnable
+                };
+                //是否选中
+                if (!string.IsNullOrEmpty(checkId) && checkId != Guid.Empty.ToString() && checkId == dr.Id)
+                    node.selected = true;
+                //子级数据赋值
+                node.children = GetChildren(treeDatas, node.value, checkId);
+                //添加数据
+                nodeList.Add(node);
+            }
+            return nodeList;
+        }
+
+        #endregion
+
+        #region  获取全称
+
+        /// <summary>
+        /// 获取全称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<string> GetFullName(string? Id)
+        {
+            //获取全部父级名称
+            var list = await GetParentNode(Id);
+            //倒叙
+            list.Reverse();
+            //拆分
+            return string.Join("-", list.ToArray());
+        }
+
+        /// <summary>
+        /// 查询父级名称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<List<string>> GetParentNode(string? Id)
+        {
+            List<string> list = new();
+            //查询父级数据
+            var type = await _caseTypeRepository.GetAsync(p => p.Id == Id);
+            if (type != null)
+            {
+                //添加名称
+                list.Add(type.Name);
+                list.AddRange(await GetParentNode(type.ParentId));
+            }
+            return list;
+        }
+
+        #endregion
+
+        #region 修改子级分类全称
+
+        /// <summary>
+        /// 修改子级分类全称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task UpdateChildNode(string Id)
+        {
+            //查询子分类
+            var list = await GetChildNode(Id);
+            if (list is not null && list.Count > 0)
+            {
+                foreach (var item in list)
+                {
+                    //获取全称
+                    string FullName = await GetFullName(item.ParentId);
+                    item.SpliceName = string.IsNullOrEmpty(FullName) ? item.Name : FullName + "-" + item.Name;
+                    //修改全称
+                    await _caseTypeRepository.UpdateAsync(item);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 查询子级节点数据
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<List<CaseType>> GetChildNode(string Id)
+        {
+            List<CaseType> list = new();
+            //查询数据
+            var typelist = await _caseTypeRepository.QueryAsync(p => p.ParentId == Id);
+            if (typelist != null)
+            {
+                //处理数据
+                foreach (var item in typelist)
+                {
+                    list.Add(item);
+                    list.AddRange(await GetChildNode(item.Id));
+                }
+            }
+            return list;
+        }
+
+        #endregion
+
+        #endregion
+
+    }
+}

+ 90 - 0
src/Hotline.Application/Caselibrary/ICaseApplication.cs

@@ -0,0 +1,90 @@
+using Hotline.Share.Dtos.Caselibrary;
+
+namespace Hotline.Application.Caselibrary
+{
+    public interface ICaseApplication
+    {
+
+        #region 案例库类型管理
+
+        /// <summary>
+        ///案例库类型 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<string> AddTypeAsync(AddCaseTypeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///案例库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task UpdateTypeAsync(UpdateCaseTypeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 案例库类型 - 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        Task RemoveTypeAsync(string Id, CancellationToken cancellationToken);
+
+        #endregion
+
+        #region 案例库管理
+
+        /// <summary>
+        /// 案例库 - 列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        Task<(int, IList<CaseDataDto>)> QueryAllCaseListAsync(CaseListDto pagedDto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 案例库 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<string> AddCaseAsync(AddCaseListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///案例库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task UpdateCaseAsync(UpdateCaseListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///案例库类型 - 删除
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task RemoveCaseAsync(UpdateCaseListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///案例库类型 - 下架审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task AuditCaseAsync(UpdateCaseListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///案例库类型 - 详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="IsAddPv">默认不增加,false不增加,true增加浏览量</param>
+        /// <returns></returns>
+        Task<CaseInfoDto> GetCaseAsync(string Id, bool? IsAddPv, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 案例库类型 - 批量导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<Dictionary<string, Stream>> CaseInfoListExportAsync(CaseInfoExportDto dto, CancellationToken cancellationToken);
+
+        #endregion
+
+    }
+}

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

@@ -861,7 +861,9 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             NextStepOption nextStepOption;
 
             //汇总节点只能选择对应节点办理对象
-            if (workflow.FlowType is EFlowType.Handle && stepDefine.StepType is EStepType.Summary)
+            if (workflow.FlowType is EFlowType.Handle 
+                && stepDefine.StepType is EStepType.Summary
+                && stepDefine.BusinessType is EBusinessType.Seat or EBusinessType.Send)
             {
                 var handler = _workflowDomainService.GetSummaryTargetFlowStepHandler(workflow, stepDefine.SummaryTargetCode);
 

+ 4 - 1
src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs

@@ -231,7 +231,10 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
                     //这里需要判断是否是警情退回
                     orderFlowDto.IsNonPoliceReturn = notification.Dto.External == null ? false : notification.Dto.External.IsPoliceReturn;
                     await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFiled, orderFlowDto, cancellationToken: cancellationToken);
-                    await _publisher.PublishAsync(new SnapshotOrderFiledNotification(order.Id), PublishStrategy.ParallelWhenAll, cancellationToken);
+                    if (_systemSettingCacheManager.Snapshot)
+                    {
+                        await _publisher.PublishAsync(new SnapshotOrderFiledNotification(order.Id), PublishStrategy.ParallelWhenAll, cancellationToken);
+                    }
                     await _orderDomainService.OrderAutomaticPublishAsync(order, cancellationToken);
                     //try
                     //{

+ 45 - 21
src/Hotline.Application/Handlers/FlowEngine/WorkflowPreviousHandler.cs

@@ -1,20 +1,24 @@
 using DotNetCore.CAP;
+using Hotline.Configurations;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
 using Hotline.Push.Notifies;
+using Hotline.Repository.SqlSugar.Orders;
+using Hotline.Settings.TimeLimitDomain;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
+using Hotline.Share.Enums.Settings;
 using Hotline.Users;
 using MapsterMapper;
 using MediatR;
-using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using XF.Domain.Authentications;
 using XF.Domain.Repository;
 
@@ -36,8 +40,11 @@ namespace Hotline.Application.Handlers.FlowEngine
         private readonly ISessionContext _sessionContext;
         private readonly IRepository<OrderScreenDetail> _orderScreenDetailRepository;
         private readonly IRepository<OrderTerminate> _orderTerminateRepository;
+        private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        private readonly ICalcExpireTime _expireTime;
+        private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
 
-		public WorkflowPreviousHandler(
+        public WorkflowPreviousHandler(
             IOrderDomainService orderDomainService,
             IOrderRepository orderRepository,
             IOrderScreenRepository orderScreenRepository,
@@ -51,8 +58,11 @@ namespace Hotline.Application.Handlers.FlowEngine
             IMediator mediator,
             ISessionContext sessionContext,
             IRepository<OrderScreenDetail> orderScreenDetailRepository,
-            IRepository<OrderTerminate> orderTerminateRepository
-			)
+            IRepository<OrderTerminate> orderTerminateRepository,
+            IOptionsSnapshot<AppConfiguration> appOptions,
+            ICalcExpireTime expireTime,
+             IRepository<OrderVisitDetail> orderVisitDetailRepository
+            )
         {
             _orderDomainService = orderDomainService;
             _orderRepository = orderRepository;
@@ -68,6 +78,9 @@ namespace Hotline.Application.Handlers.FlowEngine
             _sessionContext = sessionContext;
             _orderScreenDetailRepository = orderScreenDetailRepository;
             _orderTerminateRepository = orderTerminateRepository;
+            _appOptions = appOptions;
+            _expireTime = expireTime;
+            _orderVisitDetailRepository = orderVisitDetailRepository;
         }
 
         /// <summary>Handles a notification</summary>
@@ -170,16 +183,27 @@ namespace Hotline.Application.Handlers.FlowEngine
                         }
                         break;
                     case WorkflowModuleConsts.OrderScreen:
-	                    var workflowSteps = await _workflowDomainService.GetWorkflowAsync(workflow.Id,withSteps: true,cancellationToken:cancellationToken);
-						var screen = await _orderScreenRepository.GetAsync(workflow.ExternalId, cancellationToken);
+                        var workflowSteps = await _workflowDomainService.GetWorkflowAsync(workflow.Id, withSteps: true, cancellationToken: cancellationToken);
+                        var screen = await _orderScreenRepository.GetAsync(workflow.ExternalId, cancellationToken);
                         if (screen != null)
                         {
                             screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
                             screen.SendBackTime = DateTime.Now;
-                            screen.SendBackApply = workflowSteps.Steps.Any(x=>x is { Status: EWorkflowStepStatus.WaitForHandle or EWorkflowStepStatus.WaitForAccept, StepType: EStepType.Start,IsOrigin : true } );
+                            screen.SendBackApply = workflowSteps.Steps.Any(x => x is { Status: EWorkflowStepStatus.WaitForHandle or EWorkflowStepStatus.WaitForAccept, StepType: EStepType.Start, IsOrigin: true });
                             screen.Status = EScreenStatus.SendBack;
                             screen.SendBackNum++;
-							await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
+                            await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
+                            //自贡任务 220 修改甄别截止申请日期的计算方式
+                            //甄别截止申请日期=回访时间+2个工作日  ,若退回到申请人节点,甄别截止申请日期=退回时间+2个工作日
+                            if (screen.SendBackApply == true && _appOptions.Value.IsZiGong)
+                            {
+                                var orderVisitDetail = await _orderVisitDetailRepository.GetAsync(p => p.Id == screen.VisitDetailId, cancellationToken);
+                                if (orderVisitDetail != null)
+                                {
+                                    orderVisitDetail.ScreenByEndTime = (await _expireTime.CalcEndTime(DateTime.Now, DateTime.Now, ETimeType.WorkDay, 2, 0, 0)).EndTime;
+                                    await _orderVisitDetailRepository.UpdateAsync(orderVisitDetail, cancellationToken);
+                                }
+                            }
                         }
                         OrderScreenDetail detail = new OrderScreenDetail
                         {
@@ -192,22 +216,22 @@ namespace Hotline.Application.Handlers.FlowEngine
                     case WorkflowModuleConsts.KnowledgeUpdate:
                     case WorkflowModuleConsts.KnowledgeDelete:
                     case WorkflowModuleConsts.KnowledgeOffshelf:
-					case WorkflowModuleConsts.TelRestApply:
+                    case WorkflowModuleConsts.TelRestApply:
                         break;
                     case WorkflowModuleConsts.OrderTerminate:
-	                    var orderTerminate = await _orderTerminateRepository.Queryable()
-		                    .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
-	                    if (orderTerminate != null)
-	                    {
-		                    orderTerminate.Status = ETerminateStatus.SendBack;
-							if (notification.TargetStep.StepType is EStepType.Start)
-		                    {
+                        var orderTerminate = await _orderTerminateRepository.Queryable()
+                            .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
+                        if (orderTerminate != null)
+                        {
+                            orderTerminate.Status = ETerminateStatus.SendBack;
+                            if (notification.TargetStep.StepType is EStepType.Start)
+                            {
                                 orderTerminate.Status = ETerminateStatus.SendBackStart;
-							}
-		                    await _orderTerminateRepository.UpdateAsync(orderTerminate, cancellationToken);
-	                    }
-	                    break;
-				}
+                            }
+                            await _orderTerminateRepository.UpdateAsync(orderTerminate, cancellationToken);
+                        }
+                        break;
+                }
 
             }
             catch (Exception e)

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

@@ -17,6 +17,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Hotline.Ai.Jths\Hotline.Ai.Jths.csproj" />
+    <ProjectReference Include="..\Hotline.Ai.XingTang\Hotline.Ai.XingTang.csproj" />
     <ProjectReference Include="..\Hotline.Application.Contracts\Hotline.Application.Contracts.csproj" />
     <ProjectReference Include="..\Hotline.NewRock\Hotline.NewRock.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />

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

@@ -351,6 +351,7 @@ namespace Hotline.Application.Knowledge
                 .Includes(x => x.User)
                 .Includes(x => x.SystemOrganize)
                 .Includes(x => x.HotspotType)
+                .Includes(x => x.KnowledgeType)
                 .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))

+ 4 - 0
src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs

@@ -77,6 +77,10 @@ public class OrderScreenNextWorkflowHandler : INotificationHandler<NextStepNotif
                                     screenDto.Content = notification.Dto.Opinion;
                                     screenDto.Files = new List<Share.Dtos.File.FileDto>();
                                 }
+                                if (_appOptions.Value.IsLuZhou)
+                                {
+                                    screenDto.Content = notification.Dto.Opinion;
+                                }
                                 //推省上
                                 _capPublisher.Publish(EventNames.HotlineOrderScreenApply, new PublishScreenDto()
                                 {

+ 35 - 10
src/Hotline.Application/Orders/IOrderApplication.cs

@@ -2,6 +2,7 @@
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
 using Hotline.Settings;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
@@ -58,7 +59,7 @@ namespace Hotline.Application.Orders
         ISugarQueryable<Order> GetAboutToExpireAsync(AboutToExpireListDto dto);
 
         //Task<PagedDto<WorkflowOrderDto>> GetAboutToExpireNodeAsync(AboutToExpireListDto dto, CancellationToken cancellationToken);
-        Task OrderParticiple(string inputStr, string orderId, DateTime time, CancellationToken cancellationToken);
+        Task OrderParticiple(string inputStr, string orderId, string no, string title, DateTime time, CancellationToken cancellationToken);
         Task OrderSensitiveParticiple(string inputStr, string orderId, CancellationToken cancellationToken);
         /// <summary>
         /// 接收外部平台工单
@@ -331,11 +332,11 @@ namespace Hotline.Application.Orders
         ISugarQueryable<OrderVisitDetail> MayScreenList(MayScreenListDto dto);
 
 
-		ISugarQueryable<Order> QueryWaitedForSeat(QueryOrderWaitedDto dto);
+        ISugarQueryable<Order> QueryWaitedForSeat(QueryOrderWaitedDto dto);
 
 
-		ISugarQueryable<Order> QueryWaited(QueryOrderWaitedDto dto);
-        
+        ISugarQueryable<Order> QueryWaited(QueryOrderWaitedDto dto);
+
 
         /// <summary>
         /// 受理类型前10
@@ -357,12 +358,12 @@ namespace Hotline.Application.Orders
         /// <returns></returns>
         ISugarQueryable<OrderVisit> QueryOrderVisitList(QueryOrderVisitDto dto);
 
-		/// <summary>
-		/// 热点类型小类统计明细
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		ISugarQueryable<Order> HotspotStatisticsDetail(HotspotStatisticsRep dto);
+        /// <summary>
+        /// 热点类型小类统计明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<Order> HotspotStatisticsDetail(HotspotStatisticsRep dto);
 
         /// <summary>
         /// 坐席总体满意度分析
@@ -385,6 +386,30 @@ namespace Hotline.Application.Orders
         /// <returns></returns>
         ISugarQueryable<OrderVisitDetail> QuerySeatSatisfactionOrderVisitList(SeatSatisfactionOrderVisitRequest dto);
 
+        /// <summary>
+        /// 工单热词分析
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<OrderTsDetailsDto> QueryOrderTsDetailsList(PagedKeywordRequest dto);
+
+        /// <summary>
+        /// 知识库引用
+        /// </summary>
+        /// <param name="orderId"></param>
+        /// <param name="title"></param>
+        /// <param name="no"></param>
+        /// <param name="knowledgeQuote"></param>
+        /// <returns></returns>
+        Task AddKnowledgeQuote(string orderId, string title, string no, List<Kv> knowledgeQuote, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 知识库引用
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<OrderTsDetailsDto> QueryKnowledgeQuoteList(PagedKeywordRequest dto);
+
         /// <summary>
         /// 将工单从网格员节点办理至工单标记节点
         /// </summary>

+ 207 - 32
src/Hotline.Application/Orders/OrderApplication.cs

@@ -1,15 +1,13 @@
-using DocumentFormat.OpenXml.Drawing;
-using DotNetCore.CAP;
+using DotNetCore.CAP;
 using Hotline.Application.Quality;
 using Hotline.Authentications;
 using Hotline.Caching.Interfaces;
 using Hotline.Configurations;
-using Hotline.EventBus;
 using Hotline.File;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.FlowEngine.Workflows;
+using Hotline.KnowledgeBase;
 using Hotline.Orders;
-using Hotline.Orders.Notifications;
 using Hotline.OrderTranspond;
 using Hotline.Push.Notifies;
 using Hotline.Repository.SqlSugar.Extensions;
@@ -19,6 +17,7 @@ using Hotline.SeedData;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
 using Hotline.Settings.TimeLimitDomain;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine;
@@ -39,6 +38,8 @@ using Hotline.Users;
 using Mapster;
 using MapsterMapper;
 using MediatR;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using PanGu;
 using SqlSugar;
@@ -50,7 +51,6 @@ using XF.Domain.Dependency;
 using XF.Domain.Entities;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
-using XF.Utility.EnumExtensions;
 using WordInfo = PanGu.WordInfo;
 
 namespace Hotline.Application.Orders;
@@ -59,7 +59,6 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 {
     private readonly IMediator _mediator;
     private readonly IRepository<TranspondCityRawData> _transpondCityRawDataRepository;
-    private readonly Publisher _publisher;
     private readonly ISessionContextProvider _sessionContextProvider;
     private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
@@ -99,8 +98,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IRepository<SchedulingUser> _schedulingUserRepository;
     private readonly IRepository<StatisticsHotspotSatisfied> _statisticsHotspotSatisfiedRepository;
     private readonly IRepository<StatisticsDepartSatisfied> _statisticsDepartSatisfiedRepository;
+    private readonly IRepository<OrderTsDetails> _orderTsDetailsRepository;
+    private readonly IRepository<KnowledgeQuote> _knowledgeQuoteRepository;
+	private readonly IRepository<OrderSpecial> _orderSpecialRepository;
 
-    public OrderApplication(
+	public OrderApplication(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowDomainService workflowDomainService,
@@ -131,7 +133,6 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<OrderVisitDetail> orderVisitedDetailRepository,
         IOptionsSnapshot<AppConfiguration> appOptions,
         ISystemDicDataCacheManager sysDicDataCacheManager,
-        Publisher publisher,
         ISessionContextProvider sessionContextProvider,
         IRepository<TranspondCityRawData> transpondCityRawDataRepository,
         IRepository<OrderObserve> orderObserveRepository,
@@ -141,7 +142,10 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<SchedulingUser> schedulingUserRepository,
         IRepository<StatisticsHotspotSatisfied> statisticsHotspotSatisfiedRepository,
         IRepository<StatisticsDepartSatisfied> statisticsDepartSatisfiedRepository,
-        IOrderDelayRepository orderDelayRepository)
+        IOrderDelayRepository orderDelayRepository,
+        IRepository<OrderTsDetails> orderTsDetailsRepository,
+        IRepository<KnowledgeQuote> knowledgeQuoteRepository,
+		IRepository<OrderSpecial> orderSpecialRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -172,7 +176,6 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _orderVisitedDetailRepository = orderVisitedDetailRepository;
         _appOptions = appOptions;
         _sysDicDataCacheManager = sysDicDataCacheManager;
-        _publisher = publisher;
         _sessionContextProvider = sessionContextProvider;
         _transpondCityRawDataRepository = transpondCityRawDataRepository;
         _orderObserveRepository = orderObserveRepository;
@@ -184,8 +187,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _schedulingUserRepository = schedulingUserRepository;
         _statisticsHotspotSatisfiedRepository = statisticsHotspotSatisfiedRepository;
         _statisticsDepartSatisfiedRepository = statisticsDepartSatisfiedRepository;
+        _orderTsDetailsRepository = orderTsDetailsRepository;
+        _knowledgeQuoteRepository = knowledgeQuoteRepository;
+        _orderSpecialRepository = orderSpecialRepository;
 
-    }
+	}
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -203,9 +209,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
 
         var startTime = DateTime.Now;
-        if (order.StartTime.HasValue)
+        if (order.CenterToOrgTime.HasValue)
         {
-            startTime = order.StartTime.Value;
+            startTime = order.CenterToOrgTime!.Value;
         }
         var expiredTimeConfig =
             // _timeLimitDomainService.CalcEndTime(DateTime.Now, new TimeConfig(timeCount, timeType), order.AcceptTypeCode);
@@ -584,8 +590,13 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     /// 工单关键字分词
     /// </summary>
     /// <param name="inputStr"></param>
+    /// <param name="orderId"></param>
+    /// <param name="no"></param>
+    /// <param name="title"></param>
+    /// <param name="time"></param>
+    /// <param name="cancellationToken"></param>
     /// <returns></returns>
-    public async Task OrderParticiple(string inputStr, string orderId, DateTime time, CancellationToken cancellationToken)
+    public async Task OrderParticiple(string inputStr, string orderId, string no, string title, DateTime time, CancellationToken cancellationToken)
     {
         var seg = new Segment();
         ICollection<WordInfo> splitWords = seg.DoSegment(inputStr);
@@ -602,6 +613,28 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             var vector = await _repositoryts.SearchAsync(orderId, cancellationToken);
             if (vector != null && vector.Any()) await _repositoryts.UpdateVectorAsync(orderId, words, cancellationToken);
             else await _repositoryts.AddVectorAsync(orderId, time, words, cancellationToken);
+
+            //是否开启分词
+            var isOpenWordSegmentation = _systemSettingCacheManager.GetSetting(SettingConstants.IsOpenWordSegmentation)?.SettingValue[0];
+            if (!string.IsNullOrEmpty(isOpenWordSegmentation) && isOpenWordSegmentation == "true")
+            {
+                //先删除历史数据,在添加新数据
+                await _orderTsDetailsRepository.RemoveAsync(p => p.OrderId == orderId, false, cancellationToken);
+                List<OrderTsDetails> orderTsDetails = new();
+                List<string> listWord = words.Distinct().ToList();
+                foreach (var item in listWord)
+                {
+                    orderTsDetails.Add(new OrderTsDetails
+                    {
+                        Terms = item,
+                        OrderId = orderId,
+                        Title = title,
+                        No = no
+                    });
+                }
+                if (orderTsDetails != null && orderTsDetails.Count > 0)
+                    await _orderTsDetailsRepository.AddRangeAsync(orderTsDetails, cancellationToken);
+            }
         }
         // 结巴分词
         //var segmenter = new JiebaSegmenter();
@@ -672,11 +705,12 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             case ESource.WeChat:
             case ESource.ZGGC:
             case ESource.ZGTFTB:
+            case ESource.YBHumanSocietyAPP:
                 return ReceiveOrderFromOtherPlatformAsync(dto, dto.Files, cancellationToken);
             case ESource.Hotline:
             case ESource.HotlineImport:
             default:
-                throw new ArgumentOutOfRangeException();
+                return ReceiveOrderFromOtherPlatformAsync(dto, dto.Files, cancellationToken);
         }
     }
 
@@ -755,6 +789,12 @@ public class OrderApplication : IOrderApplication, IScopeDependency
               .WhereIF(dto.IsOverTime == false,
                   d => (d.ExpiredTime > DateTime.Now && d.Status < EOrderStatus.Filed) ||
                        (d.ExpiredTime > d.ActualHandleTime && d.Status >= EOrderStatus.Filed))//否 超期
+              .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
               .OrderByDescending(d => d.FiledTime);
     }
 
@@ -786,6 +826,12 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .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(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "1", d => d.Order.Source == ESource.ProvinceStraight &&
+                 d.Order.SourceChannelCode == "SZMHD" && d.Order.IsProvince == false) //政民互动直派
+             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "2", d => d.Order.Source == ESource.ProvinceStraight &&
+                 d.Order.SourceChannelCode == "SZMHD" && d.Order.IsProvince == true) //政民互动
+             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "3", d => d.Order.Source == ESource.ProvinceStraight &&
+                 d.Order.SourceChannelCode == "S12345" && d.Order.IsProvince == true) //省12345
             .OrderByDescending(d => d.CreationTime);
     }
 
@@ -970,8 +1016,13 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         {
             throw UserFriendlyException.SameMessage("该工单存在正在审核中的退回,不能办理");
         }
+		if (await _orderSpecialRepository.AnyAsync(x => x.OrderId == order.Id && x.State == 0,
+		   cancellationToken))
+		{
+			throw UserFriendlyException.SameMessage("该工单存在正在审核中的特提,不能办理");
+		}
 
-        ExpiredTimeWithConfig? expiredTimeConfig = null;
+		ExpiredTimeWithConfig? expiredTimeConfig = null;
         var settingBase = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
         CityBaseConfiguration cityBase = System.Text.Json.JsonSerializer.Deserialize<CityBaseConfiguration>(settingBase);
         if (dto.Workflow.NextHandlers.Any(d => d.Key == cityBase.CityProvince.OrgId || d.Key == cityBase.CityProvinceAssign.OrgId))
@@ -1405,8 +1456,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
              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(!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(!string.IsNullOrEmpty(dto.ContentRetrieval), d=> d.Content.Contains(dto.ContentRetrieval!))
+         .WhereIF(!string.IsNullOrEmpty(dto.FileOption),d=>d.FileOpinion.Contains(dto.FileOption!))
          .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.FileOrgIsCenter == true)//d => d.ProcessType == EProcessType.Zhiban
@@ -1822,10 +1874,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2", 1, 0))), //不满意
                     NoEvaluateCount = SqlFunc.IIF(dto.TypeId == 1,
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "7", 1, 0)),
-                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "7", 1, 0))), //未做评价
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "6", 1, 0))), //未做评价
                     NoPutThroughCount = SqlFunc.IIF(dto.TypeId == 1,
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "6", 1, 0)),
-                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "6", 1, 0))), //未接通
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "8", 1, 0))), //未接通
+                    NormalCount = SqlFunc.IIF(dto.TypeId == 1,
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "3", 1, 0)),
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "3", 1, 0))),//一般
+                    VeryNoSatisfiedCount = SqlFunc.IIF(dto.TypeId == 1,
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1", 1, 0)),
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1", 1, 0))),//非常不满意
                 })
                 .MergeTable()
                 .LeftJoin<SystemOrganize>((it, o) => it.OrgCode == o.Id)
@@ -1842,6 +1900,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                     NoSatisfiedCount = it.NoSatisfiedCount, //不满意
                     NoEvaluateCount = it.NoEvaluateCount, //未做评价
                     NoPutThroughCount = it.NoPutThroughCount, //未接通
+                    NormalCount = it.NormalCount,
+                    VeryNoSatisfiedCount = it.VeryNoSatisfiedCount
                 })
                 .ToListAsync();
         }
@@ -1873,10 +1933,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2", 1, 0))), //不满意
                     NoEvaluateCount = SqlFunc.IIF(dto.TypeId == 1,
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "7", 1, 0)),
-                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "7", 1, 0))), //未做评价
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "6", 1, 0))), //未做评价
                     NoPutThroughCount = SqlFunc.IIF(dto.TypeId == 1,
                         SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "6", 1, 0)),
-                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "6", 1, 0))), //未接通
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "8", 1, 0))), //未接通
+                    NormalCount = SqlFunc.IIF(dto.TypeId == 1,
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "3", 1, 0)),
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "3", 1, 0))),//一般
+                    VeryNoSatisfiedCount = SqlFunc.IIF(dto.TypeId == 1,
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1", 1, 0)),
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1", 1, 0))),//非常不满意
                 })
                 .MergeTable()
                 .LeftJoin<SystemOrganize>((it, o) => it.OrgCode == o.Id && (o.Level == orgLevel || o.Level == (orgLevel + 1)))
@@ -1893,6 +1959,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                     NoSatisfiedCount = it.NoSatisfiedCount, //不满意
                     NoEvaluateCount = it.NoEvaluateCount, //未做评价
                     NoPutThroughCount = it.NoPutThroughCount, //未接通
+                    NormalCount = it.NormalCount,//一般
+                    VeryNoSatisfiedCount = it.VeryNoSatisfiedCount//非常不满意
                 })
                 .ToListAsync();
         }
@@ -2055,12 +2123,18 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                        0))), //不满意
                NoEvaluateCount = SqlFunc.IIF(dto.TypeId == 1,
                    SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgProcessingResults, "Key") == "7", 1, 0)),
-                   SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgHandledAttitude, "Key") == "7", 1,
+                   SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgHandledAttitude, "Key") == "6", 1,
                        0))), //未做评价
                NoPutThroughCount = SqlFunc.IIF(dto.TypeId == 1,
                    SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgProcessingResults, "Key") == "6", 1, 0)),
-                   SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgHandledAttitude, "Key") == "6", 1,
-                       0))) //未接通
+                   SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgHandledAttitude, "Key") == "8", 1,
+                       0))), //未接通
+               NormalCount = SqlFunc.IIF(dto.TypeId == 1,
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgProcessingResults, "Key") == "3", 1, 0)),
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgHandledAttitude, "Key") == "3", 1, 0))),//一般
+               VeryNoSatisfiedCount = SqlFunc.IIF(dto.TypeId == 1,
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgProcessingResults, "Key") == "1", 1, 0)),
+                        SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(it.OrgHandledAttitude, "Key") == "1", 1, 0))),//非常不满意
            })
            .MergeTable()
            .LeftJoin<SystemOrganize>((x, it) =>
@@ -2078,6 +2152,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                NoSatisfiedCount = x.NoSatisfiedCount, //不满意
                NoEvaluateCount = x.NoEvaluateCount, //未做评价
                NoPutThroughCount = x.NoPutThroughCount, //未接通
+               NormalCount = x.NormalCount,//一般
+               VeryNoSatisfiedCount = x.VeryNoSatisfiedCount//非常不满意
            }).ToListAsync();
 
 
@@ -3183,14 +3259,21 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         var query = _orderVisitRepository.Queryable()
             .Includes(d => d.Order)
             .Includes(d => d.Employee)
-            .Includes(d => d.OrderVisitDetails)
-            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoVisit,
-                d => d.VisitState == EVisitState.WaitForVisit ||
-                     d.VisitState == EVisitState.NoSatisfiedWaitForVisit)
-            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.Visited, d => d.VisitState == EVisitState.Visited)
+            .Includes(d => d.OrderVisitDetails);
+
+        if (_appOptions.Value.IsZiGong || _appOptions.Value.IsLuZhou)
+        {
+            //任务217  待回访工单显示优化(自贡+泸州通用)回访状态选择“待回访”,在该状态下不查“设为未接通”工单
+            query = query.WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoVisit,
+                d => (d.VisitState == EVisitState.WaitForVisit || d.VisitState == EVisitState.NoSatisfiedWaitForVisit) && (d.IsPutThrough == true || d.IsPutThrough == null));
+        }
+        else
+            query = query.WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoVisit, d => d.VisitState == EVisitState.WaitForVisit || d.VisitState == EVisitState.NoSatisfiedWaitForVisit);
+
+        query = query.WhereIF(dto.VisitStateQuery == EVisitStateQuery.Visited, d => d.VisitState == EVisitState.Visited)
             .WhereIF(dto.VisitStateQuery == EVisitStateQuery.SMSUnsatisfied, d => d.VisitState == EVisitState.SMSUnsatisfied)
             .WhereIF(dto.VisitStateQuery == EVisitStateQuery.SMSVisiting, d => d.VisitState == EVisitState.SMSVisiting)
-            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoPutThrough, d => d.IsPutThrough == false && d.VisitState != EVisitState.Visited)
+            .WhereIF(dto.VisitStateQuery == EVisitStateQuery.NoPutThrough, d => d.IsPutThrough == false && d.VisitState != EVisitState.Visited && d.VisitState != EVisitState.None)
             .WhereIF(dto.VisitStateQuery == EVisitStateQuery.ChipVoiceVisiting, d => d.VisitState == EVisitState.AiVisiting)//任务 162:回访状态快捷查询条件
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Order.Title.Contains(dto.Keyword!))
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
@@ -3416,7 +3499,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         if (order == null)
         {
             order = _mapper.Map<Order>(dto);
-
+            if (order.IsSecret == true)
+            {
+                order.FocusOnEventsName = "保密";
+                order.FocusOnEvents = "99";
+            }
             order.InitId();
             if (files != null && files.Any())
                 order.FileJson = await _fileRepository.AddFileAsync(files, order.Id, "", cancellationToken);
@@ -3486,6 +3573,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             else
                 order.FileJson = new List<Share.Dtos.File.FileJson>();
             order.ReTransactNum++;
+            if (order.ProvinceReTransactNum.HasValue)
+                order.ProvinceReTransactNum = order.ProvinceReTransactNum + 1;
+            else
+                order.ProvinceReTransactNum = 1;
+
+            if (order.IsSecret == true)
+            {
+                order.FocusOnEventsName = "保密";
+                order.FocusOnEvents = "99";
+            }
             //await _orderRepository.UpdateAsync(order, cancellationToken);
 
             if (orderExtension is not null)
@@ -4118,4 +4215,82 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return list;
     }
     #endregion
+
+    /// <summary>
+    /// 工单热词分析
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<OrderTsDetailsDto> QueryOrderTsDetailsList(PagedKeywordRequest dto)
+    {
+        var query = _orderTsDetailsRepository.Queryable()
+            .Where(p => p.CreationTime >= dto.StartTime && p.CreationTime <= dto.EndTime)
+           .GroupBy(p => p.Terms)
+           .Select(p => new OrderTsDetailsDto
+           {
+               Name = p.Terms,
+               CountNum = SqlFunc.AggregateCount(p.Terms),
+           })
+           .MergeTable()
+           .WhereIF(!string.IsNullOrEmpty(dto.Keyword), p => p.Name.Contains(dto.Keyword))
+           .OrderByIF(dto is { SortField: "countNum", SortRule: 0 }, p => p.CountNum, OrderByType.Asc)
+           .OrderByIF(dto is { SortField: "countNum", SortRule: 1 }, p => p.CountNum, OrderByType.Desc);
+
+        return query;
+    }
+
+    /// <summary>
+    /// 知识库引用
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="title"></param>
+    /// <param name="no"></param>
+    /// <param name="knowledgeQuote"></param>
+    /// <returns></returns>
+    public async Task AddKnowledgeQuote(string orderId, string title, string no, List<Kv> knowledgeQuote, CancellationToken cancellationToken)
+    {
+        await _knowledgeQuoteRepository.RemoveAsync(p => p.OrderId == orderId, false, cancellationToken);
+        if (knowledgeQuote != null && knowledgeQuote.Count > 0)
+        {
+            List<KnowledgeQuote> list = [];
+            foreach (var item in knowledgeQuote)
+            {
+                list.Add(new KnowledgeQuote
+                {
+                    KnowledgeId = item.Key,
+                    KnowledgeTitle = item.Value,
+                    OrderId = orderId,
+                    Title = title,
+                    No = no
+                });
+            }
+            if (list != null && list.Count > 0)
+                await _knowledgeQuoteRepository.AddRangeAsync(list, cancellationToken);
+        }
+    }
+
+    /// <summary>
+    /// 知识库引用
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<OrderTsDetailsDto> QueryKnowledgeQuoteList(PagedKeywordRequest dto)
+    {
+        var query = _knowledgeQuoteRepository.Queryable()
+            .LeftJoin<Hotline.KnowledgeBase.Knowledge>((kq, kn) => kq.KnowledgeId == kn.Id)
+            .Where((kq, kn) => kq.CreationTime >= dto.StartTime && kq.CreationTime <= dto.EndTime && kn.Id != null)
+             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), (kq, kn) => kn.Title.Contains(dto.Keyword))
+           .GroupBy((kq, kn) => new { kn.Id, kn.Title })
+           .Select((kq, kn) => new OrderTsDetailsDto
+           {
+               Id = kn.Id,
+               Name = kn.Title,
+               CountNum = SqlFunc.AggregateCount(kn.Id),
+           }).MergeTable()
+           .OrderByIF(dto is { SortField: "countNum", SortRule: 0 }, p => p.CountNum, OrderByType.Asc)
+           .OrderByIF(dto is { SortField: "countNum", SortRule: 1 }, p => p.CountNum, OrderByType.Desc);
+
+        return query;
+    }
+
 }

+ 0 - 106
src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs

@@ -1,106 +0,0 @@
-using DotNetCore.CAP;
-using Hotline.Configurations;
-using Hotline.FlowEngine.Notifications;
-using Hotline.FlowEngine.WorkflowModules;
-using Hotline.Orders;
-using Hotline.Share.Dtos.Order;
-using Hotline.Share.Enums.Order;
-using Hotline.Share.Mq;
-using MapsterMapper;
-using MediatR;
-using Microsoft.Extensions.Options;
-using XF.Domain.Authentications;
-using XF.Domain.Repository;
-
-namespace Hotline.Application.Orders.OrderScreenHandler;
-public class OrderScreenNextWorkflowHandler : INotificationHandler<NextStepNotify>
-{
-	private readonly ICapPublisher _capPublisher;
-	private readonly IMapper _mapper;
-	private readonly IOrderScreenRepository _orderScreenRepository;
-	private readonly ISessionContext _sessionContext;
-	private readonly IRepository<OrderScreenDetail> _orderScreenDetailRepository;
-	private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
-
-	public OrderScreenNextWorkflowHandler(
-		ICapPublisher capPublisher,
-		IMapper mapper,
-		IOrderScreenRepository orderScreenRepository,
-		ISessionContext sessionContext,
-		IOptionsSnapshot<AppConfiguration> appOptions,
-		IRepository<OrderScreenDetail> orderScreenDetailRepository)
-	{
-		_capPublisher = capPublisher;
-		_mapper = mapper;
-		_orderScreenRepository = orderScreenRepository;
-		_sessionContext = sessionContext;
-		_orderScreenDetailRepository = orderScreenDetailRepository;
-		_appOptions = appOptions;
-	}
-
-	/// <summary>Handles a notification</summary>
-	/// <param name="notification">The notification</param>
-	/// <param name="cancellationToken">Cancellation token</param>
-	public async Task Handle(NextStepNotify notification, CancellationToken cancellationToken)
-	{
-		if (notification.Workflow.ModuleCode == WorkflowModuleConsts.OrderScreen) 
-		{
-			var workflow = notification.Workflow;
-			var nextTag = string.IsNullOrEmpty(notification.NextStepDefine.Tag)
-				? null
-				: System.Text.Json.JsonSerializer.Deserialize<DefinitionTag>(notification.NextStepDefine.Tag);
-			var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order)
-				.Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
-			if (screen != null)
-			{
-				screen.Status = EScreenStatus.Approval;
-				screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
-				//如果下个节点是省审批,则修改为省甄别
-				if (nextTag is not null && nextTag.Type == TagDefaults.TagType.Org && nextTag.Value == TagDefaults.TagValue.Province)
-					screen.IsProScreen = true;
-				await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
-			}
-
-			if (nextTag is not null && nextTag.Type == TagDefaults.TagType.Org)
-			{
-				switch (nextTag.Value)
-				{
-					case TagDefaults.TagValue.Province:
-						if (screen != null)
-						{
-							var screenDto = _mapper.Map<OrderScreenListDto>(screen);
-							if (screen.Order != null && screen.Order.Source == ESource.ProvinceStraight)
-							{
-								var screenOrderDto = _mapper.Map<OrderDto>(screen.Order);
-								//省件甄别--以省审批前一个节点整理的甄别意见为准推送省上 宜宾
-								if (_appOptions.Value.IsYiBin)
-								{
-                                    screenDto.Content = notification.Dto.Opinion;
-                                    screenDto.Files = new List<Share.Dtos.File.FileDto>();
-								}
-								if (_appOptions.Value.IsLuZhou)
-								{
-									screenDto.Content = notification.Dto.Opinion;
-								}
-								//推省上
-								_capPublisher.Publish(EventNames.HotlineOrderScreenApply, new PublishScreenDto()
-								{
-									Order = screenOrderDto,
-									Screen = screenDto,
-									ClientGuid = ""
-								});
-							}
-						}
-
-						break;
-				}
-			}
-			OrderScreenDetail detail = new OrderScreenDetail
-			{
-				ScreenId = screen.Id
-			};
-			detail.Audit(_sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, 1);
-			await _orderScreenDetailRepository.AddAsync(detail, cancellationToken);
-		}
-	}
-}

+ 13 - 10
src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs

@@ -26,6 +26,7 @@ using Novacode;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Settings.TimeLimitDomain;
 using Microsoft.Extensions.Options;
+using DocumentFormat.OpenXml.Office2010.Excel;
 
 namespace Hotline.Application.Orders
 {
@@ -248,7 +249,7 @@ namespace Hotline.Application.Orders
                 .Where(x => x.OrderVisit.Order.IsProvince == false)
                 .LeftJoin<OrderScreen>((x, s) => x.Id == s.VisitDetailId && s.Status < EScreenStatus.End && s.IsDeleted == false)
                 //.Where((x, s) => s.Id == null && (x.SecondaryHandling.State == ESecondaryHandlingState.NotApply || x.SecondaryHandling.Id == null))
-                .Where(x=> SqlFunc.Subqueryable<OrderSecondaryHandling>().Where(osh => osh.VisitDetailId == x.Id &&  osh.State == ESecondaryHandlingState.NotApply).NotAny())
+                .Where(x=> SqlFunc.Subqueryable<OrderSecondaryHandling>().Where(osh => osh.VisitDetailId == x.Id &&  osh.State != ESecondaryHandlingState.NotApply).NotAny())
                 //.Where(x => x.OrderVisit.VisitTime < dto.CreationTimeEnd && x.OrderVisit.VisitTime > dto.CreationTimeStart)
                 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order!.No!.Contains(dto.No!))
                 .WhereIF(dto.IsProvince.HasValue, x => x.OrderVisit.Order!.IsProvince == dto.IsProvince)
@@ -295,14 +296,14 @@ namespace Hotline.Application.Orders
                     ));
             }
 
-            return query.OrderByDescending((x, s) => x.CreationTime);
-        }
+			return query.OrderByDescending((x, s) => x.CreationTime);
+		}
 
-        /// <summary>
-        /// 二次办理列表查询
-        /// </summary>
-        /// <returns></returns>
-        public ISugarQueryable<OrderSecondaryHandling> Query(SecondaryHandlingListDto dto, CancellationToken cancellationToken)
+		/// <summary>
+		/// 二次办理列表查询
+		/// </summary>
+		/// <returns></returns>
+		public ISugarQueryable<OrderSecondaryHandling> Query(SecondaryHandlingListDto dto, CancellationToken cancellationToken)
         {
             if (dto.CreationTimeEnd.HasValue)
                 dto.CreationTimeEnd = dto.CreationTimeEnd.Value.AddDays(1).AddSeconds(-1);
@@ -408,8 +409,10 @@ namespace Hotline.Application.Orders
                     RegardedAsSatisfiedCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "-1", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "-1", 1, 0))),//视为满意
                     DefaultSatisfiedCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "0", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "0", 1, 0))),//默认满意
                     NoSatisfiedCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "2", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "2", 1, 0))),//不满意
-                    NoEvaluateCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "7", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "7", 1, 0))),//未做评价
-                    NoPutThroughCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "6", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "6", 1, 0))),//未接通
+                    NoEvaluateCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "7", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "6", 1, 0))),//未做评价
+                    NoPutThroughCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "6", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "8", 1, 0))),//未接通
+                    NormalCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "3", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "3", 1, 0))),//未接通
+                    VeryNoSatisfiedCount = SqlFunc.IIF(dto.VisitTypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgProcessingResults, "Key") == "1", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.VisitDetail.OrgHandledAttitude, "Key") == "1", 1, 0))),//未接通
                 });
         }
 

+ 34 - 30
src/Hotline.Application/Orders/OrderSendBackAuditApplication.cs

@@ -1,5 +1,6 @@
 using Hotline.Orders;
 using Hotline.SeedData;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Bi;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
@@ -10,35 +11,38 @@ using XF.Domain.Repository;
 
 namespace Hotline.Application.Orders
 {
-	public class OrderSendBackAuditApplication : IOrderSendBackAuditApplication, IScopeDependency
-	{
-		private readonly IRepository<OrderSendBackAudit> _orderSendBackAuditRepository;
-		private readonly ISessionContext _sessionContext;
-		public OrderSendBackAuditApplication(IRepository<OrderSendBackAudit> orderSendBackAuditRepository, ISessionContext sessionContext) 
-		{ 
-			_orderSendBackAuditRepository = orderSendBackAuditRepository; 
-			_sessionContext = sessionContext;
- 		}
+    public class OrderSendBackAuditApplication : IOrderSendBackAuditApplication, IScopeDependency
+    {
+        private readonly IRepository<OrderSendBackAudit> _orderSendBackAuditRepository;
+        private readonly ISessionContext _sessionContext;
+        public OrderSendBackAuditApplication(IRepository<OrderSendBackAudit> orderSendBackAuditRepository, ISessionContext sessionContext)
+        {
+            _orderSendBackAuditRepository = orderSendBackAuditRepository;
+            _sessionContext = sessionContext;
+        }
 
-		public ISugarQueryable<OrderSendBackAudit> AuditList(SendBackListDto dto) 
-		{
-			return _orderSendBackAuditRepository.Queryable()
-				.Includes(x => x.Order)
-				.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-					d => d.Order.Title.Contains(dto.Keyword!) || d.Order.No.Contains(dto.Keyword!))
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Order.Title.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Order.No.Contains(dto.No!))
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptTypeCode), d => d.Order.AcceptTypeCode == dto.AcceptTypeCode)
-				.WhereIF(!string.IsNullOrEmpty(dto.OrgName), d => d.ApplyOrgName == dto.OrgName)
-				.WhereIF(dto.DataScope is 1 ,d=>d.CreatorId  == _sessionContext.RequiredUserId)
-				.WhereIF(dto.StartTime.HasValue, d => d.CreationTime >= dto.StartTime)
-				.WhereIF(dto.EndTime.HasValue, d => d.CreationTime <= dto.EndTime)
-				.WhereIF(dto.AuditState == 1, d => d.State == ESendBackAuditState.Apply)
-				.WhereIF(dto is { AuditState: 2, State: null }, d => d.State > ESendBackAuditState.Apply)
-				.WhereIF(dto.AuditState is 2 or 3 && dto.State.HasValue && dto.State != ESendBackAuditState.All, d => d.State == dto.State)
-				.WhereIF(dto.AuditState == 3 && _sessionContext.RequiredOrgId != OrgSeedData.CenterId, x => x.ApplyOrgId.StartsWith(_sessionContext.OrgId))
-				.WhereIF(_sessionContext.Roles.Contains("role_sysadmin") == false && dto.AuditState != 3, x => x.SendBackOrgId == _sessionContext.OrgId) // 123 系统管理员;
-				.OrderByDescending(x => x.CreationTime);
-		}
-	}
+        public ISugarQueryable<OrderSendBackAudit> AuditList(SendBackListDto dto)
+        {
+            return _orderSendBackAuditRepository.Queryable()
+                .Includes(x => x.Order)
+                .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
+                    d => d.Order.Title.Contains(dto.Keyword!) || d.Order.No.Contains(dto.Keyword!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Order.Title.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Order.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptTypeCode), d => d.Order.AcceptTypeCode == dto.AcceptTypeCode)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgName), d => d.ApplyOrgName == dto.OrgName)
+                .WhereIF(dto.DataScope is 1, d => d.CreatorId == _sessionContext.RequiredUserId)
+                .WhereIF(dto.StartTime.HasValue, d => d.CreationTime >= dto.StartTime)
+                .WhereIF(dto.EndTime.HasValue, d => d.CreationTime <= dto.EndTime)
+                .WhereIF(dto.AuditState == 1, d => d.State == ESendBackAuditState.Apply)
+                .WhereIF(dto is { AuditState: 2, State: null }, d => d.State > ESendBackAuditState.Apply)
+                .WhereIF(dto.AuditState is 2 or 3 && dto.State.HasValue && dto.State != ESendBackAuditState.All, d => d.State == dto.State)
+                .WhereIF(dto.AuditState == 3 && _sessionContext.RequiredOrgId != OrgSeedData.CenterId, x => x.ApplyOrgId.StartsWith(_sessionContext.OrgId))
+                .WhereIF(_sessionContext.Roles.Contains("role_sysadmin") == false && dto.AuditState != 3, x => x.SendBackOrgId == _sessionContext.OrgId) // 123 系统管理员;
+                .WhereIF(dto.ExpiredTimeStart.HasValue, x => x.Order.ExpiredTime >= dto.ExpiredTimeStart)
+                .WhereIF(dto.ExpiredTimeEnd.HasValue, x => x.Order.ExpiredTime <= dto.ExpiredTimeEnd)
+
+            .OrderByDescending(x => x.CreationTime);
+        }
+    }
 }

+ 90 - 0
src/Hotline.Application/Planlibrary/IPlanApplication.cs

@@ -0,0 +1,90 @@
+using Hotline.Share.Dtos.Planlibrary;
+
+namespace Hotline.Application.Planlibrary
+{
+    public interface IPlanApplication
+    {
+
+        #region 预案库类型管理
+
+        /// <summary>
+        ///预案库类型 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<string> AddTypeAsync(AddPlanTypeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///预案库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task UpdateTypeAsync(UpdatePlanTypeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 预案库类型 - 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        Task RemoveTypeAsync(string Id, CancellationToken cancellationToken);
+
+        #endregion
+
+        #region 预案库管理
+
+        /// <summary>
+        /// 预案库 - 列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        Task<(int, IList<PlanDataDto>)> QueryAllPlanListAsync(PlanListDto pagedDto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 预案库 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<string> AddPlanAsync(AddPlanListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///预案库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task UpdatePlanAsync(UpdatePlanListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///预案库类型 - 删除
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task RemovePlanAsync(UpdatePlanListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///预案库类型 - 下架审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task AuditPlanAsync(UpdatePlanListDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        ///预案库类型 - 详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="IsAddPv">默认不增加,false不增加,true增加浏览量</param>
+        /// <returns></returns>
+        Task<PlanInfoDto> GetPlanAsync(string Id, bool? IsAddPv, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 预案库类型 - 批量导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<Dictionary<string, Stream>> PlanInfoListExportAsync(PlanInfoExportDto dto, CancellationToken cancellationToken);
+
+        #endregion
+
+    }
+}

+ 674 - 0
src/Hotline.Application/Planlibrary/PlanApplication.cs

@@ -0,0 +1,674 @@
+using Hotline.Planlibrary;
+using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos.Knowledge;
+using Hotline.Share.Dtos.Planlibrary;
+using Hotline.Share.Enums.Planlibrary;
+using MapsterMapper;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.SeedData;
+using Hotline.File;
+using Hotline.Application.Bulletin;
+using Hotline.Share.Tools;
+using Hotline.Application.Tools;
+using SqlSugar;
+using System.Numerics;
+using Microsoft.AspNetCore.Http;
+using Hotline.Share.Enums.Caselibrary;
+
+namespace Hotline.Application.Planlibrary
+{
+    /// <summary>
+    /// 预案库处理
+    /// </summary>
+    public class PlanApplication : IPlanApplication, IScopeDependency
+    {
+
+        #region 注册
+
+        private readonly IRepository<PlanList> _planListRepository;                      //预案库列表
+        private readonly IRepository<PlanType> _planTypeRepository;                      //预案库分类管理
+        private readonly IRepository<PlanTypeOrg> _planTypeOrgRepository;                //预案库分类关联机构
+        private readonly IRepository<PlanCollect> _planCollectRepository;                //预案库收藏评分
+        private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
+        private readonly IRepository<Hotspot> _hotspotTypeRepository;
+        private readonly IFileRepository _fileRepository;
+        private readonly IBulletinApplication _bulletinApplication;
+
+        public PlanApplication(
+            IRepository<PlanList> planListRepository,
+            IRepository<PlanType> planTypeRepository,
+            IRepository<PlanTypeOrg> planTypeOrgRepository,
+            ISessionContext sessionContext,
+            IMapper mapper,
+            IRepository<Hotspot> hotspotTypeRepository,
+            IFileRepository fileRepository,
+            IBulletinApplication bulletinApplication,
+            IRepository<PlanCollect> planCollectRepository)
+        {
+            _planListRepository = planListRepository;
+            _planTypeRepository = planTypeRepository;
+            _planTypeOrgRepository = planTypeOrgRepository;
+            _sessionContext = sessionContext;
+            _mapper = mapper;
+            _hotspotTypeRepository = hotspotTypeRepository;
+            _fileRepository = fileRepository;
+            _bulletinApplication = bulletinApplication;
+            _planCollectRepository = planCollectRepository;
+        }
+
+        #endregion
+
+        #region 预案库类型管理
+
+        #region 预案库类型 - 新增
+
+        /// <summary>
+        /// 预案库类型 - 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<string> AddTypeAsync(AddPlanTypeDto dto, CancellationToken cancellationToken)
+        {
+            var sandard = await _planTypeRepository.GetAsync(p => p.ParentId == dto.ParentId && p.Name == dto.Name && p.IsDeleted == false, cancellationToken);
+            if (sandard is not null)
+                throw UserFriendlyException.SameMessage("当前层级已存在相同名称的分类!");
+
+            var type = _mapper.Map<PlanType>(dto);
+            type.InitId();
+            type.IsEnable = true;
+            //获取分类名称全称
+            string FullName = await GetFullName(type.ParentId);
+            //处理全称,如果为第一级直接用全称,否则获取全称后拼接名称
+            type.SpliceName = string.IsNullOrEmpty(FullName) ? dto.Name : FullName + "-" + dto.Name;
+            var id = await _planTypeRepository.AddAsync(type, cancellationToken);
+            if (dto.TypeOrgDtos != null && dto.TypeOrgDtos.Any())
+            {
+                List<PlanTypeOrg> orgs = _mapper.Map<List<PlanTypeOrg>>(dto.TypeOrgDtos);
+                orgs.ForEach(x => x.TypeId = type.Id);
+                await _planTypeOrgRepository.AddRangeAsync(orgs, cancellationToken);
+            }
+            return id;
+        }
+
+        #endregion
+
+        #region 预案库类型 - 编辑
+
+        /// <summary>
+        /// 预案库类型 - 编辑
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task UpdateTypeAsync(UpdatePlanTypeDto dto, CancellationToken cancellationToken)
+        {
+            //查询原有数据
+            var type = await _planTypeRepository.GetAsync(dto.Id, cancellationToken);
+            if (type is null)
+                throw UserFriendlyException.SameMessage("编辑失败!");
+            bool result = type.Name != dto.Name || type.ParentId != dto.ParentId;
+            //是否更改分类名称或者层级
+
+            //转换
+            _mapper.Map(dto, type);
+            //如果更改了名称或者修改了层级,则修改全称,未更改不修改
+            if (result)
+            {
+                string FullName = await GetFullName(type.ParentId);//获取分类名称全称
+                type.SpliceName = string.IsNullOrEmpty(FullName) ? dto.Name : FullName + "-" + dto.Name;//处理全称,如果为第一级直接用全称,否则获取全称后拼接名称
+            }
+
+            //修改数据
+            await _planTypeRepository.UpdateAsync(type, cancellationToken);
+
+            //如果修改了名称,对应修改子分类全称
+            if (result)
+                await UpdateChildNode(type.Id);
+
+            // 修改关联机构
+            await _planTypeOrgRepository.RemoveAsync(x => x.TypeId == type.Id, false, cancellationToken);
+            if (dto.TypeOrgDtos != null && dto.TypeOrgDtos.Any())
+            {
+                List<PlanTypeOrg> orgs = _mapper.Map<List<PlanTypeOrg>>(dto.TypeOrgDtos);
+                orgs.ForEach(x => x.TypeId = type.Id);
+                await _planTypeOrgRepository.AddRangeAsync(orgs, cancellationToken);
+            }
+        }
+
+        #endregion
+
+        #region 预案库类型 - 删除
+
+        /// <summary>
+        /// 预案库类型 - 删除
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task RemoveTypeAsync(string Id, CancellationToken cancellationToken)
+        {
+            //查询数据是否存在
+            var sandard = await _planTypeRepository.GetAsync(p => p.Id == Id && p.IsDeleted == false, cancellationToken);
+            if (sandard is null)
+                throw UserFriendlyException.SameMessage("分类不存在!");
+
+            //查询是否有子级分类
+            var checkChild = await _planTypeRepository.CountAsync(p => p.ParentId == Id && p.IsDeleted == false, cancellationToken);
+            if (checkChild > 0)
+                throw UserFriendlyException.SameMessage("存在子级分类!");
+
+            //查询是否有预案分类
+            var checkKnowledge = await _planListRepository.CountAsync(p => p.PlanTypes.Any(t => t.Id == Id), cancellationToken);
+            if (checkKnowledge > 0)
+                throw UserFriendlyException.SameMessage("分类存在预案!");
+
+            //删除操作
+            await _planTypeRepository.RemoveAsync(sandard, true, cancellationToken);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region 预案库管理
+
+        #region 预案库 - 列表
+
+        /// <summary>
+        /// 预案库 - 列表
+        /// </summary>
+        /// <param name="pagedDto"></param>
+        /// <returns></returns>
+        public async Task<(int, IList<PlanDataDto>)> QueryAllPlanListAsync(PlanListDto pagedDto, CancellationToken cancellationToken)
+        {
+            //if (!_sessionContext.OrgIsCenter)
+            //{// 部门只能查询【部门预案库】
+            //    pagedDto.Attribution = "部门预案库";
+            //}
+
+            var typeSpliceName = string.Empty;
+            var hotspotHotSpotFullName = string.Empty;
+
+            if (!string.IsNullOrEmpty(pagedDto.PlanTypeID))
+            {
+                var type = await _planTypeRepository.GetAsync(x => x.Id == pagedDto.PlanTypeID);
+                typeSpliceName = type?.SpliceName;
+            }
+
+            if (!string.IsNullOrEmpty(pagedDto.HotspotId))
+            {
+                var hotspot = await _hotspotTypeRepository.GetAsync(x => x.Id == pagedDto.HotspotId);
+                hotspotHotSpotFullName = hotspot?.HotSpotFullName;
+            }
+
+            //单表分页
+            var (total, temp) = await _planListRepository.Queryable()
+                .Includes(x => x.PlanTypes)
+                .Includes(x => x.HotspotType)
+                .Includes(x => x.ExaminMan)
+                .Where(x => x.IsDeleted == false)
+                .WhereIF(pagedDto.IsPublic.HasValue, x => x.IsPublic == pagedDto.IsPublic)
+                .WhereIF(OrgSeedData.CenterId != pagedDto.CreateOrgId && !string.IsNullOrEmpty(pagedDto.CreateOrgId), x => x.CreatorOrgId != null && x.CreatorOrgId.StartsWith(pagedDto.CreateOrgId!))
+                .WhereIF(!string.IsNullOrEmpty(pagedDto.Attribution), x => x.Attribution == pagedDto.Attribution)
+
+                // 预案库没有关键词、去掉关键词查询
+
+                .WhereIF(!string.IsNullOrEmpty(pagedDto.Title) &&
+                          string.IsNullOrEmpty(pagedDto.Content), x => x.Title.Contains(pagedDto.Title))
+
+                .WhereIF(string.IsNullOrEmpty(pagedDto.Title) &&
+                        !string.IsNullOrEmpty(pagedDto.Content), x => x.Content.Contains(pagedDto.Content))
+
+                .WhereIF(!string.IsNullOrEmpty(pagedDto.Title) &&
+                         !string.IsNullOrEmpty(pagedDto.Content), x => x.Title.Contains(pagedDto.Title!) ||
+                                                                   x.Content!.Contains(pagedDto.Content!))
+
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == EPlanStatus.OnShelf, x => x.Status == EPlanStatus.OnShelf)
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == EPlanStatus.OffShelf, x => x.Status == EPlanStatus.OffShelf)
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == EPlanStatus.Auditing, x => x.Status == EPlanStatus.Auditing)
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == EPlanStatus.NewDrafts, x =>
+                                                                                               (x.Status == EPlanStatus.Drafts && x.CreatorId == _sessionContext.UserId) ||
+                                                                                               (x.Status == EPlanStatus.Revert && x.CreatorId == _sessionContext.UserId) ||
+                                                                                               (x.Status == EPlanStatus.NewDrafts && x.CreatorId == _sessionContext.UserId))
+                .WhereIF(pagedDto.Status.HasValue && pagedDto.Status == EPlanStatus.All, x => (x.Status == EPlanStatus.OnShelf ||
+                                                                                               x.Status == EPlanStatus.OffShelf ||
+                                                                                               x.Status == EPlanStatus.Auditing) ||
+                                                                                              (x.Status == EPlanStatus.Drafts && x.CreatorId == _sessionContext.UserId) ||
+                                                                                              (x.Status == EPlanStatus.Revert && x.CreatorId == _sessionContext.UserId) ||
+                                                                                              (x.Status == EPlanStatus.NewDrafts && x.CreatorId == _sessionContext.UserId))
+
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.PlanTypes.Any(t => t.SpliceName.StartsWith(typeSpliceName)))
+                .WhereIF(!string.IsNullOrEmpty(hotspotHotSpotFullName), x => x.HotspotType.HotSpotFullName.EndsWith(hotspotHotSpotFullName!))
+
+                .WhereIF(pagedDto.CreationTimeStart.HasValue, x => x.CreationTime >= pagedDto.CreationTimeStart)
+                .WhereIF(pagedDto.CreationTimeEnd.HasValue, x => x.CreationTime <= pagedDto.CreationTimeEnd)
+
+                .WhereIF(pagedDto.OnShelfTimeStart.HasValue, x => x.OnShelfTime >= pagedDto.OnShelfTimeStart)
+                .WhereIF(pagedDto.OnShelfTimeEnd.HasValue, x => x.OnShelfTime <= pagedDto.OnShelfTimeEnd)
+
+                .WhereIF(pagedDto.OffShelfTimeStart.HasValue, x => x.OffShelfTime >= pagedDto.OffShelfTimeStart)
+                .WhereIF(pagedDto.OffShelfTimeEnd.HasValue, x => x.OffShelfTime <= pagedDto.OffShelfTimeEnd)
+
+                .WhereIF(pagedDto.UpdateTimeStart.HasValue, x => x.UpdateTime >= pagedDto.UpdateTimeStart)
+                .WhereIF(pagedDto.UpdateTimeEnd.HasValue, x => x.UpdateTime <= pagedDto.UpdateTimeEnd)
+
+                .WhereIF(pagedDto.ExaminTimeStart.HasValue, x => x.ExaminTime >= pagedDto.ExaminTimeStart)
+                .WhereIF(pagedDto.ExaminTimeEnd.HasValue, x => x.ExaminTime <= pagedDto.ExaminTimeEnd)
+
+                .OrderByIF(string.IsNullOrEmpty(pagedDto.SortField), x => x.CreationTime, OrderByType.Desc)
+                .OrderByIF(pagedDto is { SortField: "pageView", }, x => x.PageView, OrderByType.Desc)         //阅读量
+                .OrderByIF(pagedDto is { SortField: "score", }, x => x.Score, OrderByType.Desc)               //评分
+                .OrderByIF(pagedDto is { SortField: "creationTime" }, x => x.CreationTime, OrderByType.Desc) //创建时间
+
+                .ToPagedListAsync(pagedDto.PageIndex, pagedDto.PageSize, cancellationToken);
+
+            return (total, _mapper.Map<IList<PlanDataDto>>(temp));
+        }
+
+        #endregion
+
+        #region 预案库 - 新增
+
+        /// <summary>
+        /// 新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<string> AddPlanAsync(AddPlanListDto dto, CancellationToken cancellationToken)
+        {
+            var pList = _mapper.Map<PlanList>(dto);
+
+            var any = await _planListRepository.Queryable().Where(x => x.Status == EPlanStatus.OnShelf && x.Title == dto.Title).AnyAsync();
+            if (any)
+                throw UserFriendlyException.SameMessage("当前预案标题存在重复标题!");
+
+            pList.InitId();
+
+            if (dto.Files != null && dto.Files.Count > 0)
+                pList.FileJson = await _fileRepository.AddFileAsync(dto.Files, pList.Id, "", cancellationToken);
+
+            if (dto.PlanTypes.Any())
+            {
+                pList.PlanTypes = dto.PlanTypes.Select(d => new PlanType
+                {
+                    Id = d.Id,
+                    Name = d.Name,
+                    SpliceName = d.SpliceName
+                }).ToList();
+            }
+
+            await _planListRepository.AddNav(pList)
+                                     .Include(d => d.PlanTypes)
+                                     .ExecuteCommandAsync();
+            return pList.Id;
+        }
+
+        #endregion
+
+        #region 预案库 - 修改
+
+        /// <summary>
+        /// 修改
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task UpdatePlanAsync(UpdatePlanListDto dto, CancellationToken cancellationToken)
+        {
+            var plan = await _planListRepository.GetAsync(dto.Id);
+
+            if (plan == null)
+                throw UserFriendlyException.SameMessage("预案库查询失败");
+
+            if ((plan.Status == EPlanStatus.OnShelf || plan.Status == EPlanStatus.Auditing) && (plan.ExpiredTime.HasValue && plan.ExpiredTime.Value > DateTime.Now))
+                throw UserFriendlyException.SameMessage("预案库数据不可修改");
+
+            var any = await _planListRepository.Queryable().Where(x => x.Status == EPlanStatus.OnShelf && x.Title == dto.Title && x.Id != dto.Id).AnyAsync();
+            if (any)
+                throw UserFriendlyException.SameMessage("当前预案标题存在重复标题!");
+
+            if (dto.ApplyStatus == EPlanApplyStatus.Delete)
+            {
+                plan.Status = (EPlanStatus)dto.Status;
+                plan.ApplyStatus = (EPlanApplyStatus)dto.ApplyStatus;
+                plan.Id = dto.Id;
+                plan.ApplyReason = dto.ApplyReason;
+            }
+            else
+            {
+                _mapper.Map(dto, plan);
+
+                plan.HotspotId = dto.HotspotId;
+
+                if (dto.Files != null && dto.Files.Count > 0)
+                    plan.FileJson = await _fileRepository.AddFileAsync(dto.Files, plan.Id, "", cancellationToken);
+                else
+                    plan.FileJson = new List<Share.Dtos.File.FileJson>();
+            }
+
+
+            if (dto.ApplyStatus != EPlanApplyStatus.Delete)
+            {
+                if (dto.PlanTypes.Any())
+                {
+                    plan.PlanTypes = dto.PlanTypes.Select(d => new PlanType
+                    {
+                        Id = d.Id,
+                    }).ToList();
+
+                    await _planListRepository.UpdateNav(plan)
+                        .Include(d => d.PlanTypes, new UpdateNavOptions
+                        {
+                            ManyToManyIsUpdateA = true
+                        })
+                        .ExecuteCommandAsync();
+                }
+                else
+                {
+                    await _planListRepository.UpdateAsync(plan, cancellationToken);
+                }
+            }
+            else
+            {
+                await _planListRepository.UpdateAsync(plan, cancellationToken);
+            }
+        }
+
+        #endregion
+
+        #region 预案库 - 删除
+
+        /// <summary>
+        /// 删除
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task RemovePlanAsync(UpdatePlanListDto dto, CancellationToken cancellationToken)
+        {
+            var plan = await _planListRepository.GetAsync(dto.Id);
+
+            if (plan == null)
+                throw UserFriendlyException.SameMessage("预案库查询失败");
+
+            _mapper.Map(dto, plan);
+
+            plan.IsDeleted = true;
+            await _planListRepository.UpdateNullAsync(plan, cancellationToken);
+        }
+
+        #endregion
+
+        #region 预案库 - 审核
+
+        /// <summary>
+        /// 审核
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task AuditPlanAsync(UpdatePlanListDto dto, CancellationToken cancellationToken)
+        {
+            var plan = await _planListRepository.GetAsync(dto.Id);
+
+            if (plan == null)
+                throw UserFriendlyException.SameMessage("预案库查询失败");
+
+            plan.Status = (EPlanStatus)dto.Status!;
+            plan.ApplyStatus = (EPlanApplyStatus)dto.ApplyStatus!;
+            plan.ExaminTime = dto.ExaminTime;
+            plan.ExaminManId = dto.ExaminManId;
+            plan.ExaminOrganizeId = dto.ExaminOrganizeId;
+            plan.ExaminOpinion = dto.ExaminOpinion;
+            plan.UpdateTime = dto.UpdateTime;
+            plan.OnShelfTime = dto.OnShelfTime;
+            plan.OffShelfTime = dto.OffShelfTime;
+
+            if (plan.ApplyStatus == EPlanApplyStatus.Delete)
+            {
+                plan.IsDeleted = true;
+            }
+            await _planListRepository.UpdateNullAsync(plan, cancellationToken);
+        }
+
+        #endregion
+
+        #region 预案库 - 详情
+
+        /// <summary>
+        /// 详情
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="IsAddPv">默认不增加,false不增加,true增加浏览量</param>
+        /// <returns></returns>
+        public async Task<PlanInfoDto> GetPlanAsync(string Id, bool? IsAddPv, CancellationToken cancellationToken)
+        {
+            var plan = await _planListRepository.Queryable()
+                        .Includes(x => x.PlanTypes)
+                        .FirstAsync(p => p.Id == Id, cancellationToken);
+            //var plan = await _planListRepository.GetAsync(Id);
+            if (plan == null)
+                throw UserFriendlyException.SameMessage("预案库查询失败");
+            ;
+            // 转化
+            var planInfoDto = _mapper.Map<PlanInfoDto>(plan);
+
+            // 内容
+            if (plan != null && !string.IsNullOrEmpty(plan.Content))
+                planInfoDto.Content = _bulletinApplication.GetSiteUrls(plan.Content);
+
+            // 热点
+            var hot = await _hotspotTypeRepository.GetAsync(plan.HotspotId, cancellationToken);
+            if (hot != null)
+                planInfoDto.HotspotName = hot.HotSpotFullName;
+
+            // 分类
+            //var relationType = await _planRelationTypeRepository.QueryAsync(x => x.PlanId == Id);
+            //if (relationType != null)
+            //{
+            //    planInfoDto.PlanTypes = _mapper.Map<List<PlanRelationTypeDto>>(relationType);
+            //}
+
+            // 收藏
+            var collect = await _planCollectRepository.GetAsync(x => x.PlanId == Id && x.CreatorId == _sessionContext.UserId);
+            if (collect != null)
+                planInfoDto.Collect = _mapper.Map<PlanCollectDto>(collect);
+
+            // 附件
+            if (planInfoDto.FileJson != null && planInfoDto.FileJson.Any())
+            {
+                var ids = planInfoDto.FileJson.Select(x => x.Id).ToList();
+                planInfoDto.Files = await _fileRepository.GetFilesAsync(ids, cancellationToken);
+            }
+
+            // 更新浏览量
+            if (IsAddPv == true)
+            {
+                //修改浏览量
+                plan.PageView++;
+                //修改点击量
+                await _planListRepository.UpdateAsync(plan, cancellationToken);
+            }
+            return planInfoDto;
+        }
+
+        #endregion
+
+        #region 预案库 - 批量导出
+
+        /// <summary>
+        /// 预案库 - 批量导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<Dictionary<string, Stream>> PlanInfoListExportAsync(PlanInfoExportDto dto, CancellationToken cancellationToken)
+        {
+            var streamList = new Dictionary<string, Stream>();
+            var knowList = await _planListRepository.Queryable()
+                .Where(m => dto.Ids.Contains(m.Id))
+                .Select(m => new { m.Title, m.Content })
+                .ToListAsync(cancellationToken);
+
+            var tasks = knowList.Select(async item =>
+            {
+                var stream = await Task.Run(() => item.Content.HtmlToStream(dto.FileType), cancellationToken);
+                return new KeyValuePair<string, Stream>(
+                    item.Title + dto.FileType.GetFileExtension(),
+                    stream
+                );
+            });
+
+            var results = await Task.WhenAll(tasks);
+
+            foreach (var kvp in results)
+            {
+                if (!streamList.ContainsKey(kvp.Key))
+                {
+                    streamList.Add(kvp.Key, kvp.Value);
+                }
+            }
+
+            return streamList;
+        }
+
+        #endregion
+
+        #endregion
+
+        #region 私有方法
+
+        #region 查询所有子级
+
+        /// <summary>
+        /// 查询所有子级
+        /// </summary>
+        /// <param name="treeDatas">分类数据</param>
+        /// <param name="ID">需要查询哪级下面的分类</param>
+        /// <param name="checkId">选中的数据ID</param>
+        /// <returns></returns>
+        private List<TreeListDto> GetChildren(List<PlanType> treeDatas, string ID, string? checkId)
+        {
+            List<TreeListDto> nodeList = new();
+            //根据ID查询子级
+            var children = treeDatas.Where(q => q.ParentId == ID);
+            foreach (var dr in children)
+            {
+                //组装数据
+                TreeListDto node = new()
+                {
+                    name = dr.Name,
+                    ParentID = dr.ParentId,
+                    value = dr.Id,
+                    IsEnable = dr.IsEnable
+                };
+                //是否选中
+                if (!string.IsNullOrEmpty(checkId) && checkId != Guid.Empty.ToString() && checkId == dr.Id)
+                    node.selected = true;
+                //子级数据赋值
+                node.children = GetChildren(treeDatas, node.value, checkId);
+                //添加数据
+                nodeList.Add(node);
+            }
+            return nodeList;
+        }
+
+        #endregion
+
+        #region  获取全称
+
+        /// <summary>
+        /// 获取全称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<string> GetFullName(string? Id)
+        {
+            //获取全部父级名称
+            var list = await GetParentNode(Id);
+            //倒叙
+            list.Reverse();
+            //拆分
+            return string.Join("-", list.ToArray());
+        }
+
+        /// <summary>
+        /// 查询父级名称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<List<string>> GetParentNode(string? Id)
+        {
+            List<string> list = new();
+            //查询父级数据
+            var type = await _planTypeRepository.GetAsync(p => p.Id == Id);
+            if (type != null)
+            {
+                //添加名称
+                list.Add(type.Name);
+                list.AddRange(await GetParentNode(type.ParentId));
+            }
+            return list;
+        }
+
+        #endregion
+
+        #region 修改子级分类全称
+
+        /// <summary>
+        /// 修改子级分类全称
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task UpdateChildNode(string Id)
+        {
+            //查询子分类
+            var list = await GetChildNode(Id);
+            if (list is not null && list.Count > 0)
+            {
+                foreach (var item in list)
+                {
+                    //获取全称
+                    string FullName = await GetFullName(item.ParentId);
+                    item.SpliceName = string.IsNullOrEmpty(FullName) ? item.Name : FullName + "-" + item.Name;
+                    //修改全称
+                    await _planTypeRepository.UpdateAsync(item);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 查询子级节点数据
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <returns></returns>
+        private async Task<List<PlanType>> GetChildNode(string Id)
+        {
+            List<PlanType> list = new();
+            //查询数据
+            var typelist = await _planTypeRepository.QueryAsync(p => p.ParentId == Id);
+            if (typelist != null)
+            {
+                //处理数据
+                foreach (var item in typelist)
+                {
+                    list.Add(item);
+                    list.AddRange(await GetChildNode(item.Id));
+                }
+            }
+            return list;
+        }
+
+        #endregion
+
+        #endregion
+
+    }
+}

+ 7 - 0
src/Hotline.Application/Quality/IQualityApplication.cs

@@ -1,5 +1,7 @@
 using Hotline.Share.Dtos.Quality;
 using Hotline.Share.Enums.Quality;
+using Hotline.Share.Requests;
+using SqlSugar;
 
 namespace Hotline.Application.Quality
 {
@@ -11,5 +13,10 @@ namespace Hotline.Application.Quality
 		Task UpdateQualityAsync(UpdateQualityDto model, CancellationToken cancellationToken);
 		Task AiResultAsync(List<AiQualityResultDto> model, CancellationToken cancellationToken);
 
+		Task Transfer_XT(string Id, CancellationToken cancellationToken);
+		ISugarQueryable<Hotline.Quality.Quality> SeatsQualityAnalyse(PagedKeywordRequest dto, CancellationToken cancellationToken);
+
+		ISugarQueryable<QualityOrderOverviewDto> QualityOrderOverview(QualityWorkAnalysisRequest dto, int allOrderNum, CancellationToken cancellationToken);
+
 	}
 }

+ 113 - 6
src/Hotline.Application/Quality/QualityApplication.cs

@@ -27,6 +27,14 @@ using XF.Domain.Repository;
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Configurations;
 using Hotline.Tools;
+using Hotline.Share.Dtos;
+using MathNet.Numerics;
+using Microsoft.Extensions.DependencyInjection;
+using NPOI.HPSF;
+using System.Threading;
+using Hotline.Share.Requests;
+using Hotline.Repository.SqlSugar.TextSearch;
+using SqlSugar;
 
 namespace Hotline.Application.Quality
 {
@@ -45,8 +53,11 @@ namespace Hotline.Application.Quality
         private readonly Publisher _publisher;
         private readonly IRepository<TrCallRecord> _trCallRecordRepository;
         private readonly IRepository<OrderWord> _orderWordRepository;
+		private readonly IServiceProvider _serviceProvider;
+		private readonly IRepository<QualityItem> _qualiteyItem;
+		private readonly IRepository<QualityTransferRecords> _qualityTransferRecordsRepository;
 
-        public QualityApplication(
+		public QualityApplication(
             ISessionContext sessionContext,
             IMapper mapper,
             IRepository<QualityDetail> qualiteyDetail,
@@ -59,7 +70,10 @@ namespace Hotline.Application.Quality
             ILogger<Hotline.Quality.Quality> logger,
             Publisher publisher,
             IRepository<TrCallRecord> trCallRecordRepository,
-            IRepository<OrderWord> orderWordRepository)
+			IServiceProvider serviceProvider,
+			IRepository<QualityItem> qualiteyItem,
+			IRepository<OrderWord> orderWordRepository,
+			IRepository<QualityTransferRecords> qualityTransferRecordsRepository)
         {
             _sessionContext = sessionContext;
             _mapper = mapper;
@@ -74,7 +88,10 @@ namespace Hotline.Application.Quality
             _publisher = publisher;
             _trCallRecordRepository = trCallRecordRepository;
             _orderWordRepository = orderWordRepository;
-        }
+            _serviceProvider = serviceProvider;
+			_qualiteyItem = qualiteyItem;
+            _qualityTransferRecordsRepository = qualityTransferRecordsRepository;
+		}
 
         /// <summary>
         /// 新增质检
@@ -290,7 +307,8 @@ namespace Hotline.Application.Quality
 										newDetail.Second = sentenceList.start;
 										newDetail.EndSecond = sentenceList.end == null ? 0 : sentenceList.end;
 										newDetail.Content = sentenceList.text;
-										detailsSentence.Add(newDetail);
+                                        if (newDetail.Second >= 1000)
+											detailsSentence.Add(newDetail);
 									}
 								}
 							}
@@ -309,7 +327,8 @@ namespace Hotline.Application.Quality
 										newDetail.Second = sentenceList.start;
 										newDetail.EndSecond = sentenceList.end == null ? 0 : sentenceList.end;
 										newDetail.Content = sentenceList.text;
-										detailsSentence.Add(newDetail);
+										if (newDetail.Second >= 1000)
+											detailsSentence.Add(newDetail);
 									}
 								}
 
@@ -317,7 +336,8 @@ namespace Hotline.Application.Quality
 						}
 						if (detailsSentence.Count == 0)
 						{
-							details.Add(detail);
+							if (detail.Second > 0)
+								details.Add(detail);
 						}
 						else
 						{
@@ -366,6 +386,93 @@ namespace Hotline.Application.Quality
 			}
 		}
 
+        /// <summary>
+        /// 异步转写
+        /// </summary>
+        /// <returns></returns>
+        public async Task Transfer_XT(string Id, CancellationToken cancellationToken) {
+
+			var quality = await _qualityRepository.Queryable().Includes(x => x.Order).Where(x => x.Id == Id).FirstAsync();
+			var call = await _callApplication.GetCallAsync(quality.Order.CallId, cancellationToken);
+			var items = await _qualiteyItem.Queryable().Where(x => x.IsEnable == 0 && x.IsDeleted == true && x.IsIntelligent == 1).ToListAsync();
+
+			var transfers = new List<Transfer>();
+			if (!string.IsNullOrEmpty(call?.AudioFile))
+			{
+				var filename = Path.GetFileName(call.AudioFile);
+				try
+				{
+					var aiQualityService = _serviceProvider.GetRequiredService<IAiQualityService>();
+					var res = await aiQualityService.CreateAiOrderQualityTask(filename, cancellationToken);
+					if (!string.IsNullOrEmpty(res))
+					{
+						var rowList = res.Split("\n");
+						var sort = 0;
+						foreach (var row in rowList)
+						{
+							var model = new Transfer();
+							var statement = row.Split(":");
+							if (statement.Count() == 2)
+							{
+								model.Sort = sort;
+								model.Type = statement[0].ToString() == "spk_01" ? EQualityTransferType.Seats : EQualityTransferType.Citizen;
+								model.Content = statement[1].ToString();
+								model.ReviseContent = model.Content;
+								if (!string.IsNullOrEmpty(model.Content))
+								{
+									var hitWord = items.Where(x => model.Content.Contains(x.Name)).ToList();
+									model.Prohibited = string.Join(";", hitWord);
+								}
+							}
+							transfers.Add(model);
+							sort++;
+						}
+					}
+					await _qualityRepository.Updateable().SetColumns(x => new Hotline.Quality.Quality { TransferState = EQualityTransferState.Succeed,Transfer = transfers }).Where(x => x.Id == quality.Id).ExecuteCommandAsync();
+					await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Succeed,IsFinished = true }).Where(x => x.QualityId == quality.Id).ExecuteCommandAsync();
+				}
+				catch (Exception)
+				{
+					await _qualityRepository.Updateable().SetColumns(x => new Hotline.Quality.Quality { TransferState = EQualityTransferState.Lose}).Where(x => x.Id == quality.Id).ExecuteCommandAsync();
+					await _qualityTransferRecordsRepository.Updateable().SetColumns(x => new QualityTransferRecords { TransferState = EQualityTransferState.Lose, IsFinished = true }).Where(x => x.QualityId == quality.Id).ExecuteCommandAsync();
+					throw;
+				}
+			}
+		}
+
+
+        /// <summary>
+        /// 坐席质检分析
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        public ISugarQueryable<Hotline.Quality.Quality> SeatsQualityAnalyse(PagedKeywordRequest dto, CancellationToken cancellationToken) {
+
+            var query = _qualityRepository.Queryable()
+                .Includes(x => x.Order)
+                .Includes(x => x.QualityDetails)
+                .Where(x => x.Source == EQualitySource.Accepted && x.QualityTime >= dto.StartTime && x.QualityTime <= dto.EndTime && x.State == EQualityState.End);
+            return query;
+        }
+
+        /// <summary>
+        /// 质检工单概览
+        /// </summary>
+        /// <returns></returns>
+        public  ISugarQueryable<QualityOrderOverviewDto>  QualityOrderOverview(QualityWorkAnalysisRequest dto, int allOrderNum, CancellationToken cancellationToken) {
+
+            var query = _qualiteyDetail.Queryable()
+                .Includes(x => x.Quality)
+                .GroupBy(x => x.Name)
+                .Select(x => new QualityOrderOverviewDto
+                {
+                    QualityItem = x.Name,
+                    AllOrderNum = allOrderNum,
+                    OrderNum = SqlFunc.AggregateDistinctCount(x.Quality.OrderId)
+                });
+            return query;
+        }
+
 
 	}
 }

+ 5 - 2
src/Hotline.Application/Snapshot/Notifications/SnapshotHandler.cs

@@ -57,17 +57,20 @@ public class SnapshotHandler : ICapSubscribe, IScopeDependency
 public class SnapshotOrderFiledNotificationHandler : INotificationHandler<SnapshotOrderFiledNotification>
 {
     private readonly ISnapshotApplication _snapshotApplication;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
     private readonly ILogger<SnapshotOrderFiledNotificationHandler> _logger;
-    public SnapshotOrderFiledNotificationHandler(ISnapshotApplication snapshotApplication, ILogger<SnapshotOrderFiledNotificationHandler> logger)
+    public SnapshotOrderFiledNotificationHandler(ISnapshotApplication snapshotApplication, ILogger<SnapshotOrderFiledNotificationHandler> logger, IOrderSnapshotRepository orderSnapshotRepository)
     {
         _snapshotApplication = snapshotApplication;
         _logger = logger;
+        _orderSnapshotRepository = orderSnapshotRepository;
     }
     public async Task Handle(SnapshotOrderFiledNotification notification, CancellationToken cancellationToken)
     {
         try
         {
-            await _snapshotApplication.AddRedPardAsync(notification.OrderId, cancellationToken);
+            if (_orderSnapshotRepository.HasOrder(notification.OrderId))
+                await _snapshotApplication.AddRedPardAsync(notification.OrderId, cancellationToken);
         }
         catch (Exception e)
         {

+ 3 - 0
src/Hotline.Application/Snapshot/SnapshotApplicationBase.cs

@@ -297,6 +297,9 @@ public abstract class SnapshotApplicationBase
     {
         var openId = _sessionContext.OpenId;
         var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(openId);
+        if (thirdAccount.PhoneNumber.IsNullOrEmpty() && _sessionContext.Phone.NotNullOrEmpty())
+            thirdAccount.PhoneNumber = _sessionContext.Phone;
+
         var dayTime = DateTime.Now;
         var readPack = await _redPackRecordRepository.Queryable()
             .Where(m => m.WXOpenId == openId && m.PickupStatus == ERedPackPickupStatus.Received)

+ 10 - 0
src/Hotline.Application/StatisticalReport/CallReport/CallReportApplicationBase.cs

@@ -339,4 +339,14 @@ public abstract class CallReportApplicationBase : ICallReportApplication
             .OrderByDescending(x => x.CreatedTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, cancellationToken);
     }
+
+    public virtual Task<List<QuerySeatMonthCallResp>> QuerySeatMonthCall(QuerySeatMonthCallRequest dto)
+    {
+        throw new NotImplementedException();
+    }
+
+    public virtual ISugarQueryable<QuerySeatMonthCallDetailResp> QuerySeatMonthCallDetail(QuerySeatMonthCallDetailRequest dto)
+    {
+        throw new NotImplementedException();
+    }
 }

+ 77 - 0
src/Hotline.Application/StatisticalReport/CallReport/YiBinCallReportApplication.cs

@@ -18,6 +18,7 @@ using XF.Domain.Repository;
 using Microsoft.AspNetCore.Http;
 using Hotline.Share.Dtos;
 using MapsterMapper;
+using System.Threading;
 
 namespace Hotline.Application.StatisticalReport.CallReport;
 
@@ -290,6 +291,82 @@ public class YiBinCallReportApplication : CallReportApplicationBase, ICallReport
         return await _trCallRecordRepositoryEx.QueryCallOutDateStatisticsDetail(dto.StartTime.Value, dto.EndTime.Value,enterpriseTels);
     }
 
+    public override async Task<List<QuerySeatMonthCallResp>> QuerySeatMonthCall(QuerySeatMonthCallRequest dto)
+    {
+        //获取配置
+        int noConnectByeTimes = _systemSettingCacheManager.NoConnectByeTimes;
+        int effectiveTimes = _systemSettingCacheManager.EffectiveTimes;
+        int connectByeTimes = _systemSettingCacheManager.ConnectByeTimes;
+        int ringTimes = _systemSettingCacheManager.RingTimes;
+
+        var list = await _userRepository.Queryable()
+              .LeftJoin<TrCallRecord>((u, c) => u.Id == c.UserId)
+              .Where(u => !u.IsDeleted && u.UserType == EUserType.Seat)
+              .Where((u, c) => c.BeginRingTime >= dto.StartTime)
+              .Where((u, c) => c.BeginRingTime <= dto.EndTime)
+              .Where((u, c) => c.CallDirection == ECallDirection.In)
+              .GroupBy((u, c) => new { c.UserName, c.UserId })
+              .Select((u, c) => new QuerySeatMonthCallResp
+              {
+                  Name = c.UserName,
+                  UserId = c.UserId,
+                  InAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime != null, 1, 0)), //呼入接通量
+                  InAvailableAnswer = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime != null && c.Duration >= effectiveTimes, 1, 0)), //有效接通量
+                  InHangupImmediateWhenAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime != null && c.Duration < connectByeTimes, 1, 0)),//接通秒挂
+                  OverTimeImmediate = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime != null && c.RingTimes > ringTimes, 1, 0)),//超时接通量
+                  InTimeImmediate = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime != null && c.RingTimes <= ringTimes, 1, 0)),//按时接通量
+                  InHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime == null, 1, 0)), //呼入未接通
+                  InHangupImmediate = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime == null && c.RingTimes <= noConnectByeTimes, 1, 0)),//未接通秒挂量
+                  OverTimeInHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(c.AnsweredTime == null && c.RingTimes > noConnectByeTimes, 1, 0)), //超时未接通量
+              }).ToListAsync();
+        return list;
+    }
+
+    /// <summary>
+    /// 坐席月接通明细
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public override ISugarQueryable<QuerySeatMonthCallDetailResp> QuerySeatMonthCallDetail(QuerySeatMonthCallDetailRequest dto)
+    {
+        //获取配置
+        int noConnectByeTimes = _systemSettingCacheManager.NoConnectByeTimes;
+        int effectiveTimes = _systemSettingCacheManager.EffectiveTimes;
+        int connectByeTimes = _systemSettingCacheManager.ConnectByeTimes;
+        int ringTimes = _systemSettingCacheManager.RingTimes;
+
+        return  _trCallRecordRepository.Queryable()
+            .WhereIF(dto.RingStartTime.HasValue, x => x.BeginRingTime >= dto.RingStartTime)
+            .WhereIF(dto.RingEndTime.HasValue, x => x.BeginRingTime <= dto.RingEndTime)
+            .WhereIF(!string.IsNullOrEmpty(dto.EmpId), x => x.UserId == dto.EmpId)
+            .WhereIF(dto.AnsweredStartTime.HasValue, x => x.AnsweredTime >= dto.AnsweredStartTime)
+            .WhereIF(dto.AnsweredEndTime.HasValue, x => x.AnsweredTime <= dto.AnsweredEndTime)
+            .WhereIF(!string.IsNullOrEmpty(dto.TelNo), x => x.TelNo.Contains(dto.TelNo!))
+            .WhereIF(!string.IsNullOrEmpty(dto.Cpn),x=>x.CPN.Contains(dto.Cpn!))
+            .WhereIF(!string.IsNullOrEmpty(dto.Cdpn), x => x.CDPN.Contains(dto.Cdpn!))
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 1,x=>x.AnsweredTime != null) //呼入接通总量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 2,x=> x.AnsweredTime != null && x.Duration >= effectiveTimes) //有效接通量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 3,x=> x.AnsweredTime != null && x.Duration < connectByeTimes) //接通秒挂量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 4,x=> x.AnsweredTime != null && x.RingTimes > ringTimes) //超时接通量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 5,x=> x.AnsweredTime != null && x.RingTimes <= ringTimes) //按时接通量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 6,x=> x.AnsweredTime == null) //呼入未接通量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 7,x=> x.AnsweredTime == null && x.RingTimes <= noConnectByeTimes) //未接通秒挂量
+            .WhereIF(dto.QueryType.HasValue && dto.QueryType == 8,x=> x.AnsweredTime == null && x.RingTimes > noConnectByeTimes) //超时未接通量
+            .Where(x => x.CallDirection == ECallDirection.In)
+            .OrderBy(x => x.BeginRingTime)
+            .MergeTable()
+            .Select(x => new QuerySeatMonthCallDetailResp
+            { 
+                Cpn = x.CPN,
+                Cdpn = x.CDPN,
+                TelNo = x.TelNo,
+                AnsweredTime = x.AnsweredTime,
+                RingTimeBegin  = x.BeginRingTime,
+                SeatName = x.UserName
+            });
+    }
+
+
 
     //public override async Task<PagedDto<TrCallDto>> GetCallDetailListAsync(GetCallListDto dto, CancellationToken cancellationToken)
     //{

+ 15 - 0
src/Hotline.Application/StatisticalReport/ICallReportApplication.cs

@@ -1,6 +1,7 @@
 using Hotline.CallCenter.Calls;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Requests;
 using SqlSugar;
@@ -76,5 +77,19 @@ namespace Hotline.Application.StatisticalReport
         /// <param name="dto"></param>
         /// <returns></returns>
         Task<List<QueryCallOutDateStatisticsDetailResp>> QueryCallOutDateStatisticsDetail(QueryCallDateStatisticsDetailDto dto,List<string> enterpriseTels);
+
+        /// <summary>
+        /// 坐席月接通率统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<List<QuerySeatMonthCallResp>> QuerySeatMonthCall(QuerySeatMonthCallRequest dto);
+
+        /// <summary>
+        /// 坐席月接通明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<QuerySeatMonthCallDetailResp> QuerySeatMonthCallDetail(QuerySeatMonthCallDetailRequest dto);
     }
 }

+ 36 - 1
src/Hotline.Application/StatisticalReport/IOrderReportApplication.cs

@@ -11,6 +11,7 @@ namespace Hotline.Application.StatisticalReport
 {
     public interface IOrderReportApplication
     {
+        #region 宜宾、自贡
         /// <summary>
         /// 部门办件统计表
         /// </summary>
@@ -38,7 +39,39 @@ namespace Hotline.Application.StatisticalReport
         /// <param name="dto"></param>
         /// <returns></returns>
         ISugarQueryable<SelectOrderId> DepartmentalProcessingStatisticsDetailsList(DepartmentalProcessingStatisticsRequest dto);
+        #endregion
 
+        #region 泸州
+        /// <summary>
+        /// 部门办件统计表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<List<DepartmentalProcessingStatisticsDataDto>> LZDepartmentalProcessingStatisticsNew(DepartmentalProcessingStatisticsRequest dto);
+
+        /// <summary>
+        /// 部门办件统计表--子级---新
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<List<DepartmentalProcessingStatisticsDataDto>> LZDepartmentalProcessingChildStatisticsNew(DepartmentalProcessingStatisticsRequest dto);
+
+        /// <summary>
+        /// 部门办件统计表--明细---新
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<SelectOrderId> LZGetDepartmentalProcessingStatisticsListNew(DepartmentalProcessingStatisticsRequest dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 部门办件统计明细表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<SelectOrderId> LZDepartmentalProcessingStatisticsDetailsList(DepartmentalProcessingStatisticsRequest dto);
+        #endregion
+
+        #region 暂时弃用
         /// <summary>
         /// 部门办件统计表
         /// </summary>
@@ -60,6 +93,8 @@ namespace Hotline.Application.StatisticalReport
         /// <returns></returns>
         ISugarQueryable<SelectOrderId> GetDepartmentalProcessingStatisticsList(DepartmentalProcessingStatisticsRequest dto, CancellationToken cancellationToken);
 
+        #endregion
+
         /// <summary>
         /// 部门延期统计
         /// </summary>
@@ -88,7 +123,7 @@ namespace Hotline.Application.StatisticalReport
         /// <returns></returns>
         ISugarQueryable<Order> DepartmentAcceptanceTypeOrderList(DepartmentKeyWordRequest dto);
 
-        
+
         /// <summary>
         /// 受理类型统计
         /// </summary>

File diff suppressed because it is too large
+ 68 - 16
src/Hotline.Application/StatisticalReport/OrderReportApplication.cs


+ 34 - 0
src/Hotline.Application/Xthx/IXthxApplication.cs

@@ -0,0 +1,34 @@
+using Hotline.Share.Dtos.CallCenter;
+
+namespace Hotline.Application.Xthx
+{
+    public interface IXthxApplication
+    {
+        /// <summary>
+        /// 分机状态日志新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="Id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<string> AddOperationLogAsync(TelOperationXthxDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 分机状态变更新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="Id"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<string> AddOperationAsync(TelOperationXthxDto dto, string Id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 分机状态变更修改
+        /// </summary>
+        /// <param name="Id"></param>
+        /// <param name="UId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task UpdateOperationAsync(string Id, string UId, CancellationToken cancellationToken);
+    }
+}

+ 122 - 0
src/Hotline.Application/Xthx/XthxApplication.cs

@@ -0,0 +1,122 @@
+using MapsterMapper;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+using Hotline.CallCenter.Tels;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Caching.Interfaces;
+using Hotline.Users;
+
+namespace Hotline.Application.Xthx
+{
+    /// <summary>
+    /// 兴唐恒信
+    /// </summary>
+    public class XthxApplication : IXthxApplication, IScopeDependency
+    {
+
+        #region 注册
+
+        private readonly IRepository<TelOperationXthx> _telOperationXthxRepository;            //坐席动作类型
+        private readonly IRepository<TelOperationXthxLog> _telOperationXthxLogRepository;      //坐席动作类型
+        private readonly IRepository<User> _userRepository;                                    //用户
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;                //系统参数
+        private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
+
+        public XthxApplication(
+            IRepository<TelOperationXthx> telOperationXthxRepository,
+            IRepository<TelOperationXthxLog> telOperationXthxLogRepository,
+            IRepository<User> userRepository,
+            ISystemSettingCacheManager systemSettingCacheManager,
+            ISessionContext sessionContext,
+            IMapper mapper)
+        {
+            _telOperationXthxRepository = telOperationXthxRepository;
+            _telOperationXthxLogRepository = telOperationXthxLogRepository;
+            _userRepository = userRepository;
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _sessionContext = sessionContext;
+            _mapper = mapper;
+        }
+
+        #endregion
+
+        #region 分机状态日志新增
+
+        /// <summary>
+        /// 分机状态日志新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        public async Task<string> AddOperationLogAsync(TelOperationXthxDto dto, CancellationToken cancellationToken)
+        {
+            var log = _mapper.Map<TelOperationXthxLog>(dto);
+            log.Json = System.Text.Json.JsonSerializer.Serialize(dto);
+            return await _telOperationXthxLogRepository.AddAsync(log, cancellationToken);
+        }
+
+        #endregion
+
+        #region 分机状态变更新增
+
+        /// <summary>
+        /// 分机状态变更新增
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="Id">日志Id</param>
+        /// <param name="cancellationToken"></param>
+        public async Task<string> AddOperationAsync(TelOperationXthxDto dto, string Id, CancellationToken cancellationToken)
+        {
+            var newId = string.Empty;
+            var logTemp = await _telOperationXthxLogRepository.GetAsync(Id);
+            if (logTemp != null)
+            {
+                TelOperationXthx xthxAttr = new TelOperationXthx();
+                _mapper.Map(logTemp, xthxAttr);
+
+                var user = await _userRepository.GetAsync(x => x.StaffNo == dto.StaffNo && x.IsDeleted == false);
+                if (user != null)
+                {
+                    xthxAttr.UserId = user.Id;
+                    xthxAttr.UserName = user.Name;
+                }
+                //xthxAttr.TelId = "";
+                //xthxAttr.UserGroup = "";
+
+                xthxAttr.StartTime = logTemp.CreationTime;
+                xthxAttr.OperationStatus = dto.Status;
+                newId = await _telOperationXthxRepository.AddAsync(xthxAttr, cancellationToken);
+            }
+            return newId;
+        }
+
+        #endregion
+
+        #region 分机状态变更修改
+
+        /// <summary>
+        /// 分机状态变更修改
+        /// </summary>
+        /// <param name="Id">日志Id</param>
+        /// <param name="UId">更新Id</param>
+        /// <param name="cancellationToken"></param>
+        public async Task UpdateOperationAsync(string Id, string UId, CancellationToken cancellationToken)
+        {
+            var data = await _telOperationXthxRepository.GetAsync(UId);
+            if (data != null)
+            {
+                var logTemp = await _telOperationXthxLogRepository.GetAsync(Id);
+                if (logTemp != null)
+                {
+                    data.EndTime = logTemp.CreationTime;
+                    data.RestDuration = (data.EndTime.Value - data.StartTime.Value).TotalSeconds;
+                    await _telOperationXthxRepository.UpdateAsync(data);
+                }
+            }
+        }
+
+        #endregion
+
+    }
+}

+ 4 - 3
src/Hotline.Repository.SqlSugar/CallCenter/TrCallRecordRepository.cs

@@ -323,7 +323,7 @@ namespace Hotline.Repository.SqlSugar.CallCenter
                     Date = x.CreatedTime.ToString("yyyy-MM-dd"),
                     PersonCallInCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1", 1, 0)),
                     PersonCallInPutthroughCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1" && x.OnState == EOnState.On, 1, 0)),
-                    PersonRingOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1" && x.QueueTims > 0  && x.OnState == EOnState.NoOn, 1, 0)),//个人服务挂断
+                    //PersonRingOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1" && x.OnState == EOnState.NoOn, 1, 0)),//个人服务挂断
                     PersonQueueOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1" && x.QueueTims > 0 && x.RingTimes == 0 && x.OnState == EOnState.NoOn, 1, 0)),//个人服务队列挂断
                     PersonWaitOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length -1,1) =="1" && x.RingTimes>0 && x.OnState == EOnState.NoOn,1,0)) //个人服务等待挂断
                 })
@@ -341,7 +341,7 @@ namespace Hotline.Repository.SqlSugar.CallCenter
                     Date = x.CreatedTime.ToString("yyyy-MM-dd"),
                     EnterpriseCallInCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "2", 1, 0)),
                     EnterpriseCallInPutthroughCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "2" && x.OnState == EOnState.On, 1, 0)),
-                    EnterpriseRingOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "2" && x.QueueTims > 0 && x.RingTimes == 0 && x.OnState == EOnState.NoOn, 1, 0)), //企业挂断
+                    //EnterpriseRingOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "2" && x.QueueTims > 0 && x.RingTimes == 0 && x.OnState == EOnState.NoOn, 1, 0)), //企业挂断
                     EnterpriseQueueOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1" && x.QueueTims > 0 && x.RingTimes == 0 && x.OnState == EOnState.NoOn, 1, 0)),//个人服务队列挂断
                     EnterpriseWaitOffCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.IvrDtmf.Substring(x.IvrDtmf.Length - 1, 1) == "1" && x.RingTimes > 0 && x.OnState == EOnState.NoOn, 1, 0)) //个人服务等待挂断
                 })
@@ -385,8 +385,9 @@ namespace Hotline.Repository.SqlSugar.CallCenter
                     EnterpriseCallOutPutthroughCount = a.EnterpriseCallOutPutthroughCount,
                     AiVisitCallOutPutthroughCount = b.AiVisitCallOutPutthroughCount,
                     AiCallOutPutthroughCount = 0,
-                }).ToListAsync();
+                }).OrderBy(a=>a.Date).ToListAsync();
             return list;
         }
+
     }
 }

+ 542 - 139
src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs

@@ -31,6 +31,12 @@ using Hotline.Statistics;
 using System.Dynamic;
 using Hotline.Share.Dtos;
 using Org.BouncyCastle.Crypto.Fpe;
+using Org.BouncyCastle.Utilities.Zlib;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+using System;
+using static System.Runtime.CompilerServices.RuntimeHelpers;
+using Hotline.Identity.Accounts;
+using Hotline.Share.Dtos.Enterprise;
 
 namespace Hotline.Repository.SqlSugar.Orders
 {
@@ -376,7 +382,7 @@ namespace Hotline.Repository.SqlSugar.Orders
         /// <param name="dto"></param>
         /// <returns></returns>
         public async Task<DataTable> OrderHotspotTimeExport(TimeSharingPagedKeywordRequest dto)
-		{
+        {
             List<int> dts = new();
             for (int i = 0; i < 24; i++)
             {
@@ -429,7 +435,7 @@ namespace Hotline.Repository.SqlSugar.Orders
         /// <param name="dto"></param>
         /// <returns></returns>
         public async Task<object> OrderAcceptanceTime(TimeSharingPagedKeywordRequest dto)
-		{
+        {
             List<int> dts = new List<int>();
             for (int i = 0; i < 24; i++)
             {
@@ -450,7 +456,7 @@ namespace Hotline.Repository.SqlSugar.Orders
 
             var listOrg = Db.Queryable<SystemDicData>()
               .LeftJoin(listOrder, (s, p) => s.DicDataValue == p.AcceptTypeCode)
-			  .Where((s, p) => s.DicTypeCode == "AcceptType")
+              .Where((s, p) => s.DicTypeCode == "AcceptType")
               .GroupBy((s, p) => s.DicDataValue).GroupBy((s, p) => s.DicDataName)
               .GroupBy((s, p) => p.Hour)
               .OrderBy((s, p) => s.DicDataValue)
@@ -464,34 +470,34 @@ namespace Hotline.Repository.SqlSugar.Orders
               .MergeTable();
 
             var list = await listHour.LeftJoin(listOrg, (x, p) => x.ColumnName.ToString() == p.Hour)
-	            //.Where((x,p)=> !string.IsNullOrEmpty(p.DicDataValue))
-	            .OrderBy((x,p) =>  x.ColumnName )
+                //.Where((x,p)=> !string.IsNullOrEmpty(p.DicDataValue))
+                .OrderBy((x, p) => x.ColumnName)
                .Select((x, p) => new
                {
                    Hour = x.ColumnName.ToString() + ":00 - " + x.ColumnName.ToString() + ":59",
                    p.DicDataValue,
                    p.count
                })
-	            .MergeTable()
-               .ToPivotTableAsync(p => p.DicDataValue, p => p.Hour, p =>  p.Sum(x => x.count));
+                .MergeTable()
+               .ToPivotTableAsync(p => p.DicDataValue, p => p.Hour, p => p.Sum(x => x.count));
 
             var col = list.Columns.Contains("Column1");
             if (col)
             {
-				list.Columns.Remove("Column1");
-			}
+                list.Columns.Remove("Column1");
+            }
             var dtList = list.Clone();
 
-			if (await Db.Queryable<StatisticsPurTypeSatisfied>().Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime).AnyAsync())
+            if (await Db.Queryable<StatisticsPurTypeSatisfied>().Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime).AnyAsync())
             {
-				// 老系统数据
-				var oldData = await Db.Queryable<StatisticsPurTypeSatisfied>()
+                // 老系统数据
+                var oldData = await Db.Queryable<StatisticsPurTypeSatisfied>()
                     .Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime)
                     .GroupBy(x => x.Month)
                     .Select(x => new
                     {
-						Hour = x.Month.ToString() + ":00 - " + x.Month.ToString() + ":59",
-						_1 = SqlFunc.AggregateSum(x.Opinion),
+                        Hour = x.Month.ToString() + ":00 - " + x.Month.ToString() + ":59",
+                        _1 = SqlFunc.AggregateSum(x.Opinion),
                         _2 = SqlFunc.AggregateSum(x.PeopleHelp),
                         _3 = SqlFunc.AggregateSum(x.PollutionReporting),
                         _10 = SqlFunc.AggregateSum(x.Consult),
@@ -505,62 +511,432 @@ namespace Hotline.Repository.SqlSugar.Orders
                         _SB = SqlFunc.AggregateSum(x.Declare),
                     }).ToDataTableAsync();
 
-				#region  处理数据
-
-				foreach (DataColumn column in dtList.Columns)
-				{
-					if (column.ColumnName != "Hour") column.DataType = typeof(Int64);
-				}
-
-				for (int i = 0; i < list.Rows.Count; i++)
-				{
-					DataRow newRow = dtList.NewRow();
-					for (int j = 0; j < list.Columns.Count; j++)
-					{
-						if (oldData.Columns[j].ColumnName == "Hour")
-						{
-							newRow[j] = list.Rows[i][j];
-						}
-						else
-						{
-							var num = string.IsNullOrEmpty(list.Rows[i][j].ToString()) ? 0 : int.Parse(list.Rows[i][j].ToString());
-							var oldRow = oldData.Select("Hour ='" + list.Rows[i]["Hour"] + "'").FirstOrDefault();
+                #region  处理数据
+
+                foreach (DataColumn column in dtList.Columns)
+                {
+                    if (column.ColumnName != "Hour") column.DataType = typeof(Int64);
+                }
+
+                for (int i = 0; i < list.Rows.Count; i++)
+                {
+                    DataRow newRow = dtList.NewRow();
+                    for (int j = 0; j < list.Columns.Count; j++)
+                    {
+                        if (oldData.Columns[j].ColumnName == "Hour")
+                        {
+                            newRow[j] = list.Rows[i][j];
+                        }
+                        else
+                        {
+                            var num = string.IsNullOrEmpty(list.Rows[i][j].ToString()) ? 0 : int.Parse(list.Rows[i][j].ToString());
+                            var oldRow = oldData.Select("Hour ='" + list.Rows[i]["Hour"] + "'").FirstOrDefault();
                             var oldColName = "_" + dtList.Columns[j].ColumnName;
                             var allNum = oldRow == null ? num : oldRow.Field<Int64>(oldColName) + num;
-							newRow[j] = allNum;
-						}
-					}
-					dtList.Rows.Add(newRow);
-				}
-
-				#endregion
-			}
-			return ToDynamicList(dtList);
+                            newRow[j] = allNum;
+                        }
+                    }
+                    dtList.Rows.Add(newRow);
+                }
+
+                #endregion
+            }
+            return ToDynamicList(dtList);
+        }
+
+        /// <summary>
+        /// 中心报表统计
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <returns></returns>
+        public async Task<DataTable> CenterReportFormsStatistics(DateTime StartTime, DateTime EndTime)
+        {
+            var listAny = await Db.Queryable<StatisticsCenter>()
+                .Where(x => x.Time >= StartTime && x.Time <= EndTime).AnyAsync();
+            if (!listAny)
+            {
+                return null;
+            }
+            var list = await Db.Queryable<StatisticsCenter>()
+                .Where(x => x.Time >= StartTime && x.Time <= EndTime)
+                .Select(x => new StatisticsCenter
+                {
+                    CallNum = SqlFunc.AggregateSum(x.CallNum),
+                    FamilyCallNum = SqlFunc.AggregateSum(x.FamilyCallNum),
+                    FamilyCallConnNum = SqlFunc.AggregateSum(x.FamilyCallConnNum),
+                    CallInConn = SqlFunc.AggregateSum(x.CallInConn),
+                    CallInNotConn = SqlFunc.AggregateSum(x.CallInNotConn),
+                    CallInIVR = SqlFunc.AggregateSum(x.CallInIVR),
+                    CallInQueue = SqlFunc.AggregateSum(x.CallInQueue),
+                    VisitAlreadyNum = SqlFunc.AggregateSum(x.VisitAlreadyNum),
+                    VisitWaitNum = SqlFunc.AggregateSum(x.VisitWaitNum),
+                    SatisfactionSeat = SqlFunc.AggregateAvg(x.SatisfactionSeat),
+                    SatisfactionDepartment = SqlFunc.AggregateAvg(x.SatisfactionDepartment),
+                    OrderAllNum = SqlFunc.AggregateSum(x.OrderAllNum),
+                    OrderNormalNum = SqlFunc.AggregateSum(x.OrderNormalNum),
+                    OrderInvalidNum = SqlFunc.AggregateSum(x.OrderInvalidNum),
+                    OrderEndNum = SqlFunc.AggregateSum(x.OrderEndNum),
+                    OrderWaitNum = SqlFunc.AggregateSum(x.OrderWaitNum),
+                    FromAllNum = SqlFunc.AggregateSum(x.FromAllNum),
+                    FromPhone = SqlFunc.AggregateSum(x.FromPhone),
+                    FromInternet = SqlFunc.AggregateSum(x.FromInternet),
+                    FromOther = SqlFunc.AggregateSum(x.FromOther),
+                    FromSelfBuild = SqlFunc.AggregateSum(x.FromSelfBuild),
+                    FromWeChat = SqlFunc.AggregateSum(x.FromWeChat),
+                    FromWeibo = SqlFunc.AggregateSum(x.FromWeibo),
+                    FromApp = SqlFunc.AggregateSum(x.FromApp),
+                    FromSmartYibin = SqlFunc.AggregateSum(x.FromSmartYibin),
+                    FromPlatformZZ = SqlFunc.AggregateSum(x.FromPlatformZZ),
+                    FromPlatform12328 = SqlFunc.AggregateSum(x.FromPlatform12328),
+                    FromMayorNetizen = SqlFunc.AggregateSum(x.FromMayorNetizen),
+                    FromPlatformRMT = SqlFunc.AggregateSum(x.FromPlatformRMT),
+                    FromPlatformProvince = SqlFunc.AggregateSum(x.FromPlatformProvince),
+                    FromPlatformZMHD = SqlFunc.AggregateSum(x.FromPlatformZMHD),
+                    FromPlatformYBS = SqlFunc.AggregateSum(x.FromPlatformYBS),
+                    FromPlatformSZHZ = SqlFunc.AggregateSum(x.FromPlatformSZHZ),
+                    FromPlatform110 = SqlFunc.AggregateSum(x.FromPlatform110),
+                    FromPlatformBBCS = SqlFunc.AggregateSum(x.FromPlatformBBCS),
+                    FromPlatformIYB = SqlFunc.AggregateSum(x.FromPlatformIYB)
+                })
+                .ToDataTableAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 中心报表统计(受理类型)
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <returns></returns>
+        public async Task<DataTable> CenterReportPurTypeStatistics(DateTime StartTime, DateTime EndTime)
+        {
+            var any = await Db.Queryable<StatisticsPurTypeSatisfied>()
+                .Where(x => x.Time >= StartTime && x.Time <= EndTime).AnyAsync();
+            if (!any)
+            {
+                return null;
+            }
+
+            var list = await Db.Queryable<StatisticsPurTypeSatisfied>()
+                .Where(x => x.Time >= StartTime && x.Time <= EndTime)
+                .Select(x => new StatisticsPurTypeSatisfied
+                {
+                    Total = SqlFunc.AggregateSum(x.Total),
+                    Consult = SqlFunc.AggregateSum(x.Consult),
+                    Suggest = SqlFunc.AggregateSum(x.Suggest),
+                    SeekHelp = SqlFunc.AggregateSum(x.SeekHelp),
+                    Praise = SqlFunc.AggregateSum(x.Praise),
+                    Report = SqlFunc.AggregateSum(x.Report),
+                    Complaint = SqlFunc.AggregateSum(x.Complaint),
+                    Rests = SqlFunc.AggregateSum(x.Rests),
+                    PollutionReporting = SqlFunc.AggregateSum(x.PollutionReporting),
+                    PeopleHelp = SqlFunc.AggregateSum(x.PeopleHelp),
+                    Opinion = SqlFunc.AggregateSum(x.Opinion),
+                    Epidemic = SqlFunc.AggregateSum(x.Epidemic),
+                    Declare = SqlFunc.AggregateSum(x.Declare),
+                    Invalid = SqlFunc.AggregateSum(x.Invalid)
+                })
+                .ToDataTableAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 中心报表统计(部门办件)
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <param name="Type">市级部门 区县部门</param>
+        /// <returns></returns>
+        public async Task<DataTable> CenterReportDepartStatistics(DateTime StartTime, DateTime EndTime, string Type)
+        {
+            var any = await Db.Queryable<StatisticsDepart>()
+                .LeftJoin<SystemOrganize>((x, so) => x.DepartmentId == so.oldBmid)
+                .Where(x => x.Time >= StartTime && x.Time <= EndTime && x.Type == Type).AnyAsync();
+            if (!any)
+            {
+                return null;
+            }
+
+
+            var list = await Db.Queryable<StatisticsDepart>()
+                .LeftJoin<SystemOrganize>((x, so) => x.DepartmentId == so.oldBmid)
+                .Where(x => x.Time >= StartTime && x.Time <= EndTime && x.Type == Type)
+                .GroupBy((x, so) => new
+                {
+                    OrgCode = so.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                })
+                .Select((x, so) => new
+                {
+                    OrgCode = so.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                    OrderAllNum = SqlFunc.AggregateSum(x.OrderAllNum)
+                })
+                .MergeTable()
+                .LeftJoin<SystemOrganize>((temp, so) => temp.OrgCode == so.Id)
+                .Select((temp, so) => new
+                {
+                    OrgCode = temp.OrgCode,
+                    Name = so.Name,
+                    OrderAllNum = temp.OrderAllNum
+                })
+                .ToDataTableAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 中心报表统计(新增加的)
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <returns></returns>
+        public async Task<DataTable> CenterReportNewStatistics(DateTime StartTime, DateTime EndTime)
+        {
+            var any = await Db.Queryable<StatisticsBaseInfo>()
+                .Where(x => x.AddDate >= StartTime && x.AddDate <= EndTime).AnyAsync();
+
+            if (!any)
+            {
+                return null;
+            }
+            var list = await Db.Queryable<StatisticsBaseInfo>()
+                .Where(x => x.AddDate >= StartTime && x.AddDate <= EndTime)
+                .Select(x => new
+                {
+                    // 回访情况
+                    AllCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false, 1, 0),                                                                                         // 总体条数
+                    SatisfactionCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.AppTypeName.Contains("不满意") == true, 1, 0),                                    // 总体不满意条数
+                    DepartCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.DepartID != 23, 1, 0),                                                                  // 部门总体条数
+                    DepartSatisfactionCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.DepartID != 23 && x.AppTypeName.Contains("不满意") == true, 1, 0),          // 部门总体不满意条数
+                    CenterCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.DepartID == 23, 1, 0),                                                                  // 中心总体条数
+                    CenterSatisfactionCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.DepartID == 23 && x.AppTypeName.Contains("不满意") == true, 1, 0),          // 中心总体不满意条数
+                    CityCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.TypeName == "市直部门", 1, 0),                                                            // 市级总体条数
+                    CitySatisfactionCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.TypeName == "市直部门" && x.AppTypeName.Contains("不满意") == true, 1, 0),    // 市级总体不满意条数
+                    CountyCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.TypeName == "区县部门", 1, 0),                                                          // 区县总体条数
+                    CountySatisfactionCount = SqlFunc.IIF(string.IsNullOrEmpty(x.AppTypeName) == false && x.TypeName == "区县部门" && x.AppTypeName.Contains("不满意") == true, 1, 0),   // 区县总体不满意条数
+
+                    // 信件情况
+                    OrderAllCount = 1,                                                   // 来件总计
+                    OrderAlready = SqlFunc.IIF(x.StateFlag == "办理完成", 1, 0),         // 已办件数
+                    OrderWait = SqlFunc.IIF(x.StateFlag != "办理完成", 1, 0),            // 在办件数
+                    OrderCenterCount = SqlFunc.IIF(x.DepartID == 23, 1, 0),              // 中心受理
+                    OrderCityCount = SqlFunc.IIF(x.TypeName == "市直部门", 1, 0),        // 市直受理
+                    OrderCountyCount = SqlFunc.IIF(x.TypeName == "区县部门", 1, 0),      // 区县受理
+
+                    // 按时办结情况
+                    CompleteOnTime = SqlFunc.IIF(x.CompleteOnTime == 1, 1, 0),           // 按时办结
+                    DepartCompleteOnTime = SqlFunc.IIF(x.DepartID != 23 && x.CompleteOnTime == 1, 1, 0),           // 部门按时办结
+                    CityCompleteOnTime = SqlFunc.IIF(x.TypeName == "市直部门" && x.CompleteOnTime == 1, 1, 0),     // 市级部门按时办结
+                    CountyCompleteOnTime = SqlFunc.IIF(x.TypeName == "区县部门" && x.CompleteOnTime == 1, 1, 0),   // 县(区)按时办结
+                    CenterCompleteOnTime = SqlFunc.IIF(x.DepartID == 23 && x.CompleteOnTime == 1, 1, 0),           // 12345中心按时办结
+                    DepartAlready = SqlFunc.IIF(x.StateFlag == "办理完成" && x.DepartID != 23, 1, 0),              // 部门已办
+                    CenterAlready = SqlFunc.IIF(x.StateFlag == "办理完成" && x.DepartID == 23, 1, 0),              // 中心已办
+                    CityAlready = SqlFunc.IIF(x.StateFlag == "办理完成" && x.TypeName == "市直部门", 1, 0),        // 市直已办
+                    CountyAlready = SqlFunc.IIF(x.StateFlag == "办理完成" && x.TypeName == "区县部门", 1, 0),      // 区县已办
+
+                    // 办理时效情况
+                    OrderWorkTime = SqlFunc.IIF(x.StateFlag == "办理完成", x.CallHandToEndWork, 0),                // 总办理时长
+                    CityWorkTime = SqlFunc.IIF(x.TypeName == "市直部门", x.CallHandToEndWork, 0),                  // 市直办理时长
+                    CountyWorkTime = SqlFunc.IIF(x.TypeName == "区县部门", x.CallHandToEndWork, 0),                // 区县办理时长
+                    CenterWorkTime = SqlFunc.IIF(x.DepartID == 23, x.CallHandToEndWork, 0),                        // 中心办理时长
+
+                    // 企业服务办件情况
+                    EnterpriseAllCount = SqlFunc.IIF(x.PersonnelType == "企业", 1, 0),
+                    EnterpriseAlready = SqlFunc.IIF(x.PersonnelType == "企业" && x.StateFlag == "办理完成", 1, 0),                          // 已办信件
+                    EnterpriseWait = SqlFunc.IIF(x.PersonnelType == "企业" && x.StateFlag != "办理完成", 1, 0),                             // 在办信件
+                    EnterpriseVisit = SqlFunc.IIF(x.PersonnelType == "企业" && string.IsNullOrWhiteSpace(x.AppTypeName) == false, 1, 0),    // 回访总量
+                    EnterpriseSatisfaction = SqlFunc.IIF(x.PersonnelType == "企业" && x.AppTypeName.Contains("不满意") == false, 1, 0),     // 满意量
+                    EnterpriseCenter = SqlFunc.IIF(x.PersonnelType == "企业" && x.DepartID == 23, 1, 0),                                    // 12345直办件
+                    EnterpriseCity = SqlFunc.IIF(x.PersonnelType == "企业" && x.TypeName == "市直部门", 1, 0),                              // 市级部门办件
+                    EnterpriseCounty = SqlFunc.IIF(x.PersonnelType == "企业" && x.TypeName == "区县部门", 1, 0),                            // 县(区)办件
+                    EnterpriseCitySatisfaction = SqlFunc.IIF(x.PersonnelType == "企业" && x.TypeName == "市直部门" && x.AppTypeName.Contains("不满意") == false, 1, 0),     // 市级部门满意量
+                    EnterpriseCountySatisfaction = SqlFunc.IIF(x.PersonnelType == "企业" && x.TypeName == "区县部门" && x.AppTypeName.Contains("不满意") == false, 1, 0),   // 县(区)满意量
+                    EnterpriseWorkTime = SqlFunc.IIF(x.PersonnelType == "企业" && x.StateFlag == "办理完成", x.CallHandToEndWork, 0),        // 企业信件办理平均时长(工作日)
+                    EnterpriseCityWorkTime = SqlFunc.IIF(x.PersonnelType == "企业" && x.TypeName == "市直部门", x.CallHandToEndWork, 0),     // 市级部门企业信件平均时长(工作日)		
+                    EnterpriseCountyWorkTime = SqlFunc.IIF(x.PersonnelType == "企业" && x.TypeName == "区县部门", x.CallHandToEndWork, 0),   // 县(区)企业信件平均时长(工作日)
+                    EnterpriseCenterWorkTime = SqlFunc.IIF(x.PersonnelType == "企业" && x.DepartID == 23, x.CallHandToEndWork, 0),           // 12345直办企业信件平均时长(工作日)
+                    EnterpriseConsult = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "咨询", 1, 0),           // 企业咨询
+                    EnterpriseSeekHelp = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "求助", 1, 0),          // 企业求助
+                    EnterpriseComplaint = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "投诉", 1, 0),         // 企业投诉
+                    EnterpriseReport = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "举报", 1, 0),            // 企业举报
+                    EnterpriseSuggest = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "建议", 1, 0),           // 企业建议
+                    EnterprisePraise = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "表扬", 1, 0),            // 企业表扬
+                    EnterpriseOpinion = SqlFunc.IIF(x.PersonnelType == "企业" && x.PurName == "意见", 1, 0)            // 企业意见
+                })
+                .MergeTable()
+                .Select(x => new
+                {
+                    // 回访情况
+                    AllCount = SqlFunc.AggregateSum(x.AllCount),
+                    SatisfactionCount = SqlFunc.AggregateSum(x.SatisfactionCount),
+                    DepartCount = SqlFunc.AggregateSum(x.DepartCount),
+                    DepartSatisfactionCount = SqlFunc.AggregateSum(x.DepartSatisfactionCount),
+                    CenterCount = SqlFunc.AggregateSum(x.CenterCount),
+                    CenterSatisfactionCount = SqlFunc.AggregateSum(x.CenterSatisfactionCount),
+                    CityCount = SqlFunc.AggregateSum(x.CityCount),
+                    CitySatisfactionCount = SqlFunc.AggregateSum(x.CitySatisfactionCount),
+                    CountyCount = SqlFunc.AggregateSum(x.CountyCount),
+                    CountySatisfactionCount = SqlFunc.AggregateSum(x.CountySatisfactionCount),
+
+                    // 信件情况
+                    OrderAllCount = SqlFunc.AggregateSum(x.OrderAllCount),
+                    OrderAlready = SqlFunc.AggregateSum(x.OrderAlready),
+                    OrderWait = SqlFunc.AggregateSum(x.OrderWait),
+                    OrderCenterCount = SqlFunc.AggregateSum(x.OrderCenterCount),
+                    OrderCityCount = SqlFunc.AggregateSum(x.OrderCityCount),
+                    OrderCountyCount = SqlFunc.AggregateSum(x.OrderCountyCount),
+
+                    // 按时办结情况
+                    CompleteOnTime = SqlFunc.AggregateSum(x.CompleteOnTime),
+                    DepartCompleteOnTime = SqlFunc.AggregateSum(x.DepartCompleteOnTime),
+                    CityCompleteOnTime = SqlFunc.AggregateSum(x.CityCompleteOnTime),
+                    CountyCompleteOnTime = SqlFunc.AggregateSum(x.CountyCompleteOnTime),
+                    CenterCompleteOnTime = SqlFunc.AggregateSum(x.CenterCompleteOnTime),
+                    DepartAlready = SqlFunc.AggregateSum(x.DepartAlready),
+                    CenterAlready = SqlFunc.AggregateSum(x.CenterAlready),
+                    CityAlready = SqlFunc.AggregateSum(x.CityAlready),
+                    CountyAlready = SqlFunc.AggregateSum(x.CountyAlready),
+
+                    // 办理时效情况
+                    OrderWorkTime = SqlFunc.AggregateSum(x.OrderWorkTime),
+                    CityWorkTime = SqlFunc.AggregateSum(x.CityWorkTime),
+                    CountyWorkTime = SqlFunc.AggregateSum(x.CountyWorkTime),
+                    CenterWorkTime = SqlFunc.AggregateSum(x.CenterWorkTime),
+
+                    // 企业服务办件情况
+                    EnterpriseAllCount = SqlFunc.AggregateSum(x.EnterpriseAllCount),
+                    EnterpriseAlready = SqlFunc.AggregateSum(x.EnterpriseAlready),
+                    EnterpriseWait = SqlFunc.AggregateSum(x.EnterpriseWait),
+                    EnterpriseVisit = SqlFunc.AggregateSum(x.EnterpriseVisit),
+                    EnterpriseSatisfaction = SqlFunc.AggregateSum(x.EnterpriseSatisfaction),
+                    EnterpriseCenter = SqlFunc.AggregateSum(x.EnterpriseCenter),
+                    EnterpriseCity = SqlFunc.AggregateSum(x.EnterpriseCity),
+                    EnterpriseCounty = SqlFunc.AggregateSum(x.EnterpriseCounty),
+                    EnterpriseCitySatisfaction = SqlFunc.AggregateSum(x.EnterpriseCitySatisfaction),
+                    EnterpriseCountySatisfaction = SqlFunc.AggregateSum(x.EnterpriseCountySatisfaction),
+                    EnterpriseWorkTime = SqlFunc.AggregateSum(x.EnterpriseWorkTime),
+                    EnterpriseCityWorkTime = SqlFunc.AggregateSum(x.EnterpriseCityWorkTime),
+                    EnterpriseCountyWorkTime = SqlFunc.AggregateSum(x.EnterpriseCountyWorkTime),
+                    EnterpriseCenterWorkTime = SqlFunc.AggregateSum(x.EnterpriseCenterWorkTime),
+                    EnterpriseConsult = SqlFunc.AggregateSum(x.EnterpriseConsult),
+                    EnterpriseSeekHelp = SqlFunc.AggregateSum(x.EnterpriseSeekHelp),
+                    EnterpriseComplaint = SqlFunc.AggregateSum(x.EnterpriseComplaint),
+                    EnterpriseReport = SqlFunc.AggregateSum(x.EnterpriseReport),
+                    EnterpriseSuggest = SqlFunc.AggregateSum(x.EnterpriseSuggest),
+                    EnterprisePraise = SqlFunc.AggregateSum(x.EnterprisePraise),
+                    EnterpriseOpinion = SqlFunc.AggregateSum(x.EnterpriseOpinion)
+                })
+                .MergeTable()
+                .Select(x => new
+                {
+                    AllCount = x.AllCount,
+                    // 回访情况
+                    SatisfactionRate = x.AllCount > 0 ? SqlFunc.Round((decimal)(x.AllCount - x.SatisfactionCount) * 100 / (decimal)x.AllCount, 2) : 0,                     //总体满意率
+                    DepartSatisfactionRate = x.DepartCount > 0 ? SqlFunc.Round((decimal)(x.DepartCount - x.DepartSatisfactionCount) * 100 / (decimal)x.DepartCount, 2) : 0,   //部门满意率
+                    CenterSatisfactionRate = x.CenterCount > 0 ? SqlFunc.Round((decimal)(x.CenterCount - x.CenterSatisfactionCount) * 100 / (decimal)x.CenterCount, 2) : 0,   //12345中心满意率
+                    CitySatisfactionRate = x.CityCount > 0 ? SqlFunc.Round((decimal)(x.CityCount - x.CitySatisfactionCount) * 100 / (decimal)x.CityCount, 2) : 0,           //市级部门满意率
+                    CountySatisfactionRate = x.CountyCount > 0 ? SqlFunc.Round((decimal)(x.CountyCount - x.CountySatisfactionCount) * 100 / (decimal)x.CountyCount, 2) : 0,   //县(区)满意率
+                    SatisfactionCount = x.SatisfactionCount,                                                                                          //不满意件
+                    CenterSatisfactionCount = x.CenterSatisfactionCount,                                                                              //市级部门不满意件
+                    CountySatisfactionCount = x.CountySatisfactionCount,
+                    CitySatisfactionCount = x.CitySatisfactionCount,                                                                           //县(区)不满意件
+                    CityCount = x.CityCount,
+                    CountyCount = x.CountyCount,
+
+                    // 信件情况
+                    OrderAllCount = x.OrderAllCount,                                                                    // 来件总数
+                    OrderAlready = x.OrderAlready,                                                                      // 已办件数
+                    OrderWait = x.OrderWait,                                                                            // 在办件数
+                    OrderAlreadyRate = x.OrderAllCount > 0 ? SqlFunc.Round((decimal)(x.OrderAlready) * 100 / (decimal)x.OrderAllCount, 2) : 0,    //总体办结率
+                    OrderCityCount = x.OrderCityCount,                                                                  //市级部门受理
+                    OrderCountyCount = x.OrderCountyCount,                                                              //县(区)受理
+                    OrderCenterCount = x.OrderCenterCount,                                                              //12345中心受理
+                    //OrderCityRate = SqlFunc.Round((decimal)(x.OrderCityCount) * 100 / (decimal)x.OrderAllCount, 2),     //市级部门受理占比                                                        
+                    //OrderCountyRate = SqlFunc.Round((decimal)(x.OrderCountyCount) * 100 / (decimal)x.OrderAllCount, 2), //县(区)受理占比                                                        
+                    //OrderCenterRate = SqlFunc.Round((decimal)(x.OrderCenterCount) * 100 / (decimal)x.OrderAllCount, 2), //12345中心受理占比
+
+                    // 按时办结情况
+                    CompleteOnTime = x.CompleteOnTime,
+                    DepartCompleteOnTime = x.DepartCompleteOnTime,
+                    CityCompleteOnTime = x.CityCompleteOnTime,
+                    CountyCompleteOnTime = x.CountyCompleteOnTime,
+                    CenterCompleteOnTime = x.CenterCompleteOnTime,
+                    CityAlready = x.CityAlready,
+                    CountyAlready = x.CountyAlready,
+                    CenterAlready = x.CenterAlready,
+                    //CompleteOnTimeRate = SqlFunc.Round((decimal)(x.CompleteOnTime) * 100 / (decimal)x.OrderAlready, 2),                 //总体按时办结率
+                    //DepartCompleteOnTimeRate = SqlFunc.Round((decimal)(x.DepartCompleteOnTime) * 100 / (decimal)x.DepartAlready, 2),    //部门按时办结率
+                    //CityCompleteOnTimeRate = SqlFunc.Round((decimal)(x.CityCompleteOnTime) * 100 / (decimal)x.CityAlready, 2),          //市级部门按时办结率
+                    //CountyCompleteOnTimeRate = SqlFunc.Round((decimal)(x.CountyCompleteOnTime) * 100 / (decimal)x.CountyAlready, 2),    //县(区)按时办结率
+                    //CenterCompleteOnTimeRate = SqlFunc.Round((decimal)(x.CenterCompleteOnTime) * 100 / (decimal)x.CenterAlready, 2),    //12345中心按时办结率
+
+                    // 办理时效情况
+                    OrderWorkTime = x.OrderWorkTime,
+                    CityWorkTime = x.CityWorkTime,
+                    CountyWorkTime = x.CountyWorkTime,
+                    CenterWorkTime = x.CenterWorkTime,
+                    OrderWorkTimeRate = x.OrderAlready > 0 ? SqlFunc.Round((decimal)(x.OrderWorkTime) / (decimal)x.OrderAlready / 60 / 8, 2) : 0,          //信件办理平均时长(工作日)
+                    CityWorkTimeRate = x.CityAlready > 0 ? SqlFunc.Round((decimal)(x.CityWorkTime) / (decimal)x.CityAlready / 60 / 8, 2) : 0,             //市级部门平均时长(工作日)
+                    CountyWorkTimeRate = x.CountyAlready > 0 ? SqlFunc.Round((decimal)(x.CountyWorkTime) / (decimal)x.CountyAlready / 60 / 8, 2) : 0,       //县(区)平均时长(工作日)
+                    CenterWorkTimeRate = x.CenterAlready > 0 ? SqlFunc.Round((decimal)(x.CenterWorkTime) / (decimal)x.CenterAlready / 60 / 8, 2) : 0,        //12345中心平均时长(工作日)
+
+                    // 企业服务办件情况
+                    EnterpriseAllCount = x.EnterpriseAllCount,
+                    EnterpriseAlready = x.EnterpriseAlready,                                                                                            //已办信件
+                    EnterpriseWait = x.EnterpriseWait,                                                                                                  //在办信件
+                    EnterpriseVisit = x.EnterpriseVisit,                                                                                                //回访总量
+                    EnterpriseSatisfactionRate = x.EnterpriseVisit > 0 ? SqlFunc.Round((decimal)(x.EnterpriseSatisfaction) * 100 / (decimal)x.EnterpriseVisit, 2) : 0,              //总满意率
+                    EnterpriseCenter = x.EnterpriseCenter,                                                                                              //12345直办件
+                    EnterpriseCity = x.EnterpriseCity,                                                                                                  //市级部门办件
+                    EnterpriseCounty = x.EnterpriseCounty,                                                                                              //县(区)办件
+                    EnterpriseDisSatisfaction = x.EnterpriseVisit - x.EnterpriseSatisfaction,                                                           //不满意信件总量
+                    EnterpriseCitySatisfaction = x.EnterpriseCitySatisfaction,
+                    EnterpriseCitySatisfactionRate = x.EnterpriseCity > 0 ? SqlFunc.Round((decimal)(x.EnterpriseCitySatisfaction) * 100 / (decimal)x.EnterpriseCity, 2) : 0,       //市级部门满意率
+                    EnterpriseCountySatisfaction = x.EnterpriseCountySatisfaction,
+                    EnterpriseCountySatisfactionRate = x.EnterpriseCounty > 0 ? SqlFunc.Round((decimal)(x.EnterpriseCountySatisfaction) * 100 / (decimal)x.EnterpriseCounty, 2) : 0, //县(区)满意率
+                    EnterpriseWorkTime = x.EnterpriseWorkTime,
+                    EnterpriseCityWorkTime = x.EnterpriseCityWorkTime,
+                    EnterpriseCountyWorkTime = x.EnterpriseCountyWorkTime,
+                    EnterpriseCenterWorkTime = x.EnterpriseCenterWorkTime,
+                    //EnterpriseWorkTime = SqlFunc.Round((decimal)(x.EnterpriseWorkTime) / (decimal)x.EnterpriseAlready / 60 / 8, 2),                     //企业信件办理平均时长(工作日)	
+                    //EnterpriseCityWorkTime = SqlFunc.Round((decimal)(x.EnterpriseCityWorkTime) / (decimal)x.EnterpriseCenter / 60 / 8, 2),              //市级部门企业信件平均时长(工作日)
+                    //EnterpriseCountyWorkTime = SqlFunc.Round((decimal)(x.EnterpriseCountyWorkTime) / (decimal)x.EnterpriseCounty / 60 / 8, 2),          //县(区)企业信件平均时长(工作日)
+                    //EnterpriseCenterWorkTime = SqlFunc.Round((decimal)(x.EnterpriseCenterWorkTime) / (decimal)x.EnterpriseCenter / 60 / 8, 2),          //12345直办企业信件平均时长(工作日)
+                    EnterpriseConsult = x.EnterpriseConsult,                                                                                            //咨询
+                    EnterpriseSeekHelp = x.EnterpriseSeekHelp,                                                                                          //求助
+                    EnterpriseComplaint = x.EnterpriseComplaint,                                                                                        //投诉
+                    EnterpriseReport = x.EnterpriseReport,                                                                                              //举报
+                    EnterpriseSuggest = x.EnterpriseSuggest,                                                                                            //建议
+                    EnterprisePraise = x.EnterprisePraise,                                                                                              //表扬
+                    EnterpriseOpinion = x.EnterprisePraise                                                                                              //意见
+                })
+                .ToDataTableAsync();
+            return list;
+        }
+
+        public static List<dynamic> ToDynamicList(DataTable dt)
+        {
+            List<dynamic> dynamicList = new List<dynamic>();
+
+            foreach (DataRow row in dt.Rows)
+            {
+                dynamic dynamicObj = new ExpandoObject();
+                var expandoDict = (IDictionary<string, object>)dynamicObj;
+                foreach (DataColumn column in dt.Columns)
+                {
+                    expandoDict[column.ColumnName] = row[column];
+                }
+                dynamicList.Add(dynamicObj);
+            }
+
+            return dynamicList;
         }
-		public static List<dynamic> ToDynamicList( DataTable dt)
-		{
-			List<dynamic> dynamicList = new List<dynamic>();
-
-			foreach (DataRow row in dt.Rows)
-			{
-				dynamic dynamicObj = new ExpandoObject();
-				var expandoDict = (IDictionary<string, object>)dynamicObj;
-				foreach (DataColumn column in dt.Columns)
-				{
-					expandoDict[column.ColumnName] = row[column];
-				}
-				dynamicList.Add(dynamicObj);
-			}
-
-			return dynamicList;
-		}
-
-		/// <summary>
-		/// 受理类型分时统计---导出
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		public async Task<DataTable> OrderAcceptanceTimeExport(TimeSharingPagedKeywordRequest dto, List<Kv> title)
+
+        /// <summary>
+        /// 受理类型分时统计---导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        public async Task<DataTable> OrderAcceptanceTimeExport(TimeSharingPagedKeywordRequest dto, List<Kv> title)
         {
             List<int> dts = new();
             for (int i = 0; i < 24; i++)
@@ -604,71 +980,76 @@ namespace Hotline.Repository.SqlSugar.Orders
                   p.count
               })
                .ToPivotTableAsync(p => p.DicDataName, p => p.Hour, p => p.Sum(x => x.count));
-			var dtList = list.Clone();
-
-			if (await Db.Queryable<StatisticsPurTypeSatisfied>().Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime).AnyAsync())
-			{
-				// 老系统数据
-				var oldData = await Db.Queryable<StatisticsPurTypeSatisfied>()
-					.Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime)
-					.GroupBy(x => x.Month)
-					.Select(x => new
-					{
-						Hour = x.Month.ToString() + ":00 - " + x.Month.ToString() + ":59",
-						_1 = SqlFunc.AggregateSum(x.Opinion),
-						_2 = SqlFunc.AggregateSum(x.PeopleHelp),
-						_3 = SqlFunc.AggregateSum(x.PollutionReporting),
-						_10 = SqlFunc.AggregateSum(x.Consult),
-						_15 = SqlFunc.AggregateSum(x.Suggest),
-						_20 = SqlFunc.AggregateSum(x.SeekHelp),
-						_25 = SqlFunc.AggregateSum(x.Praise),
-						_30 = SqlFunc.AggregateSum(x.Report),
-						_35 = SqlFunc.AggregateSum(x.Complaint),
-						_40 = SqlFunc.AggregateSum(x.Rests) + SqlFunc.AggregateSum(x.Invalid),
-						_50 = SqlFunc.AggregateSum(x.Epidemic),
-						_SB = SqlFunc.AggregateSum(x.Declare),
-					}).ToDataTableAsync();
-
-				#region  处理数据
-
-				foreach (var item in title)
-				{
-					var isColumn = oldData.Columns.Contains("_"+item.Key);
-					if (isColumn)
-					{
-						oldData.Columns["_" + item.Key].ColumnName = item.Value;
-					}
-				}
-
-				foreach (DataColumn col in dtList.Columns)
-				{
-					if (col.ColumnName != "Hour") col.DataType = typeof(Int64);
-				}
-
-				for (int i = 0; i < list.Rows.Count; i++)
-				{
-					DataRow newRow = dtList.NewRow();
-					for (int j = 0; j < list.Columns.Count; j++)
-					{
-						if (oldData.Columns[j].ColumnName == "Hour")
-						{
-							newRow[j] = list.Rows[i][j];
-						}
-						else
-						{
-							var num = string.IsNullOrEmpty(list.Rows[i][j].ToString()) ? 0 : int.Parse(list.Rows[i][j].ToString());
-							var oldRow = oldData.Select("Hour ='"+ list.Rows[i]["Hour"] + "'").FirstOrDefault();
-							//var oldColName = "_" + dtList.Columns[j].ColumnName;
-							var allNum = oldRow == null ? num : oldRow.Field<Int64>(dtList.Columns[j].ColumnName) + num;
-							newRow[j] = allNum;
-						}
-					}
-					dtList.Rows.Add(newRow);
-				}
-
-				#endregion
-			}
-			return InitDatatTable(dtList, dto.AddColumnName);
+            var col1 = list.Columns.Contains("Column1");
+            if (col1)
+            {
+                list.Columns.Remove("Column1");
+            }
+            var dtList = list.Clone();
+
+            if (await Db.Queryable<StatisticsPurTypeSatisfied>().Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime).AnyAsync())
+            {
+                // 老系统数据
+                var oldData = await Db.Queryable<StatisticsPurTypeSatisfied>()
+                    .Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime)
+                    .GroupBy(x => x.Month)
+                    .Select(x => new
+                    {
+                        Hour = x.Month.ToString() + ":00 - " + x.Month.ToString() + ":59",
+                        _1 = SqlFunc.AggregateSum(x.Opinion),
+                        _2 = SqlFunc.AggregateSum(x.PeopleHelp),
+                        _3 = SqlFunc.AggregateSum(x.PollutionReporting),
+                        _10 = SqlFunc.AggregateSum(x.Consult),
+                        _15 = SqlFunc.AggregateSum(x.Suggest),
+                        _20 = SqlFunc.AggregateSum(x.SeekHelp),
+                        _25 = SqlFunc.AggregateSum(x.Praise),
+                        _30 = SqlFunc.AggregateSum(x.Report),
+                        _35 = SqlFunc.AggregateSum(x.Complaint),
+                        _40 = SqlFunc.AggregateSum(x.Rests) + SqlFunc.AggregateSum(x.Invalid),
+                        _50 = SqlFunc.AggregateSum(x.Epidemic),
+                        _SB = SqlFunc.AggregateSum(x.Declare),
+                    }).ToDataTableAsync();
+
+                #region  处理数据
+
+                foreach (var item in title)
+                {
+                    var isColumn = oldData.Columns.Contains("_" + item.Key);
+                    if (isColumn)
+                    {
+                        oldData.Columns["_" + item.Key].ColumnName = item.Value;
+                    }
+                }
+
+                foreach (DataColumn col in dtList.Columns)
+                {
+                    if (col.ColumnName != "Hour") col.DataType = typeof(Int64);
+                }
+
+                for (int i = 0; i < list.Rows.Count; i++)
+                {
+                    DataRow newRow = dtList.NewRow();
+                    for (int j = 0; j < list.Columns.Count; j++)
+                    {
+                        if (oldData.Columns[j].ColumnName == "Hour")
+                        {
+                            newRow[j] = list.Rows[i][j];
+                        }
+                        else
+                        {
+                            var num = string.IsNullOrEmpty(list.Rows[i][j].ToString()) ? 0 : int.Parse(list.Rows[i][j].ToString());
+                            var oldRow = oldData.Select("Hour ='" + list.Rows[i]["Hour"] + "'").FirstOrDefault();
+                            //var oldColName = "_" + dtList.Columns[j].ColumnName;
+                            var allNum = oldRow == null ? num : oldRow.Field<Int64>(dtList.Columns[j].ColumnName) + num;
+                            newRow[j] = allNum;
+                        }
+                    }
+                    dtList.Rows.Add(newRow);
+                }
+
+                #endregion
+            }
+            return InitDatatTable(dtList, dto.AddColumnName);
         }
 
         /// <summary>
@@ -1401,6 +1782,8 @@ namespace Hotline.Repository.SqlSugar.Orders
         public ISugarQueryable<OrgVisitDetailListResp> OrgVisitDetailList(OrgVisitDetailListReq dto)
         {
             var IsCenter = _sessionContext.OrgIsCenter;
+            if (dto.OrgProcessingResults == null || !dto.OrgProcessingResults.Any() || dto.OrgProcessingResults.First() == null)
+                dto.OrgProcessingResults = null;
 
             return _orderVisitDetailRepository.Queryable()
                 .Includes(x => x.OrderVisit, x => x.Order)
@@ -1414,7 +1797,12 @@ namespace Hotline.Repository.SqlSugar.Orders
                     x => x.OrderVisit.Order.ActualHandleOrgCode != OrgSeedData.CenterId)
                 .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)
+                //任务 218 市州通用-部门满意度明细:办件结果查询优化为多选
+                //.WhereIF(dto.OrgProcessingResults != null && dto.OrgProcessingResults.Any(),
+                //      x => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(x.OrgProcessingResults, "Key")))
+                //.WhereIF(dto.OrgHandledAttitude != null && dto.OrgHandledAttitude.Any(),
+                //     x => dto.OrgHandledAttitude.Contains(SqlFunc.JsonField(x.OrgHandledAttitude, "Key")))
+                .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)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order.Title.Contains(dto.Title))
@@ -1428,6 +1816,9 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .WhereIF(dto.VisitTimeStart.HasValue, x => x.OrderVisit.VisitTime >= dto.VisitTimeStart) //回访时间
                 .WhereIF(dto.VisitTimeEnd.HasValue, x => x.OrderVisit.VisitTime < dto.VisitTimeEnd) //回访时间
                 .WhereIF(dto.VisitType != null, x => x.OrderVisit.VisitType == dto.VisitType) //回访方式
+                .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, x => x.OrderVisit.Order.Source == ESource.ProvinceStraight)
+                .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, x => x.OrderVisit.Order.Source != ESource.ProvinceStraight)
+                .WhereIF(!string.IsNullOrEmpty(dto.ContentRetrieval), x => x.VisitContent.Contains(dto.ContentRetrieval!))
                 .Select(x => new OrgVisitDetailListResp()
                 {
                     Id = x.Id,
@@ -1471,9 +1862,9 @@ namespace Hotline.Repository.SqlSugar.Orders
         {
             bool IsCenter = _sessionContext.OrgIsCenter;
             if (IsCenter == false && string.IsNullOrEmpty(dto.PickOrgCode))
-	            dto.PickOrgCode = _sessionContext.RequiredOrgId;
+                dto.PickOrgCode = _sessionContext.RequiredOrgId;
 
-			return _orderVisitDetailRepository.Queryable()
+            return _orderVisitDetailRepository.Queryable()
                 .Includes(x => x.OrderVisit, o => o.Order, d => d.CallRecord)
                 .Where(x => x.OrderVisit.VisitTime >= dto.StartTime && x.OrderVisit.VisitTime <= dto.EndTime && x.VisitTarget == EVisitTarget.Org && x.OrderVisit.VisitState == EVisitState.Visited)
                 .WhereIF(dto.OrgCode == "001", x => x.VisitOrgCode == dto.OrgCode)
@@ -1485,12 +1876,15 @@ namespace Hotline.Repository.SqlSugar.Orders
                 //.WhereIF(IsCenter == true && dto.IsOnlyMy == null, x => x.VisitOrgCode.StartsWith(dto.OrgCode))
                 //.WhereIF(IsCenter == true , x=> x.VisitOrgCode.StartsWith(dto.OrgCode))
                 //.WhereIF(IsCenter == false, x => x.VisitOrgCode == dto.OrgCode)
-                .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && (string.IsNullOrEmpty(dto.PickOrgCode) ||  dto.OrgCode != dto.PickOrgCode), x => x.VisitOrgCode.StartsWith(dto.OrgCode))
-                .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && (!string.IsNullOrEmpty(dto.PickOrgCode)  && dto.PickOrgCode == dto.OrgCode) ,x=>x.VisitOrgCode == dto.OrgCode)  
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && (string.IsNullOrEmpty(dto.PickOrgCode) || dto.OrgCode != dto.PickOrgCode), x => x.VisitOrgCode.StartsWith(dto.OrgCode))
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && (!string.IsNullOrEmpty(dto.PickOrgCode) && dto.PickOrgCode == dto.OrgCode), x => x.VisitOrgCode == dto.OrgCode)
                 .WhereIF(dto.TypeId is 1, x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.DateValue)
                 .WhereIF(dto.TypeId is 2, x => SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == dto.DateValue)
                 .WhereIF(!string.IsNullOrEmpty(dto.LineNum), x => x.OrderVisit.Order.CallRecord.Gateway == dto.LineNum)
                 .WhereIF(dto.VisitType != null, x => x.OrderVisit.VisitType == dto.VisitType)
+                .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, x => x.OrderVisit.Order.Source == ESource.ProvinceStraight)
+                .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, x => x.OrderVisit.Order.Source != ESource.ProvinceStraight)
+                .WhereIF(!string.IsNullOrEmpty(dto.ContentRetrieval), x => x.VisitContent.Contains(dto.ContentRetrieval!))
                 .Select(x => new OrgVisitDetailListResp
                 {
                     Id = x.Id,
@@ -1539,10 +1933,19 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .Where(x => x.OrderVisit.VisitState == EVisitState.Visited && x.VisitTarget == EVisitTarget.Org)
                 .WhereIF(hiddenOrder.Any(), x => !hiddenOrder.Contains(x.OrderVisit.Order.No))
                 .WhereIF(IsCenter == false, x => x.VisitOrgCode.StartsWith(orgId))
-                .WhereIF(dto.OrgVisitStatisticsType.HasValue, x => x.OrderVisit.Order.FileOrgIsCenter == (dto.OrgVisitStatisticsType== EOrgVisitStatisticsType.CallCenter))
+                .WhereIF(dto.OrgVisitStatisticsType.HasValue, x => x.OrderVisit.Order.FileOrgIsCenter == (dto.OrgVisitStatisticsType == EOrgVisitStatisticsType.CallCenter))
                 .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)
+                  //任务 218 市州通用-部门满意度明细:办件结果查询优化为多选
+                  //.WhereIF(dto.OrgProcessingResults != null && dto.OrgProcessingResults.Any(),
+                  //      x => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(x.OrgProcessingResults, "Key")))
+                  //.WhereIF(dto.OrgHandledAttitude != null && dto.OrgHandledAttitude.Any(),
+                  //     x => dto.OrgHandledAttitude.Contains(SqlFunc.JsonField(x.OrgHandledAttitude, "Key")))
+                  .WhereIF(dto.OrgProcessingResults != null && dto.OrgProcessingResults.Any(),
+                    dto.AttitudeType == EAttitudeType.ProcessingResult ?
+                    x => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(x.OrgProcessingResults, "Key")) :
+                    x => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(x.OrgHandledAttitude, "Key")))
+
                 .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)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order.Title.Contains(dto.Title))

+ 5 - 0
src/Hotline.Repository.SqlSugar/Snapshot/OrderSnapshotRepository.cs

@@ -27,6 +27,11 @@ public class OrderSnapshotRepository : BaseRepository<OrderSnapshot>, IOrderSnap
         return await Queryable().Where(m => m.NetworkENumber == appealNumber).FirstAsync();
     }
 
+    public bool HasOrder(string orderId)
+    {
+        return Queryable().Any(m => m.Id == orderId);
+    }
+
     public async Task<OrderSnapshot> UpdateSafetyAsync(string orderId, bool isSafety, string remark)
     {
         OrderSnapshot order = null;

+ 10 - 1
src/Hotline.Share/Dtos/Bi/BiOrderDto.cs

@@ -62,7 +62,16 @@ namespace Hotline.Share.Dtos.Bi
         /// </summary>
         public string? PickOrgCode { get; set; }
 
-	}
+        /// <summary>
+        /// 是否省工单(空为全部  true为省工单 false为市工单)
+        /// </summary>
+        public bool? IsProvinceOrder { get; set; }
+
+        /// <summary>
+        /// 内容检索(回访内容)
+        /// </summary>
+        public string? ContentRetrieval { get; set; }
+    }
 
     public record HighFrequencyCallStatisticsRequest : PagedRequest
     {

+ 57 - 7
src/Hotline.Share/Dtos/Bigscreen/BigscreenDto.cs

@@ -1,4 +1,6 @@
 
+using Hotline.Share.Dtos.Order;
+
 namespace Hotline.Share.Dtos.Bigscreen
 {
     public class OrderStatisticsDto
@@ -20,7 +22,7 @@ namespace Hotline.Share.Dtos.Bigscreen
         /// <summary>
         /// 待受理率
         /// </summary>
-        public double HaveToAcceptRate { get; set;}
+        public double HaveToAcceptRate { get; set; }
 
         /// <summary>
         /// 超期工单数
@@ -104,11 +106,11 @@ namespace Hotline.Share.Dtos.Bigscreen
 
         public double ToDayQoQ { get; set; }
 
-        public int ToMonthCount { get; set;}
+        public int ToMonthCount { get; set; }
 
         public double ToMonthQoQ { get; set; }
 
-        public int ToYearCount { get; set;}
+        public int ToYearCount { get; set; }
 
         public double ToYearQoQ { get; set; }
     }
@@ -126,11 +128,11 @@ namespace Hotline.Share.Dtos.Bigscreen
 
         public double CalcCompletion()
         {
-            if(CompletionCount==0 || AcceptedCount == 0)
+            if (CompletionCount == 0 || AcceptedCount == 0)
             {
                 return 0;
             }
-            return Math.Round((CompletionCount / (double)AcceptedCount) * 100, 2) ;
+            return Math.Round((CompletionCount / (double)AcceptedCount) * 100, 2);
         }
 
     }
@@ -198,7 +200,7 @@ namespace Hotline.Share.Dtos.Bigscreen
             {
                 return 0;
             }
-            return Math.Round((SatisfiedCount / (double)VisitCount) * 100, 2) ;
+            return Math.Round((SatisfiedCount / (double)VisitCount) * 100, 2);
         }
     }
 
@@ -214,11 +216,59 @@ namespace Hotline.Share.Dtos.Bigscreen
 
         public double CalcHasRate()
         {
-            if (HasCount == 0|| SumCount==0)
+            if (HasCount == 0 || SumCount == 0)
             {
                 return 0;
             }
             return Math.Round((HasCount / (double)SumCount) * 100, 2);
         }
     }
+
+    /// <summary>
+    /// 二次办理统计
+    /// </summary>
+    public class SecondaryProcessingOrderStatisticsDto
+    {
+        /// <summary>
+        /// 二次办理工单
+        /// </summary>
+        public int OrderCount { get; set; }
+
+        /// <summary>
+        /// 二次办理超期
+        /// </summary>
+        public int OrderOverdueCount { get; set; }
+
+        /// <summary>
+        /// 二次办理即将超期
+        /// </summary>
+        public int OrderSoonOverdueCount { get; set; }
+
+        /// <summary>
+        /// 二次办理满意率
+        /// </summary>
+        public string SatisfactionRate { get; set; }
+    }
+
+    public class SecondarySatisfactionDto
+    {
+        public int Count { get; set; }
+
+        public int NoSatisfiedCount { get; set; }
+
+        /// <summary>
+        /// 满意率
+        /// </summary>
+        public string SatisfiedRate => CalcSatisfiedRate();
+
+        public string CalcSatisfiedRate()
+        {
+            if (Count == 0)
+            {
+                return "0.000%";
+            }
+            return Math.Round(((Count - NoSatisfiedCount) / (double)Count) * 100, 3) + "%";
+        }
+    }
+
 }

+ 173 - 3
src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs

@@ -1,5 +1,6 @@
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Requests;
+using Hotline.Share.Tools;
 using System.ComponentModel.DataAnnotations;
 using XF.Utility.EnumExtensions;
 
@@ -288,7 +289,7 @@ public class QueryCallDateStatisticsDetailResp
     /// <summary>
     /// 服务接通率
     /// </summary>
-    public double ServicePutthorughRate => PutthroughCount == 0 ? 0 : Math.Round(((double)PutthroughCount / CallInTotal) * 100, 2);
+    public double ServicePutthorughRate => (PersonCallInCount + EnterpriseCallInCount + AiCallInCount) == 0 ? 0 : Math.Round(((double)PutthroughCount / (PersonCallInCount + EnterpriseCallInCount + AiCallInCount)) * 100, 2);
 
     /// <summary>
     /// 服务接通率(文本)
@@ -327,7 +328,7 @@ public class QueryPersonCallDateStatisticsDetailResp
     /// <summary>
     /// 个人服务挂断总量
     /// </summary>
-    public int PersonRingOffCount { get; set; }
+    public int PersonRingOffCount => PersonQueueOffCount + PersonWaitOffCount;
 
     /// <summary>
     /// 个人服务队列挂断
@@ -371,7 +372,7 @@ public class QueryEnterpriseCallDateStatisticsDetailResp
     /// <summary>
     /// 企业服务挂断总量
     /// </summary>
-    public int EnterpriseRingOffCount { get; set; }
+    public int EnterpriseRingOffCount => EnterpriseQueueOffCount + EnterpriseWaitOffCount;
 
     /// <summary>
     /// 企业服务队列挂断
@@ -488,4 +489,173 @@ public class QueryCallOutDateStatisticsDetailResp
     public string AiCallOutPutthroughRateText => AiCallOutPutthroughRate + "%";
 }
 
+
+public class QuerySeatMonthCallResp
+{
+    /// <summary>
+    /// 坐席姓名
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 用户ID
+    /// </summary>
+    public string UserId { get; set; }
+
+    /// <summary>
+    /// 呼入总量
+    /// </summary>
+    public int InTotal => InAnswered + InHanguped;
+
+    /// <summary>
+    /// 呼入接通量
+    /// </summary>
+    public int InAnswered { get; set; }
+
+    /// <summary>
+    /// 有效接通量
+    /// </summary>
+    public int InAvailableAnswer { get; set; }
+
+    /// <summary>
+    /// 呼入接通秒挂
+    /// </summary>
+    public int InHangupImmediateWhenAnswered { get; set; }
+
+    /// <summary>
+    /// 超时接通量
+    /// </summary>
+    public int OverTimeImmediate { get; set; }
+
+    /// <summary>
+    /// 按时接通量
+    /// </summary>
+    public int InTimeImmediate { get; set; }
+
+    /// <summary>
+    /// 呼入未接通总量
+    /// </summary>
+    public int InHanguped { get; set; }
+
+    /// <summary>
+    /// 未接通秒挂量
+    /// </summary>
+    public int InHangupImmediate { get; set; }
+
+    /// <summary>
+    /// 超时未接通量
+    /// </summary>
+    public int OverTimeInHanguped { get; set; }
+
+    /// <summary>
+    /// 呼入接通率
+    /// </summary>
+    public double InAnsweredRate => InTotal > 0 ? Math.Round(((double)InAnswered / (double)InTotal) * 100, digits: 2) : 0;
+
+    /// <summary>
+    /// 呼入接通率(显示)
+    /// </summary>
+    public string InAnsweredRateString => this.InAnsweredRate + "%";
+
+}
+
+public class QuerySeatMonthCallRequest
+{
+    /// <summary>
+    /// 振铃开始时间开始
+    /// </summary>
+    public DateTime? StartTime { get; set; }
+
+    /// <summary>
+    /// 振铃开始时间结束
+    /// </summary>
+    public DateTime? EndTime { get; set; }
+
+    /// <summary>
+    /// 员工ID
+    /// </summary>
+    public string? EmpId { get; set; }
+}
+
+
+public record QuerySeatMonthCallDetailRequest:PagedRequest
+{
+    /// <summary>
+    /// 坐席ID
+    /// </summary>
+    public string? EmpId { get; set; }
+
+    /// <summary>
+    /// 主叫
+    /// </summary>
+    public string? Cpn { get; set; }
+
+    /// <summary>
+    /// 被叫
+    /// </summary>
+    public string? Cdpn { get; set; }
+
+    /// <summary>
+    /// 分机号
+    /// </summary>
+    public string? TelNo { get; set; }
+
+    /// <summary>
+    /// 接通时间开始
+    /// </summary>
+    public DateTime? AnsweredStartTime { get; set; }
+
+    /// <summary>
+    /// 接通时间结束
+    /// </summary>
+    public DateTime? AnsweredEndTime { get; set; }
+
+    /// <summary>
+    /// 振铃时间开始
+    /// </summary>
+    public DateTime? RingStartTime { get; set; }
+
+    /// <summary>
+    /// 振铃时间结束
+    /// </summary>
+    public DateTime? RingEndTime { get;set; }
+
+    /// <summary>
+    /// 点数进入明细类型(1:呼入接通总量 2:有效接通量 3:接通秒挂量 4:超时接通量 5:按时接通量 6:呼入未接通总量 7:未接通秒挂量 8:超时未接通量)
+    /// </summary>
+    public int? QueryType { get; set; }
+}
+
+public class QuerySeatMonthCallDetailResp
+{
+    /// <summary>
+    /// 主叫
+    /// </summary>
+    public string Cpn { get; set; }
+
+    /// <summary>
+    /// 被叫
+    /// </summary>
+    public string Cdpn { get; set; }
+
+    /// <summary>
+    /// 振铃开始时间
+    /// </summary>
+    public DateTime? RingTimeBegin { get; set; }
+
+    /// <summary>
+    /// 接通时间
+    /// </summary>
+    public DateTime? AnsweredTime { get; set; }
+
+    /// <summary>
+    /// 响应分机
+    /// </summary>
+    public string? TelNo { get; set; }
+
+    /// <summary>
+    /// 坐席名称
+    /// </summary>
+    public string? SeatName { get; set; }
+}
 #endregion

+ 13 - 0
src/Hotline.Share/Dtos/CallCenter/CallDto.cs

@@ -179,4 +179,17 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public string CallId { get; set; }
     }
+
+    public class CallRemarkDto
+    {
+        /// <summary>
+        /// 电话号码
+        /// </summary>
+        public string CallNumber { get; set; }
+
+        /// <summary>
+        /// CallNo
+        /// </summary>
+        public string CallId { get; set; }
+    }
 }

+ 634 - 46
src/Hotline.Share/Dtos/CallCenter/CenterReportStatisticsDto.cs

@@ -9,24 +9,44 @@ namespace Hotline.Share.Dtos.CallCenter
     public class CenterReportStatisticsDto
     {
         /// <summary>
-        /// 电话
+        /// 电话--自贡
         /// </summary>
         public CenterReportCallInfoDto CenterReportCallInfoDto { get; set; }
 
         /// <summary>
-        /// 电话
+        /// 电话--宜宾
         /// </summary>
         public CenterReportCallDto CenterReportCall { get; set; }
 
         /// <summary>
-        /// 工单
+        /// 语音分类
         /// </summary>
-        public CenterReportOrderDto CenterReportOrder { get; set; }
+        public IVRCallTypeDto iVRCallTypeDto { get; set; }
+
+		/// <summary>
+		/// 工单
+		/// </summary>
+		public CenterReportOrderDto CenterReportOrder { get; set; }
 
         /// <summary>
-        /// 信件回访量
+        /// 按时办结情况
         /// </summary>
-        public CenterReportVisitdDto CenterReportVisitd { get; set; }
+        public OrderCompletedDto OrderCompletedDto { get; set; }
+
+        /// <summary>
+        /// 办理时效
+        /// </summary>
+        public OrderAgingDto OrderAgingDto { get; set; }
+
+		/// <summary>
+		/// 企业办件情况
+		/// </summary>
+		public EnterpriseOrderDto EnterpriseOrderDto { get; set; }
+
+		/// <summary>
+		/// 信件回访量
+		/// </summary>
+		public CenterReportVisitdDto CenterReportVisitd { get; set; }
 
         /// <summary>
         /// 来源
@@ -38,10 +58,15 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public List<CenterReportOrderSourceChannelDto> CenterReportOrderAcceptTypes { get; set; }
 
-        /// <summary>
-        /// 专线统计
-        /// </summary>
-        public List<CenterReportOrderSourceChannelDto>  CenterReportOrderDedicatedLine{get;set ;}
+		/// <summary>
+		/// 信件分类
+		/// </summary>
+		public List<CenterReportOrderSourceChannelDto> EnterpriseCenterReportOrderAcceptTypes { get; set; }
+
+		/// <summary>
+		/// 专线统计
+		/// </summary>
+		public List<CenterReportOrderSourceChannelDto>  CenterReportOrderDedicatedLine{get;set ;}
 
         /// <summary>
         /// 市直部门
@@ -116,14 +141,39 @@ namespace Hotline.Share.Dtos.CallCenter
     public class CenterReportCallDto
     {
         /// <summary>
-        /// 话总量
+        /// 话总量
         /// </summary>
-        public int AllCallCount => EffectiveCount + InvalidCount + QueueByeCount + IvrByeCount;
+        public int AllCallCount => InTotal + OutTotal;
 
-        /// <summary>
-        /// 有效
-        /// </summary>
-        public int EffectiveCount { get; set; }
+		/// <summary>
+		/// 接通总量
+		/// </summary>
+		public int AllConnectionCount => InConnectionQuantity + OutConnectionQuantity;
+
+		/// <summary>
+		/// 呼入总量
+		/// </summary>
+		public int InTotal { get; set; }
+
+		/// <summary>
+		/// 呼出总量
+		/// </summary>
+		public int OutTotal { get; set; }
+
+		/// <summary>
+		/// 呼入接通量
+		/// </summary>
+		public int InConnectionQuantity { get; set; }
+
+		/// <summary>
+		/// 呼出接通量
+		/// </summary>
+		public int OutConnectionQuantity { get; set; }
+
+		/// <summary>
+		/// 有效
+		/// </summary>
+		public int EffectiveCount { get; set; }
 
         /// <summary>
         /// 无效
@@ -139,8 +189,105 @@ namespace Hotline.Share.Dtos.CallCenter
         /// IVR挂断
         /// </summary>
         public int IvrByeCount { get; set; }
-    }
 
+
+		/// <summary>
+		/// 挂断总量
+		/// </summary>
+		public int AllByeCount => QueueByeCount + IvrByeCount;
+
+
+		/// <summary>
+		/// 总体接通率
+		/// </summary>
+		public double AllEffectiveCountRate => AllEffectiveRate();
+
+		/// <summary>
+		/// 计算总体接通率
+		/// </summary>
+		/// <returns></returns>
+		public double AllEffectiveRate()
+		{
+			if (AllCallCount > 0 && EffectiveCount > 0)
+				return Math.Round(((double)EffectiveCount / AllCallCount) * 100, 2);
+			return 0;
+		}
+	}
+
+    /// <summary>
+    /// 语音分类
+    /// </summary>
+    public class IVRCallTypeDto {
+
+		/// <summary>
+		/// 个人服务呼入总量
+		/// </summary>
+		public int PersonCallInCount { get; set; }
+
+		/// <summary>
+		/// 企业服务呼入总量
+		/// </summary>
+		public int EnterpriseCallInCount { get; set; }
+
+		/// <summary>
+		/// 智能应答呼入总量
+		/// </summary>
+		public int AiCallInCount { get; set; }
+
+
+		/// <summary>
+		/// 呼入电话量
+		/// </summary>
+		public int AllCallInCount => PersonCallInCount + EnterpriseCallInCount + AiCallInCount;
+
+		/// <summary>
+		/// 个人服务呼入总量 占比
+		/// </summary>
+		public double PersonCallInRate => GetPersonCallInRate();
+
+		/// <summary>
+		/// 个人服务呼入总量 占比
+		/// </summary>
+		/// <returns></returns>
+		public double GetPersonCallInRate()
+		{
+			if (AllCallInCount > 0 && PersonCallInCount > 0)
+				return Math.Round(((double)PersonCallInCount / AllCallInCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 企业服务呼入总量 占比
+		/// </summary>
+		public double EnterpriseCallInRate => GetEnterpriseCallInRate();
+
+		/// <summary>
+		/// 企业服务呼入总量 占比
+		/// </summary>
+		/// <returns></returns>
+		public double GetEnterpriseCallInRate()
+		{
+			if (AllCallInCount > 0 && EnterpriseCallInCount > 0)
+				return Math.Round(((double)EnterpriseCallInCount / AllCallInCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 智能应答呼入总量 占比
+		/// </summary>
+		public double AiCallInRate => GetAiCallInRate();
+
+		/// <summary>
+		/// 智能应答呼入总量 占比
+		/// </summary>
+		/// <returns></returns>
+		public double GetAiCallInRate()
+		{
+			if (AllCallInCount > 0 && AiCallInCount > 0)
+				return Math.Round(((double)AiCallInCount / AllCallInCount) * 100, 2);
+			return 0;
+		}
+	}
     /// <summary>
     /// 工单数据
     /// </summary>
@@ -151,10 +298,15 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public int AllCallCount => EffectiveCount + InvalidCount;
 
-        /// <summary>
-        /// 有效
-        /// </summary>
-        public int EffectiveCount { get; set; }
+		/// <summary>
+		/// 中心已办结
+		/// </summary>
+		public int CenterCompletedCount { get; set; }
+
+		/// <summary>
+		/// 有效
+		/// </summary>
+		public int EffectiveCount { get; set; }
 
         /// <summary>
         /// 无效
@@ -162,14 +314,79 @@ namespace Hotline.Share.Dtos.CallCenter
         public int InvalidCount { get; set; }
 
         /// <summary>
-        /// 已办结
-        /// </summary>
-        public int CompletedCount { get; set; }
-
-        /// <summary>
-        /// 按时办结
-        /// </summary>
-        public int OnTimeCompletedCount { get; set; }
+        /// 市级受理
+        /// </summary>
+        public int CityAccept { get; set; }
+
+		/// <summary>
+		/// 市级受理占比
+		/// </summary>
+		public double CityAcceptRate => GetCityAcceptRate();
+
+		/// <summary>
+		/// 市级受理占比
+		/// </summary>
+		/// <returns></returns>
+		public double GetCityAcceptRate()
+		{
+			if (CityAccept > 0 && AllCallCount > 0)
+				return Math.Round(((double)CityAccept / AllCallCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 区县受理
+		/// </summary>
+		public int CountyAccept { get; set; }
+
+
+		/// <summary>
+		/// 区县受理占比
+		/// </summary>
+		public double CountyAcceptRate => GetCountyAcceptRate();
+
+		/// <summary>
+		/// 区县受理占比
+		/// </summary>
+		/// <returns></returns>
+		public double GetCountyAcceptRate()
+		{
+			if (CountyAccept > 0 && AllCallCount > 0)
+				return Math.Round(((double)CountyAccept / AllCallCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 中心受理
+		/// </summary>
+		public int CenterAccept { get; set; }
+
+		/// <summary>
+		/// 中心受理占比
+		/// </summary>
+		public double CenterAcceptRate => GetCenterAcceptRate();
+
+		/// <summary>
+		/// 中心受理占比
+		/// </summary>
+		/// <returns></returns>
+		public double GetCenterAcceptRate()
+		{
+			if (CenterAccept > 0 && AllCallCount > 0)
+				return Math.Round(((double)CenterAccept / AllCallCount) * 100, 2);
+			return 0;
+		}
+
+
+		/// <summary>
+		/// 已办结
+		/// </summary>
+		public int CompletedCount { get; set; }
+
+		/// <summary>
+		/// 按时办结
+		/// </summary>
+		public int OnTimeCompletedCount { get; set; }
 
         /// <summary>
         /// 按时办结率
@@ -187,11 +404,6 @@ namespace Hotline.Share.Dtos.CallCenter
             return 0;
         }
 
-        /// <summary>
-        /// 中心已办结
-        /// </summary>
-        public int CenterCompletedCount { get; set; }
-
         /// <summary>
         /// 部门已办结
         /// </summary>
@@ -213,12 +425,328 @@ namespace Hotline.Share.Dtos.CallCenter
         public int OrgInProgressCount { get; set; }
 
 
-    }
+		/// <summary>
+		/// 总体办结率
+		/// </summary>
+		public double AllCompletedRate => GetAllCompletedRate();
+
+		public double GetAllCompletedRate()
+		{
+			if (CompletedCount > 0 && AllCallCount > 0)
+				return Math.Round(((double)CompletedCount / AllCallCount) * 100, 2);
+			return 0;
+		}
+
+	}
 
     /// <summary>
-    /// 信件来源
+    /// 按时办结情况
     /// </summary>
-    public class CenterReportOrderSourceChannelDto
+    public class OrderCompletedDto {
+
+
+		/// <summary>
+		/// 已办结
+		/// </summary>
+		public int CompletedCount { get; set; }
+
+		/// <summary>
+		/// 市级已办结
+		/// </summary>
+		public int CityCompletedCount { get; set; }
+
+		/// <summary>
+		/// 区县已办结
+		/// </summary>
+		public int CountyCompletedCount { get; set; }
+
+		/// <summary>
+		/// 中心已办结
+		/// </summary>
+		public int CenterCompletedCount { get; set; }
+
+		/// <summary>
+		/// 超期已办办结
+		/// </summary>
+		public int ExpiredTimeCompletedCount { get; set; }
+
+		/// <summary>
+		/// 总体按时办结率
+		/// </summary>
+		public double AllCompletedRate => GetAllCompletedRate();
+
+		public double GetAllCompletedRate()
+		{
+			if (CompletedCount > 0)
+				return Math.Round(((double)(CompletedCount - ExpiredTimeCompletedCount) / CompletedCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 市级超期已办办结
+		/// </summary>
+		public int CityExpiredTimeCompletedCount { get; set; }
+
+		/// <summary>
+		/// 市级按时办结率
+		/// </summary>
+		public double CityCompletedRate => GetCityCompletedRate();
+
+		public double GetCityCompletedRate()
+		{
+			if (CityCompletedCount > 0)
+				return Math.Round(((double)(CityCompletedCount - CityExpiredTimeCompletedCount) / CityCompletedCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 区县超期已办办结
+		/// </summary>
+		public int CountyExpiredTimeCompletedCount { get; set; }
+
+		/// <summary>
+		/// 区县按时办结率
+		/// </summary>
+		public double CountyCompletedRate => GetCountyCompletedRate();
+
+		public double GetCountyCompletedRate()
+		{
+			if (CountyCompletedCount > 0)
+				return Math.Round(((double)(CountyCompletedCount - CountyExpiredTimeCompletedCount) / CountyCompletedCount) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 部门办结率
+		/// </summary>
+		public double OrgCompletedRate => GetOrgCompletedRate();
+
+		public double GetOrgCompletedRate()
+		{
+			if (CityCompletedCount > 0 || CountyCompletedCount > 0)
+				return Math.Round(((double)((CityCompletedCount + CountyCompletedCount) - (CityExpiredTimeCompletedCount + CountyExpiredTimeCompletedCount)) / (CityCompletedCount + CountyCompletedCount) ) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 中心超期已办办结
+		/// </summary>
+		public int CenterExpiredTimeCompletedCount { get; set; }
+
+		/// <summary>
+		/// 中心按时办结率
+		/// </summary>
+		public double CenterCompletedRate => GetCenterCompletedRate();
+
+		public double GetCenterCompletedRate()
+		{
+			if (CenterCompletedCount > 0)
+				return Math.Round(((double)(CenterCompletedCount - CenterExpiredTimeCompletedCount) / CenterCompletedCount) * 100, 2);
+			return 0;
+		}
+	}
+
+    /// <summary>
+    /// 办理时效情况
+    /// </summary>
+    public class OrderAgingDto {
+    
+        /// <summary>
+        /// 来件总计
+        /// </summary>
+        public int OrderCount {  get; set; }
+
+        /// <summary>
+        /// 办理总时长
+        /// </summary>
+        public double? CompletedAging {  get; set; }
+
+        /// <summary>
+        /// 办理总时长平均
+        /// </summary>
+        public double CompletedAgingMean => GetCompletedAgingMean();
+
+        public double GetCompletedAgingMean() {
+            if (OrderCount > 0 && CompletedAging > 0)
+				return Math.Round(((CompletedAging.Value / OrderCount) / 3600), 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 市级来件总计
+		/// </summary>
+		public int CityOrderCount { get; set; }
+
+		/// <summary>
+		/// 市级办理总时长
+		/// </summary>
+		public double? CityCompletedAging { get; set; }
+
+		/// <summary>
+		/// 市级办理总时长平均
+		/// </summary>
+		public double CityCompletedAgingMean => GetCityCompletedAgingMean();
+
+		public double GetCityCompletedAgingMean()
+		{
+			if (CityOrderCount > 0 && CityCompletedAging > 0)
+				return Math.Round(((CityCompletedAging.Value / CityOrderCount) / 3600), 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 区县来件总计
+		/// </summary>
+		public int CountyOrderCount { get; set; }
+
+		/// <summary>
+		/// 区县办理总时长
+		/// </summary>
+		public double? CountyCompletedAging { get; set; }
+
+		/// <summary>
+		/// 区县办理总时长平均
+		/// </summary>
+		public double CountyCompletedAgingMean => GetCountyCompletedAgingMean();
+
+		public double GetCountyCompletedAgingMean()
+		{
+			if (CountyOrderCount > 0 && CountyCompletedAging > 0)
+				return Math.Round(((CountyCompletedAging.Value / CountyOrderCount) / 3600), 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 中心来件总计
+		/// </summary>
+		public int CenterOrderCount { get; set; }
+
+		/// <summary>
+		/// 中心办理总时长
+		/// </summary>
+		public double? CenterCompletedAging { get; set; }
+
+		/// <summary>
+		/// 中心办理总时长平均
+		/// </summary>
+		public double CenterCompletedAgingMean => GetCenterCompletedAgingMean();
+
+		public double GetCenterCompletedAgingMean()
+		{
+			if (CenterOrderCount > 0 && CenterCompletedAging > 0)
+				return Math.Round(((CenterCompletedAging.Value / CenterOrderCount) / 3600), 2);
+			return 0;
+		}
+	}
+
+
+    public class EnterpriseOrderDto : OrderAgingDto
+	{
+
+		/// <summary>
+		/// 在办
+		/// </summary>
+		public int InProgressCount { get; set; }
+
+		/// <summary>
+		/// 已办结
+		/// </summary>
+		public int CompletedCount { get; set; }
+
+		/// <summary>
+		/// 中心件
+		/// </summary>
+		public int CenterCount { get; set; }
+
+		/// <summary>
+		/// 市级件
+		/// </summary>
+		public int CityCount { get; set; }
+
+		/// <summary>
+		/// 区县件
+		/// </summary>
+		public int CountyCount { get; set; }
+
+		/// <summary>
+		/// 已回访
+		/// </summary>
+		public int VisitdCount { get; set; }
+
+		/// <summary>
+		/// 不满意
+		/// </summary>
+		public int Dissatisfied { get; set; }
+
+		/// <summary>
+		/// 市级部门不满意
+		/// </summary>
+		public int CityDissatisfied { get; set; }
+
+		/// <summary>
+		/// 县(区)不满意
+		/// </summary>
+		public int CountyDissatisfied { get; set; }
+
+		/// <summary>
+		/// 满意
+		/// </summary>
+		public int Satisfied { get; set; }
+
+		/// <summary>
+		/// 市级部门满意
+		/// </summary>
+		public int CitySatisfied { get; set; }
+
+		/// <summary>
+		/// 县(区)满意
+		/// </summary>
+		public int CountySatisfied { get; set; }
+
+		/// <summary>
+		/// 总满意率
+		/// </summary>
+		public double SatisfiedRate => GetSatisfiedRate();
+
+		
+		public double GetSatisfiedRate()
+		{
+			if (Satisfied > 0 )
+				return Math.Round(((double)Satisfied / (double)(Satisfied + Dissatisfied)) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 市级满意率
+		/// </summary>
+		/// <returns></returns>
+		public double CitySatisfiedRate => GetCitySatisfiedRate();
+
+		public double GetCitySatisfiedRate()
+		{
+			if (CitySatisfied > 0)
+				return Math.Round(((double)CitySatisfied / (double)(CitySatisfied + CityDissatisfied)) * 100, 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 区县满意率
+		/// </summary>
+		/// <returns></returns>
+		public double CountySatisfiedRate => GetCountySatisfiedRate();
+
+		public double GetCountySatisfiedRate()
+		{
+			if (CountySatisfied > 0)
+				return Math.Round(((double)CountySatisfied / (double)(CountySatisfied + CountyDissatisfied)) * 100, 2);
+			return 0;
+		}
+	}
+
+	/// <summary>
+	/// 信件来源
+	/// </summary>
+	public class CenterReportOrderSourceChannelDto
     {
         /// <summary>
         /// 名称
@@ -245,10 +773,28 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public string Remark { get; set; }
 
-        /// <summary>
-        /// 占比
-        /// </summary>
-        public double ProportionRate => CalcProportionRate();
+
+		/// <summary>
+		/// 办理总时长
+		/// </summary>
+		public double? CompletedAging { get; set; }
+
+		/// <summary>
+		/// 办理总时长平均
+		/// </summary>
+		public double CompletedAgingMean => GetCompletedAgingMean();
+
+		public double GetCompletedAgingMean()
+		{
+			if (CountNum > 0 && CompletedAging > 0)
+				return Math.Round(((CompletedAging.Value / CountNum) / 3600), 2);
+			return 0;
+		}
+
+		/// <summary>
+		/// 占比
+		/// </summary>
+		public double ProportionRate => CalcProportionRate();
 
         public double CalcProportionRate()
         {
@@ -265,10 +811,16 @@ namespace Hotline.Share.Dtos.CallCenter
     /// </summary>
     public class CenterReportVisitdDto
     {
-        /// <summary>
-        /// 已回访
-        /// </summary>
-        public int Visitd { get; set; }
+
+		/// <summary>
+		/// 回访总量
+		/// </summary>
+		public int AllVisitd => Visitd + WaitVisitd;
+
+		/// <summary>
+		/// 已回访
+		/// </summary>
+		public int Visitd { get; set; }
 
         /// <summary>
         /// 已回访--电话
@@ -299,7 +851,43 @@ namespace Hotline.Share.Dtos.CallCenter
         /// 部门满意度
         /// </summary>
         public double OrgRate { get; set; }
-    }
+
+        /// <summary>
+        /// 总体满意度
+        /// </summary>
+        public double AllRate { get; set; }
+
+		/// <summary>
+		/// 市级部门满意率
+		/// </summary>
+		public double CityRate { get; set; }
+
+		/// <summary>
+		/// 县(区)满意率
+		/// </summary>
+		public double CountyRate { get; set; }
+
+		/// <summary>
+		/// 12345中心满意率
+		/// </summary>
+		public double CenterRate { get; set; }
+
+		/// <summary>
+		/// 不满意
+		/// </summary>
+		public int Dissatisfied { get; set; }
+
+		/// <summary>
+		/// 市级部门不满意
+		/// </summary>
+		public int CityDissatisfied { get; set; }
+
+		/// <summary>
+		/// 县(区)不满意
+		/// </summary>
+		public int CountyDissatisfied { get; set; }
+
+	}
 
     public class Satisfaction
     {

+ 10 - 0
src/Hotline.Share/Dtos/CallCenter/QueryTelOperationsFixedDto.cs

@@ -28,5 +28,15 @@ namespace Hotline.Share.Dtos.CallCenter
         /// 操作类型
         /// </summary>
         public int? OperateState { get; set; }
+
+        /// <summary>
+        /// 动作开始时间Star
+        /// </summary>
+        public DateTime? OperateTimeStart { get; set; }
+
+        /// <summary>
+        /// 动作开始时间End
+        /// </summary>
+        public DateTime? OperateTimeEnd { get; set; }
     }
 }

+ 48 - 0
src/Hotline.Share/Dtos/CallCenter/TelOperationXthxDto.cs

@@ -0,0 +1,48 @@
+using Hotline.Share.Enums.CallCenter;
+
+namespace Hotline.Share.Dtos.CallCenter
+{
+    /// <summary>
+    /// 坐席动作类型
+    /// </summary>
+    public class TelOperationXthxDto
+    {
+        /// <summary>
+        /// 应用ID
+        /// </summary>
+        public string appId { get; set; }
+
+        /// <summary>
+        /// 应用密钥
+        /// </summary>
+        public string appSecret { get; set; }
+
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        public string TelNo { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string StaffNo { get; set; }
+
+        /// <summary>
+        /// 分机状态
+        /// </summary>
+        public EOperationStatus Status { get; set; }
+    }
+
+    public class TelAppXthxDto
+    {
+        /// <summary>
+        /// 应用ID
+        /// </summary>
+        public string appId { get; set; }
+
+        /// <summary>
+        /// 应用密钥
+        /// </summary>
+        public string appSecret { get; set; }
+    }
+}

+ 257 - 0
src/Hotline.Share/Dtos/Caselibrary/CaseDataDto.cs

@@ -0,0 +1,257 @@
+using Hotline.Share.Dtos.Knowledge;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Users;
+using Hotline.Share.Enums.Article;
+using Hotline.Share.Enums.Caselibrary;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Caselibrary
+{
+    public record CaseDataDto
+    {
+        /// <summary>
+        /// 案例ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 创建人
+        /// </summary>
+        public string CreatorName { get; set; }
+
+        /// <summary>
+        /// 浏览量
+        /// </summary>
+        public int PageView { get; set; }
+
+        /// <summary>
+        /// 是否热门
+        /// </summary>
+        public bool IsPopular { get; set; }
+
+        /// <summary>
+        /// 申请部门ID
+        /// </summary>
+        public string CreatorOrgId { get; set; }
+
+        /// <summary>
+        /// 申请部门
+        /// </summary>
+        public string CreatorOrgName { get; set; }
+
+        /// <summary>
+        /// 申请时间
+        /// </summary>
+        public DateTime CreationTime { get; set; }
+
+        /// <summary>
+        /// 上架时间
+        /// </summary>
+        public DateTime? OnShelfTime { get; set; }
+
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        public DateTime? UpdateTime { get; set; }
+
+        /// <summary>
+        /// 审核时间
+        /// </summary>
+        public DateTime? ExaminTime { get; set; }
+
+        /// <summary>
+        /// 下架时间
+        /// </summary>
+        public DateTime? OffShelfTime { get; set; }
+
+        /// <summary>
+        /// 过期时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 审核意见
+        /// </summary>
+        public string? ExaminOpinion { get; set; }
+
+        /// <summary>
+        /// 审核人ID
+        /// </summary>
+        public string? ExaminManId { get; set; }
+
+        /// <summary>
+        /// 审核人
+        /// </summary>
+        public UserDto? ExaminMan { get; set; }
+
+        /// <summary>
+        /// 文档状态
+        /// </summary>
+        public ECaseStatus Status { get; set; }
+
+        public ECaseStatus NewStatus => Status != ECaseStatus.Drafts && DateTime.Now > ExpiredTime ? ECaseStatus.Overdue : Status;
+
+        /// <summary>
+        /// 文档状态名称
+        /// </summary>
+        public string StatusName => NewStatus.GetDescription();
+
+        /// <summary>
+        /// 案例状态申请
+        /// </summary>
+        public ECaseApplyStatus? ApplyStatus { get; set; }
+
+        /// <summary>
+        /// 案例分类
+        /// </summary>
+        public List<CaseRelationTypeDto> CaseTypes { get; set; }
+
+        /// <summary>
+        /// 案例分类名称
+        /// </summary>
+        public string CaseTypeText => GetCaseTypeText(CaseTypes);
+
+        /// <summary>
+        /// 获取案例分类名称
+        /// </summary>
+        /// <returns></returns>
+        public string GetCaseTypeText(List<CaseRelationTypeDto> items)
+        {
+
+            if (CaseTypes != null && CaseTypes.Any())
+            {
+                var names = CaseTypes.Select(x => x.Name).ToList();
+                return string.Join(",", names);
+            }
+            return "";
+        }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string OrderId { get; set; }
+
+        /// <summary>
+        /// 工单
+        /// </summary>
+        //public OrderDto Order { get; set; }
+
+        //public string OrderTitle => Order != null ? Order.Title : string.Empty;
+
+        public string OrderTitle { get; set; }
+
+        /// <summary>
+        /// 知识库ID
+        /// </summary>
+        public string KnowledgeId { get; set; }
+
+        /// <summary>
+        /// 知识库
+        /// </summary>
+        //public KnowledgeDto Knowledge { get; set; }
+        public string KnowledgeTitle { get; set; }
+
+        /// <summary>
+        /// 关键字
+        /// </summary>
+        public string Keywords { get; set; }
+    }
+
+    public record CaseRelationTypeDto
+    {
+        /// <summary>
+        /// 案例库类型ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 案例库类型名称
+        /// </summary>
+        public string Name { get; set; }
+
+
+        /// <summary>
+        /// 案例库类型名称
+        /// </summary>
+        public string SpliceName { get; set; }
+    }
+}
+
+
+public class CaseExistDto
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    public string Title { get; set; }
+
+    /// <summary>
+    /// 摘要
+    /// </summary>
+    public string Abstract { get; set; }
+
+    /// <summary>
+    /// 案例描述
+    /// </summary>
+    public string Describe { get; set; }
+
+    /// <summary>
+    /// 案例结果
+    /// </summary>
+    public string Result { get; set; }
+
+    /// <summary>
+    /// 推荐理由
+    /// </summary>
+    public string Reason { get; set; }
+
+    /// <summary>
+    /// ID
+    /// </summary>
+    public string? Id { get; set; }
+}
+
+public class CaseInfoExportDto
+{
+    /// <summary>
+    /// 导出的案例Id集合
+    /// </summary>
+    public string[] Ids { get; set; }
+
+    /// <summary>
+    /// 导出格式
+    /// </summary>
+    public EFileType FileType { get; set; }
+}
+
+
+public class CaseInfoPopularDto
+{
+    /// <summary>
+    /// 设置的案例Id集合
+    /// </summary>
+    public string[] Ids { get; set; }
+
+    /// <summary>
+    /// true热门  false非热门
+    /// </summary>
+    public bool Popular { get; set; }
+}
+
+
+public class CasePageViewDto
+{
+    public string Id { get; set; }
+    public string Title { get; set; }
+    public int PageView { get; set; }
+}

+ 511 - 0
src/Hotline.Share/Dtos/Caselibrary/CaseListDto.cs

@@ -0,0 +1,511 @@
+using Hotline.Share.Dtos.File;
+using Hotline.Share.Dtos.Planlibrary;
+using Hotline.Share.Enums.Caselibrary;
+using Hotline.Share.Enums.Planlibrary;
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.Caselibrary
+{
+    /// <summary>
+    /// 案例库列表
+    /// </summary>
+    public record CaseListDto : PagedRequest
+    {
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 关键词
+        /// </summary>
+        public string? Keywords { get; set; }
+
+        /// <summary>
+        /// 摘要
+        /// </summary>
+        public string? Abstract { get; set; }
+
+        /// <summary>
+        /// 案例描述
+        /// </summary>
+        public string? Describe { get; set; }
+
+        /// <summary>
+        /// 案例结果
+        /// </summary>
+        public string? Result { get; set; }
+
+        /// <summary>
+        /// 推荐理由
+        /// </summary>
+        public string? Reason { get; set; }
+
+        /// <summary>
+        /// 是否热门
+        /// </summary>
+        public bool? IsPopular { get; set; }
+
+        /// <summary>
+        /// 案例状态
+        /// </summary>
+        public ECaseStatus? Status { get; set; }
+
+        /// <summary>
+        /// 分类ID
+        /// </summary>
+        public string? CaseTypeID { get; set; }
+
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string? OrderId { get; set; }
+
+        /// <summary>
+        /// 知识库ID
+        /// </summary>
+        public string? KnowledgeId { get; set; }
+
+        /// <summary>
+        /// 是否删除
+        /// </summary>
+        public bool? IsDeleted { get; set; } = false;
+
+        /// <summary>
+        /// 阅读次数
+        /// </summary>
+        public int PageView { get; set; } = 0;
+
+        /// <summary>
+        /// 创建开始时间
+        /// </summary>
+        public DateTime? CreationTimeStart { get; set; }
+
+        /// <summary>
+        /// 创建结束时间
+        /// </summary>
+        public DateTime? CreationTimeEnd { get; set; }
+
+        /// <summary>
+        /// 上架开始时间
+        /// </summary>
+        public DateTime? OnShelfTimeStart { get; set; }
+        /// <summary>
+        /// 上架结束时间
+        /// </summary>
+        public DateTime? OnShelfTimeEnd { get; set; }
+
+        /// <summary>
+        /// 下架开始时间
+        /// </summary>
+        public DateTime? OffShelfTimeStart { get; set; }
+
+        /// <summary>
+        /// 下架结束时间
+        /// </summary>
+        public DateTime? OffShelfTimeEnd { get; set; }
+
+        /// <summary>
+        /// 更新开始时间
+        /// </summary>
+        public DateTime? UpdateTimeStart { get; set; }
+
+        /// <summary>
+        /// 更新结束时间
+        /// </summary>
+        public DateTime? UpdateTimeEnd { get; set; }
+
+        /// <summary>
+        /// 审核人ID
+        /// </summary>
+        public string? ExaminManId { get; set; }
+
+        /// <summary>
+        /// 审核部门ID
+        /// </summary>
+        public string? ExaminOrganizeId { get; set; }
+
+        /// <summary>
+        /// 当前部门ID
+        /// </summary>
+        public string CreateOrgId { get; set; }
+
+        /// <summary>
+        /// 审核开始时间
+        /// </summary>
+        public DateTime? ExaminTimeStart { get; set; }
+
+        /// <summary>
+        /// 审核结束时间
+        /// </summary>
+        public DateTime? ExaminTimeEnd { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 排序(需要被排序的字段名)
+        /// </summary>
+        public string? SortField { get; set; }
+
+        /// <summary>
+        /// 排序方向 (0: 升序Asc; 1: 降序Desc)
+        /// </summary>
+        public int OrderByType { get; set; } = 1;
+    }
+
+
+    public record AddCaseListDto
+    {
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 关键词
+        /// </summary>
+        public string? Keywords { get; set; }
+
+        /// <summary>
+        /// 摘要
+        /// </summary>
+        public string? Abstract { get; set; }
+
+        /// <summary>
+        /// 案例描述
+        /// </summary>
+        public string Describe { get; set; }
+
+        /// <summary>
+        /// 案例结果
+        /// </summary>
+        public string Result { get; set; }
+
+        /// <summary>
+        /// 推荐理由
+        /// </summary>
+        public string Reason { get; set; }
+
+        /// <summary>
+        /// 是否热门
+        /// </summary>
+        public bool? IsPopular { get; set; } = false;
+
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string? OrderId { get; set; }
+
+        /// <summary>
+        /// 知识库ID
+        /// </summary>
+        public string? KnowledgeId { get; set; }
+
+        /// <summary>
+        /// 分类
+        /// </summary>
+        public List<CaseRelationTypeDto> CaseTypes { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 案例状态
+        /// </summary>
+        public ECaseApplyStatus? ApplyStatus { get; set; }
+
+        /// <summary>
+        /// 案例状态
+        /// </summary>
+        public ECaseStatus? Status { get; set; }
+
+        /// <summary>
+        /// 上传附件
+        /// </summary>
+        public List<FileDto>? Files { get; set; }
+    }
+
+    public record UpdateCaseListDto : AddCaseListDto
+    {
+        /// <summary>
+        /// 案例库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 审核人ID
+        /// </summary>
+        public string? ExaminManId { get; set; }
+
+        /// <summary>
+        /// 审核部门ID
+        /// </summary>
+        public string? ExaminOrganizeId { get; set; }
+
+        /// <summary>
+        /// 审核时间
+        /// </summary>
+        public DateTime? ExaminTime { get; set; }
+
+        /// <summary>
+        /// 修改时间
+        /// </summary>
+        public DateTime? UpdateTime { get; set; }
+        /// <summary>
+        /// 申请理由
+        /// </summary>
+        public string ApplyReason { get; set; }
+
+        /// <summary>
+        /// 审核意见
+        /// </summary>
+        public string ExaminOpinion { get; set; }
+
+        /// <summary>
+        /// 上架时间
+        /// </summary>
+        public DateTime? OnShelfTime { get; set; }
+
+        /// <summary>
+        /// 下架时间
+        /// </summary>
+        public DateTime? OffShelfTime { get; set; }
+    }
+
+    public record DelCaseListDto
+    {
+        /// <summary>
+        /// 案例库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 申请理由
+        /// </summary>
+        public string ApplyReason { get; set; }
+    }
+
+
+    /// <summary>
+    /// 案例库列表
+    /// </summary>
+    public record CaseInfoDto
+    {
+        /// <summary>
+        /// 标识
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 关键词
+        /// </summary>
+        public string? Keywords { get; set; }
+
+        /// <summary>
+        /// 摘要
+        /// </summary>
+        public string? Abstract { get; set; }
+
+        /// <summary>
+        /// 案例描述
+        /// </summary>
+        public string Describe { get; set; }
+
+        /// <summary>
+        /// 案例结果
+        /// </summary>
+        public string Result { get; set; }
+
+        /// <summary>
+        /// 推荐理由
+        /// </summary>
+        public string Reason { get; set; }
+
+        /// <summary>
+        /// 是否热门
+        /// </summary>
+        public bool? IsPopular { get; set; }
+
+        /// <summary>
+        /// 阅读次数
+        /// </summary>
+        public int PageView { get; set; } = 0;
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime? CreationTime { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 创建人
+        /// </summary>
+        public string? CreatorName { get; set; }
+
+        /// <summary>
+        /// 创建部门
+        /// </summary>
+        public string? CreatorOrgName { get; set; }
+
+
+
+
+
+        /// <summary>
+        /// 案例分类
+        /// </summary>
+        public List<CaseRelationTypeDto> CaseTypes { get; set; }
+
+        /// <summary>
+        /// 案例分类名称
+        /// </summary>
+        public string CaseTypeText => GetCaseTypeText(CaseTypes);
+
+        /// <summary>
+        /// 获取案例分类名称
+        /// </summary>
+        /// <returns></returns>
+        public string GetCaseTypeText(List<CaseRelationTypeDto> items)
+        {
+
+            if (CaseTypes != null && CaseTypes.Any())
+            {
+                var names = CaseTypes.Select(x => x.Name).ToList();
+                return string.Join(",", names);
+            }
+            return "";
+        }
+
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string OrderId { get; set; }
+
+        /// <summary>
+        /// 工单
+        /// </summary>
+        public string OrderTitle { get; set; }
+
+        /// <summary>
+        /// 知识库ID
+        /// </summary>
+        public string? KnowledgeId { get; set; }
+
+        /// <summary>
+        /// 知识库
+        /// </summary>
+        public string? KnowledgeTitle { get; set; }
+
+
+        public CaseCollectDto Collect { get; set; }
+
+        /// <summary>
+        /// 案例申请状态
+        /// </summary>
+        public ECaseApplyStatus ApplyStatus { get; set; }
+
+        /// <summary>
+        /// 案例状态
+        /// </summary>
+        public ECaseStatus Status { get; set; }
+
+
+
+        public List<FileDto> Files { get; set; }
+
+        /// <summary>
+        /// 附件
+        /// </summary>
+        public List<FileJson>? FileJson { get; set; }
+    }
+
+    public class CaseCollectDto
+    {
+        /// <summary>
+        /// 案例库ID
+        /// </summary>
+        public string CaseId { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 是否收藏
+        /// </summary>
+        public bool? Collect { get; set; }
+    }
+
+    public class CaseApplyReasonDto
+    {
+        /// <summary>
+        /// 案例库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 申请理由
+        /// </summary>
+        public string? ApplyReason { get; set; }
+    }
+    public record AuditCaseListDto
+    {
+        /// <summary>
+        /// 案例库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 审核状态 0不同意 1同意
+        /// </summary>
+        public int State { get; set; }
+
+        /// <summary>
+        /// 审核意见
+        /// </summary>
+        public string ExaminOpinion { get; set; }
+    }
+
+    public record PvCaseListDto
+    {
+        /// <summary>
+        /// 案例库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 浏览量:默认不增加,false不增加,true增加浏览量
+        /// </summary>
+        public bool IsAddPv { get; set; } = false;
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal Score { get; set; }
+    }
+}

+ 189 - 0
src/Hotline.Share/Dtos/Caselibrary/CaseTypeDto.cs

@@ -0,0 +1,189 @@
+using Hotline.Settings;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Caselibrary
+{
+    /// <summary>
+    /// 编辑
+    /// </summary>
+    public record UpdateCaseTypeDto : AddCaseTypeDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+    }
+
+    /// <summary>
+    ///新增
+    /// </summary>
+    public record AddCaseTypeDto
+    {
+        /// <summary>
+        /// 类型名称
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 父级ID
+        /// </summary>
+        public string? ParentId { get; set; } 
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        public int Sort { get; set; } = 0;
+
+        public List<AddCaseTypeOrgDto>? TypeOrgDtos { get; set; }
+
+    }
+
+    /// <summary>
+    /// 初始化返回数据
+    /// </summary>
+    public record CaseTypeDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 类型名称拼接(所有父级分类名称)
+        /// <example>
+        /// A类型/A.1类型/A.1.1类型
+        /// </example>
+        /// </summary>
+        public string SpliceName { get; set; }
+
+		/// <summary>
+		/// 类型名称
+		/// </summary>
+		public string Name { get; set; }
+
+        /// <summary>
+        /// 是否启用
+        /// </summary>
+        public bool IsEnable { get; set; }
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        public int Sort { get; set; }
+
+        /// <summary>
+        /// 父级ID
+        /// </summary>
+        public string ParentId { get; set; }
+
+        /// <summary>
+        /// 树形分类
+        /// </summary>
+        public List<Knowledge.TreeListDto> TreeLists { get; set; } = new();
+
+        /// <summary>
+        /// 子级
+        /// </summary>
+        public List<CaseTypeDto> children { get; set; }
+
+        public int CaseNum { get; set; }
+	}
+
+    public record CaseOrgDto
+    {
+	    /// <summary>
+	    /// ID
+	    /// </summary>
+	    public string Id { get; set; }
+
+		/// <summary>
+		/// 组织架构名称
+		/// </summary>
+		public string Name { get; set; }
+
+	    /// <summary>
+	    /// 组织架构简称
+	    /// </summary>
+	    public string ShortName { get; set; }
+
+	    /// <summary>
+	    /// 区域Code(行政区域代码)
+	    /// </summary>
+	    public string? AreaCode { get; set; }
+
+	    /// <summary>
+	    /// 区域名称(行政区域名称)
+	    /// </summary>
+	    public string? AreaName { get; set; }
+
+	    /// <summary>
+	    /// 部门级别
+	    /// </summary>
+	    public int Level { get; set; }
+
+	    /// <summary>
+	    /// 部门类型
+	    /// </summary>
+	    public EOrgType OrgType { get; set; }
+
+	    /// <summary>
+	    /// 上级ID
+	    /// </summary>
+	    public string? ParentId { get; set; }
+
+	    /// <summary>
+	    /// 上级名称
+	    /// </summary>
+	    public string? ParentName { get; set; }
+
+	    /// <summary>
+	    /// 是否启用
+	    /// </summary>
+	    public bool IsEnable { get; set; }
+
+	    /// <summary>
+	    /// 是否为中心
+	    /// </summary>
+	    public bool IsCenter { get; set; }
+
+	    public List<CaseOrgDto> Children { get; set; }
+
+	    public string OrgTypeText => OrgType.GetDescription();
+
+
+	    /// <summary>
+	    /// 旧系统id
+	    /// </summary>
+	    public int? oldBmid { get; set; }
+
+	    public int CaseNum { get; set; }
+	}
+
+    public record CaseHotSpotDto
+    {
+	    /// <summary>
+	    /// ID
+	    /// </summary>
+	    public string Id { get; set; }
+
+	    public int CaseNum { get; set; }
+
+		public string HotSpotName { get; set; }
+	    public string ParentId { get; set; }
+	    public string PYCode { get; set; }
+
+	    public string FullPYCode { get; set; }
+	    public string ProvinceCode { get; set; }
+	    public int OrderBy { get; set; }
+	    public string TrunkNum { get; set; }
+	    /// <summary>
+	    /// 生成的时候写入
+	    /// </summary>
+	    public string HotSpotFullName { get; set; }
+
+	    public List<CaseHotSpotDto> Children { get; set; }
+
+	    public bool HasChild { get; set; }
+
+	}
+}

+ 51 - 0
src/Hotline.Share/Dtos/Caselibrary/CaseTypeOrgDto.cs

@@ -0,0 +1,51 @@
+
+namespace Hotline.Share.Dtos.Caselibrary
+{
+	public record AddCaseTypeOrgDto
+	{
+		/// <summary>
+		/// 机构ID
+		/// </summary>
+		public string OrgId { get; set; }
+
+		/// <summary>
+		/// 机构名称
+		/// </summary>
+		public string OrgName { get; set; }
+	}
+
+	public record CaseTypeOrgDto : AddCaseTypeOrgDto
+	{
+		public DateTime? LastModificationTime { get; set; }
+
+		public bool IsDeleted { get; set; }
+
+		/// <summary>
+		/// 删除时间
+		/// </summary>
+		public DateTime? DeletionTime { get; set; }
+
+
+		/// <summary>
+		/// 创建时间
+		/// </summary>
+		public DateTime CreationTime { get; set; }
+
+		public string Id { get; set; }
+
+		/// <summary>
+		/// 组织Id
+		/// </summary>
+		public string? CreatorOrgId { get; set; }
+
+
+		public string? CreatorOrgName { get; set; }
+
+		/// <summary>
+		/// 创建人
+		/// </summary>
+		public string? CreatorId { get; set; }
+
+		public string? CreatorName { get; set; }
+	}
+}

+ 42 - 0
src/Hotline.Share/Dtos/File/UploadAudioFilesRequestDto.cs

@@ -0,0 +1,42 @@
+namespace Hotline.Share.Dtos.File
+{
+    public class UploadAudioFilesRequestDto
+    {
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 附件Id
+        /// </summary>
+        public string FileId { get; set; }
+
+        /// <summary>
+		/// 附件全称
+		/// </summary>
+        public string FileName { get; set; }
+
+        /// <summary>
+        /// 附件名称
+        /// </summary>
+        public string? Name { get; set; }
+
+        /// <summary>
+		/// 附件类型
+		/// </summary>
+        public string? Type { get; set; }
+
+        /// <summary>
+        /// 附件路径
+        /// </summary>
+        public string Path { get; set; }
+
+        /// <summary>
+        /// 完整附件路径
+        /// </summary>
+        public string? AllPath { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime CreationTime { get; set; }
+    }
+}

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

@@ -63,6 +63,11 @@ public class NextStepsDto
     /// 临时保存的办理意见
     /// </summary>
     public string? Opinion { get; set; }
+
+    /// <summary>
+    /// 受理类容
+    /// </summary>
+    public string? Content { get; set; }
 }
 
 public class NextStepsDto<TSteps> : NextStepsDto

+ 25 - 0
src/Hotline.Share/Dtos/Knowledge/KnowledgeDataDto.cs

@@ -258,6 +258,31 @@ namespace Hotline.Share.Dtos.Knowledge
         /// </summary>
         public OrgDto SourceOrganize { get; set; }
 
+		/// <summary>
+		/// 知识分类
+		/// </summary>
+		public List<KnowledgeRelationTypeDto> KnowledgeType { get; set; }
+
+		/// <summary>
+		/// 知识分类名称
+		/// </summary>
+		public string KnowledgeTypeText => GetKnowledgeTypeText(KnowledgeType);
+
+		/// <summary>
+		/// 获取知识分类名称
+		/// </summary>
+		/// <returns></returns>
+		public string GetKnowledgeTypeText(List<KnowledgeRelationTypeDto> items)
+		{
+
+			if (KnowledgeType != null && KnowledgeType.Any())
+			{
+				var names = KnowledgeType.Select(x => x.KnowledgeTypeName).ToList();
+				return string.Join(",", names);
+			}
+			return "";
+		}
+
 	}
 
     /// <summary>

+ 10 - 0
src/Hotline.Share/Dtos/Knowledge/KnowledgeDto.cs

@@ -466,6 +466,16 @@ namespace Hotline.Share.Dtos.Knowledge
         /// 浏览时间
         /// </summary>
         public DateTime CreationTime { get; set; }
+
+        /// <summary>
+        /// 浏览完成时间
+        /// </summary>
+        public DateTime? PvEndTime { get; set; }
+
+        /// <summary>
+        /// 浏览时长
+        /// </summary>
+        public int? BrowseTime { get; set; } = 0;
     }
 
     public class KnowledgeWordOutDto

+ 1 - 1
src/Hotline.Share/Dtos/Order/Detail/OrderFlowTraceDto.cs

@@ -25,7 +25,7 @@ public class OrderFlowTraceDto
     /// <summary>
     /// 交班人部门
     /// </summary>
-    public string AssignOrgName { get; set; }
+    public string AssignerOrgName { get; set; }
 
     /// <summary>
     /// 受理人

+ 99 - 2
src/Hotline.Share/Dtos/Order/OrderBiDto.cs

@@ -199,7 +199,7 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 小计
         /// </summary>
-        public int AllTotal { get; set; }
+        public int AllTotal => PassTotal + NoPassTotal + ExaminingTotal + WithdrawTotal;
         /// <summary>
         /// 已同意次数
         /// </summary>
@@ -213,6 +213,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public int ExaminingTotal { get; set; }
 
+        /// <summary>
+        /// 撤销次数
+        /// </summary>
+        public int WithdrawTotal { get; set; }
+
     }
 
 
@@ -743,6 +748,58 @@ namespace Hotline.Share.Dtos.Order
             return Math.Round((NoPutThroughCount / (double)TotalSumCount) * 100, 2);
         }
         public string NoPutThroughRateText => NoPutThroughRate + "%";
+
+        /// <summary>
+        /// 一般数
+        /// </summary>
+        public int NormalCount { get; set; }
+
+        /// <summary>
+        /// 一般Key
+        /// </summary>
+        public string NormalKey => "3";
+
+        /// <summary>
+        /// 一般率
+        /// </summary>
+        public double NormalRate => CalcNormalRate();
+
+        public double CalcNormalRate()
+        {
+            if (NormalCount == 0 || TotalSumCount == 0)
+            {
+                return 0.000;
+            }
+            return Math.Round((NormalCount / (double)TotalSumCount) * 100, 3);
+        }
+
+        public string NormalRateText => NormalRate + "%";
+
+        /// <summary>
+        /// 非常不满意数
+        /// </summary>
+        public int VeryNoSatisfiedCount { get; set; }
+
+        /// <summary>
+        /// 非常不满意数Key
+        /// </summary>
+        public string VeryNoSatisfiedKey => "1";
+
+        /// <summary>
+        /// 非常不满意率
+        /// </summary>
+        public double VeryNoSatisfiedRate => CalcVeryNoSatisfiedRate();
+
+        public double CalcVeryNoSatisfiedRate()
+        {
+            if (VeryNoSatisfiedCount == 0 || TotalSumCount == 0)
+            {
+                return 0.000;
+            }
+            return Math.Round((VeryNoSatisfiedCount / (double)TotalSumCount) * 100, 3);
+        }
+
+        public string VeryNoSatisfiedRateText => VeryNoSatisfiedRate + "%";
     }
 
     public class BiOrderSendVo
@@ -981,7 +1038,6 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public double NoPutThroughRate => CalcNoPutThroughRate();
 
-
         public double CalcNoPutThroughRate()
         {
             if (NoPutThroughCount == 0 || TotalSumCount == 0)
@@ -993,6 +1049,47 @@ namespace Hotline.Share.Dtos.Order
 
         public string NoPutThroughRateText => NoPutThroughRate + "%";
 
+        /// <summary>
+        /// 一般数
+        /// </summary>
+        public int NormalCount { get; set; }
+
+        /// <summary>
+        /// 一般率
+        /// </summary>
+        public double NormalRate => CalcNormalRate();
+
+        public double CalcNormalRate()
+        {
+            if (NormalCount == 0 || TotalSumCount == 0)
+            {
+                return 0.000;
+            }
+            return Math.Round((NormalCount / (double)TotalSumCount) * 100, 3);
+        }
+
+        public string NormalRateText => NormalRate + "%";
+
+        /// <summary>
+        /// 非常不满意数
+        /// </summary>
+        public int VeryNoSatisfiedCount { get; set; }
+
+        /// <summary>
+        /// 非常不满意率
+        /// </summary>
+        public double VeryNoSatisfiedRate => CalcVeryNoSatisfiedRate();
+
+        public double CalcVeryNoSatisfiedRate()
+        {
+            if (VeryNoSatisfiedCount == 0 || TotalSumCount == 0)
+            {
+                return 0.000;
+            }
+            return Math.Round((VeryNoSatisfiedCount / (double)TotalSumCount) * 100, 3);
+        }
+
+        public string VeryNoSatisfiedRateText => VeryNoSatisfiedRate + "%";
     }
 
     public class OrderSourceVo

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

@@ -750,6 +750,41 @@ namespace Hotline.Share.Dtos.Order
 
         public int ReTransactNum { get; set; }
 
+        /// <summary>
+        /// 省重办次数
+        /// </summary>
+        public int? ProvinceReTransactNum { get; set; }
+
+        /// <summary>
+        /// 工单交办处理方式 10:直接办结,20:交办,30:派单员重派,40:结果审核重派,50:回访不满意重派
+        /// </summary>
+        public string? CaseProcessTypeText => GetCaseProcessTypeText();
+        public string GetCaseProcessTypeText()
+        {
+            var text = "";
+            if (!string.IsNullOrEmpty(CaseProcessType))
+            {
+                switch (CaseProcessType)
+                {
+                    case "10":
+                    case "20":
+                        break;
+                    case "30":
+                        text = "派单员重派";
+                        break;
+                    case "40":
+                        text = "结果审核重派";
+                        break;
+                    case "50":
+                        text = "回访不满意重派";
+                        break;
+                    default:
+                        break;
+                }
+            }
+            return text;
+        }
+
         /// <summary>
         /// 敏感标签
         /// </summary>
@@ -879,6 +914,16 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public string ProvinceRevokeString { get; set; }
 
+        /// <summary>
+        /// 省甄别
+        /// </summary>
+        public string ProvinceScreenString { get; set; }
+
+        /// <summary>
+        /// 省延期
+        /// </summary>
+        public string? ProvinceDelayString { get; set; }
+
         /// <summary>
         /// 省工单退回
         /// </summary>
@@ -975,6 +1020,7 @@ namespace Hotline.Share.Dtos.Order
         public string? IndustryName { get; set; }
 
         #endregion
+
         public bool? IsReTransact { get; set; }
     }
 
@@ -1237,6 +1283,13 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public DateTime? ExpiredTimeProvince { get; set; }
 
+        /// <summary>
+        /// 工单交办处理方式 10:直接办结,20:交办,30:派单员重派,40:结果审核重派,50:回访不满意重派
+        /// 处理方式为结果审核重派时禁止退单
+        /// 处理方式为回访不满意重派时禁止退单
+        /// </summary>
+        public string? CaseProcessType { get; set; }
+
         /// <summary>
         /// 外部工单唯一标识
         /// </summary>
@@ -1373,6 +1426,11 @@ namespace Hotline.Share.Dtos.Order
         public string? IndustryName { get; set; }
 
         #endregion
+
+        /// <summary>
+        ///知识库引用
+        /// </summary>
+        public List<Kv>? KnowledgeQuote { get; set; }
     }
 
     public record CanLinkCallRecordOrderDto : PagedKeywordRequest

+ 12 - 0
src/Hotline.Share/Dtos/Order/OrderStartFlowDto.cs

@@ -268,4 +268,16 @@ namespace Hotline.Share.Dtos.Order
 
         public NextWorkflowDto NextWorkflow { get; set; }
     }
+
+    public class BatchDelayNextFlowDto {
+
+        public string[] DelayId { get; set; }
+        
+		public NextWorkflowDto NextWorkflow { get; set; }
+
+		/// <summary>
+		/// 是否通过
+		/// </summary>
+		public bool IsPass { get; set; }
+	}
 }

+ 12 - 0
src/Hotline.Share/Dtos/Order/OrderTsDetailsDto.cs

@@ -0,0 +1,12 @@
+namespace Hotline.Share.Dtos.Order
+{
+    public class OrderTsDetailsDto
+    {
+        public string Name { get; set; }
+
+        public int CountNum { get; set; }
+
+        public string?  Id { get; set; }
+    }
+
+}

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

@@ -152,4 +152,9 @@ public record QueryOrderPublishDto : PagedKeywordRequest
     /// 国家平台转办件
     /// </summary>
     public bool? Isgjzwfwpt { get; set; }
+
+    /// <summary>
+    /// 省来源分类 1:政民互动直派 2:政民互动  3:省12345
+    /// </summary>
+    public string? ProvinceChannel { get; set; }
 }

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

@@ -178,10 +178,15 @@ namespace Hotline.Share.Dtos.Order
         public string? ProvinceChannel { get; set; }
 
         /// <summary>
-        /// 内容检索
+        /// 内容检索(受理内容)
         /// </summary>
         public string? ContentRetrieval { get; set; }
 
+        /// <summary>
+        /// 内容检索(归档意见)
+        /// </summary>
+        public string? FileOption { get; set; }
+
         /// <summary>
         /// 归档方式
         /// </summary>

+ 12 - 1
src/Hotline.Share/Dtos/Order/SendBackDto.cs

@@ -202,5 +202,16 @@ namespace Hotline.Share.Dtos.Order
 		/// 申请部门
 		/// </summary>
 		public string? OrgName { get; set; }
-	}
+
+		/// <summary>
+		/// 期满时间开始
+		/// </summary>
+		public DateTime? ExpiredTimeStart { get; set; }
+
+		/// <summary>
+		/// 期满时间结束
+		/// </summary>
+        public DateTime? ExpiredTimeEnd { get; set; }
+
+    }
 }

+ 240 - 0
src/Hotline.Share/Dtos/Planlibrary/PlanDataDto.cs

@@ -0,0 +1,240 @@
+using Hotline.Share.Dtos.Hotspots;
+using Hotline.Share.Dtos.Users;
+using Hotline.Share.Enums.Article;
+using Hotline.Share.Enums.Planlibrary;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Planlibrary
+{
+    public record PlanDataDto
+    {
+        /// <summary>
+        /// 预案ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 创建人
+        /// </summary>
+        public string CreatorName { get; set; }
+
+        /// <summary>
+        /// 浏览量
+        /// </summary>
+        public int PageView { get; set; }
+
+        /// <summary>
+        /// 是否公开
+        /// </summary>
+        public bool IsPublic { get; set; }
+
+        /// <summary>
+        /// 申请部门ID
+        /// </summary>
+        public string CreatorOrgId { get; set; }
+
+        /// <summary>
+        /// 申请部门
+        /// </summary>
+        public string CreatorOrgName { get; set; }
+
+        /// <summary>
+        /// 申请时间
+        /// </summary>
+        public DateTime CreationTime { get; set; }
+
+        /// <summary>
+        /// 上架时间
+        /// </summary>
+        public DateTime? OnShelfTime { get; set; }
+
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        public DateTime? UpdateTime { get; set; }
+
+        /// <summary>
+        /// 审核时间
+        /// </summary>
+        public DateTime? ExaminTime { get; set; }
+
+        /// <summary>
+        /// 下架时间
+        /// </summary>
+        public DateTime? OffShelfTime { get; set; }
+
+        /// <summary>
+        /// 过期时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 审核意见
+        /// </summary>
+        public string? ExaminOpinion { get; set; }
+
+        /// <summary>
+        /// 审核人ID
+        /// </summary>
+        public string? ExaminManId { get; set; }
+
+        /// <summary>
+        /// 审核人
+        /// </summary>
+        public UserDto? ExaminMan { get; set; }
+
+        /// <summary>
+        /// 预案归属
+        /// </summary>
+        public string? Attribution { get; set; }
+
+        /// <summary>
+        /// 文档状态
+        /// </summary>
+        public EPlanStatus Status { get; set; }
+
+        public EPlanStatus NewStatus => Status != EPlanStatus.Drafts && DateTime.Now > ExpiredTime ? EPlanStatus.Overdue : Status;
+
+        /// <summary>
+        /// 文档状态申请
+        /// </summary>
+        public EPlanApplyStatus ApplyStatus { get; set; }
+
+        /// <summary>
+        /// 文档状态名称
+        /// </summary>
+        public string StatusName => NewStatus.GetDescription();
+
+        /// <summary>
+        /// 预案分类
+        /// </summary>
+        public List<PlanRelationTypeDto2> PlanTypes { get; set; }
+
+        /// <summary>
+        /// 预案分类名称
+        /// </summary>
+        public string PlanTypeText => GetPlanTypeText(PlanTypes);
+
+        /// <summary>
+        /// 获取预案分类名称
+        /// </summary>
+        /// <returns></returns>
+        public string GetPlanTypeText(List<PlanRelationTypeDto2> items)
+        {
+
+            if (PlanTypes != null && PlanTypes.Any())
+            {
+                var names = PlanTypes.Select(x => x.Name).ToList();
+                return string.Join(",", names);
+            }
+            return "";
+        }
+
+        /// <summary>
+        /// 热点
+        /// </summary>
+        public string? HotspotId { get; set; }
+
+        /// <summary>
+        /// 外部数据(为前端提供级联功能)
+        /// </summary>
+        public string? HotspotExternal { get; set; }
+
+        public HotspotDto? HotspotType { get; set; }
+
+        /// <summary>
+        /// 热点
+        /// </summary>
+        public string HotspotName => HotspotType != null ? HotspotType.HotSpotFullName : string.Empty;
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+    }
+
+    public record PlanRelationTypeDto
+    {
+        /// <summary>
+        /// 预案库类型ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 预案库类型名称
+        /// </summary>
+        public string Name { get; set; }
+
+
+        /// <summary>
+        /// 预案库类型名称
+        /// </summary>
+        public string SpliceName { get; set; }
+    }
+
+    public record PlanRelationTypeDto2
+    {
+        /// <summary>
+        /// 预案库类型ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 预案库类型名称
+        /// </summary>
+        public string Name { get; set; }
+
+
+        /// <summary>
+        /// 预案库类型名称
+        /// </summary>
+        public string SpliceName { get; set; }
+    }
+}
+
+
+public class PlanExistDto
+{
+    /// <summary>
+    /// 标题
+    /// </summary>
+    public string Title { get; set; }
+
+    /// <summary>
+    /// 内容
+    /// </summary>
+    public string Content { get; set; }
+
+    /// <summary>
+    /// ID
+    /// </summary>
+    public string? Id { get; set; }
+}
+
+
+public class PlanInfoExportDto
+{
+    /// <summary>
+    /// 导入的预案Id集合
+    /// </summary>
+    public string[] Ids { get; set; }
+
+    /// <summary>
+    /// 导出格式
+    /// </summary>
+    public EFileType FileType { get; set; }
+}
+
+
+public class PlanPageViewDto
+{
+    public string Id { get; set; }
+    public string Title { get; set; }
+    public int PageView { get; set; }
+}

+ 469 - 0
src/Hotline.Share/Dtos/Planlibrary/PlanListDto.cs

@@ -0,0 +1,469 @@
+using Hotline.Share.Dtos.File;
+using Hotline.Share.Dtos.Knowledge;
+using Hotline.Share.Enums.Planlibrary;
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.Planlibrary
+{
+    /// <summary>
+    /// 预案库列表
+    /// </summary>
+    public record PlanListDto : PagedRequest
+    {
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 关键词
+        /// </summary>
+        public string? Keywords { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string Content { get; set; }
+
+
+        /// <summary>
+        /// 预案状态
+        /// </summary>
+        public EPlanStatus? Status { get; set; }
+
+        /// <summary>
+        /// 预案归属
+        /// </summary>
+        public string? Attribution { get; set; }
+
+        /// <summary>
+        /// 分类ID
+        /// </summary>
+        public string? PlanTypeID { get; set; }
+
+        /// <summary>
+        /// 热点
+        /// </summary>
+        public string? HotspotId { get; set; }
+
+        /// <summary>
+        /// 是否公开
+        /// </summary>
+        public bool? IsPublic { get; set; }
+
+        /// <summary>
+        /// 是否删除
+        /// </summary>
+        public bool? IsDeleted { get; set; } = false;
+
+        /// <summary>
+        /// 阅读次数
+        /// </summary>
+        public int PageView { get; set; } = 0;
+
+        /// <summary>
+        /// 创建开始时间
+        /// </summary>
+        public DateTime? CreationTimeStart { get; set; }
+
+        /// <summary>
+        /// 创建结束时间
+        /// </summary>
+        public DateTime? CreationTimeEnd { get; set; }
+
+        /// <summary>
+        /// 上架开始时间
+        /// </summary>
+        public DateTime? OnShelfTimeStart { get; set; }
+        /// <summary>
+        /// 上架结束时间
+        /// </summary>
+        public DateTime? OnShelfTimeEnd { get; set; }
+
+        /// <summary>
+        /// 下架开始时间
+        /// </summary>
+        public DateTime? OffShelfTimeStart { get; set; }
+
+        /// <summary>
+        /// 下架结束时间
+        /// </summary>
+        public DateTime? OffShelfTimeEnd { get; set; }
+
+        /// <summary>
+        /// 更新开始时间
+        /// </summary>
+        public DateTime? UpdateTimeStart { get; set; }
+
+        /// <summary>
+        /// 更新结束时间
+        /// </summary>
+        public DateTime? UpdateTimeEnd { get; set; }
+
+        /// <summary>
+        /// 审核人ID
+        /// </summary>
+        public string? ExaminManId { get; set; }
+
+        /// <summary>
+        /// 审核部门ID
+        /// </summary>
+        public string? ExaminOrganizeId { get; set; }
+
+        /// <summary>
+        /// 当前部门ID
+        /// </summary>
+        public string CreateOrgId { get; set; }
+
+        /// <summary>
+        /// 审核开始时间
+        /// </summary>
+        public DateTime? ExaminTimeStart { get; set; }
+
+        /// <summary>
+        /// 审核结束时间
+        /// </summary>
+        public DateTime? ExaminTimeEnd { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 排序(需要被排序的字段名)
+        /// </summary>
+        public string? SortField { get; set; }
+    }
+
+
+    public record AddPlanListDto
+    {
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 关键词
+        /// </summary>
+        public string? Keywords { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 预案归属
+        /// </summary>
+        public string Attribution { get; set; }
+
+        ///// <summary>
+        ///// 分类ID
+        ///// </summary>
+        //public string? PlanTypeID { get; set; }
+
+        /// <summary>
+        /// 分类
+        /// </summary>
+        public List<PlanRelationTypeDto> PlanTypes { get; set; }
+
+        /// <summary>
+        /// 热点
+        /// </summary>
+        public string? HotspotId { get; set; }
+
+        /// <summary>
+        /// 外部数据(为前端提供级联功能)
+        /// </summary>
+        public string? HotspotExternal { get; set; }
+
+        /// <summary>
+        /// 是否公开
+        /// </summary>
+        public bool? IsPublic { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 预案状态
+        /// </summary>
+        public EPlanApplyStatus? ApplyStatus { get; set; }
+
+        /// <summary>
+        /// 预案状态
+        /// </summary>
+        public EPlanStatus? Status { get; set; }
+
+        /// <summary>
+        /// 上传附件
+        /// </summary>
+        public List<FileDto>? Files { get; set; }
+    }
+
+    public record UpdatePlanListDto : AddPlanListDto
+    {
+        /// <summary>
+        /// 预案库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 审核人ID
+        /// </summary>
+        public string? ExaminManId { get; set; }
+
+        /// <summary>
+        /// 审核部门ID
+        /// </summary>
+        public string? ExaminOrganizeId { get; set; }
+
+        /// <summary>
+        /// 审核时间
+        /// </summary>
+        public DateTime? ExaminTime { get; set; }
+
+        /// <summary>
+        /// 修改时间
+        /// </summary>
+        public DateTime? UpdateTime { get; set; }
+
+        /// <summary>
+        /// 申请理由
+        /// </summary>
+        public string ApplyReason { get; set; }
+
+        /// <summary>
+        /// 审核意见
+        /// </summary>
+        public string ExaminOpinion { get; set; }
+
+        /// <summary>
+        /// 上架时间
+        /// </summary>
+        public DateTime? OnShelfTime { get; set; }
+
+        /// <summary>
+        /// 下架时间
+        /// </summary>
+        public DateTime? OffShelfTime { get; set; }
+    }
+
+    public record DelPlanListDto
+    {
+        /// <summary>
+        /// 预案库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 申请理由
+        /// </summary>
+        public string ApplyReason { get; set; }
+    }
+
+
+    /// <summary>
+    /// 预案库列表
+    /// </summary>
+    public record PlanInfoDto
+    {
+        /// <summary>
+        /// 标识
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 关键词
+        /// </summary>
+        public string? Keywords { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 预案归属
+        /// </summary>
+        public string? Attribution { get; set; }
+
+        /// <summary>
+        /// 是否公开
+        /// </summary>
+        public bool? IsPublic { get; set; }
+
+        /// <summary>
+        /// 阅读次数
+        /// </summary>
+        public int PageView { get; set; } = 0;
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime? CreationTime { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 创建人
+        /// </summary>
+        public string? CreatorName { get; set; }
+
+        /// <summary>
+        /// 创建部门
+        /// </summary>
+        public string? CreatorOrgName { get; set; }
+
+
+        /// <summary>
+        /// 预案分类
+        /// </summary>
+        public List<PlanRelationTypeDto2> PlanTypes { get; set; }
+
+        /// <summary>
+        /// 预案分类名称
+        /// </summary>
+        public string PlanTypeText => GetPlanTypeText(PlanTypes);
+
+        /// <summary>
+        /// 获取预案分类名称
+        /// </summary>
+        /// <returns></returns>
+        public string GetPlanTypeText(List<PlanRelationTypeDto2> items)
+        {
+
+            if (PlanTypes != null && PlanTypes.Any())
+            {
+                var names = PlanTypes.Select(x => x.Name).ToList();
+                return string.Join(",", names);
+            }
+            return "";
+        }
+
+        /// <summary>
+        /// 热点
+        /// </summary>
+        public string HotspotId { get; set; }
+
+        /// <summary>
+        /// 热点名称
+        /// </summary>
+        public string HotspotName { get; set; }
+
+        /// <summary>
+        /// 外部数据(为前端提供级联功能)
+        /// </summary>
+        public string HotspotExternal { get; set; }
+
+        public PlanCollectDto Collect { get; set; }
+
+        /// <summary>
+        /// 预案申请状态
+        /// </summary>
+        public EPlanApplyStatus ApplyStatus { get; set; }
+
+        /// <summary>
+        /// 预案状态
+        /// </summary>
+        public EPlanStatus Status { get; set; }
+
+
+        public List<FileDto> Files { get; set; }
+
+        /// <summary>
+        /// 附件
+        /// </summary>
+        public List<FileJson>? FileJson { get; set; }
+    }
+
+    public class PlanCollectDto
+    {
+        /// <summary>
+        /// 预案库ID
+        /// </summary>
+        public string PlanId { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal? Score { get; set; } = decimal.Zero;
+
+        /// <summary>
+        /// 是否收藏
+        /// </summary>
+        public bool? Collect { get; set; }
+    }
+
+    public class PlanApplyReasonDto
+    {
+        /// <summary>
+        /// 预案库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 申请理由
+        /// </summary>
+        public string? ApplyReason { get; set; }
+    }
+
+
+    public record AuditPlanListDto
+    {
+        /// <summary>
+        /// 预案库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 审核状态 0不同意 1同意
+        /// </summary>
+        public int State { get; set; }
+
+        /// <summary>
+        /// 审核意见
+        /// </summary>
+        public string ExaminOpinion { get; set; }
+    }
+
+    public record PvPlanListDto
+    {
+        /// <summary>
+        /// 预案库ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 浏览量:默认不增加,false不增加,true增加浏览量
+        /// </summary>
+        public bool IsAddPv { get; set; } = false;
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public decimal Score { get; set; }
+    }
+}

+ 189 - 0
src/Hotline.Share/Dtos/Planlibrary/PlanTypeDto.cs

@@ -0,0 +1,189 @@
+using Hotline.Settings;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Planlibrary
+{
+    /// <summary>
+    /// 编辑
+    /// </summary>
+    public record UpdatePlanTypeDto : AddPlanTypeDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+    }
+
+    /// <summary>
+    ///新增
+    /// </summary>
+    public record AddPlanTypeDto
+    {
+        /// <summary>
+        /// 类型名称
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 父级ID
+        /// </summary>
+        public string? ParentId { get; set; } 
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        public int Sort { get; set; } = 0;
+
+        public List<AddPlanTypeOrgDto>? TypeOrgDtos { get; set; }
+
+    }
+
+    /// <summary>
+    /// 初始化返回数据
+    /// </summary>
+    public record PlanTypeDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 类型名称拼接(所有父级分类名称)
+        /// <example>
+        /// A类型/A.1类型/A.1.1类型
+        /// </example>
+        /// </summary>
+        public string SpliceName { get; set; }
+
+		/// <summary>
+		/// 类型名称
+		/// </summary>
+		public string Name { get; set; }
+
+        /// <summary>
+        /// 是否启用
+        /// </summary>
+        public bool IsEnable { get; set; }
+
+        /// <summary>
+        /// 排序
+        /// </summary>
+        public int Sort { get; set; }
+
+        /// <summary>
+        /// 父级ID
+        /// </summary>
+        public string ParentId { get; set; }
+
+        /// <summary>
+        /// 树形分类
+        /// </summary>
+        public List<Knowledge.TreeListDto> TreeLists { get; set; } = new();
+
+        /// <summary>
+        /// 子级
+        /// </summary>
+        public List<PlanTypeDto> children { get; set; }
+
+        public int PlanNum { get; set; }
+	}
+
+    public record PlanOrgDto
+    {
+	    /// <summary>
+	    /// ID
+	    /// </summary>
+	    public string Id { get; set; }
+
+		/// <summary>
+		/// 组织架构名称
+		/// </summary>
+		public string Name { get; set; }
+
+	    /// <summary>
+	    /// 组织架构简称
+	    /// </summary>
+	    public string ShortName { get; set; }
+
+	    /// <summary>
+	    /// 区域Code(行政区域代码)
+	    /// </summary>
+	    public string? AreaCode { get; set; }
+
+	    /// <summary>
+	    /// 区域名称(行政区域名称)
+	    /// </summary>
+	    public string? AreaName { get; set; }
+
+	    /// <summary>
+	    /// 部门级别
+	    /// </summary>
+	    public int Level { get; set; }
+
+	    /// <summary>
+	    /// 部门类型
+	    /// </summary>
+	    public EOrgType OrgType { get; set; }
+
+	    /// <summary>
+	    /// 上级ID
+	    /// </summary>
+	    public string? ParentId { get; set; }
+
+	    /// <summary>
+	    /// 上级名称
+	    /// </summary>
+	    public string? ParentName { get; set; }
+
+	    /// <summary>
+	    /// 是否启用
+	    /// </summary>
+	    public bool IsEnable { get; set; }
+
+	    /// <summary>
+	    /// 是否为中心
+	    /// </summary>
+	    public bool IsCenter { get; set; }
+
+	    public List<PlanOrgDto> Children { get; set; }
+
+	    public string OrgTypeText => OrgType.GetDescription();
+
+
+	    /// <summary>
+	    /// 旧系统id
+	    /// </summary>
+	    public int? oldBmid { get; set; }
+
+	    public int PlanNum { get; set; }
+	}
+
+    public record PlanHotSpotDto
+    {
+	    /// <summary>
+	    /// ID
+	    /// </summary>
+	    public string Id { get; set; }
+
+	    public int PlanNum { get; set; }
+
+		public string HotSpotName { get; set; }
+	    public string ParentId { get; set; }
+	    public string PYCode { get; set; }
+
+	    public string FullPYCode { get; set; }
+	    public string ProvinceCode { get; set; }
+	    public int OrderBy { get; set; }
+	    public string TrunkNum { get; set; }
+	    /// <summary>
+	    /// 生成的时候写入
+	    /// </summary>
+	    public string HotSpotFullName { get; set; }
+
+	    public List<PlanHotSpotDto> Children { get; set; }
+
+	    public bool HasChild { get; set; }
+
+	}
+}

+ 51 - 0
src/Hotline.Share/Dtos/Planlibrary/PlanTypeOrgDto.cs

@@ -0,0 +1,51 @@
+
+namespace Hotline.Share.Dtos.Planlibrary
+{
+	public record AddPlanTypeOrgDto
+	{
+		/// <summary>
+		/// 机构ID
+		/// </summary>
+		public string OrgId { get; set; }
+
+		/// <summary>
+		/// 机构名称
+		/// </summary>
+		public string OrgName { get; set; }
+	}
+
+	public record PlanTypeOrgDto : AddPlanTypeOrgDto
+	{
+		public DateTime? LastModificationTime { get; set; }
+
+		public bool IsDeleted { get; set; }
+
+		/// <summary>
+		/// 删除时间
+		/// </summary>
+		public DateTime? DeletionTime { get; set; }
+
+
+		/// <summary>
+		/// 创建时间
+		/// </summary>
+		public DateTime CreationTime { get; set; }
+
+		public string Id { get; set; }
+
+		/// <summary>
+		/// 组织Id
+		/// </summary>
+		public string? CreatorOrgId { get; set; }
+
+
+		public string? CreatorOrgName { get; set; }
+
+		/// <summary>
+		/// 创建人
+		/// </summary>
+		public string? CreatorId { get; set; }
+
+		public string? CreatorName { get; set; }
+	}
+}

+ 90 - 0
src/Hotline.Share/Dtos/Quality/BiQualityDto.cs

@@ -0,0 +1,90 @@
+using Hotline.Share.Dtos.Order;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Quality
+{
+	public class BiQualityDto
+	{
+	}
+
+	public class SeatsQualityAnalyseDto {
+
+		/// <summary>
+		///  工单
+		/// </summary>
+		public OrderDto? Order { get; set; }
+
+		/// <summary>
+		/// 受理人名称
+		/// </summary>
+		public string? AcceptorName => Order.AcceptorName;
+
+		/// <summary>
+		/// 工单编码
+		/// </summary>
+		public string? No => Order.No;
+
+		/// <summary>
+		///  质检明细
+		/// </summary>
+		public List<QualityDetailDto>? QualityDetails { get; set; }
+
+		/// <summary>
+		/// 质检项
+		/// </summary>
+		public string? QualityDetail => QualityDetails != null && QualityDetails.Any() ? string.Join(";", QualityDetails.Select(x => x.Name)) : string.Empty;
+
+
+		/// <summary>
+		/// 质检分数
+		/// </summary>
+		public int? Grade { get; set; }
+	}
+
+
+	public class QualityOrderOverviewDto 
+	{
+		/// <summary>
+		/// 质检项
+		/// </summary>
+		public string? QualityItem { get; set; }
+
+		/// <summary>
+		/// 质检工单数
+		/// </summary>
+		public int OrderNum { get; set; }
+
+		/// <summary>
+		/// 全量质检工单数
+		/// </summary>
+		public int AllOrderNum { get; set; }
+
+		/// <summary>
+		/// 占比
+		/// </summary>
+		public string? Rate => GetRate();
+
+		/// <summary>
+		/// 计算占比
+		/// </summary>
+		/// <returns></returns>
+		public string GetRate()
+		{
+			if (OrderNum != 0 && AllOrderNum != 0)
+			{
+				return Math.Round((OrderNum / (double)AllOrderNum) * 100, 2) + "%";
+			}
+			return "0%";
+		}
+
+	}
+
+
+	public class SeatsQualityGradeAnalyseDto 
+	{
+	}
+}

+ 17 - 0
src/Hotline.Share/Dtos/Quality/QualityDto.cs

@@ -172,6 +172,18 @@ namespace Hotline.Share.Dtos.Quality
 		/// </summary>
 		public bool AiQuality { get; set; }
 
+		/// <summary>
+		/// 转写状态
+		/// </summary>
+		public EQualityTransferState? TransferState { get; set; }
+
+		public string TransferStateText => TransferState.HasValue ? TransferState.GetDescription() : string.Empty;
+
+		/// <summary>
+		/// 转写内容
+		/// </summary>
+		public List<Transfer>? Transfer { get; set; }
+
 	}
 	public class QualityBaseDto {
 		public DateTime? LastModificationTime { get; set; }
@@ -230,4 +242,9 @@ namespace Hotline.Share.Dtos.Quality
 		/// </summary>
 		public int? MinGrade { get; set; }
 	}
+
+	public class AiQualityXTDto 
+	{
+		public string Id { get; set; }
+	}
 }

+ 145 - 0
src/Hotline.Share/Dtos/QualityExportWord/QualityCertificate.cs

@@ -0,0 +1,145 @@
+using Hotline.Share.Dtos.ExportWord;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.QualityExportWord
+{
+	public class QualityCertificate : IWordExportTemplate
+	{
+		/// <summary>
+		/// 市州名称
+		/// </summary>
+		public string CityName { get; set; }
+
+		/// <summary>
+		/// 编号
+		/// </summary>
+		public string No { get; set; }
+
+		/// <summary>
+		/// 来信时间
+		/// </summary>
+		public string? CreationTime { get; set; }
+
+		/// <summary>
+		/// 来电/信人姓名
+		/// </summary>
+		public string? FromName { get; set; }
+
+		/// <summary>
+		/// 来电/信人性别
+		/// </summary>
+		public string? FromGender { get; set; }
+
+		/// <summary>
+		/// 来电号码
+		/// </summary>
+		public string? FromPhone { get; set; }
+
+		/// <summary>
+		/// 联系电话
+		/// </summary>
+		public string? Contact { get; set; }
+
+		/// <summary>
+		/// 来电地址
+		/// </summary>
+		public string? FullAddress { get; set; }
+
+		/// <summary>
+		/// 标题
+		/// </summary>
+		public string Title { get; set; }
+
+		/// <summary>
+		/// 内容
+		/// </summary>
+		public string Content { get; set; }
+
+		/// <summary>
+		/// 受理类型
+		/// </summary>
+		public string? AcceptType { get; set; }
+
+		/// <summary>
+		/// 热点
+		/// </summary>
+		public string? HotspotSpliceName { get; set; }
+
+		/// <summary>
+		/// 实际办理部门名称
+		/// </summary>
+		public string? ActualHandleOrgName { get; set; }
+
+		/// <summary>
+		/// 办理时间限制(如:24小时、7个工作日)
+		/// </summary>
+		public string? TimeLimit { get; set; }
+
+		/// <summary>
+		/// 交办意见
+		/// </summary>
+		public string? CenterToOrgOpinion { get; set; }
+
+		/// <summary>
+		/// 实际办理意见
+		/// </summary>
+		public string? FileOpinion { get; set; }
+
+		/// <summary>
+		/// 回访部门信息
+		/// </summary>
+		public string? VisitOrg { get; set; }
+
+		/// <summary>
+		/// 回访内容
+		/// </summary>
+		public string? VisitContent { get; set; }
+
+		/// <summary>
+		/// 交办时间(中心交部门办理时间)
+		/// </summary>
+		public string? CenterToOrgTime { get; set; }
+
+		/// <summary>
+		/// 超期时间(期满时间)
+		/// </summary>
+		public string? ExpiredTime { get; set; }
+
+		/// <summary>
+		///  交办人
+		/// </summary>
+		public string? CenterToOrgHandlerName { get; set; }
+
+
+		/// <summary>
+		/// 受理人名称
+		/// </summary>
+		public string? AcceptorName { get; set; }
+
+
+		/// <summary>
+		/// 质检分数
+		/// </summary>
+		public int? Grade { get; set; }
+
+		/// <summary>
+		/// 质检评价
+		/// </summary>
+		public string? QualityContent { get; set; }
+
+
+		/// <summary>
+		/// 质检人
+		/// </summary>
+		public string? UserName { get; set; }
+
+		/// <summary>
+		/// 质检时间
+		/// </summary>
+		public string? QualityTime { get; set; }
+	}
+}

+ 2 - 1
src/Hotline.Share/Dtos/Snapshot/RedPackDto.cs

@@ -3,6 +3,7 @@ using Hotline.Share.Enums.Snapshot;
 using Hotline.Share.Requests;
 using Hotline.Share.Tools;
 using System.ComponentModel.DataAnnotations;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.Snapshot;
 public class RedPackOutDto 
@@ -171,7 +172,7 @@ public class SnapshotRedPackRecordSupplementItemsOutDto
     /// <summary>
     /// 信件状态
     /// </summary>
-    public string StatusTxt { get; set; }
+    public string StatusTxt => Status.GetDescription();
 
     /// <summary>
     /// 来源

+ 115 - 22
src/Hotline.Share/Dtos/TrCallCenter/TrTelDao.cs

@@ -1,4 +1,5 @@
 
+using Hotline.Share.Dtos.Caselibrary;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.CallCenter;
@@ -227,7 +228,7 @@ namespace Hotline.Share.Dtos.TrCallCenter
         /// 工号
         /// </summary>
         public string? StaffNo { get; set; }
-        
+
         public int Second => CalcSecond();
 
         public ETelModel? TelModel { get; set; }
@@ -582,13 +583,13 @@ namespace Hotline.Share.Dtos.TrCallCenter
     public class TrCallDto
     {
         public string Id { get; set; }
-		/// <summary>
-		/// 可直接访问的通话录音地址
-		/// </summary>
-		public string? RecordingFileUrl { get; set; }
+        /// <summary>
+        /// 可直接访问的通话录音地址
+        /// </summary>
+        public string? RecordingFileUrl { get; set; }
         public string? RecordingBaseAddress { get; set; }
         public string? RecordingAbsolutePath { get; set; }
-        
+
         /// <summary>
         /// 呼叫方向
         /// </summary>
@@ -778,10 +779,10 @@ namespace Hotline.Share.Dtos.TrCallCenter
 
         public string TransliterationStateText => TransliterationState.GetDescription();
 
-		/// <summary>
-		/// 转写id  涉及转写失败后再次推送  id需要更换
-		/// </summary>
-		public string? TransliterationId { get; set; }
+        /// <summary>
+        /// 转写id  涉及转写失败后再次推送  id需要更换
+        /// </summary>
+        public string? TransliterationId { get; set; }
 
         /// <summary>
         /// 敏感标签
@@ -793,7 +794,7 @@ namespace Hotline.Share.Dtos.TrCallCenter
         #endregion
     }
 
-    public class CallRecordOutDto 
+    public class CallRecordOutDto
     {
         /// <summary>
         /// 可直接访问的通话录音地址
@@ -1102,7 +1103,7 @@ namespace Hotline.Share.Dtos.TrCallCenter
         /// <summary>
         /// 工单Id
         /// </summary>
-        public string  OrderId { get; set; }
+        public string OrderId { get; set; }
 
         /// <summary>
         /// 工单编号
@@ -1117,17 +1118,18 @@ namespace Hotline.Share.Dtos.TrCallCenter
     }
 
 
-    public record class CallTransliteration {
-		/// <summary>
-		/// 主叫
-		/// </summary>
-		public List<string> Ids { get; set; }
-	}
-	#endregion
+    public record class CallTransliteration
+    {
+        /// <summary>
+        /// 主叫
+        /// </summary>
+        public List<string> Ids { get; set; }
+    }
+    #endregion
 
-	#region 关联
+    #region 关联
 
-	public class LinkCallRecordDto
+    public class LinkCallRecordDto
     {
         public bool IsOrder { get; set; }
 
@@ -1140,7 +1142,7 @@ namespace Hotline.Share.Dtos.TrCallCenter
 
     #region 坐席动作类型
 
-    public record TelActionListDto:PagedRequest
+    public record TelActionListDto : PagedRequest
     {
         public string TelNo { get; set; }
 
@@ -1153,6 +1155,18 @@ namespace Hotline.Share.Dtos.TrCallCenter
         public DateTime? EndTime { get; set; }
     }
 
+    public record TelActionXthxDto : PagedRequest
+    {
+        public string TelNo { get; set; }
+
+        public EOperationStatus? OperationStatus { get; set; }
+
+        public string? UserName { get; set; }
+
+        public DateTime? StartTime { get; set; }
+        public DateTime? EndTime { get; set; }
+    }
+
     public class TelActionListRep
     {
         public string Id { get; set; }
@@ -1201,5 +1215,84 @@ namespace Hotline.Share.Dtos.TrCallCenter
 
     }
 
+    public class TelActionListXthx
+    {
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 分机id
+        /// </summary>
+        public string? TelId { get; set; }
+
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        public string? TelNo { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string? StaffNo { get; set; }
+
+        /// <summary>
+        /// 用户id
+        /// </summary>
+        public string? UserId { get; set; }
+
+        /// <summary>
+        /// 用户名称
+        /// </summary>
+        public string? UserName { get; set; }
+
+        /// <summary>
+        /// 分组
+        /// </summary>
+        public string? UserGroup { get; set; }
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        public DateTime? StartTime { get; set; }
+
+        /// <summary>
+        /// 结束时间
+        /// </summary>
+        public DateTime? EndTime { get; set; }
+
+        /// <summary>
+        /// 时长(单位:秒)
+        /// </summary>
+        public decimal RestDuration { get; set; }
+
+        /// <summary>
+        /// 案例分类名称
+        /// </summary>
+        public decimal RestDuration2 => GetRestDuration(RestDuration);
+
+        /// <summary>
+        /// 获取案例分类名称
+        /// </summary>
+        /// <returns></returns>
+        public decimal GetRestDuration(decimal value)
+        {
+            if (value != 0)
+            {
+                return Math.Round(value, 2);
+            }
+            return 0;
+        }
+
+        /// <summary>
+        /// 备注(冗余)
+        /// </summary>
+        public string? Reason { get; set; }
+
+        /// <summary>
+        /// 分机状态
+        /// </summary>
+        public EOperationStatus OperationStatus { get; set; }
+
+    }
+
     #endregion
 }

+ 55 - 0
src/Hotline.Share/Dtos/Transfer.cs

@@ -0,0 +1,55 @@
+using Hotline.Share.Enums.Quality;
+using Lucene.Net.Search;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos
+{
+	/// <summary>
+	/// 转写
+	/// </summary>
+	public class Transfer
+	{
+		public Transfer()
+		{
+		}
+
+		public Transfer(EQualityTransferType type, string content,string reviseContent, int sort,string prohibited)
+		{
+			Type  = type;
+			Content = content;
+			ReviseContent = reviseContent;
+			Sort = sort;
+			Prohibited = prohibited;
+		}
+
+		/// <summary>
+		/// 转写类型
+		/// </summary>
+		public EQualityTransferType Type { get; set; }
+
+		/// <summary>
+		/// 转写内容
+		/// </summary>
+		public string? Content { get; set; }
+
+		/// <summary>
+		/// 转写修改后内容
+		/// </summary>
+		public string? ReviseContent { get; set; }
+
+		/// <summary>
+		/// 排序
+		/// </summary>
+		public int Sort { get; set; }
+
+		/// <summary>
+		/// 违禁词
+		/// </summary>
+		public string? Prohibited { get; set; }
+	}
+}

+ 1 - 1
src/Hotline.Share/Dtos/WebPortal/WebFlowAcceptDto.cs

@@ -81,7 +81,7 @@ namespace Hotline.Share.Dtos.WebPortal
         public string Content { get; set; }
 
         /// <summary>
-        /// 来源方式 APP来源为2,微信来源为3,
+        /// 来源方式 APP来源为2,微信来源为3,宜宾人社来源9,
         /// </summary>
         public string? FromID { get; set; }
 

+ 123 - 0
src/Hotline.Share/Enums/CallCenter/EOperationStatus.cs

@@ -0,0 +1,123 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.CallCenter;
+
+/// <summary>
+/// 动作状态
+/// </summary>
+public enum EOperationStatus
+{
+    /// <summary>
+    /// 签入
+    /// </summary>
+    [Description("签入")]
+    SignIn = 100,
+
+    /// <summary>
+    /// 示忙
+    /// </summary>
+    [Description("示忙")]
+    ShowBusy = 202,
+
+    /// <summary>
+    /// 小休
+    /// </summary>
+    [Description("小休")]
+    ShortBreak = 201,
+
+    /** 上面是话机动作类型一共只有的3个状态
+     *  上下总共是兴唐可传入得状态
+     **/
+
+    /// <summary>
+    /// 签出
+    /// </summary>
+    [Description("签出")]
+    SignOut = 0,
+
+    /// <summary>
+    /// 空闲
+    /// </summary>
+    [Description("空闲")]
+    FreeTime = 200,
+
+    /// <summary>
+    /// 呼入振铃
+    /// </summary>
+    [Description("呼入振铃")]
+    InComingRinging = 300,
+
+    /// <summary>
+    /// 呼入通话
+    /// </summary>
+    [Description("呼入通话")]
+    InComingCall = 301,
+
+    /// <summary>
+    /// 呼出振铃
+    /// </summary>
+    [Description("呼出振铃")]
+    OutSideRinging = 302,
+
+    /// <summary>
+    /// 呼出通话
+    /// </summary>
+    [Description("呼出通话")]
+    OutSideCall = 303,
+
+    /// <summary>
+    /// 通话保持
+    /// </summary>
+    [Description("通话保持")]
+    CallHold = 310,
+
+    /// <summary>
+    /// 会议
+    /// </summary>
+    [Description("会议")]
+    Meeting = 320,
+
+    /// <summary>
+    /// 咨询
+    /// </summary>
+    [Description("咨询")]
+    Consult = 330,
+
+    /// <summary>
+    /// 整理
+    /// </summary>
+    [Description("整理")]
+    Arrange = 400,
+
+    /// <summary>
+    /// 注册
+    /// </summary>
+    [Description("注册")]
+    Register = 900,
+
+    /// <summary>
+    /// 注销
+    /// </summary>
+    [Description("注销")]
+    LogOff = 901
+
+}
+
+
+/*
+- '0': '签出'
+- '100': '签入'
+- '200': '空闲'
+- '201': '小休'
+- '202': '繁忙'
+- '300': '呼入振铃'
+- '301': '呼入通话'
+- '302': '呼出振铃'
+- '303': '呼出通话'
+- '310': '通话保持'
+- '320': '会议'
+- '330': '咨询'
+- '400': '整理'
+- '900': '注册'
+- '901': '注销'
+ */

+ 33 - 0
src/Hotline.Share/Enums/Caselibrary/ECaseApplyStatus.cs

@@ -0,0 +1,33 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Caselibrary;
+
+/// <summary>
+/// 案例审批-申请类型
+/// </summary>
+public enum ECaseApplyStatus
+{
+    /// <summary>
+    /// 新增
+    /// </summary>
+    [Description("新增")]
+    Add = 0,
+
+    /// <summary>
+    /// 修改
+    /// </summary>
+    [Description("修改")]
+    Update = 1,
+
+    /// <summary>
+    /// 删除
+    /// </summary>
+    [Description("删除")]
+    Delete = 2,
+
+	/// <summary>
+	/// 下架
+	/// </summary>
+	[Description("下架")]
+    Offshelf = 3,
+}

+ 64 - 0
src/Hotline.Share/Enums/Caselibrary/ECaseStatus.cs

@@ -0,0 +1,64 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Caselibrary
+{
+    /// <summary>
+    /// 案例状态
+    /// </summary>
+    public enum ECaseStatus
+    {
+        /// <summary>
+        /// 全部
+        /// </summary>
+        [Description("全部")]
+        All = -1,
+
+        /// <summary>
+        /// 待提交
+        /// </summary>
+        [Description("待提交")]
+        Drafts = 0,
+
+        /// <summary>
+        /// 审批中
+        /// </summary>
+        [Description("审批中")]
+        Auditing = 1,
+
+        /// <summary>
+        /// 失败
+        /// </summary>
+        [Description("失败")]
+        Failed = 2,
+
+        /// <summary>
+        /// 已上架
+        /// </summary>
+        [Description("已上架")]
+        OnShelf = 3,
+
+        /// <summary>
+        /// 已下架
+        /// </summary>
+        [Description("已下架")]
+        OffShelf = 4,
+
+        /// <summary>
+        /// 已驳回
+        /// </summary>
+        [Description("已驳回")]
+        Revert = 5,
+
+        /// <summary>
+        /// 已过期
+        /// </summary>
+        [Description("已过期")]
+        Overdue = 6,
+
+        /// <summary>
+        /// 草稿
+        /// </summary>
+        [Description("草稿")]
+        NewDrafts = 7
+    }
+}

Some files were not shown because too many files changed in this diff