qinchaoyue hace 3 meses
padre
commit
45cf1411a6
Se han modificado 70 ficheros con 1640 adiciones y 568 borrados
  1. 53 12
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  2. 8 1
      src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs
  3. 1 0
      src/Hotline.Api/Controllers/CallController.cs
  4. 1 4
      src/Hotline.Api/Controllers/CaseController.cs
  5. 24 5
      src/Hotline.Api/Controllers/OrderController.cs
  6. 3 7
      src/Hotline.Api/Controllers/PlanController.cs
  7. 28 24
      src/Hotline.Api/Controllers/SysController.cs
  8. 149 4
      src/Hotline.Api/Controllers/TestController.cs
  9. 2 6
      src/Hotline.Api/Controllers/WebPortalController.cs
  10. 261 0
      src/Hotline.Api/Controllers/XthxController.cs
  11. 0 3
      src/Hotline.Api/StartupExtensions.cs
  12. 2 2
      src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs
  13. 67 1
      src/Hotline.Application.Tests/Controller/OrderControllerTest.cs
  14. 39 12
      src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs
  15. 1 1
      src/Hotline.Application.Tests/appsettings.Development.json
  16. 34 23
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  17. 1 1
      src/Hotline.Application/CallCenter/ICallApplication.cs
  18. 2 2
      src/Hotline.Application/CallCenter/TianRunCallApplication.cs
  19. 36 20
      src/Hotline.Application/Caselibrary/CaseApplication.cs
  20. 4 1
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  21. 4 0
      src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs
  22. 27 5
      src/Hotline.Application/Orders/OrderApplication.cs
  23. 0 106
      src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs
  24. 34 30
      src/Hotline.Application/Orders/OrderSendBackAuditApplication.cs
  25. 40 25
      src/Hotline.Application/Planlibrary/PlanApplication.cs
  26. 5 2
      src/Hotline.Application/Snapshot/Notifications/SnapshotHandler.cs
  27. 36 1
      src/Hotline.Application/StatisticalReport/IOrderReportApplication.cs
  28. 66 49
      src/Hotline.Application/StatisticalReport/OrderReportApplication.cs
  29. 34 0
      src/Hotline.Application/Xthx/IXthxApplication.cs
  30. 122 0
      src/Hotline.Application/Xthx/XthxApplication.cs
  31. 1 1
      src/Hotline.Repository.SqlSugar/CallCenter/TrCallRecordRepository.cs
  32. 29 21
      src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs
  33. 5 0
      src/Hotline.Repository.SqlSugar/Snapshot/OrderSnapshotRepository.cs
  34. 22 0
      src/Hotline.Repository.SqlSugar/System/SettingOrderVisitSmsReplyRuleRepository.cs
  35. 10 1
      src/Hotline.Share/Dtos/Bi/BiOrderDto.cs
  36. 1 1
      src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs
  37. 13 0
      src/Hotline.Share/Dtos/CallCenter/CallDto.cs
  38. 48 0
      src/Hotline.Share/Dtos/CallCenter/TelOperationXthxDto.cs
  39. 4 4
      src/Hotline.Share/Dtos/Caselibrary/CaseDataDto.cs
  40. 1 1
      src/Hotline.Share/Dtos/Caselibrary/CaseListDto.cs
  41. 1 1
      src/Hotline.Share/Dtos/Order/Detail/OrderFlowTraceDto.cs
  42. 1 1
      src/Hotline.Share/Dtos/Order/OrderBiDto.cs
  43. 5 0
      src/Hotline.Share/Dtos/Order/Publish/QueryOrderPublishDto.cs
  44. 12 1
      src/Hotline.Share/Dtos/Order/SendBackDto.cs
  45. 25 6
      src/Hotline.Share/Dtos/Planlibrary/PlanDataDto.cs
  46. 3 3
      src/Hotline.Share/Dtos/Planlibrary/PlanListDto.cs
  47. 1 1
      src/Hotline.Share/Dtos/Settings/SystemLogDto.cs
  48. 2 1
      src/Hotline.Share/Dtos/Snapshot/RedPackDto.cs
  49. 20 1
      src/Hotline.Share/Dtos/TrCallCenter/TrTelDao.cs
  50. 99 3
      src/Hotline.Share/Enums/CallCenter/EOperationStatus.cs
  51. 15 2
      src/Hotline.Share/Requests/PagedKeywordRequest.cs
  52. 2 0
      src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs
  53. 5 0
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  54. 0 5
      src/Hotline/CallCenter/Tels/TelOperationXthx.cs
  55. 34 0
      src/Hotline/CallCenter/Tels/TelOperationXthxLog.cs
  56. 7 5
      src/Hotline/CaseLibrary/CaseList.cs
  57. 3 23
      src/Hotline/CaseLibrary/CaseRelationType.cs
  58. 2 1
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  59. 0 64
      src/Hotline/FlowEngine/Workflows/WorkflowStep.cs
  60. 5 2
      src/Hotline/Orders/DatabaseEventHandler/OrderVisitDetailEventHandler.cs
  61. 3 2
      src/Hotline/Orders/IOrderVisitDomainService.cs
  62. 30 43
      src/Hotline/Orders/OrderVisitDomainService.cs
  63. 7 2
      src/Hotline/Planlibrary/PlanList.cs
  64. 4 22
      src/Hotline/Planlibrary/PlanRelationType.cs
  65. 12 0
      src/Hotline/Settings/ISettingOrderVisitSmsReplyRuleRepository.cs
  66. 5 0
      src/Hotline/Settings/SettingConstants.cs
  67. 88 0
      src/Hotline/Settings/SettingOrderVisitSmsReplyRule.cs
  68. 23 2
      src/Hotline/Settings/TimeLimitDomain/ExpireTimeSupplier/WorkDaySupplier.cs
  69. 7 0
      src/Hotline/Snapshot/Interfaces/IOrderSnapshotRepository.cs
  70. 1 1
      src/Tr.Sdk/Tr.Sdk.csproj

+ 53 - 12
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -901,10 +901,11 @@ namespace Hotline.Api.Controllers.Bi
             {
                 OrgName = "合计",
                 OrgCode = "",
-                AllTotal = list.Sum(p => p.AllTotal),
+                //AllTotal = list.Sum(p => p.AllTotal),
                 PassTotal = list.Sum(p => p.PassTotal),
                 NoPassTotal = list.Sum(p => p.NoPassTotal),
-                ExaminingTotal = list.Sum(p => p.ExaminingTotal)
+                ExaminingTotal = list.Sum(p => p.ExaminingTotal),
+                WithdrawTotal = list.Sum(p=> p.WithdrawTotal)
             });
 
             var orderDtos = _mapper.Map<ICollection<BiOrderDelayDataDto>>(list);
@@ -3103,7 +3104,11 @@ namespace Hotline.Api.Controllers.Bi
             //var items = await _orderReportApplication.DepartmentalProcessingStatistics(dto)
             //    .ToListAsync();
 
-            var items = await _orderReportApplication.DepartmentalProcessingStatisticsNew(dto);
+            var items = new List<DepartmentalProcessingStatisticsDataDto>();
+            if (_appOptions.Value.IsLuZhou == true)
+                items = await _orderReportApplication.LZDepartmentalProcessingStatisticsNew(dto);
+            else
+                items = await _orderReportApplication.DepartmentalProcessingStatisticsNew(dto);
 
             var total = new DepartmentalProcessingStatisticsDataDto
             {
@@ -3144,7 +3149,11 @@ namespace Hotline.Api.Controllers.Bi
         [HttpPost("departmental_processing_statistics_export")]
         public async Task<FileStreamResult> ExportDepartmentalProcessingStatistics([FromBody] ExportExcelDto<DepartmentalProcessingStatisticsRequest> dto)
         {
-            var list = await _orderReportApplication.DepartmentalProcessingStatisticsNew(dto.QueryDto);
+            var list = new List<DepartmentalProcessingStatisticsDataDto>();
+            if (_appOptions.Value.IsLuZhou == true)
+                list = await _orderReportApplication.LZDepartmentalProcessingStatisticsNew(dto.QueryDto);
+            else
+                list = await _orderReportApplication.DepartmentalProcessingStatisticsNew(dto.QueryDto);
 
             //增加合计
             list.Add(new DepartmentalProcessingStatisticsDataDto
@@ -3198,7 +3207,11 @@ namespace Hotline.Api.Controllers.Bi
         [HttpGet("departmental_processing_child_statistics")]
         public async Task<object> DepartmentalProcessingChildStatistics([FromQuery] DepartmentalProcessingStatisticsRequest dto)
         {
-            var items = await _orderReportApplication.DepartmentalProcessingChildStatisticsNew(dto);
+            var items = new List<DepartmentalProcessingStatisticsDataDto>();
+            if (_appOptions.Value.IsLuZhou == true)
+                items = await _orderReportApplication.LZDepartmentalProcessingChildStatisticsNew(dto);
+            else
+                items = await _orderReportApplication.DepartmentalProcessingChildStatisticsNew(dto);
 
             //计算合计
             var total = new DepartmentalProcessingStatisticsDataDto
@@ -3240,7 +3253,12 @@ namespace Hotline.Api.Controllers.Bi
         [HttpPost("departmental_processing_child_statistics_export")]
         public async Task<FileStreamResult> ExportDepartmentalProcessingChildStatistics([FromBody] ExportExcelDto<DepartmentalProcessingStatisticsRequest> dto)
         {
-            var list = await _orderReportApplication.DepartmentalProcessingChildStatisticsNew(dto.QueryDto);
+            var list = new List<DepartmentalProcessingStatisticsDataDto>();
+            if (_appOptions.Value.IsLuZhou == true)
+                list = await _orderReportApplication.LZDepartmentalProcessingChildStatisticsNew(dto.QueryDto);
+            else
+                list = await _orderReportApplication.DepartmentalProcessingChildStatisticsNew(dto.QueryDto);
+
             //增加合计
             list.Add(new DepartmentalProcessingStatisticsDataDto
             {
@@ -3295,7 +3313,13 @@ namespace Hotline.Api.Controllers.Bi
         {
             RefAsync<int> total = 0;
 
-            var queryData = await _orderReportApplication.GetDepartmentalProcessingStatisticsListNew(dto, HttpContext.RequestAborted)
+            ISugarQueryable<SelectOrderId> query;
+            if (_appOptions.Value.IsLuZhou == true)
+                query = _orderReportApplication.LZGetDepartmentalProcessingStatisticsListNew(dto, HttpContext.RequestAborted);
+            else
+                query = _orderReportApplication.GetDepartmentalProcessingStatisticsListNew(dto, HttpContext.RequestAborted);
+
+            var queryData = await query
                         .LeftJoin<Order>((x, o) => x.Id == o.Id)
                         .OrderByIF(string.IsNullOrEmpty(dto.SortField), (x, o) => o.CreationTime, OrderByType.Desc)
                         .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, (x, o) => o.StartTime, OrderByType.Asc) //受理时间升序
@@ -3323,7 +3347,13 @@ namespace Hotline.Api.Controllers.Bi
         [HttpPost("departmental_processing_statistics_list_export")]
         public async Task<FileStreamResult> ExportGetDepartmentalProcessingStatisticsList([FromBody] ExportExcelDto<DepartmentalProcessingStatisticsRequest> dto)
         {
-            var query = _orderReportApplication.GetDepartmentalProcessingStatisticsListNew(dto.QueryDto, HttpContext.RequestAborted)
+            ISugarQueryable<SelectOrderId> queryDa;
+            if (_appOptions.Value.IsLuZhou == true)
+                queryDa = _orderReportApplication.LZGetDepartmentalProcessingStatisticsListNew(dto.QueryDto, HttpContext.RequestAborted);
+            else
+                queryDa = _orderReportApplication.GetDepartmentalProcessingStatisticsListNew(dto.QueryDto, HttpContext.RequestAborted);
+
+            var query = queryDa
                  .LeftJoin<Order>((x, o) => x.Id == o.Id)
                 .OrderByIF(string.IsNullOrEmpty(dto.QueryDto.SortField), (x, o) => o.CreationTime, OrderByType.Desc)
                 .OrderByIF(dto.QueryDto is { SortField: "startTime", SortRule: 0 }, (x, o) => o.StartTime, OrderByType.Asc) //受理时间升序
@@ -3376,8 +3406,13 @@ namespace Hotline.Api.Controllers.Bi
         {
             RefAsync<int> total = 0;
 
-            var queryData = await _orderReportApplication.DepartmentalProcessingStatisticsDetailsList(dto)
-                        .LeftJoin<Order>((x, o) => x.Id == o.Id)
+            ISugarQueryable<SelectOrderId> query;
+            if (_appOptions.Value.IsLuZhou == true)
+                query = _orderReportApplication.LZDepartmentalProcessingStatisticsDetailsList(dto);
+            else
+                query = _orderReportApplication.DepartmentalProcessingStatisticsDetailsList(dto);
+
+            var queryData = await query.LeftJoin<Order>((x, o) => x.Id == o.Id)
                         .OrderByIF(string.IsNullOrEmpty(dto.SortField), (x, o) => o.CreationTime, OrderByType.Desc)
                         .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, (x, o) => o.StartTime, OrderByType.Asc) //受理时间升序
                         .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, (x, o) => o.StartTime, OrderByType.Desc) //受理时间降序
@@ -3405,8 +3440,14 @@ namespace Hotline.Api.Controllers.Bi
         [HttpPost("departmental_processing_statistics_details_list_export")]
         public async Task<FileStreamResult> ExportDepartmentalProcessingStatisticsDetailsList([FromBody] ExportExcelDto<DepartmentalProcessingStatisticsRequest> dto)
         {
-            var query = _orderReportApplication.DepartmentalProcessingStatisticsDetailsList(dto.QueryDto)
-                  .LeftJoin<Order>((x, o) => x.Id == o.Id)
+            ISugarQueryable<SelectOrderId> queryDa;
+            if (_appOptions.Value.IsLuZhou == true)
+                queryDa = _orderReportApplication.LZDepartmentalProcessingStatisticsDetailsList(dto.QueryDto);
+            else
+                queryDa = _orderReportApplication.DepartmentalProcessingStatisticsDetailsList(dto.QueryDto);
+
+
+            var query = queryDa.LeftJoin<Order>((x, o) => x.Id == o.Id)
                   .OrderByIF(string.IsNullOrEmpty(dto.QueryDto.SortField), (x, o) => o.CreationTime, OrderByType.Desc)
                         .OrderByIF(dto.QueryDto is { SortField: "startTime", SortRule: 0 }, (x, o) => o.StartTime, OrderByType.Asc) //受理时间升序
                         .OrderByIF(dto.QueryDto is { SortField: "startTime", SortRule: 1 }, (x, o) => o.StartTime, OrderByType.Desc) //受理时间降序

+ 8 - 1
src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs

@@ -10,6 +10,7 @@ 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;
@@ -617,7 +618,13 @@ namespace Hotline.Api.Controllers.Bigscreen
 
                 OrderOverdueCount = await _orderSecondaryHandlingRepository.Queryable()
                 .Includes(x => x.Order)
-                 .Where(x => x.Order.ExpiredStatus == EExpiredStatus.Expired && x.AuditTime >= StartTime && x.AuditTime <= EndTime
+                 //.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(),

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

@@ -21,6 +21,7 @@ using XF.Domain.Exceptions;
 using XF.Utility.EnumExtensions;
 using XF.Domain.Repository;
 using MapsterMapper;
+using SqlSugar;
 
 namespace Hotline.Api.Controllers
 {

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

@@ -12,13 +12,10 @@ using Hotline.Share.Tools;
 using Hotline.Share.Enums.Caselibrary;
 using Hotline.Application.ExportWord;
 using Hotline.Application.Tools;
-using Hotline.Share.Dtos.Planlibrary;
-using Hotline.Share.Enums.Planlibrary;
 using Hotline.Share.Enums.Article;
 using XF.Utility.EnumExtensions;
 using Hotline.Share.Dtos.Order;
 using Hotline.Application.ExportExcel;
-using System.Threading;
 
 namespace Hotline.Api.Controllers
 {
@@ -156,7 +153,7 @@ namespace Hotline.Api.Controllers
                 {
                     Id = x.Id.SelectAll(),
                     CaseNum = SqlFunc.Subqueryable<CaseRelationType>().LeftJoin<CaseList>((kr, k) => kr.CaseId == k.Id)
-                         .Where((kr, k) => kr.CaseTypeSpliceName.StartsWith(x.SpliceName))
+                         .Where((kr, k) => kr.CaseTypeId == x.Id)
                          .DistinctCount(kr => kr.CaseId)
                 }
                 )

+ 24 - 5
src/Hotline.Api/Controllers/OrderController.cs

@@ -1318,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);
     }
@@ -2083,8 +2083,12 @@ public class OrderController : BaseController
 			try
 			{
                 var workflow = dto.NextWorkflow;
-				var delay = await _orderDelayRepository.GetAsync(item, HttpContext.RequestAborted);
+				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
@@ -2104,7 +2108,23 @@ public class OrderController : BaseController
 					}
 				}
 				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)
                 {
@@ -4174,10 +4194,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)
             {
@@ -4484,6 +4502,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);
     }

+ 3 - 7
src/Hotline.Api/Controllers/PlanController.cs

@@ -14,10 +14,6 @@ using Hotline.Application.ExportWord;
 using Hotline.Application.Tools;
 using Hotline.Share.Enums.Article;
 using XF.Utility.EnumExtensions;
-using Quartz.Simpl;
-using DocumentFormat.OpenXml.Office2010.Excel;
-using Hotline.Share.Dtos.Caselibrary;
-using Hotline.Share.Dtos.Knowledge;
 using Hotline.Share.Dtos.Order;
 using Hotline.Application.ExportExcel;
 
@@ -151,13 +147,13 @@ namespace Hotline.Api.Controllers
             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()
+                            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.PlanTypeSpliceName.StartsWith(x.SpliceName))
+                         .Where((kr, k) => kr.PlanTypeId == x.Id)
                          .DistinctCount(kr => kr.PlanId)
                 }
                 )
@@ -325,7 +321,7 @@ namespace Hotline.Api.Controllers
                 if (planDto.ApplyStatus == EPlanApplyStatus.Add)
                 {
                     planDto.Status = EPlanStatus.OnShelf;
-                    planDto.OnShelfTime = DateTime.Now; 
+                    planDto.OnShelfTime = DateTime.Now;
                 }
                 if (planDto.ApplyStatus == EPlanApplyStatus.Update)
                 {

+ 28 - 24
src/Hotline.Api/Controllers/SysController.cs

@@ -565,30 +565,34 @@ namespace Hotline.Api.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpGet("log_list")]
-        public async Task<PagedDto<SystemLogDto>> List([FromQuery] SysLogPagedKeywordRequest dto)
-        {
-	        var (total, items) = await _systemLogRepository.Queryable()
-		        .WhereIF(dto.IsAll.HasValue && !dto.IsAll.Value, x => !string.IsNullOrEmpty(x.Name))
-		        .WhereIF(!string.IsNullOrEmpty(dto.Name), x => x.Name.Contains(dto.Name))
-		        .WhereIF(!string.IsNullOrEmpty(dto.CreatorName), x => x.CreatorName.Contains(dto.CreatorName))
-		        .WhereIF(!string.IsNullOrEmpty(dto.ExecuteUrl), x => x.ExecuteUrl.Contains(dto.ExecuteUrl))
-		        .WhereIF(!string.IsNullOrEmpty(dto.ExecuteParam),
-			        x => SqlFunc.JsonLike(x.ExecuteParam, dto.ExecuteParam))
-		        .WhereIF(dto.StartTime.HasValue && dto.EndTime.HasValue,
-			        x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
-		        //.WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Name.Contains(dto.Keyword!) || x.CreatorName.Contains(dto.Keyword!))
-		        .OrderByDescending(x => x.CreationTime)
-		        .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
-            return new PagedDto<SystemLogDto>(total, _mapper.Map<IReadOnlyList<SystemLogDto>>(items));
-            
-        }
-
-        /// <summary>
-        /// 获取日志实体
-        /// </summary>
-        /// <param name="id"></param>
-        /// <returns></returns>
-        [HttpGet("log/{id}")]
+		public async Task<PagedDto<SystemLogDto>> List([FromQuery] SysLogPagedKeywordRequest dto)
+		{
+			var (total, items) = await _systemLogRepository.Queryable()
+				.WhereIF(dto.IsAll.HasValue && !dto.IsAll.Value, x => !string.IsNullOrEmpty(x.Name))
+				.WhereIF(!string.IsNullOrEmpty(dto.Name), x => x.Name.Contains(dto.Name))
+				.WhereIF(!string.IsNullOrEmpty(dto.CreatorName), x => x.CreatorName.Contains(dto.CreatorName))
+				.WhereIF(!string.IsNullOrEmpty(dto.ExecuteUrl), x => x.ExecuteUrl.Contains(dto.ExecuteUrl))
+				.WhereIF(!string.IsNullOrEmpty(dto.ExecuteParam),
+					x => SqlFunc.JsonLike(x.ExecuteParam, dto.ExecuteParam))
+				.WhereIF(dto.StartTime.HasValue && dto.EndTime.HasValue,
+					x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
+				//.WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Name.Contains(dto.Keyword!) || x.CreatorName.Contains(dto.Keyword!))
+				.Select(x => new SystemLogDto
+				{
+					ExecuteParam = x.ExecuteParam
+				}, true)
+				.OrderByDescending(x => x.CreationTime)
+				.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+			return new PagedDto<SystemLogDto>(total, items);
+
+		}
+
+		/// <summary>
+		/// 获取日志实体
+		/// </summary>
+		/// <param name="id"></param>
+		/// <returns></returns>
+		[HttpGet("log/{id}")]
         public async Task<SystemLogDto> ItemEntity(string id)
         {
             var log = await _systemLogRepository.Queryable()

+ 149 - 4
src/Hotline.Api/Controllers/TestController.cs

@@ -79,6 +79,10 @@ using Microsoft.AspNetCore.DataProtection;
 using Hotline.Share.Tools;
 using NETCore.Encrypt;
 using Hotline.Ai.Quality;
+using Hotline.EventBus;
+using Hotline.JudicialManagement.Notifies;
+using Hotline.Snapshot.Notifications;
+using XF.Domain.Entities;
 
 namespace Hotline.Api.Controllers;
 
@@ -558,9 +562,10 @@ ICallApplication callApplication,
         //_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" });
         //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 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());
     }
 
 
@@ -1394,7 +1399,8 @@ ICallApplication callApplication,
 		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).ToListAsync();
+			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
 		{
@@ -1409,7 +1415,7 @@ ICallApplication callApplication,
 			var CenterToOrgStep = steps.Where(x => x.AssignerOrgId == "001" && x.HandlerOrgId != "001").OrderByDescending(x => x.CreationTime).FirstOrDefault();
 
 			order.ActualHandleTime = actualHandleStep.HandleTime;
-			order.CenterToOrgTime = CenterToOrgStep.CreationTime;
+			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();
 
@@ -1477,4 +1483,143 @@ ICallApplication callApplication,
         var strString = dto.AppId + dto.Timestamp;
         return dto.AppId + EncryptProvider.AESEncrypt(strString, dto.Secret, dto.AppId);
     }
+
+	/// <summary>
+	/// 归档handle错误数据处理
+	/// </summary>
+	/// <returns></returns>
+	[HttpPost("end_order_data_dispose")]
+	[AllowAnonymous]
+	public async Task EndOrderDataDispose([FromBody] string No ) {
+        var orderNo = await _orderRepository.Queryable().Where(x => x.No == No).FirstAsync(HttpContext.RequestAborted);
+
+        var workflow =  await _workflowRepository.Queryable().Where(x=>x.ExternalId == orderNo.Id).FirstAsync(HttpContext.RequestAborted);
+
+        var notification = await _workflowStepRepository.Queryable().Where(x => x.ExternalId == orderNo.Id && x.BusinessType == EBusinessType.File && x.StepType == EStepType.End).FirstAsync(HttpContext.RequestAborted);
+		var order = await _orderDomainService.GetOrderAsync(orderNo.Id,
+					   withExtension: true, cancellationToken: HttpContext.RequestAborted);
+		//order.CheckIfFiled();
+		//order.UpdateHandlingStatus(workflow.IsInCountersign);
+		_mapper.Map(workflow, order);
+		var now = DateTime.Now;
+		var handleDuration = order.CenterToOrgTime.HasValue && order.ActualHandleTime.HasValue
+			? // _timeLimitDomainService.CalcWorkTime(
+			await _expireTime.CalcWorkTime(
+				order.CenterToOrgTime.Value,
+			order.ActualHandleTime.Value, 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.CalcWorkTimeEx(
+			await _expireTime.CalcWorkTimeEx(
+				order.CreationTime, now,
+			order.ProcessType is EProcessType.Zhiban)
+			: 0;
+		var centerToOrgHandleDurationWorkday = order.ActualHandleTime.HasValue && order.CenterToOrgTime.HasValue
+			? //_timeLimitDomainService.CalcWorkTimeEx(
+			await _expireTime.CalcWorkTimeEx(
+				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);
+		order.FileUserId = notification.HandlerId;
+		order.FileUserName = notification.HandlerName;
+		order.FileUserOrgId = notification.HandlerOrgId;
+		order.FileUserOrgName = notification.HandlerOrgName;
+		order.FileOrgIsCenter = notification.HandlerOrgIsCenter;
+		order.FileOpinion = notification.Opinion;
+
+		//记录冗余归档数据
+		if (notification.Workflow.Steps.Any(x => x.BusinessType == Share.Enums.FlowEngine.EBusinessType.Send))
+		{
+			order.FileUserRole = EFileUserType.Dispatch;
+		}
+		else
+		{
+			order.FileUserRole = EFileUserType.Seat;
+		}
+		if (order.ProcessType == EProcessType.Jiaoban)
+		{
+			order.FileUserRole = EFileUserType.Org;
+		}
+
+		//是否已解决
+		order.IsResolved = true;
+
+		await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted);
+		//var callRecord = await _trCallRecordRepository.GetAsync(p => p.CallAccept == order.CallId, cancellationToken); //由CallAccept改为OtherAccept
+		//var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
+		//var orderFlowDto = new OrderFlowDto
+		//{
+		//	Order = _mapper.Map<OrderDto>(order),
+		//	WorkflowTrace = _mapper.Map<WorkflowTraceDto>(notification.Trace)
+		//};
+		//// if (callRecord != null)
+		//// {
+		////     orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+		//// }
+		//if (order.SourceChannelCode == AppDefaults.SourceChannel.DianHua &&
+		//	!string.IsNullOrEmpty(order.CallId))
+		//{
+		//	if (_appOptions.Value.GetDefaultAppScopeConfiguration().CallCenterType == AppDefaults.CallCenterType.TianRun)
+		//	{
+		//		// var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
+		//		var callRecord = await _callApplication.GetTianrunCallAsync(order.CallId, HttpContext.RequestAborted);
+		//		if (callRecord != null)
+		//		{
+		//			orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+		//		}
+		//	}
+		//	else if (_appOptions.Value.GetDefaultAppScopeConfiguration().CallCenterType == AppDefaults.CallCenterType.XingTang)
+		//	{
+		//		var call = await _callApplication.GetCallAsync(order.CallId, HttpContext.RequestAborted);
+		//		if (call is not null)
+		//		{
+		//			orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(call);
+
+		//			// 工单开始办理如果获取的通话记录是呼出并且录音文件是空就不推送通话记录
+		//			// 如果 通话记录是呼入, 并且没有录音文件
+		//			if (_systemSettingCacheManager.OrderStartHandlerPushCallIsNull && call.Direction == ECallDirection.Out && call.AudioFile.IsNullOrEmpty())
+		//			{
+		//				orderFlowDto.TrCallRecordDto = null;
+		//			}
+		//		}
+		//	}
+		//}
+
+		//这里需要判断是否是警情退回
+		//orderFlowDto.IsNonPoliceReturn = notification.Dto.External == null ? false : notification.Dto.External.IsPoliceReturn;
+		//await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFiled, orderFlowDto, cancellationToken: HttpContext.RequestAborted);
+		//await _publisher.PublishAsync(new SnapshotOrderFiledNotification(order.Id), PublishStrategy.ParallelWhenAll, HttpContext.RequestAborted);
+		//await _orderDomainService.OrderAutomaticPublishAsync(order, HttpContext.RequestAborted);
+		//try
+		//{
+		//    //写入质检  针对受理之后直接结束的工单
+		//    await _qualityApplication.AddQualityAsync(EQualitySource.Accepted, order.Id, cancellationToken);
+		//}
+		//catch (Exception e)
+		//{
+		//    _logger.LogError($"写入质检异常!orderId: {order.Id}, \r\n{e.Message}");
+		//}
+
+		//司法行政监督管理-工单处理
+		//如果没开启则不处理
+		//var isOpenJudicialManagement = _systemSettingCacheManager.GetSetting(SettingConstants.IsOpenJudicialManagement)?.SettingValue[0];
+		//if (isOpenJudicialManagement == "true")
+		//	await _publisher.PublishAsync(new JudicialManagementOrderNotify(order), PublishStrategy.ParallelWhenAll, HttpContext.RequestAborted);
+	}
 }

+ 2 - 6
src/Hotline.Api/Controllers/WebPortalController.cs

@@ -761,7 +761,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 +780,6 @@ namespace Hotline.Api.Controllers
             return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(returnDto, "成功"));
         }
 
-        /// <summary>
-
-
         /// <summary>
         /// 查询工单发布后公开的数据
         /// </summary>
@@ -1370,8 +1367,7 @@ namespace Hotline.Api.Controllers
             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 => p.Keywords.Contains(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
+
+    }
+}

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

@@ -152,14 +152,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.AddAiXingTang(appConfiguration.ZiGong.AiQuality.Url);
-				//services.AddProxiedScoped<ISnapshotApplication, ZiGongSnapshotApplication>();
                 break;
             case AppDefaults.AppScope.LuZhou:
-                //services.AddProxiedScoped<ISnapshotApplication, DefaultSnapshotApplication>();
                 break;
         }
 

+ 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字符");

+ 67 - 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,22 @@ public class OrderControllerTest : TestBase
     private readonly ISqlSugarClient _capSqlClient;
     private readonly IIndustryRepository _industryRepository;
     private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly ISystemLogRepository _systemLogRepository;
+    private readonly IOrderVisitDomainService _orderVisitDomainService;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
 
-    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, ISystemDicDataCacheManager systemDicDataCacheManager)
+        : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _hotspotRepository = hotspotRepository;
         _orderController = orderController;
@@ -90,6 +108,10 @@ public class OrderControllerTest : TestBase
         _capSqlClient = capDbContext.Db;
         _industryRepository = industryRepository;
         _orderSnapshotRepository = orderSnapshotRepository;
+        _systemLogRepository = systemLogRepository;
+        _orderVisitDomainService = orderVisitDomainService;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
     }
 
     /// <summary>
@@ -159,6 +181,50 @@ 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;
+            var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt("0");
+            m.OrgProcessingResults = smsReply.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
+            m.OrgHandledAttitude = smsReply.GetOrgHandledAttitude(_systemDicDataCacheManager.VisitSatisfaction);
+        });
+
+        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>

+ 39 - 12
src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs

@@ -1,11 +1,13 @@
 using Hotline.Api.Controllers;
 using Hotline.Application.Tests.Mock;
+using Hotline.Caching.Interfaces;
 using Hotline.EventBus;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Orders;
 using Hotline.Push.FWMessage;
 using Hotline.Push.Notifies;
+using Hotline.Settings;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.Order;
@@ -28,8 +30,10 @@ public class OrderVisitDomainServiceTest : TestBase
     private readonly Publisher _publisher;
     private readonly IOrderRepository _orderRepository;
     private readonly OrderServiceMock _orderServiceMock;
+    private readonly ISettingOrderVisitSmsReplyRuleRepository _settingOrderVisitSmsReplyRuleRepository;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
 
-    public OrderVisitDomainServiceTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher, IOrderRepository orderRepository, OrderServiceMock orderServiceMock, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
+    public OrderVisitDomainServiceTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher, IOrderRepository orderRepository, OrderServiceMock orderServiceMock, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, ISettingOrderVisitSmsReplyRuleRepository settingOrderVisitSmsReplyRuleRepository, ISystemDicDataCacheManager systemDicDataCacheManager) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _orderVisitDomainService = orderVisitDomainService;
         _orderVisitRepository = orderVisitRepository;
@@ -37,6 +41,8 @@ public class OrderVisitDomainServiceTest : TestBase
         _publisher = publisher;
         _orderRepository = orderRepository;
         _orderServiceMock = orderServiceMock;
+        _settingOrderVisitSmsReplyRuleRepository = settingOrderVisitSmsReplyRuleRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
     }
 
 
@@ -52,13 +58,14 @@ public class OrderVisitDomainServiceTest : TestBase
         visit.ShouldNotBeNull("缺少测试数据");
 
         var msg = new MessageDto()
-        { 
+        {
             ExternalId = visit.Id,
         };
 
         await _orderVisitDomainService.UpdateSmsReplyDefaultAsync(msg);
         var replyTxt = "默认满意";
-        var kv = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>("默认满意");
+        var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt("默认满意");
+        var kv = smsReply.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
         var order = await _orderRepository.Queryable()
             .Includes(m => m.OrderVisits)
             .Where(m => m.OrderVisits.Any(o => o.Id == visit.Id))
@@ -72,8 +79,8 @@ public class OrderVisitDomainServiceTest : TestBase
         {
             if (detail.VisitTarget == EVisitTarget.Seat)
             {
-                var seatEvaluate = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt);
-                var voiceEvaluate = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt);
+                var seatEvaluate = smsReply.SeatEvaluate;
+                var voiceEvaluate = smsReply.VoiceEvaluate;
 
                 detail.SeatEvaluate.ShouldBe(seatEvaluate);
                 detail.VoiceEvaluate.ShouldBe(voiceEvaluate);
@@ -126,7 +133,7 @@ public class OrderVisitDomainServiceTest : TestBase
         if (visit == null) return;
         visit.ShouldNotBeNull("缺少测试数据");
 
-        var message = new MessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content , TelNumber = visit.Order.Contact};
+        var message = new MessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content, TelNumber = visit.Order.Contact };
         var dto = new PushReceiveMessageDto();
         await _orderVisitDomainService.UpdateSmsReplyAsync(message);
         visit = _orderVisitRepository.Get(visit.Id);
@@ -141,7 +148,7 @@ public class OrderVisitDomainServiceTest : TestBase
             visit.VisitType.ShouldBeNull();
             orderEntity.Status.ShouldNotBe(EOrderStatus.Visited);
         }
-        else 
+        else
         {
             orderEntity.Status.ShouldBe(EOrderStatus.Visited);
         }
@@ -176,22 +183,42 @@ public class OrderVisitDomainServiceTest : TestBase
     [InlineData("默认满意", "默认满意", "Visited", "DefaultSatisfied", "DefaultSatisfied", "默认满意|0")]
     public void GetVisitEvaluateByReplyTxt_Test(string replyTxt, string assertReplyTxt, string visitState, string seatEvaluate, string voiceEvaluate, string kv)
     {
-        var replyString = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<string>(replyTxt);
+        var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt(replyTxt);
+        var replyString = smsReply.VisitContent;
         replyString.ShouldBe(assertReplyTxt);
 
-        var visitStateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVisitState>(replyTxt);
+        var visitStateEnum = smsReply.VisitState;
         visitStateEnum.ShouldBe(visitState.ToEnum<EVisitState>());
 
-        var seatEvaluateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt);
+        var seatEvaluateEnum = smsReply.SeatEvaluate;
         seatEvaluateEnum.ShouldBe(seatEvaluate.ToEnum<ESeatEvaluate>());
 
-        var voiceEvaluateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt);
+        var voiceEvaluateEnum = smsReply.VoiceEvaluate;
         voiceEvaluateEnum.ShouldBe(voiceEvaluate.ToEnum<EVoiceEvaluate>());
 
-        var kvResult = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>(replyTxt);
+        var kvResult = smsReply.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
         var sp = kv.Split('|');
         var kV = new Kv(sp[1].ToString(), sp[0].ToString());
         kvResult.Key.ShouldBe(kV.Key);
         kvResult.Value.ShouldBe(kV.Value);
     }
+
+    [Fact]
+    public async Task Init_SettingOrderVisitSmsReplyRule_Data()
+    {
+        IEnumerable<SettingOrderVisitSmsReplyRule> entities = [
+            new () {AppScope = "ZiGong", ReplyRegular = "1", VisitContent = "非常满意", VisitState = EVisitState.Visited, SeatEvaluate = ESeatEvaluate.VerySatisfied,VoiceEvaluate = EVoiceEvaluate.VerySatisfied,OrgProcessingResults=5, OrgHandledAttitude = 5 },
+            new () {AppScope = "ZiGong", ReplyRegular = "2", VisitContent = "满意", VisitState = EVisitState.Visited, SeatEvaluate = ESeatEvaluate.Satisfied,VoiceEvaluate = EVoiceEvaluate.Satisfied,OrgProcessingResults=4, OrgHandledAttitude = 4 },
+            new () {AppScope = "ZiGong", ReplyRegular = "3", VisitContent = "一般", VisitState = EVisitState.Visited, SeatEvaluate = ESeatEvaluate.Normal,VoiceEvaluate = EVoiceEvaluate.Normal,OrgProcessingResults=3, OrgHandledAttitude = 3 },
+            new () {AppScope = "ZiGong", ReplyRegular = "4", VisitContent = "不满意", VisitState = EVisitState.SMSUnsatisfied, SeatEvaluate = ESeatEvaluate.NoSatisfied,VoiceEvaluate = EVoiceEvaluate.NoSatisfied,OrgProcessingResults=2, OrgHandledAttitude = 2 },
+            new () {AppScope = "ZiGong", ReplyRegular = "5", VisitContent = "非常不满意", VisitState = EVisitState.SMSUnsatisfied, SeatEvaluate = ESeatEvaluate.NoSatisfied,VoiceEvaluate = EVoiceEvaluate.VeryNoSatisfied,OrgProcessingResults=2, OrgHandledAttitude = 2 }
+            ];
+
+        foreach (var item in entities)
+        {
+            item.UniqueKey = (item.AppScope + item.ReplyRegular).GetMD5();
+            if (await _settingOrderVisitSmsReplyRuleRepository.AnyAsync(m => m.UniqueKey == item.UniqueKey) == false)
+                await _settingOrderVisitSmsReplyRuleRepository.AddAsync(item);
+        }
+    }
 }

+ 1 - 1
src/Hotline.Application.Tests/appsettings.Development.json

@@ -110,7 +110,7 @@
         }
     },
     "DatabaseConfiguration": {
-        "ApplyDbMigrations": false,
+        "ApplyDbMigrations": true,
         "ApplySeed": false
     },
     "MqConfiguration": {

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

@@ -333,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);
@@ -459,7 +437,8 @@ public abstract class DefaultCallApplication : ICallApplication
 
         try
         {
-            return _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(callNative.ReplyTxt!.Trim());
+            var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt(callNative.ReplyTxt!.Trim());
+            return smsReply!.VoiceEvaluate!.Value;
         }
         catch (UserFriendlyException)
         {
@@ -468,6 +447,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>

+ 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)

+ 36 - 20
src/Hotline.Application/Caselibrary/CaseApplication.cs

@@ -20,6 +20,7 @@ using Hotline.KnowledgeBase;
 using Hotline.Share.Enums.Planlibrary;
 using System.Numerics;
 using Hotline.Share.Dtos.Planlibrary;
+using Hotline.Planlibrary;
 
 namespace Hotline.Application.Caselibrary
 {
@@ -32,7 +33,6 @@ namespace Hotline.Application.Caselibrary
         #region 注册
 
         private readonly IRepository<CaseList> _caseListRepository;                           //案例库列表
-        private readonly IRepository<CaseRelationType> _caseRelationTypeRepository;           //案例库关联类型
         private readonly IRepository<CaseType> _caseTypeRepository;                           //案例库分类管理
         private readonly IRepository<CaseTypeOrg> _caseTypeOrgRepository;                     //案例库分类关联机构
         private readonly IRepository<CaseCollect> _caseCollectRepository;                     //案例库收藏评分
@@ -46,7 +46,6 @@ namespace Hotline.Application.Caselibrary
 
         public CaseApplication(
             IRepository<CaseList> caseListRepository,
-            IRepository<CaseRelationType> caseRelationTypeRepository,
             IRepository<CaseType> caseTypeRepository,
             IRepository<CaseTypeOrg> caseTypeOrgRepository,
             IRepository<CaseCollect> caseCollectRepository,
@@ -59,7 +58,6 @@ namespace Hotline.Application.Caselibrary
             IBulletinApplication bulletinApplication)
         {
             _caseListRepository = caseListRepository;
-            _caseRelationTypeRepository = caseRelationTypeRepository;
             _caseTypeRepository = caseTypeRepository;
             _caseTypeOrgRepository = caseTypeOrgRepository;
             _caseCollectRepository = caseCollectRepository;
@@ -175,7 +173,7 @@ namespace Hotline.Application.Caselibrary
                 throw UserFriendlyException.SameMessage("存在子级分类!");
 
             //查询是否有案例分类
-            var checkKnowledge = await _caseListRepository.CountAsync(p => p.CaseTypes.Any(t => t.CaseId == Id), cancellationToken);
+            var checkKnowledge = await _caseListRepository.CountAsync(p => p.CaseTypes.Any(t => t.Id == Id), cancellationToken);
             if (checkKnowledge > 0)
                 throw UserFriendlyException.SameMessage("分类存在案例!");
 
@@ -251,7 +249,7 @@ namespace Hotline.Application.Caselibrary
                                                                                               (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.CaseTypeSpliceName.StartsWith(typeSpliceName)))
+                .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)
@@ -296,26 +294,32 @@ namespace Hotline.Application.Caselibrary
         /// <returns></returns>
         public async Task<string> AddCaseAsync(AddCaseListDto dto, CancellationToken cancellationToken)
         {
-            var pList = _mapper.Map<CaseList>(dto);
+            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("当前案例标题存在重复标题!");
 
-            pList.InitId();
+            cList.InitId();
 
             if (dto.Files != null && dto.Files.Count > 0)
-                pList.FileJson = await _fileRepository.AddFileAsync(dto.Files, pList.Id, "", cancellationToken);
-            await _caseListRepository.AddAsync(pList, cancellationToken);
+                cList.FileJson = await _fileRepository.AddFileAsync(dto.Files, cList.Id, "", cancellationToken);
 
             if (dto.CaseTypes.Any())
             {
-                List<CaseRelationType> types = _mapper.Map<List<CaseRelationType>>(dto.CaseTypes);
-                types.ForEach(x => x.CaseId = pList.Id);
-                await _caseRelationTypeRepository.AddRangeAsync(types, cancellationToken);
+                cList.CaseTypes = dto.CaseTypes.Select(d => new CaseType
+                {
+                    Id = d.Id,
+                    Name = d.Name,
+                    SpliceName = d.SpliceName
+                }).ToList();
             }
 
-            return pList.Id;
+            await _caseListRepository.AddNav(cList)
+                                     .Include(d => d.CaseTypes)
+                                     .ExecuteCommandAsync();
+
+            return cList.Id;
         }
 
         #endregion
@@ -359,20 +363,32 @@ namespace Hotline.Application.Caselibrary
                     Case.FileJson = new List<Share.Dtos.File.FileJson>();
             }
 
-            await _caseListRepository.UpdateNullAsync(Case, cancellationToken);
 
             if (dto.ApplyStatus != ECaseApplyStatus.Delete)
             {
                 if (dto.CaseTypes.Any())
                 {
-                    var anyRelationTypes = await _caseRelationTypeRepository.Queryable().Where(x => x.CaseId == Case.Id).ToListAsync();
-                    if (anyRelationTypes.Any())
-                        await _caseRelationTypeRepository.RemoveRangeAsync(anyRelationTypes);
-                    List<CaseRelationType> types = _mapper.Map<List<CaseRelationType>>(dto.CaseTypes);
-                    types.ForEach(x => x.CaseId = dto.Id);
-                    await _caseRelationTypeRepository.AddRangeAsync(types, cancellationToken);
+                    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

+ 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
                     //{

+ 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()
                                 {

+ 27 - 5
src/Hotline.Application/Orders/OrderApplication.cs

@@ -100,8 +100,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     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,
@@ -143,7 +144,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<StatisticsDepartSatisfied> statisticsDepartSatisfiedRepository,
         IOrderDelayRepository orderDelayRepository,
         IRepository<OrderTsDetails> orderTsDetailsRepository,
-        IRepository<KnowledgeQuote> knowledgeQuoteRepository)
+        IRepository<KnowledgeQuote> knowledgeQuoteRepository,
+		IRepository<OrderSpecial> orderSpecialRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -187,7 +189,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _statisticsDepartSatisfiedRepository = statisticsDepartSatisfiedRepository;
         _orderTsDetailsRepository = orderTsDetailsRepository;
         _knowledgeQuoteRepository = knowledgeQuoteRepository;
-    }
+        _orderSpecialRepository = orderSpecialRepository;
+
+	}
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -701,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);
         }
     }
 
@@ -784,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);
     }
 
@@ -815,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);
     }
 
@@ -999,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))

+ 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);
-		}
-	}
-}

+ 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);
+        }
+    }
 }

+ 40 - 25
src/Hotline.Application/Planlibrary/PlanApplication.cs

@@ -15,9 +15,7 @@ using Hotline.Application.Bulletin;
 using Hotline.Share.Tools;
 using Hotline.Application.Tools;
 using SqlSugar;
-using Hotline.KnowledgeBase;
-using Hotline.Repository.SqlSugar.Knowledge;
-using Hotline.Share.Enums.KnowledgeBase;
+using System.Numerics;
 using Microsoft.AspNetCore.Http;
 using Hotline.Share.Enums.Caselibrary;
 
@@ -32,7 +30,6 @@ namespace Hotline.Application.Planlibrary
         #region 注册
 
         private readonly IRepository<PlanList> _planListRepository;                      //预案库列表
-        private readonly IRepository<PlanRelationType> _planRelationTypeRepository;      //预案库关联类型
         private readonly IRepository<PlanType> _planTypeRepository;                      //预案库分类管理
         private readonly IRepository<PlanTypeOrg> _planTypeOrgRepository;                //预案库分类关联机构
         private readonly IRepository<PlanCollect> _planCollectRepository;                //预案库收藏评分
@@ -44,7 +41,6 @@ namespace Hotline.Application.Planlibrary
 
         public PlanApplication(
             IRepository<PlanList> planListRepository,
-            IRepository<PlanRelationType> planRelationTypeRepository,
             IRepository<PlanType> planTypeRepository,
             IRepository<PlanTypeOrg> planTypeOrgRepository,
             ISessionContext sessionContext,
@@ -55,7 +51,6 @@ namespace Hotline.Application.Planlibrary
             IRepository<PlanCollect> planCollectRepository)
         {
             _planListRepository = planListRepository;
-            _planRelationTypeRepository = planRelationTypeRepository;
             _planTypeRepository = planTypeRepository;
             _planTypeOrgRepository = planTypeOrgRepository;
             _sessionContext = sessionContext;
@@ -169,7 +164,7 @@ namespace Hotline.Application.Planlibrary
                 throw UserFriendlyException.SameMessage("存在子级分类!");
 
             //查询是否有预案分类
-            var checkKnowledge = await _planListRepository.CountAsync(p => p.PlanTypes.Any(t => t.PlanId == Id), cancellationToken);
+            var checkKnowledge = await _planListRepository.CountAsync(p => p.PlanTypes.Any(t => t.Id == Id), cancellationToken);
             if (checkKnowledge > 0)
                 throw UserFriendlyException.SameMessage("分类存在预案!");
 
@@ -248,7 +243,7 @@ namespace Hotline.Application.Planlibrary
                                                                                               (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.PlanTypeSpliceName.StartsWith(typeSpliceName)))
+                .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)
@@ -298,15 +293,20 @@ namespace Hotline.Application.Planlibrary
 
             if (dto.Files != null && dto.Files.Count > 0)
                 pList.FileJson = await _fileRepository.AddFileAsync(dto.Files, pList.Id, "", cancellationToken);
-            await _planListRepository.AddAsync(pList, cancellationToken);
 
             if (dto.PlanTypes.Any())
             {
-                List<PlanRelationType> types = _mapper.Map<List<PlanRelationType>>(dto.PlanTypes);
-                types.ForEach(x => x.PlanId = pList.Id);
-                await _planRelationTypeRepository.AddRangeAsync(types, cancellationToken);
+                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;
         }
 
@@ -353,20 +353,32 @@ namespace Hotline.Application.Planlibrary
                     plan.FileJson = new List<Share.Dtos.File.FileJson>();
             }
 
-            await _planListRepository.UpdateNullAsync(plan, cancellationToken);
 
             if (dto.ApplyStatus != EPlanApplyStatus.Delete)
             {
                 if (dto.PlanTypes.Any())
                 {
-                    var anyRelationTypes = await _planRelationTypeRepository.Queryable().Where(x => x.PlanId == plan.Id).ToListAsync();
-                    if (anyRelationTypes.Any())
-                        await _planRelationTypeRepository.RemoveRangeAsync(anyRelationTypes);
-                    List<PlanRelationType> types = _mapper.Map<List<PlanRelationType>>(dto.PlanTypes);
-                    types.ForEach(x => x.PlanId = dto.Id);
-                    await _planRelationTypeRepository.AddRangeAsync(types, cancellationToken);
+                    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
@@ -438,7 +450,10 @@ namespace Hotline.Application.Planlibrary
         /// <returns></returns>
         public async Task<PlanInfoDto> GetPlanAsync(string Id, bool? IsAddPv, CancellationToken cancellationToken)
         {
-            var plan = await _planListRepository.GetAsync(Id);
+            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("预案库查询失败");
             ;
@@ -455,11 +470,11 @@ namespace Hotline.Application.Planlibrary
                 planInfoDto.HotspotName = hot.HotSpotFullName;
 
             // 分类
-            var relationType = await _planRelationTypeRepository.QueryAsync(x => x.PlanId == Id && x.CreatorId == _sessionContext.UserId);
-            if (relationType != null)
-            {
-                planInfoDto.PlanTypes = _mapper.Map<List<PlanRelationTypeDto>>(relationType);
-            }
+            //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);

+ 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)
         {

+ 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>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 66 - 49
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
+
+    }
+}

+ 1 - 1
src/Hotline.Repository.SqlSugar/CallCenter/TrCallRecordRepository.cs

@@ -385,7 +385,7 @@ namespace Hotline.Repository.SqlSugar.CallCenter
                     EnterpriseCallOutPutthroughCount = a.EnterpriseCallOutPutthroughCount,
                     AiVisitCallOutPutthroughCount = b.AiVisitCallOutPutthroughCount,
                     AiCallOutPutthroughCount = 0,
-                }).OrderBy(x=>x.Date).ToListAsync();
+                }).OrderBy(a=>a.Date).ToListAsync();
             return list;
         }
 

+ 29 - 21
src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs

@@ -558,7 +558,7 @@ namespace Hotline.Repository.SqlSugar.Orders
             {
                 return null;
             }
-			var list = await Db.Queryable<StatisticsCenter>()
+            var list = await Db.Queryable<StatisticsCenter>()
                 .Where(x => x.Time >= StartTime && x.Time <= EndTime)
                 .Select(x => new StatisticsCenter
                 {
@@ -618,7 +618,7 @@ namespace Hotline.Repository.SqlSugar.Orders
                 return null;
             }
 
-			var list = await Db.Queryable<StatisticsPurTypeSatisfied>()
+            var list = await Db.Queryable<StatisticsPurTypeSatisfied>()
                 .Where(x => x.Time >= StartTime && x.Time <= EndTime)
                 .Select(x => new StatisticsPurTypeSatisfied
                 {
@@ -659,7 +659,7 @@ namespace Hotline.Repository.SqlSugar.Orders
             }
 
 
-			var list = await Db.Queryable<StatisticsDepart>()
+            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
@@ -694,7 +694,7 @@ namespace Hotline.Repository.SqlSugar.Orders
             var any = await Db.Queryable<StatisticsBaseInfo>()
                 .Where(x => x.AddDate >= StartTime && x.AddDate <= EndTime).AnyAsync();
 
-			if (!any)
+            if (!any)
             {
                 return null;
             }
@@ -830,11 +830,11 @@ namespace Hotline.Repository.SqlSugar.Orders
                 {
                     AllCount = x.AllCount,
                     // 回访情况
-                    SatisfactionRate = SqlFunc.Round((decimal)(x.AllCount - x.SatisfactionCount) * 100 / (decimal)x.AllCount, 2),                     //总体满意率
-                    DepartSatisfactionRate = SqlFunc.Round((decimal)(x.DepartCount - x.DepartSatisfactionCount) * 100 / (decimal)x.DepartCount, 2),   //部门满意率
-                    CenterSatisfactionRate = SqlFunc.Round((decimal)(x.CenterCount - x.CenterSatisfactionCount) * 100 / (decimal)x.CenterCount, 2),   //12345中心满意率
-                    CitySatisfactionRate = SqlFunc.Round((decimal)(x.CityCount - x.CitySatisfactionCount) * 100 / (decimal)x.CityCount, 2),           //市级部门满意率
-                    CountySatisfactionRate = SqlFunc.Round((decimal)(x.CountyCount - x.CountySatisfactionCount) * 100 / (decimal)x.CountyCount, 2),   //县(区)满意率
+                    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,
@@ -846,7 +846,7 @@ namespace Hotline.Repository.SqlSugar.Orders
                     OrderAllCount = x.OrderAllCount,                                                                    // 来件总数
                     OrderAlready = x.OrderAlready,                                                                      // 已办件数
                     OrderWait = x.OrderWait,                                                                            // 在办件数
-                    OrderAlreadyRate = SqlFunc.Round((decimal)(x.OrderAlready) * 100 / (decimal)x.OrderAllCount, 2),    //总体办结率
+                    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中心受理
@@ -874,25 +874,25 @@ namespace Hotline.Repository.SqlSugar.Orders
                     CityWorkTime = x.CityWorkTime,
                     CountyWorkTime = x.CountyWorkTime,
                     CenterWorkTime = x.CenterWorkTime,
-                    OrderWorkTimeRate = SqlFunc.Round((decimal)(x.OrderWorkTime) / (decimal)x.OrderAlready / 60 / 8, 2),          //信件办理平均时长(工作日)
-                    CityWorkTimeRate = SqlFunc.Round((decimal)(x.CityWorkTime) / (decimal)x.CityAlready / 60 / 8, 2),             //市级部门平均时长(工作日)
-                    CountyWorkTimeRate = SqlFunc.Round((decimal)(x.CountyWorkTime) / (decimal)x.CountyAlready / 60 / 8, 2),       //县(区)平均时长(工作日)
-                    CenterWorkTimeRate = SqlFunc.Round((decimal)(x.CenterWorkTime) / (decimal)x.CenterAlready / 60 / 8, 2),        //12345中心平均时长(工作日)
+                    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 = SqlFunc.Round((decimal)(x.EnterpriseSatisfaction) * 100 / (decimal)x.EnterpriseVisit, 2),              //总满意率
+                    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 = SqlFunc.Round((decimal)(x.EnterpriseCitySatisfaction) * 100 / (decimal)x.EnterpriseCity, 2),       //市级部门满意率
+                    EnterpriseCitySatisfactionRate = x.EnterpriseCity > 0 ? SqlFunc.Round((decimal)(x.EnterpriseCitySatisfaction) * 100 / (decimal)x.EnterpriseCity, 2) : 0,       //市级部门满意率
                     EnterpriseCountySatisfaction = x.EnterpriseCountySatisfaction,
-                    EnterpriseCountySatisfactionRate = SqlFunc.Round((decimal)(x.EnterpriseCountySatisfaction) * 100 / (decimal)x.EnterpriseCounty, 2), //县(区)满意率
+                    EnterpriseCountySatisfactionRate = x.EnterpriseCounty > 0 ? SqlFunc.Round((decimal)(x.EnterpriseCountySatisfaction) * 100 / (decimal)x.EnterpriseCounty, 2) : 0, //县(区)满意率
                     EnterpriseWorkTime = x.EnterpriseWorkTime,
                     EnterpriseCityWorkTime = x.EnterpriseCityWorkTime,
                     EnterpriseCountyWorkTime = x.EnterpriseCountyWorkTime,
@@ -980,6 +980,11 @@ namespace Hotline.Repository.SqlSugar.Orders
                   p.count
               })
                .ToPivotTableAsync(p => p.DicDataName, p => p.Hour, p => p.Sum(x => x.count));
+            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())
@@ -1797,10 +1802,7 @@ namespace Hotline.Repository.SqlSugar.Orders
                 //      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.Count > 0,
-                    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.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))
@@ -1814,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,
@@ -1877,6 +1882,9 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .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,

+ 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;

+ 22 - 0
src/Hotline.Repository.SqlSugar/System/SettingOrderVisitSmsReplyRuleRepository.cs

@@ -0,0 +1,22 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Settings;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.System;
+public class SettingOrderVisitSmsReplyRuleRepository : BaseRepository<SettingOrderVisitSmsReplyRule>, ISettingOrderVisitSmsReplyRuleRepository, IScopeDependency
+{
+    public SettingOrderVisitSmsReplyRuleRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+
+    public List<SettingOrderVisitSmsReplyRule> GetByAppScope(string appscope)
+    {
+        return Queryable().Where(x => x.AppScope == appscope).ToList();
+    }
+}

+ 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
     {

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

@@ -289,7 +289,7 @@ public class QueryCallDateStatisticsDetailResp
     /// <summary>
     /// 服务接通率
     /// </summary>
-    public double ServicePutthorughRate => (PersonCallInPutthroughCount + EnterpriseCallInPutthroughCount + AiCallInPutthroughCount) == 0 ? 0 : Math.Round(((double)PutthroughCount / (PersonCallInPutthroughCount + EnterpriseCallInPutthroughCount + AiCallInPutthroughCount)) * 100, 2);
+    public double ServicePutthorughRate => (PersonCallInCount + EnterpriseCallInCount + AiCallInCount) == 0 ? 0 : Math.Round(((double)PutthroughCount / (PersonCallInCount + EnterpriseCallInCount + AiCallInCount)) * 100, 2);
 
     /// <summary>
     /// 服务接通率(文本)

+ 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; }
+    }
 }

+ 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; }
+    }
+}

+ 4 - 4
src/Hotline.Share/Dtos/Caselibrary/CaseDataDto.cs

@@ -125,7 +125,7 @@ namespace Hotline.Share.Dtos.Caselibrary
 
             if (CaseTypes != null && CaseTypes.Any())
             {
-                var names = CaseTypes.Select(x => x.CaseTypeName).ToList();
+                var names = CaseTypes.Select(x => x.Name).ToList();
                 return string.Join(",", names);
             }
             return "";
@@ -172,18 +172,18 @@ namespace Hotline.Share.Dtos.Caselibrary
         /// <summary>
         /// 案例库类型ID
         /// </summary>
-        public string CaseTypeId { get; set; }
+        public string Id { get; set; }
 
         /// <summary>
         /// 案例库类型名称
         /// </summary>
-        public string CaseTypeName { get; set; }
+        public string Name { get; set; }
 
 
         /// <summary>
         /// 案例库类型名称
         /// </summary>
-        public string CaseTypeSpliceName { get; set; }
+        public string SpliceName { get; set; }
     }
 }
 

+ 1 - 1
src/Hotline.Share/Dtos/Caselibrary/CaseListDto.cs

@@ -394,7 +394,7 @@ namespace Hotline.Share.Dtos.Caselibrary
 
             if (CaseTypes != null && CaseTypes.Any())
             {
-                var names = CaseTypes.Select(x => x.CaseTypeName).ToList();
+                var names = CaseTypes.Select(x => x.Name).ToList();
                 return string.Join(",", names);
             }
             return "";

+ 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>
     /// 受理人

+ 1 - 1
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>

+ 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; }
 }

+ 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; }
+
+    }
 }

+ 25 - 6
src/Hotline.Share/Dtos/Planlibrary/PlanDataDto.cs

@@ -113,7 +113,7 @@ namespace Hotline.Share.Dtos.Planlibrary
         /// <summary>
         /// 预案分类
         /// </summary>
-        public List<PlanRelationTypeDto> PlanTypes { get; set; }
+        public List<PlanRelationTypeDto2> PlanTypes { get; set; }
 
         /// <summary>
         /// 预案分类名称
@@ -124,12 +124,12 @@ namespace Hotline.Share.Dtos.Planlibrary
         /// 获取预案分类名称
         /// </summary>
         /// <returns></returns>
-        public string GetPlanTypeText(List<PlanRelationTypeDto> items)
+        public string GetPlanTypeText(List<PlanRelationTypeDto2> items)
         {
 
             if (PlanTypes != null && PlanTypes.Any())
             {
-                var names = PlanTypes.Select(x => x.PlanTypeName).ToList();
+                var names = PlanTypes.Select(x => x.Name).ToList();
                 return string.Join(",", names);
             }
             return "";
@@ -164,18 +164,37 @@ namespace Hotline.Share.Dtos.Planlibrary
         /// <summary>
         /// 预案库类型ID
         /// </summary>
-        public string PlanTypeId { get; set; }
+        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 PlanTypeName { get; set; }
+        public string Name { get; set; }
 
 
         /// <summary>
         /// 预案库类型名称
         /// </summary>
-        public string PlanTypeSpliceName { get; set; }
+        public string SpliceName { get; set; }
     }
 }
 

+ 3 - 3
src/Hotline.Share/Dtos/Planlibrary/PlanListDto.cs

@@ -341,7 +341,7 @@ namespace Hotline.Share.Dtos.Planlibrary
         /// <summary>
         /// 预案分类
         /// </summary>
-        public List<PlanRelationTypeDto> PlanTypes { get; set; }
+        public List<PlanRelationTypeDto2> PlanTypes { get; set; }
 
         /// <summary>
         /// 预案分类名称
@@ -352,12 +352,12 @@ namespace Hotline.Share.Dtos.Planlibrary
         /// 获取预案分类名称
         /// </summary>
         /// <returns></returns>
-        public string GetPlanTypeText(List<PlanRelationTypeDto> items)
+        public string GetPlanTypeText(List<PlanRelationTypeDto2> items)
         {
 
             if (PlanTypes != null && PlanTypes.Any())
             {
-                var names = PlanTypes.Select(x => x.PlanTypeName).ToList();
+                var names = PlanTypes.Select(x => x.Name).ToList();
                 return string.Join(",", names);
             }
             return "";

+ 1 - 1
src/Hotline.Share/Dtos/Settings/SystemLogDto.cs

@@ -66,7 +66,7 @@ namespace Hotline.Share.Dtos.Settings
 		/// <summary>
 		/// 请求参数 
 		///</summary>
-		public Dictionary<string, object>? ExecuteParam { get; set; }
+		public object? ExecuteParam { get; set; }
 
 		/// <summary>
 		/// 请求结果 

+ 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>
     /// 来源

+ 20 - 1
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;
@@ -1261,7 +1262,25 @@ namespace Hotline.Share.Dtos.TrCallCenter
         /// <summary>
         /// 时长(单位:秒)
         /// </summary>
-        public double? RestDuration { get; set; }
+        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>
         /// 备注(冗余)

+ 99 - 3
src/Hotline.Share/Enums/CallCenter/EOperationStatus.cs

@@ -11,7 +11,7 @@ public enum EOperationStatus
     /// 签入
     /// </summary>
     [Description("签入")]
-    CheckIn = 100,
+    SignIn = 100,
 
     /// <summary>
     /// 示忙
@@ -23,5 +23,101 @@ public enum EOperationStatus
     /// 小休
     /// </summary>
     [Description("小休")]
-    ShortBreak = 201
-}
+    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': '注销'
+ */

+ 15 - 2
src/Hotline.Share/Requests/PagedKeywordRequest.cs

@@ -245,7 +245,10 @@ public record PublishedPagedRequest : PagedKeywordRequest
     /// </summary>
     public EIdentityType? IdentityType { get; set; }
 
-
+    /// <summary>
+    /// 省来源分类 1:政民互动直派 2:政民互动  3:省12345
+    /// </summary>
+    public string? ProvinceChannel { get; set; }
 }
 
 public record HotspotSubtotalReportPagedRequest : ReportPagedRequest
@@ -485,7 +488,7 @@ public record OrgVisitDetailListReq : PagedKeywordRequest
     /// <summary>
     /// 部门办件结果
     /// </summary>
-    public List<string>? OrgProcessingResults { get; set; } 
+    public string? OrgProcessingResults { get; set; } 
 
     /// <summary>
     /// 回访人
@@ -561,6 +564,16 @@ public record OrgVisitDetailListReq : PagedKeywordRequest
     /// 回访方式
     /// </summary>
     public EVisitType? VisitType { get; set; }
+
+    /// <summary>
+    /// 是否省工单(空为全部  true为省工单 false为市工单)
+    /// </summary>
+    public bool? IsProvinceOrder { get; set; }
+
+    /// <summary>
+    /// 内容检索(回访内容)
+    /// </summary>
+    public string? ContentRetrieval { get; set; }
 }
 
 

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

@@ -70,5 +70,7 @@ namespace Hotline.Caching.Interfaces
         /// 工单标签
         /// </summary>
         IReadOnlyCollection<SystemDicDataOutDto> OrderTag { get; }
+
+        IReadOnlyCollection<SystemDicDataOutDto> VisitSatisfaction { get; }
     }
 }

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

@@ -132,6 +132,11 @@ namespace Hotline.Caching.Services
         /// </summary>
         public IReadOnlyCollection<SystemDicDataOutDto> OrderTag => GetOrAdd(SysDicTypeConsts.OrderTag);
 
+        /// <summary>
+        /// 回访满意度
+        /// </summary>
+        public IReadOnlyCollection<SystemDicDataOutDto> VisitSatisfaction => GetOrAdd(SysDicTypeConsts.VisitSatisfaction);
+
         public void RemoveSysDicDataCache(string code)
         {
             _cacheSysDicData.Remove(code);

+ 0 - 5
src/Hotline/CallCenter/Tels/TelOperationXthx.cs

@@ -1,11 +1,6 @@
 using Hotline.Share.Enums.CallCenter;
 using SqlSugar;
-using System;
-using System.Collections.Generic;
 using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using XF.Domain.Repository;
 
 namespace Hotline.CallCenter.Tels

+ 34 - 0
src/Hotline/CallCenter/Tels/TelOperationXthxLog.cs

@@ -0,0 +1,34 @@
+using Hotline.Share.Enums.CallCenter;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Tels
+{
+    [Description("坐席动作类型log")]
+    public class TelOperationXthxLog : CreationEntity
+    {
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "分机号")]
+        public string? TelNo { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "工号")]
+        public string? StaffNo { get; set; }
+
+        /// <summary>
+        /// 分机状态
+        /// </summary>
+        public EOperationStatus Status { get; set; }
+
+        /// <summary>
+        /// 传入Json
+        /// </summary>
+        [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true, ColumnDescription = "传入Json")]
+        public string? Json { get; set; }
+    }
+}

+ 7 - 5
src/Hotline/CaseLibrary/CaseList.cs

@@ -1,5 +1,4 @@
 using Hotline.Settings;
-using Hotline.Settings.Hotspots;
 using Hotline.Users;
 using SqlSugar;
 using System.ComponentModel;
@@ -193,10 +192,13 @@ public class CaseList : CreationEntity
     public Knowledge? Knowledge { get; set; }
 
     /// <summary>
-	/// 案例库类型关联ID
-	/// </summary>
-	[Navigate(NavigateType.OneToMany, nameof(CaseRelationType.CaseId))]
-    public List<CaseRelationType> CaseTypes { get; set; }
+    /// 案例库类型关联ID
+    /// </summary>
+    //[Navigate(NavigateType.OneToMany, nameof(CaseRelationType.CaseId))]
+    //   public List<CaseRelationType> CaseTypes { get; set; }
+
+    [Navigate(typeof(CaseRelationType), nameof(CaseRelationType.CaseId), nameof(CaseRelationType.CaseTypeId))]
+    public List<CaseType> CaseTypes { get; set; }
 
     /// <summary>
     /// 附件

+ 3 - 23
src/Hotline/CaseLibrary/CaseRelationType.cs

@@ -1,6 +1,5 @@
-using SqlSugar;
-using System.ComponentModel;
-using XF.Domain.Repository;
+using System.ComponentModel;
+using XF.Domain.Entities;
 
 namespace Hotline.CaseLibrary;
 
@@ -8,10 +7,7 @@ namespace Hotline.CaseLibrary;
 /// 案例库关联类型
 /// </summary>
 [Description("案例库关联类型")]
-[SugarIndex("index_caseRelationType_caseId", nameof(CaseRelationType.CaseId), OrderByType.Desc)]
-[SugarIndex("index_caseRelationType_typeId", nameof(CaseRelationType.CaseTypeId), OrderByType.Desc)]
-[SugarIndex("index_caseRelationType_spliceName", nameof(CaseRelationType.CaseTypeSpliceName), OrderByType.Desc)]
-public class CaseRelationType : FullStateEntity
+public class CaseRelationType : ITable, IEntity
 {
     /// <summary>
     /// 案例库ID
@@ -22,21 +18,5 @@ public class CaseRelationType : FullStateEntity
     /// 案例库类型ID
     /// </summary>
     public string CaseTypeId { get; set; }
-
-    /// <summary>
-    /// 案例库类型名称
-    /// </summary>
-    public string CaseTypeName { get; set; }
-
-    /// <summary>
-    /// 案例库类型名称
-    /// </summary>
-    public string CaseTypeSpliceName { get; set; }
-
-    /// <summary>
-    /// 案例库类型
-    /// </summary>
-    [Navigate(NavigateType.OneToOne, nameof(CaseTypeId))]
-    public CaseType CaseType { get; set; }
 }
 

+ 2 - 1
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -3229,6 +3229,7 @@ namespace Hotline.FlowEngine.Workflows
         private async Task<WorkflowTrace> CreatePublishTraceAsync(WorkflowTrace endTrace, CancellationToken cancellation)
         {
             var pubTrace = _mapper.Map<WorkflowTrace>(endTrace);
+            
             pubTrace.TraceStyle = ETraceStyle.Publish;
             pubTrace.Name = "中心发布";
             pubTrace.Status = EWorkflowStepStatus.WaitForAccept;
@@ -3251,8 +3252,8 @@ namespace Hotline.FlowEngine.Workflows
             pubTrace.HandlerOrgName = null;
             pubTrace.HandlerOrgAreaCode = null;
             pubTrace.HandlerOrgAreaName = null;
-            pubTrace.StepExpiredTime = null;
             pubTrace.HandleTime = null;
+            pubTrace.StepExpiredTime = null;
 
             pubTrace.CreationTime = endTrace.CreationTime.AddSeconds(1);
             await _workflowTraceRepository.AddAsync(pubTrace, cancellation);

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

@@ -19,75 +19,11 @@ public class WorkflowStep : StepBasicEntity
     /// </summary>
     public bool IsInCountersign() => CountersignPosition != ECountersignPosition.None;
 
-    ///// <summary>
-    ///// 是否发起过会签
-    ///// </summary>
-    //public bool HasStartedCountersign() => !string.IsNullOrEmpty(StartCountersignId);
-
-    ///// <summary>
-    ///// 接办
-    ///// </summary>
-    //public void Accept(string userId, string? userName, string orgId, string? orgName, string? orgAreaCode,
-    //    string? orgAreaName)
-    //{
-    //    AcceptorId = userId;
-    //    AcceptorName = userName;
-    //    AcceptorOrgId = orgId;
-    //    AcceptorOrgName = orgName;
-    //    AcceptorOrgAreaCode = orgAreaCode;
-    //    AcceptorOrgAreaName = orgAreaName;
-    //    AcceptTime = DateTime.Now;
-    //    Status = EWorkflowStepStatus.WaitForHandle;
-    //}
-
-    ///// <summary>
-    ///// 节点办理完成
-    ///// </summary>
-    //public void Handle(
-    //    string userId, string? userName,
-    //    string orgId, string? orgName,
-    //    string? orgAreaCode, string? orgAreaName,
-    //    bool orgIsCenter, string opinion, string nextStepCode)
-    //{
-    //    base.Handle(userId, userName, orgId, orgName, orgAreaCode, orgAreaName, orgIsCenter, opinion);
-        
-    //    if (!IsInCountersign()
-    //        && InstanceMode is EInstanceMode.Config
-    //        && StepType is not EStepType.End)
-    //    {
-    //        var step = NextSteps.FirstOrDefault(d => d.Code == nextStepCode);
-    //        if (step != null)
-    //            step.Selected = true;
-    //    }
-    //}
-
     /// <summary>
     /// 会签结束
     /// </summary>
     public void CountersignEnd() => IsStartedCountersignEnd = true;
 
-    //public void CreateCountersignSteps(List<WorkflowStep> nextSteps) =>
-    //    CountersignSteps = nextSteps
-    //        .Select(d => new CountersignStep { StepId = d.Id })
-    //        .ToList();
-
-
-    /// <summary>
-    /// step设置为可接办状态
-    /// </summary>
-    public void SetAssigned() => Status = EWorkflowStepStatus.WaitForAccept;
-
-    ///// <summary>
-    ///// 依据当前节点获取下一节点会签状态
-    ///// </summary>
-    ///// <returns></returns>
-    //public ECountersignPosition GetNextStepCountersignPosition() =>
-    //    IsStartCountersign
-    //        ? ECountersignPosition.Direct
-    //        : IsInCountersign()
-    //            ? ECountersignPosition.Single
-    //            : ECountersignPosition.None;
-
     public bool IsCenter() => BusinessType is EBusinessType.Seat or EBusinessType.Send;
 
     public bool IsOrg() => BusinessType is EBusinessType.Department;

+ 5 - 2
src/Hotline/Orders/DatabaseEventHandler/OrderVisitDetailEventHandler.cs

@@ -45,26 +45,29 @@ public class OrderVisitDetailEventHandler : IUpdateDatabaseEvent<OrderVisitDetai
         var name = "回填Order回访字段";
         if (visit.VisitTarget == EVisitTarget.Org && visit.OrgProcessingResults != null)
         {
+            if (visit.OrgProcessingResults.Value == "不满意") return;
+
             var orderId = _orderVisitRepository.Queryable()
                 .Where(m => m.Id == visit.VisitId)
                 .Select(m => new { m.OrderId, m.No })
                 .First();
             _orderRepository.Updateable()
                 .SetColumns(m => m.OrgProcessingResults == visit.OrgProcessingResults)
-                .Where(m => m.Id == orderId.OrderId)
+                .Where(m => m.Id == orderId.OrderId && m.Status == EOrderStatus.Visited)
                 .ExecuteCommand();
             _systemLogRepository.Add(name, $"OrgProcessingResults: {visit.OrgProcessingResults.ToJson()}", orderId.No, orderId.No, 1);
         }
 
         if (visit.VisitTarget == EVisitTarget.Seat && visit.SeatEvaluate != null)
         {
+            if (visit.SeatEvaluate == ESeatEvaluate.NoSatisfied || visit.SeatEvaluate == ESeatEvaluate.VeryNoSatisfied) return;
             var orderId = _orderVisitRepository.Queryable()
                 .Where(m => m.Id == visit.VisitId)
                 .Select(m => new { m.OrderId, m.No })
                 .First();
             _orderRepository.Updateable()
                 .SetColumns(m => m.SeatEvaluate == visit.SeatEvaluate)
-                .Where(m => m.Id == orderId.OrderId)
+                .Where(m => m.Id == orderId.OrderId && m.Status == EOrderStatus.Visited)
                 .ExecuteCommand();
             _systemLogRepository.Add(name, $"SeatEvaluate: {visit.SeatEvaluate}",orderId.No, orderId.No, 1);
         }

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

@@ -1,4 +1,5 @@
-using Hotline.Share.Dtos.Push;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Push;
 
 namespace Hotline.Orders;
 public interface IOrderVisitDomainService
@@ -9,7 +10,7 @@ public interface IOrderVisitDomainService
     /// <typeparam name="T"></typeparam>
     /// <param name="replyTxt"></param>
     /// <returns></returns>
-    T GetVisitEvaluateByReplyTxt<T>(string replyTxt);
+    SettingOrderVisitSmsReplyRule? GetVisitEvaluateByReplyTxt(string replyTxt);
 
     /// <summary>
     /// 收到用户回复的短信后回填回访信息

+ 30 - 43
src/Hotline/Orders/OrderVisitDomainService.cs

@@ -14,6 +14,8 @@ using Hotline.EventBus;
 using Hotline.Orders.Notifications;
 using Hotline.Configurations;
 using Microsoft.Extensions.Options;
+using Hotline.Settings;
+using System.Text.RegularExpressions;
 
 namespace Hotline.Orders;
 public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependency
@@ -26,8 +28,9 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     private readonly ICapPublisher _capPublisher;
     private readonly Publisher _publisher;
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+    private readonly ISettingOrderVisitSmsReplyRuleRepository _settingOrderVisitSmsReplyRuleRepository;
 
-    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository, ICapPublisher capPublisher, Publisher publisher, IOptionsSnapshot<AppConfiguration> appOptions)
+    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository, ICapPublisher capPublisher, Publisher publisher, IOptionsSnapshot<AppConfiguration> appOptions, ISettingOrderVisitSmsReplyRuleRepository settingOrderVisitSmsReplyRuleRepository)
     {
         _orderVisitDetailRepository = orderVisitDetailRepository;
         _logger = logger;
@@ -37,6 +40,7 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
         _capPublisher = capPublisher;
         _publisher = publisher;
         _appOptions = appOptions;
+        _settingOrderVisitSmsReplyRuleRepository = settingOrderVisitSmsReplyRuleRepository;
     }
 
     /// <summary>
@@ -54,7 +58,7 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .Where(m => m.Order.Contact == data.TelNumber)
             .Where(m => m.VisitState == EVisitState.SMSVisiting)
             .ToListAsync();
-            
+
         foreach (var orderVisit in orderVisits)
         {
             await UpdateSmsReplyAsync(orderVisit, data.SmsReplyContent!.Trim());
@@ -68,45 +72,21 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     /// <param name="replyTxt"></param>
     /// <returns></returns>
     /// <exception cref="UserFriendlyException"></exception>
-    public T GetVisitEvaluateByReplyTxt<T>(string replyTxt)
+    public SettingOrderVisitSmsReplyRule GetVisitEvaluateByReplyTxt(string replyTxt)
     {
-        Dictionary<string, string> ReplyToEnumMap = new()
-        {
-            { "1", $"非常满意|{EVisitState.Visited}|{ESeatEvaluate.VerySatisfied}|{EVoiceEvaluate.VerySatisfied}|5" },
-            { "2", $"满意|{EVisitState.Visited}|{ESeatEvaluate.Satisfied}|{EVoiceEvaluate.Satisfied}|4"},
-            { "3", $"一般|{EVisitState.Visited}|{ESeatEvaluate.Normal}|{EVoiceEvaluate.Normal}|4"},
-            { "4", $"不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.NoSatisfied}|2"},
-            { "5", $"非常不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.VeryNoSatisfied}|2"},
-            { "0", $"默认满意|{EVisitState.Visited}|{ESeatEvaluate.DefaultSatisfied}|{EVoiceEvaluate.DefaultSatisfied}|0"},
-        };
+        var replyToEnumMap = _settingOrderVisitSmsReplyRuleRepository.GetByAppScope(_appOptions.Value.AppScope);
+        if (replyToEnumMap.IsNullOrEmpty()) throw new UserFriendlyException("系统未配置短信回访规则");
+
         replyTxt = replyTxt.Trim();
-        if (ReplyToEnumMap.TryGetValue(replyTxt, out var result) == false)
+        foreach (var smsRule in replyToEnumMap)
         {
-            var m = ReplyToEnumMap.FirstOrDefault(item => item.Value.StartsWith(replyTxt));
-            replyTxt = m.Key;
-            result = m.Value;
+            var success = new Regex(smsRule.ReplyRegular).IsMatch(replyTxt);
+            if (success)
+            {
+                return smsRule;
+            }
         }
-
-        if (result.IsNullOrEmpty()) throw new UserFriendlyException($"用户回复内容异常; replyTxt: {replyTxt}");
-        var replySplit = result.Split("|");
-
-        if (typeof(T) == typeof(string))
-            return (T)(replySplit[0] as object);
-
-        if (typeof(T) == typeof(EVisitState))
-            return (T)Enum.Parse(typeof(T), replySplit[1]);
-
-        if (typeof(T) == typeof(ESeatEvaluate))
-            return (T)Enum.Parse(typeof(T), replySplit[2]);
-
-        if (typeof(T) == typeof(EVoiceEvaluate))
-            return (T)Enum.Parse(typeof(T), replySplit[3]);
-
-        if (typeof(T) == typeof(Kv))
-            return _systemDicDataCacheManager.GetVisitSatisfaction()
-           .First(m => m.DicDataValue == replySplit[4]).Adapt<T>();
-
-        return default;
+        throw new UserFriendlyException($"用户回复内容异常; replyTxt: {replyTxt}");
     }
 
     /// <summary>
@@ -139,17 +119,20 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     {
         orderVisit.Order ??= await _orderRepository.GetAsync(orderVisit.OrderId);
         orderVisit.VisitType = EVisitType.SmsVisit;
-        if (new string[] { "4", "5" , "不满意", "非常不满意" }.Contains(replyTxt))
+        if (new string[] { "4", "5", "不满意", "非常不满意" }.Contains(replyTxt))
         {
             // “短信不满意待回访”状态下,由其他方式再次进行回访,回访方式需更新为最新的回访方式
             // 故在此置为空
             orderVisit.VisitType = null;
         }
-        var visitSatisfactionKv = GetVisitEvaluateByReplyTxt<Kv>(replyTxt);
-        orderVisit.NowEvaluate = visitSatisfactionKv;
+        var smsReplyRule = GetVisitEvaluateByReplyTxt(replyTxt);
+        var visitSatisfactionKv = smsReplyRule.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
+        if (smsReplyRule.OrgProcessingResults != null)
+            orderVisit.NowEvaluate = visitSatisfactionKv;
 
         orderVisit.VisitTime = DateTime.Now;
-        orderVisit.VisitState = GetVisitEvaluateByReplyTxt<EVisitState>(replyTxt);
+        if (smsReplyRule.VisitState != null)
+            orderVisit.VisitState = smsReplyRule.VisitState.Value;
         await _orderVisitRepository.UpdateAsync(orderVisit, ignoreNullColumns: false);
 
         if (orderVisit.VisitState == EVisitState.Visited)
@@ -178,12 +161,16 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .FirstAsync()
             .Then(async detailSeat =>
             {
-                detailSeat.ReplyBackfill(GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt));
-                await _orderVisitDetailRepository.UpdateAsync(detailSeat);
+                if (smsReplyRule.SeatEvaluate != null)
+                {
+                    detailSeat.ReplyBackfill(smsReplyRule.SeatEvaluate.Value);
+                    await _orderVisitDetailRepository.UpdateAsync(detailSeat);
+                }
             });
 
 
         if (orderVisit.VisitState != EVisitState.Visited) return;
+
         orderVisit.Order.Visited(visitSatisfactionKv.Key, visitSatisfactionKv.Value);
         await _orderRepository.UpdateAsync(orderVisit.Order);
         var orderDto = orderVisit.Order.Adapt<OrderDto>();

+ 7 - 2
src/Hotline/Planlibrary/PlanList.cs

@@ -175,8 +175,13 @@ public class PlanList : CreationEntity
     /// <summary>
 	/// 预案库类型关联ID
 	/// </summary>
-	[Navigate(NavigateType.OneToMany, nameof(PlanRelationType.PlanId))]
-    public List<PlanRelationType> PlanTypes { get; set; }
+	//[Navigate(NavigateType.OneToMany, nameof(PlanRelationType.PlanId))]
+    //public List<PlanRelationType> PlanTypes { get; set; }
+
+
+    [Navigate(typeof(PlanRelationType), nameof(PlanRelationType.PlanId), nameof(PlanRelationType.PlanTypeId))]
+    public List<PlanType> PlanTypes { get; set; }
+
 
     /// <summary>
     /// 附件

+ 4 - 22
src/Hotline/Planlibrary/PlanRelationType.cs

@@ -1,6 +1,6 @@
 using SqlSugar;
 using System.ComponentModel;
-using XF.Domain.Repository;
+using XF.Domain.Entities;
 
 namespace Hotline.Planlibrary;
 
@@ -8,35 +8,17 @@ namespace Hotline.Planlibrary;
 /// 预案库关联类型
 /// </summary>
 [Description("预案库关联类型")]
-[SugarIndex("index_planRelationType_planId", nameof(PlanRelationType.PlanId), OrderByType.Desc)]
-[SugarIndex("index_planRelationType_typeId", nameof(PlanRelationType.PlanTypeId), OrderByType.Desc)]
-[SugarIndex("index_planRelationType_spliceName", nameof(PlanRelationType.PlanTypeSpliceName), OrderByType.Desc)]
-public class PlanRelationType : FullStateEntity
+public class PlanRelationType : ITable, IEntity
 {
     /// <summary>
     /// 预案库ID
     /// </summary>
+    [SugarColumn(IsPrimaryKey = true)]
     public string PlanId { get; set; }
 
     /// <summary>
     /// 预案库类型ID
     /// </summary>
+    [SugarColumn(IsPrimaryKey = true)]
     public string PlanTypeId { get; set; }
-
-    /// <summary>
-    /// 预案库类型名称
-    /// </summary>
-    public string PlanTypeName { get; set; }
-
-    /// <summary>
-    /// 预案库类型名称
-    /// </summary>
-    public string PlanTypeSpliceName { get; set; }
-
-    /// <summary>
-    /// 预案库类型
-    /// </summary>
-    [Navigate(NavigateType.OneToOne, nameof(PlanTypeId))]
-    public PlanType PlanType { get; set; }
 }
-

+ 12 - 0
src/Hotline/Settings/ISettingOrderVisitSmsReplyRuleRepository.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Settings;
+public interface ISettingOrderVisitSmsReplyRuleRepository : IRepository<SettingOrderVisitSmsReplyRule>
+{
+    List<SettingOrderVisitSmsReplyRule> GetByAppScope(string appscope);
+}

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

@@ -708,5 +708,10 @@ namespace Hotline.Settings
         /// 是否查询老数据
         /// </summary>
         public const string IsGetOld = "IsGetOld";
+
+        /// <summary>
+        /// 兴唐恒信 - 分机状态变更接收
+        /// </summary>
+        public const string XthxVerificationStatusAdd = "XthxVerificationStatusAdd";
     }
 }

+ 88 - 0
src/Hotline/Settings/SettingOrderVisitSmsReplyRule.cs

@@ -0,0 +1,88 @@
+using Hotline.Caching.Interfaces;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Settings;
+using Hotline.Share.Enums.Order;
+using Mapster;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Settings;
+
+/// <summary>
+/// 短信回访规则设置
+/// </summary>
+[Description("短信回访规则设置")]
+public class SettingOrderVisitSmsReplyRule : FullStateEntity
+{
+    /// <summary>
+    /// 回复内容匹配正则
+    /// </summary>
+    [SugarColumn(ColumnDescription = "回复内容匹配正则")]
+    public string ReplyRegular { get; set; }
+
+    /// <summary>
+    /// 当前系统环境:ZiGong,YiBin,LuZhou
+    /// </summary>
+    [SugarColumn(ColumnDescription = "当前系统环境")]
+    public string AppScope { get; set; }
+
+    /// <summary>
+    /// 唯一标识(ReplyRegular + AppScope 的 MD5)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "唯一标识")]
+    public string UniqueKey { get; set; }
+
+    /// <summary>
+    /// 回访状态(不需要更新就填NULL)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "回访状态(不需要更新就填NULL)")]
+    public EVisitState? VisitState { get; set; }
+
+    /// <summary>
+    /// 话务员评价(话务评价)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "话务员评价(不需要更新就填NULL)")]
+    public ESeatEvaluate? SeatEvaluate { get; set; }
+
+    /// <summary>
+    /// 语音评价(话务评价)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "语音评价(不需要更新就填NULL)")]
+    public EVoiceEvaluate? VoiceEvaluate { get; set; }
+
+    /// <summary>
+    /// 部门办件态度
+    /// </summary>
+    [SugarColumn(ColumnDescription = "部门办件态度(不需要更新就填NULL)")]
+    public int? OrgHandledAttitude { get; set; }
+
+    /// <summary>
+    /// 部门办件结果
+    /// </summary>
+    [SugarColumn(ColumnDescription = "部门办件结果(不需要更新就填NULL)")]
+    public int? OrgProcessingResults { get; set; }
+
+    /// <summary>
+    /// 回访内容
+    /// </summary>
+    [SugarColumn(ColumnDescription = "回访内容(不需要更新就填NULL)")]
+    public string? VisitContent { get; set; }
+
+    public Kv? GetOrgHandledAttitude(IReadOnlyCollection<SystemDicDataOutDto> visitSatisfaction)
+    {
+        if (this.OrgHandledAttitude == null) return null;
+        return visitSatisfaction.First(m => m.DicDataValue == OrgHandledAttitude.ToString()).Adapt<Kv>();
+    }
+
+    public Kv? GetOrgProcessingResults(IReadOnlyCollection<SystemDicDataOutDto> visitSatisfaction)
+    {
+        if (OrgProcessingResults == null) return null;
+        return visitSatisfaction.First(m => m.DicDataValue == OrgHandledAttitude.ToString()).Adapt<Kv>();
+    }
+}

+ 23 - 2
src/Hotline/Settings/TimeLimitDomain/ExpireTimeSupplier/WorkDaySupplier.cs

@@ -186,8 +186,6 @@ public class WorkDaySupplier : IExpireTimeSupplier, IScopeDependency
                 //计算一天多少个工作小时
                 var durationType1 = WorkEndTime - WorkBeginTime;
                 double workMinutesType1 = durationType1.TotalMinutes;
-                double totalWorkMinutesType1 = (workMinutesType1 * timeConfig.Count) * (timeConfig.Percentage / 100.00);
-                double totalWorkMinutesOneType1 = (workMinutesType1 * timeConfig.Count) * (timeConfig.PercentageOne / 100.00);
 
                 #region 计算期满时间
                 if (!isWorkSend)
@@ -245,6 +243,29 @@ public class WorkDaySupplier : IExpireTimeSupplier, IScopeDependency
                 }
                 #endregion
 
+                var dayIndex = 1;
+                var configCount = 1;
+                while (true)
+                {
+                    if (startTime.AddDays(dayIndex).Date != beginTime.Date)
+                    {
+                        if (await IsWorkDay(startTime.AddDays(dayIndex)))
+                        {
+
+                            configCount++;
+                        }
+                        dayIndex++;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+
+                double totalWorkMinutesType1 = (workMinutesType1 * configCount) * (timeConfig.Percentage / 100.00);
+                double totalWorkMinutesOneType1 = (workMinutesType1 * configCount) * (timeConfig.PercentageOne / 100.00);
+
+
                 //计算即将超期时间
                 while (true)
                 {

+ 7 - 0
src/Hotline/Snapshot/Interfaces/IOrderSnapshotRepository.cs

@@ -18,4 +18,11 @@ public interface IOrderSnapshotRepository : IRepository<OrderSnapshot>
     Task<OrderSnapshot> GetByNetworkENumberAsync(string appealNumber);
 
     Task<OrderSnapshot> UpdateSafetyAsync(string orderId, bool isSafety, string remark);
+
+    /// <summary>
+    /// 是否有随手拍工单
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    bool HasOrder(string orderId);
 }

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

@@ -9,7 +9,7 @@
   <ItemGroup>
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
     <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
-    <PackageReference Include="RestSharp" Version="110.2.0" />
+    <PackageReference Include="RestSharp" Version="112.1.0" />
   </ItemGroup>
 
   <ItemGroup>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio