瀏覽代碼

合并解决冲突

Dun.Jason 7 月之前
父節點
當前提交
193a61951c
共有 60 個文件被更改,包括 1076 次插入245 次删除
  1. 3 2
      src/Hotline.Api/Controllers/AiController.cs
  2. 7 1
      src/Hotline.Api/Controllers/Bigscreen/SeatController.cs
  3. 1 1
      src/Hotline.Api/Controllers/HotSpotController.cs
  4. 13 4
      src/Hotline.Api/Controllers/KnowledgeController.cs
  5. 65 58
      src/Hotline.Api/Controllers/OrderController.cs
  6. 3 2
      src/Hotline.Api/Controllers/OrderProvinceZmhdController.cs
  7. 9 0
      src/Hotline.Api/Controllers/ProvinceStatisticsController.cs
  8. 1 1
      src/Hotline.Api/Controllers/TestController.cs
  9. 10 0
      src/Hotline.Api/StartupHelper.cs
  10. 17 17
      src/Hotline.Api/config/appsettings.Development.json
  11. 12 0
      src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs
  12. 23 1
      src/Hotline.Application.Tests/Application/OrderApplicationTest.cs
  13. 103 0
      src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs
  14. 24 0
      src/Hotline.Application.Tests/Repository/CallNativeRepositoryTest.cs
  15. 4 34
      src/Hotline.Application.Tests/Repository/OrderVisitRepositoryTest.cs
  16. 8 5
      src/Hotline.Application/Bulletin/BulletinApplication.cs
  17. 44 0
      src/Hotline.Application/CallCenter/CallNativeApplication.cs
  18. 13 0
      src/Hotline.Application/CallCenter/ICallNativeApplication.cs
  19. 1 1
      src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs
  20. 28 0
      src/Hotline.Application/Handlers/Order/OrderVisitSmsHandler.cs
  21. 1 1
      src/Hotline.Application/Jobs/CheckAiVisitStateJob.cs
  22. 98 0
      src/Hotline.Application/Jobs/XingTangCallSatisfactionSyncJob.cs
  23. 2 0
      src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs
  24. 5 0
      src/Hotline.Application/Mappers/CallMapperConfigs.cs
  25. 9 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  26. 1 0
      src/Hotline.Application/Mappers/OrderMapperConfigs.cs
  27. 1 0
      src/Hotline.Application/Orders/OrderApplication.cs
  28. 72 0
      src/Hotline.Application/Subscribers/CallSubscriber.cs
  29. 18 14
      src/Hotline.Application/Subscribers/DatasharingSubscriber.cs
  30. 30 0
      src/Hotline.Repository.SqlSugar/CallCenter/CallNativeRepository.cs
  31. 2 70
      src/Hotline.Repository.SqlSugar/Orders/OrderVisitRepository.cs
  32. 5 0
      src/Hotline.Share/Dtos/Order/OrderProvinceZmhdDto.cs
  33. 1 1
      src/Hotline.Share/Dtos/Order/OrderVisitDto.cs
  34. 11 0
      src/Hotline.Share/Dtos/Order/PublishedDto.cs
  35. 5 0
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  36. 4 1
      src/Hotline.Share/Dtos/Order/SendBackDto.cs
  37. 10 0
      src/Hotline.Share/Dtos/ProvinceStatistics/QueryProvinceSendBackDto.cs
  38. 2 0
      src/Hotline.Share/Dtos/Push/MessageDto.cs
  39. 1 1
      src/Hotline.Share/Hotline.Share.csproj
  40. 6 1
      src/Hotline.Share/Mq/EventNames.Call.cs
  41. 38 0
      src/Hotline.Share/Tools/RetryHelper.cs
  42. 1 1
      src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs
  43. 3 0
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  44. 38 7
      src/Hotline/CallCenter/Calls/CallDomainService.cs
  45. 3 3
      src/Hotline/CallCenter/Calls/CallNative.cs
  46. 25 0
      src/Hotline/CallCenter/Calls/CallSatisfaction.cs
  47. 9 0
      src/Hotline/CallCenter/Calls/ICallDomainService.cs
  48. 14 0
      src/Hotline/CallCenter/Calls/ICallNativeRepository.cs
  49. 20 0
      src/Hotline/Orders/IOrderVisitDomainService.cs
  50. 2 8
      src/Hotline/Orders/IOrderVisitRepository.cs
  51. 6 0
      src/Hotline/Orders/OrderPublish.cs
  52. 5 1
      src/Hotline/Orders/OrderSendBackAudit.cs
  53. 18 0
      src/Hotline/Orders/OrderVisitDetail.cs
  54. 150 0
      src/Hotline/Orders/OrderVisitDomainService.cs
  55. 9 4
      src/Hotline/Push/FWMessage/PushDomainService.cs
  56. 6 0
      src/Hotline/Push/Notifies/PushMessageNotify.cs
  57. 12 1
      src/Hotline/Settings/SettingConstants.cs
  58. 4 3
      src/Hotline/Settings/TimeLimitDomain/ExpireTimeLimitBase.cs
  59. 1 1
      src/Hotline/Settings/TimeLimitDomain/ICalcExpireTime.cs
  60. 39 0
      src/XingTang.Sdk/XingtangSatisfaction.cs

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

@@ -674,7 +674,7 @@ namespace Hotline.Api.Controllers
                                     x.VisitContent = visitContent;
                                     x.Volved = isSolve;
                                     x.IsContact = isContact;
-                                    if (string.IsNullOrEmpty(orgProcessingResults.Key) || seatEvaluate == null || isSolve == null || isContact == null ||  orgProcessingResults.Value == "不满意" || seatEvaluate== ESeatEvaluate.NoSatisfied)
+                                    if (string.IsNullOrEmpty(orgProcessingResults?.Key) || seatEvaluate == null || isSolve == null || isContact == null ||  orgProcessingResults?.Value == "不满意" || seatEvaluate== ESeatEvaluate.NoSatisfied)
                                     {
                                         //x.OrgNoSatisfiedReason = new List<Kv>() { new Kv() { Key = "7", Value = "未回复" } };
                                         //TODO 记录不满意原因到内容中供人工回访甄别选择不满意原因
@@ -1008,7 +1008,7 @@ namespace Hotline.Api.Controllers
         }
 
         /// <summary>
-        /// 可进行智能回访记录
+        /// 可进行智能回访记录 
         /// </summary>
         /// <returns></returns>
         [HttpGet("aivisit/canaivisit-list")]
@@ -1021,6 +1021,7 @@ namespace Hotline.Api.Controllers
                 .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.Order.AcceptTypeCode == dto.AcceptType)//受理类型
                 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No)) //工单编码
                 .WhereIF(!string.IsNullOrEmpty(dto.Title),x=> x.Order.Title.Contains(dto.Title))
+                .Where(x=> !string.IsNullOrEmpty(x.Order.Contact) && SqlFunc.Length(x.Order.Contact)>6)
                 .ToListAsync();
             return _mapper.Map<IReadOnlyList<OrderVisitDto>>(items);
         }

+ 7 - 1
src/Hotline.Api/Controllers/Bigscreen/SeatController.cs

@@ -50,10 +50,16 @@ namespace Hotline.Api.Controllers.Bigscreen
         public async Task<List<TelOutDto>> GetListenTels()
         {
             var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue;
-            return (await _trClient.QueryTelsAsync(new QueryTelRequest(), HttpContext.RequestAborted))
+            var callOuttQueueId = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutQueueId)?.SettingValue;
+            var list = (await _trClient.QueryTelsAsync(new QueryTelRequest(), HttpContext.RequestAborted))
                 .Where(m => listenTels.Contains(m.TelNo))
                 .ToList()
                 .Adapt<List<TelOutDto>>();
+            list.ForEach(x =>
+            {
+                x.Queue = callOuttQueueId[0];
+            });
+            return list;
         }
     }
 }

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

@@ -216,7 +216,7 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="dto"></param>
         /// <returns></returns>
-        [HttpPost("event/modify-event")]
+        [HttpPut("event/modify-event")]
         public async Task ModifyEventCategory([FromBody] ModifyEventCategoryDto dto)
         {
             var model = await _eventCategoryRepository.GetAsync(dto.Id, HttpContext.RequestAborted);

+ 13 - 4
src/Hotline.Api/Controllers/KnowledgeController.cs

@@ -1,5 +1,6 @@
 using DotNetCore.CAP;
 using Hotline.Api.Filter;
+using Hotline.Application.Bulletin;
 using Hotline.Application.FlowEngine;
 using Hotline.Application.Knowledge;
 using Hotline.File;
@@ -21,6 +22,7 @@ using Hotline.Users;
 using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
 using SqlSugar;
 using System.Threading;
 using XF.Domain.Authentications;
@@ -57,9 +59,10 @@ namespace Hotline.Api.Controllers
         private readonly IFileRepository _fileRepository;
         private readonly ICapPublisher _capPublisher;
         private readonly IRepository<KnowledgeRelationType> _knowledgeRelationTypeRepository;
+        private readonly IBulletinApplication _bulletinApplication;
 
 
-		public KnowledgeController(
+        public KnowledgeController(
            IKnowledgeRepository knowledgeRepository,
            ISessionContext sessionContext,
            IKnowledgeDomainService knowledgeDomainService,
@@ -81,7 +84,8 @@ namespace Hotline.Api.Controllers
            ISystemOrganizeRepository systemOrganizeRepository,
            IFileRepository fileRepository,
            ICapPublisher capPublisher,
-           IRepository<KnowledgeRelationType> knowledgeRelationTypeRepository
+           IRepository<KnowledgeRelationType> knowledgeRelationTypeRepository,
+            IBulletinApplication bulletinApplication
 		   )
         {
             _knowledgeRepository = knowledgeRepository;
@@ -106,8 +110,8 @@ namespace Hotline.Api.Controllers
             _fileRepository = fileRepository;
             _capPublisher = capPublisher;
             _knowledgeRelationTypeRepository = knowledgeRelationTypeRepository;
-
-		}
+            _bulletinApplication= bulletinApplication;
+        }
 
         #endregion
 
@@ -362,6 +366,8 @@ namespace Hotline.Api.Controllers
                 throw UserFriendlyException.SameMessage("知识查询失败!");
 
             var knowledgeInfoDto = _mapper.Map<KnowledgeInfoDto>(know);
+            if (knowledgeInfoDto != null && !string.IsNullOrEmpty(knowledgeInfoDto.Content))
+                knowledgeInfoDto.Content = _bulletinApplication.GetSiteUrls(knowledgeInfoDto.Content);
             //分类
             //var type = await _knowledgeTypeRepository.GetAsync(know.KnowledgeTypeId, HttpContext.RequestAborted);
             //if (type != null)
@@ -396,6 +402,9 @@ namespace Hotline.Api.Controllers
             //转化
             var knowledgeShowInfoDto = _mapper.Map<KnowledgeInfoDto>(knowledge);
 
+            if (knowledgeShowInfoDto != null && !string.IsNullOrEmpty(knowledgeShowInfoDto.Content))
+                knowledgeShowInfoDto.Content = _bulletinApplication.GetSiteUrls(knowledgeShowInfoDto.Content);
+
             //var type = await _knowledgeTypeRepository.GetAsync(knowledge.KnowledgeTypeId, HttpContext.RequestAborted);
             //if (type != null)
             //{

+ 65 - 58
src/Hotline.Api/Controllers/OrderController.cs

@@ -8,6 +8,7 @@ using Hotline.Application.Quality;
 using Hotline.Application.Systems;
 using Hotline.Caching.Interfaces;
 using Hotline.Caching.Services;
+using Hotline.CallCenter.Calls;
 using Hotline.Configurations;
 using Hotline.ContingencyManagement.Notifies;
 using Hotline.EventBus;
@@ -22,6 +23,7 @@ using Hotline.OrderTranspond;
 using Hotline.Permissions;
 using Hotline.Push.FWMessage;
 using Hotline.Push.Notifies;
+using Hotline.Repository.SqlSugar.CallCenter;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Repository.SqlSugar.Ts;
 using Hotline.SeedData;
@@ -132,9 +134,11 @@ public class OrderController : BaseController
     private readonly IRepository<ExternalCitizens> _externalCitizensRepository;
     private readonly IRepository<OrderModifyingRecords> _orderModifyingRecordsRepository;
     private readonly ICallApplication _callApplication;
+    private readonly ICallNativeRepository _callNativeRepository;
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
     private readonly IOrderSendBackAuditApplication _orderSendBackAuditApplication;
     private readonly Publisher _publisher;
+    private readonly ICallNativeApplication _callNativeApplication;
     private readonly IOrderAnalysisApplication _orderAnalysisApplication;
     private readonly ICalcExpireTime _expireTime;
     private readonly IRepository<OrderPushType> _orderPushTypeRepository;
@@ -201,6 +205,9 @@ public class OrderController : BaseController
         ICalcExpireTime expireTime,
         IOptions<CityBaseConfiguration> cityBaseConfiguration,
         IRepository<OrderPushType> orderPushTypeRepository,
+        IOptions<CityBaseConfiguration> cityBaseConfiguration,
+        ICallNativeRepository callNativeRepository,
+        ICallNativeApplication callNativeApplication)
         BaseDataApplication baseDataApplication)
     {
         _orderDomainService = orderDomainService;
@@ -263,6 +270,9 @@ public class OrderController : BaseController
         _expireTime = expireTime;
         _orderPushTypeRepository = orderPushTypeRepository;
         _cityBaseConfiguration = cityBaseConfiguration;
+        _callNativeRepository = callNativeRepository;
+        _callNativeApplication = callNativeApplication;
+    }
         _baseDataApplication = baseDataApplication;
     }
     #endregion 
@@ -362,7 +372,7 @@ public class OrderController : BaseController
                     orderVisit.PublishTime = DateTime.Now;
                     orderVisit.IsCanHandle = true;
                     orderVisit.EmployeeId = _sessionContext.RequiredUserId;
-                    if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null })
+                    if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null } && !order.IsProvince)
                     {
                         orderVisit.VisitState = EVisitState.Visited;
                         orderVisit.VisitTime = DateTime.Now;
@@ -395,7 +405,7 @@ public class OrderController : BaseController
                     orgDetail.VisitOrgCode = order.ActualHandleOrgCode;
                     orgDetail.VisitOrgName = order.ActualHandleOrgName;
                     orgDetail.VisitTarget = EVisitTarget.Org;
-                    if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null })
+                    if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null, IsProvince: false })
                     {
                         var satisfy = new Kv() { Key = "4", Value = "满意" };
                         orgDetail.OrgProcessingResults = satisfy;
@@ -921,7 +931,8 @@ public class OrderController : BaseController
         var histories = await _orderVisitRepository.Queryable()
             .Includes(m => m.OrderVisitDetails)
             .Where(m => m.OrderId == orderVisit.OrderId)
-            .Select(m => new OrderVisitDetailHistiryDto
+            .Where(m => m.VisitState == EVisitState.None && m.NowEvaluate != null)
+            .Select(m => new OrderVisitDetailHistoryDto
             {
                 VoiceEvaluate = m.OrderVisitDetails.Where(n => n.VisitTarget == EVisitTarget.Seat).Select(s => s.VoiceEvaluate).First(),
                 SeatEvaluate = m.OrderVisitDetails.Where(n => n.VisitTarget == EVisitTarget.Seat).Select(s => s.SeatEvaluate).First(),
@@ -932,6 +943,12 @@ public class OrderController : BaseController
                 VisitTime = m.VisitTime
             }).ToListAsync();
 
+        var seat = orderVisit.OrderVisitDetails.FirstOrDefault(m => m.VisitTarget == EVisitTarget.Seat);
+        if (orderVisit.VisitState == EVisitState.WaitForVisit)
+        {   // 如果是待回访, 就取用户电话评价时评价的内容;
+            var callNative = await _callNativeApplication.GetReplyVoiceOrDefaultByOrderIdAsync(orderVisit.OrderId);
+            seat.VoiceEvaluate = callNative;
+        }
         return new
         {
             OrderVisitModel = _mapper.Map<OrderVisitDto>(orderVisit),
@@ -1014,38 +1031,42 @@ public class OrderController : BaseController
             .Where(m => m.VisitId == visit.VisitId)
             .ToListAsync();
                 var seatDetail = details.First(m => m.VisitTarget == EVisitTarget.Seat);
+                var seatDetailDto = new VisitDetailDto()
+                {
+                    Id = seatDetail.Id,
+                    VisitId = visit.VisitId,
+                    VisitContent = dto.SeatVisitContent,
+                    SeatEvaluate = dto.SeatEvaluate,
+                    VisitTarget = EVisitTarget.Seat,
+                };
+                if (seatDetail.VoiceEvaluate == null)
+                    seatDetailDto.VoiceEvaluate = EVoiceEvaluate.DefaultSatisfied;
+
                 var visitDto = new VisitDto
                 {
                     Id = visit.VisitId,
                     IsPutThrough = true,
                     IsAgain = false,
-                    VisitDetails = new List<VisitDetailDto>
-                    {
-                new()
-                {
-                Id = seatDetail.Id,
-                VisitId = visit.VisitId,
-                VisitContent = dto.SeatVisitContent,
-                SeatEvaluate = dto.SeatEvaluate,
-                                VisitTarget = EVisitTarget.Seat
-                }
-                    }
+                    VisitDetails = new List<VisitDetailDto>()
                 };
+                visitDto.VisitDetails.Add(seatDetailDto);
 
                 var orgDetails = details.Where(m => m.VisitTarget == EVisitTarget.Org).ToList();
                 foreach (var orgDetail in orgDetails)
                 {
                     visitDto.VisitDetails.Add(
-            new()
-            {
-                Id = orgDetail.Id,
-                VisitId = visit.VisitId,
-                VisitContent = dto.OrgVisitContent,
-                VisitTarget = EVisitTarget.Org,
-                OrgNoSatisfiedReason = dto.OrgNoSatisfiedReason,
-                OrgProcessingResults = dto.OrgProcessingResults,
-                OrgHandledAttitude = dto.OrgHandledAttitude
-            });
+                new()
+                {
+                    Id = orgDetail.Id,
+                    VisitId = visit.VisitId,
+                    VisitContent = dto.OrgVisitContent,
+                    VisitTarget = EVisitTarget.Org,
+                    OrgNoSatisfiedReason = dto.OrgNoSatisfiedReason,
+                    OrgProcessingResults = dto.OrgProcessingResults,
+                    OrgHandledAttitude = dto.OrgHandledAttitude,
+                    VisitOrgName = orgDetail.VisitOrgName,
+                    VisitOrgCode = orgDetail.VisitOrgCode,
+                });
                 }
                 await _orderApplication.SaveOrderVisit(visitDto, HttpContext.RequestAborted);
                 outDto.CompleteCount += 1;
@@ -2694,8 +2715,10 @@ public class OrderController : BaseController
     [HttpGet("fixed")]
     public async Task<IReadOnlyList<OrderDto>> QueryFixed([FromQuery] QueryOrderFixedDto dto)
     {
+        var hasSetting = Int32.TryParse(
+             _systemSettingCacheManager.GetSetting(SettingConstants.FixedQueryCount)?.SettingValue[0], out var queryCount);
         var query = _orderApplication.QueryOrders(dto);
-        var orders = await query.ToFixedListAsync(dto.QueryIndex, cancellationToken: HttpContext.RequestAborted);
+        var orders = await query.ToFixedListAsync(dto.QueryIndex, hasSetting ? queryCount : null, HttpContext.RequestAborted);
         return _mapper.Map<IReadOnlyList<OrderDto>>(orders);
     }
 
@@ -2703,7 +2726,7 @@ public class OrderController : BaseController
     /// 查询总数
     /// </summary>
     [HttpGet("count")]
-    public async Task<int> Count([FromQuery] QueryOrderFixedDto dto)
+    public async Task<int> Count([FromQuery] QueryOrderDto dto)
     {
         var query = _orderApplication.QueryOrders(dto);
         return await query.CountAsync(HttpContext.RequestAborted);
@@ -3242,7 +3265,7 @@ public class OrderController : BaseController
             var pushTypeAny = await _orderPushTypeRepository.AnyAsync(x => x.OrderId == order.Id);
             if (pushTypeAny)
                 await _orderPushTypeRepository.RemoveAsync(x => x.OrderId == order.Id);
-            order.OrderPushTypes.ForEach(x => x.OrderId = order.Id);
+            dto.OrderPushTypes.ForEach(x => x.OrderId = order.Id);
             var orderPushTypes = _mapper.Map<List<OrderPushType>>(dto.OrderPushTypes);
             await _orderPushTypeRepository.AddRangeAsync(orderPushTypes);
             var pushTypes = dto.OrderPushTypes.Select(x => x.PushType);
@@ -3293,18 +3316,6 @@ public class OrderController : BaseController
                 NearlyExpiredTimeOne = timeResult.NearlyExpiredTimeOne,
             };
         }
-        //else if (dto.Workflow.FlowDirection == EFlowDirection.CenterToCenter)
-        //{
-        // var timeResult = _timeLimitDomainService.CalcEndTime(DateTime.Now, ETimeType.WorkDay, 1, 0);
-        // expiredTimeConfig = new ExpiredTimeWithConfig
-        // {
-        //  Count = 1,
-        //  TimeType = ETimeType.WorkDay,
-        //  TimeText = "1个工作日",
-        //  ExpiredTime = timeResult.EndTime,
-        //  NearlyExpiredTime = timeResult.NearlyExpiredTime
-        // };
-        //}
         else
         {
             if (_appOptions.Value.IsZiGong)
@@ -3345,21 +3356,6 @@ public class OrderController : BaseController
         }
         //开启流程处理事件,处理市州互转
         await _publisher.PublishAsync(new OrderStartWorkflowNotify(order.Id), PublishStrategy.ParallelWhenAll, HttpContext.RequestAborted);
-        //{
-        //    var order = await _orderRepository.GetAsync(id, HttpContext.RequestAborted);
-        //    var orderDto = _mapper.Map<OrderDto>(order);
-        //    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderTranspondCity, orderDto);
-        //    //保存本地数据
-        //    TranspondCityRawData cityRawData = new TranspondCityRawData
-        //    {
-        //        OrderCode = order.No,
-        //        TransferOutTime = DateTime.Now,
-        //        CityName = order.TranspondCityName,
-        //        Direction = ETranspondDirection.Out
-        //    };
-
-        //    await _transpondCityRawDataRepository.AddAsync(cityRawData, HttpContext.RequestAborted);
-        //}
     }
 
     /// <summary>
@@ -4048,7 +4044,10 @@ public class OrderController : BaseController
             Status = order.Status,
             TraceId = currentStep.Id
         };
-        if (_appOptions.Value.IsZiGong && prevStep.BusinessType == EBusinessType.Send)
+        audit.InitId();
+		if (dto.Files.Any())
+	        audit.FileJson = await _fileRepository.AddFileAsync(dto.Files, audit.Id, "", HttpContext.RequestAborted);
+		if (_appOptions.Value.IsZiGong && prevStep.BusinessType == EBusinessType.Send)
         {
             // 平均派单
             var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
@@ -4276,12 +4275,20 @@ public class OrderController : BaseController
     /// <param name="id"></param>
     /// <returns></returns>
     [HttpGet("order_previous/{id}")]
-    public async Task<OrderSendBackAudit> OrderSendBackEntity(string id)
+    public async Task<SendBackDto> OrderSendBackEntity(string id)
     {
-        return await _orderSendBackAuditRepository.Queryable()
+        var res =  await _orderSendBackAuditRepository.Queryable()
             .Includes(x => x.Order)
             .FirstAsync(x => x.Id == id);
-    }
+        var resDto = _mapper.Map<SendBackDto>(res);
+        if (res.FileJson != null && res.FileJson.Any())
+        {
+	        var ids = res.FileJson.Select(x => x.Id).ToList();
+	        var files = await _fileRepository.GetFilesAsync(ids, HttpContext.RequestAborted);
+	        resDto.Files = files.Where(x => x.Classify == "退回附件" && string.IsNullOrEmpty(x.FlowKey)).ToList();
+        }
+        return resDto;
+	}
 
     /// <summary>
     /// 列表页面基础数据

+ 3 - 2
src/Hotline.Api/Controllers/OrderProvinceZmhdController.cs

@@ -48,9 +48,10 @@ namespace Hotline.Api.Controllers
         {
             RefAsync<int> total = 0;
             var items = await _orderRepository.Queryable()
-                .Where(p => p.SourceChannelCode == "SZMHD" && p.IsProvince == false && p.Status >= EOrderStatus.Filed && p.Source == ESource.ProvinceStraight)
+                .Where(p => p.SourceChannelCode == "SZMHD"  && p.Status >= EOrderStatus.Filed && p.Source == ESource.ProvinceStraight)
                 .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.No == dto.No)
-                .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.ReceiveProvinceNo == dto.ProvinceNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.ProvinceNo == dto.ProvinceNo)
+                 .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Title.StartsWith(dto.Title!))
                 .WhereIF(!string.IsNullOrEmpty(dto.AnswerOu), p => p.ActualHandleOrgName == dto.AnswerOu)
                 .WhereIF(!string.IsNullOrEmpty(dto.AuditFirstName), p => p.AuditFirstName == dto.AuditFirstName)

+ 9 - 0
src/Hotline.Api/Controllers/ProvinceStatisticsController.cs

@@ -70,6 +70,7 @@ namespace Hotline.Api.Controllers
                            .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(!string.IsNullOrEmpty(dto.ApplyUserName), p => p.CreatorName == dto.ApplyUserName)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.CreationTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.CreationTime <= dto.ApplyEndTime.Value)
@@ -127,6 +128,7 @@ namespace Hotline.Api.Controllers
                            .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(!string.IsNullOrEmpty(dto.ApplyUserName), p => p.EmployeeName == dto.ApplyUserName)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.ApplyDelayTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.ApplyDelayTime <= dto.ApplyEndTime.Value)
@@ -192,6 +194,7 @@ namespace Hotline.Api.Controllers
                           .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(!string.IsNullOrEmpty(dto.ApplyUserName), p => p.CreatorName == dto.ApplyUserName)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.CreationTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.CreationTime <= dto.ApplyEndTime.Value)
@@ -248,6 +251,7 @@ namespace Hotline.Api.Controllers
                           .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.RevokeTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.RevokeTime <= dto.ApplyEndTime.Value)
                            .OrderByDescending(x => x.RevokeTime)
@@ -284,6 +288,7 @@ namespace Hotline.Api.Controllers
                           .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.WarnDate >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.WarnDate <= dto.ApplyEndTime.Value)
                            .OrderByDescending(x => x.WarnDate)
@@ -320,6 +325,7 @@ namespace Hotline.Api.Controllers
                           .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.RemindTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.RemindTime <= dto.ApplyEndTime.Value)
                            .OrderByDescending(x => x.RemindTime)
@@ -356,6 +362,7 @@ namespace Hotline.Api.Controllers
                           .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.SupplyTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.SupplyTime <= dto.ApplyEndTime.Value)
                            .OrderByDescending(x => x.SupplyTime)
@@ -392,6 +399,7 @@ namespace Hotline.Api.Controllers
                           .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.Order.No == dto.No)
                            .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Order.Title == dto.Title)
                            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.Order.ProvinceNo == dto.ProvinceNo)
+                           .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.Order.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                            .WhereIF(dto.ApplyStartTime.HasValue, p => p.SuperviseTime >= dto.ApplyStartTime.Value)
                            .WhereIF(dto.ApplyEndTime.HasValue, p => p.SuperviseTime <= dto.ApplyEndTime.Value)
                            .WhereIF(!string.IsNullOrEmpty(dto.OrgName), p => p.OrgName.Contains(dto.OrgName))
@@ -432,6 +440,7 @@ namespace Hotline.Api.Controllers
                .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.No == dto.No)
                .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Title == dto.Title)
                .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), p => p.ProvinceNo == dto.ProvinceNo)
+               .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), p => p.ReceiveProvinceNo == dto.ReceiveProvinceNo)
                .WhereIF(dto.ApplyStartTime.HasValue, p => p.CreationTime >= dto.ApplyStartTime.Value)
                .WhereIF(dto.ApplyEndTime.HasValue, p => p.CreationTime <= dto.ApplyEndTime.Value)
                .OrderByDescending(p => p.CreationTime)

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

@@ -697,7 +697,7 @@ ICallApplication callApplication,
         //return users.Id;
         var b = _cityBaseConfiguration.Value;
 
-		var a = await _expireTime.WorkDay_ZG(DateTime.Now);
+		var a = await _expireTime.GetWorkDay(DateTime.Now);
         return string.Empty;
 	}
 

+ 10 - 0
src/Hotline.Api/StartupHelper.cs

@@ -312,6 +312,16 @@ namespace Hotline.Api
                             .StartNow()
                             .WithCronSchedule("0/10 * * * * ?")
                         );
+
+                        //var getCallSatisfactionJobKey = new JobKey(nameof(XingTangCallSatisfactionSyncJob));
+                        //d.AddJob<XingTangCallSatisfactionSyncJob>(getCallSatisfactionJobKey);
+                        //d.AddTrigger(t => t
+                        //    .WithIdentity("get-callsatisfaction-trigger")
+                        //    .ForJob(getCallSatisfactionJobKey)
+                        //    .StartNow()
+                        //    .WithCronSchedule("0/30 * * * * ?")
+                        //);
+
                         break;
                 }
             });

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

@@ -1,34 +1,34 @@
 {
   "CityBaseConfiguration": {
     "CityProvince": {
-      "UserId": "03aba148-e7b1-cd03-bf00-3a1177930508",
-      "UserName": "省12345平台",
-      "OrgId": "001171",
-      "OrgName": "省12345平台"
+      "UserId": "9fd11bfd-0c39-f1f0-80b1-3a1384977b8e",
+      "UserName": "省12345交办",
+      "OrgId": "001090",
+      "OrgName": "省12345交办"
     },
     "CityProvinceAssign": {
-      "UserId": "",
-      "UserName": "",
-      "OrgId": "001178",
+      "UserId": "9fd11bfd-0c39-f1f0-80b1-3a1384977b8e",
+      "UserName": "省12345交办",
+      "OrgId": "001090",
       "OrgName": "省12345交办"
     },
     "PublicSecurity": {
-      "UserId": "e90501d7-c453-e18a-f1fa-3a1177930699",
+      "UserId": "a5a57c15-fe42-5782-449b-3a1384976aaf",
       "UserName": "市公安局110",
-      "OrgId": "001180",
+      "OrgId": "001096",
       "OrgName": "市公安局110"
     },
     "CityEnterprise": {
-      "UserId": "ce42562c-afc1-764b-1dc8-3a1177930346",
-      "UserName": "联系服务企业",
-      "OrgId": "001181",
-      "OrgName": "联系服务企业"
+      "UserId": "",
+      "UserName": "",
+      "OrgId": "",
+      "OrgName": ""
     },
     "ComprehensiveTreatment": {
-      "UserId": "d4cb7151-41fa-a810-6c1e-3a117792fc0c",
-      "UserName": "综治平台",
-      "OrgId": "001143",
-      "OrgName": "综治平台"
+      "UserId": "",
+      "UserName": "",
+      "OrgId": "",
+      "OrgName": ""
     }
   },
   "AllowedHosts": "*",

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

@@ -0,0 +1,12 @@
+using FluentValidation;
+using Hotline.Share.Dtos.Order;
+
+namespace Hotline.Application.Contracts.Validators.Order;
+
+public class AddOrderDtoValidator : AbstractValidator<AddOrderDto>
+{
+    public AddOrderDtoValidator()
+    {
+        RuleFor(d => d.Content).NotEmpty().WithMessage("请填写工单内容");
+    }
+}

+ 23 - 1
src/Hotline.Application.Tests/Application/OrderApplicationTest.cs

@@ -1,11 +1,14 @@
 using Hotline.Application.Orders;
 using Hotline.Caching.Interfaces;
 using Hotline.Orders;
+using Hotline.Push.FWMessage;
+using Hotline.Push.Notifies;
 using Hotline.Settings;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
+using Mapster;
 using Shouldly;
 using System;
 using System.Collections.Generic;
@@ -21,24 +24,33 @@ public class OrderApplicationTest
     private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
     private readonly IRepository<OrderVisit> _orderVisitRepository;
     private readonly IRepository<Order> _orderRepository;
+    private readonly IRepository<Message> _messageRepository;
 
-    public OrderApplicationTest(IOrderApplication orderApplication, IRepository<OrderVisit> orderVisitRepository, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager)
+
+    public OrderApplicationTest(IOrderApplication orderApplication, IRepository<OrderVisit> orderVisitRepository, IRepository<Message> messageRepository)
     {
         _orderApplication = orderApplication;
         _orderVisitRepository = orderVisitRepository;
         _orderRepository = orderRepository;
         _systemDicDataCacheManager = systemDicDataCacheManager;
+        _messageRepository = messageRepository;
     }
 
     [Theory]
     [InlineData(1)]
     [InlineData(2)]
+    [InlineData(3)]
+    [InlineData(4)]
+    [InlineData(5)]
+    [InlineData(6)]
+    [InlineData(7)]
     public async Task VisitPushSMS_Test(int count)
     {
         var orderVisit = await _orderVisitRepository.Queryable()
             .Where(m => m.VisitState == EVisitState.WaitForVisit)
             .OrderByDescending(m => m.CreationTime)
             .FirstAsync();
+        orderVisit.ShouldNotBeNull("缺少 回访单 (order_visit) VisitState = 10 的数据.");
         var dto = new VisitSmsInDto
         {
             Ids = new List<string> { orderVisit.Id }
@@ -95,4 +107,14 @@ public class OrderApplicationTest
         order.TranspondCityName.ShouldBe(dicSystem.DicDataName);
         order.TranspondCityValue.ShouldBe(dicSystem.DicDataValue);
     }
+
+    [Fact]
+    public async Task MapConfig_Test()
+    {
+        var data = await _messageRepository.Queryable().OrderByDescending(m => m.CreationTime)
+            .FirstAsync();
+        var m = data.Adapt<ReceiveMessageNotify>();
+        m.NotifyDto.Name.ShouldBe(data.Name);
+        m.NotifyDto.PushBusiness.ShouldBe(data.PushBusiness);
+    }
 }

+ 103 - 0
src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs

@@ -0,0 +1,103 @@
+using Hotline.EventBus;
+using Hotline.Orders;
+using Hotline.Push.FWMessage;
+using Hotline.Push.Notifies;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Push;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Push;
+using Hotline.Share.Tools;
+using Mapster;
+using Shouldly;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Tests.Domain;
+public class OrderVisitDomainServiceTest
+{
+    private readonly IOrderVisitDomainService _orderVisitDomainService;
+    private readonly IOrderVisitRepository _orderVisitRepository;
+    private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+    private readonly Publisher _publisher;
+
+    public OrderVisitDomainServiceTest(IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher)
+    {
+        _orderVisitDomainService = orderVisitDomainService;
+        _orderVisitRepository = orderVisitRepository;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+        _publisher = publisher;
+    }
+
+    [Fact]
+    public async Task OnSmsUpdate_Test()
+    {
+        var data = new Message() { IsSmsReply = true, SmsReplyContent = "1", PushBusiness = EPushBusiness.VisitSms };
+        await _publisher.PublishAsync(data.Adapt<ReceiveMessageNotify>(), PublishStrategy.ParallelNoWait, new CancellationToken());
+    }
+
+    [Theory]
+    [InlineData("4", "SMSUnsatisfied", "2", "不满意")]
+    [InlineData("1", "Visited", "5", "非常满意")]
+    [InlineData("非常满意", "Visited", "5", "非常满意")]
+    [InlineData("满意", "Visited", "4", "满意")]
+    [InlineData("一般", "Visited", "4", "满意")]
+    [InlineData("不满意", "SMSUnsatisfied", "2", "不满意")]
+    [InlineData("非常不满意", "SMSUnsatisfied", "2", "不满意")]
+    public async Task UpdateSmsReply_Test(string content, string visitState, string orgResuktKey, string orgResuktValue)
+    {
+        var visit = await _orderVisitRepository.Queryable()
+            .Where(m => m.VisitState == EVisitState.SMSVisiting)
+            .OrderByDescending(m => m.CreationTime)
+            .FirstAsync();
+        visit.ShouldNotBeNull("缺少测试数据");
+
+        var message = new MessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content };
+        var dto = new PushReceiveMessageDto();
+        await _orderVisitDomainService.UpdateSmsReplyAsync(message);
+        visit = _orderVisitRepository.Get(visit.Id);
+        visit.VisitState.ShouldBe(visitState.ToEnum<EVisitState>());
+        visit.NowEvaluate.Key.ShouldBe(orgResuktKey);
+        visit.NowEvaluate.Value.ShouldBe(orgResuktValue);
+
+        if (content == "4" || content == "5" || content == "不满意" || content == "非常不满意")
+            visit.VisitType.ShouldBeNull();
+
+        await _orderVisitDetailRepository.Queryable()
+            .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Org)
+            .FirstAsync()
+            .Then(async org =>
+            {
+                org.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
+                org.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
+                org.OrgHandledAttitude.Key.ShouldBe(orgResuktKey);
+                org.OrgHandledAttitude.Value.ShouldBe(orgResuktValue);
+            });
+    }
+
+    [Theory]
+    [InlineData("1", "非常满意", "Visited", "VerySatisfied", "VerySatisfied", "非常满意|5")]
+    [InlineData("2", "满意", "Visited", "Satisfied", "Satisfied", "满意|4")]
+    [InlineData("3", "一般", "Visited", "Normal", "Normal", "满意|4")]
+    [InlineData("4", "不满意", "SMSUnsatisfied", "NoSatisfied", "NoSatisfied", "不满意|2")]
+    [InlineData("5", "非常不满意", "SMSUnsatisfied", "NoSatisfied", "VeryNoSatisfied", "不满意|2")]
+    [InlineData("默认满意", "默认满意", "Visited", "DefaultSatisfied", "DefaultSatisfied", "满意|4")]
+    public void GetVisitEvaluateByReplyTxt_Test(string replyTxt, string assertReplyTxt, string visitState, string seatEvaluate, string voiceEvaluate, string kv)
+    {
+        var replyString = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<string>(replyTxt);
+        replyString.ShouldBe(assertReplyTxt);
+
+        var visitStateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVisitState>(replyTxt);
+        visitStateEnum.ShouldBe(visitState.ToEnum<EVisitState>());
+
+        var seatEvaluateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt);
+        seatEvaluateEnum.ShouldBe(seatEvaluate.ToEnum<ESeatEvaluate>());
+
+        var voiceEvaluateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt);
+        voiceEvaluateEnum.ShouldBe(voiceEvaluate.ToEnum<EVoiceEvaluate>());
+
+        var kvResult = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>(replyTxt);
+        var sp = kv.Split('|');
+        var kV = new Kv(sp[1].ToString(), sp[0].ToString());
+        kvResult.Key.ShouldBe(kV.Key);
+        kvResult.Value.ShouldBe(kV.Value);
+    }
+}

+ 24 - 0
src/Hotline.Application.Tests/Repository/CallNativeRepositoryTest.cs

@@ -0,0 +1,24 @@
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.CallCenter;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Tests.Repository;
+public class CallNativeRepositoryTest
+{
+    private readonly ICallNativeRepository _callNativeRepository;
+    private readonly IRepository<Order> _orderRepository;
+
+    public CallNativeRepositoryTest(ICallNativeRepository callNativeRepository, IRepository<Order> orderRepository)
+    {
+        _callNativeRepository = callNativeRepository;
+        _orderRepository = orderRepository;
+    }
+}

+ 4 - 34
src/Hotline.Application.Tests/Repository/OrderVisitRepositoryTest.cs

@@ -16,43 +16,13 @@ public class OrderVisitRepositoryTest
 {
     private readonly IOrderVisitRepository _orderVisitRepository;
     private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+    private readonly IOrderVisitDomainService _orderVisitDomainService;
 
-    public OrderVisitRepositoryTest(IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository)
+    public OrderVisitRepositoryTest(IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, IOrderVisitDomainService orderVisitDomainService)
     {
         _orderVisitRepository = orderVisitRepository;
         _orderVisitDetailRepository = orderVisitDetailRepository;
+        _orderVisitDomainService = orderVisitDomainService;
     }
 
-    [Theory]
-    [InlineData("4", "SMSUnsatisfied", "2" , "不满意")]
-    [InlineData("1", "Visited", "5", "非常满意")]
-    public async Task UpdateSmsReply_Test(string content, string visitState, string orgResuktKey,  string orgResuktValue)
-    {
-        var visit = await _orderVisitRepository.Queryable()
-            .Where(m => m.VisitState == EVisitState.SMSVisiting)
-            .OrderByDescending(m => m.CreationTime)
-            .FirstAsync();
-
-        var dto = new PushReceiveMessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content };
-        var message = new Message();
-        await _orderVisitRepository.UpdateSmsReplyAsync(dto, message);
-        visit = _orderVisitRepository.Get(visit.Id);
-        visit.VisitState.ShouldBe(visitState.ToEnum<EVisitState>());
-        visit.NowEvaluate.Key.ShouldBe(content);
-        visit.NowEvaluate.Value.ShouldBe(orgResuktValue);
-
-        if (content == "4" || content == "5")
-            visit.VisitType.ShouldBeNull();
-
-        await _orderVisitDetailRepository.Queryable()
-            .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Org)
-            .FirstAsync()
-            .Then(async org =>
-            {
-                org.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
-                org.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
-                org.OrgHandledAttitude.Key.ShouldBe(orgResuktKey);
-                org.OrgHandledAttitude.Value.ShouldBe(orgResuktValue);
-            });
-    }
-}
+  }

+ 8 - 5
src/Hotline.Application/Bulletin/BulletinApplication.cs

@@ -1,4 +1,6 @@
-using Hotline.Configurations;
+using Hotline.Caching.Interfaces;
+using Hotline.Configurations;
+using Hotline.Settings;
 using Microsoft.Extensions.Options;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -9,10 +11,13 @@ namespace Hotline.Application.Bulletin
     public class BulletinApplication : IBulletinApplication, IScopeDependency
     {
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
 
-        public BulletinApplication(IOptionsSnapshot<AppConfiguration> appOptions)
+        public BulletinApplication(IOptionsSnapshot<AppConfiguration> appOptions,
+                 ISystemSettingCacheManager systemSettingCacheManager)
         {
             _appOptions = appOptions;
+            _systemSettingCacheManager= systemSettingCacheManager; 
         }
 
         /// <summary>
@@ -37,9 +42,7 @@ namespace Hotline.Application.Bulletin
             // 搜索匹配的字符串
             MatchCollection matchesvideo = regvideo.Matches(sHtmlText);
 
-
-            // 获取服务器地址配置 
-            string strSiteUrl = _appOptions.Value.OldFilesUrls;
+            var strSiteUrl = _systemSettingCacheManager.GetSetting(SettingConstants.OldFilesUrls)?.SettingValue[0];
          
             // 取得匹配项列表 视频
             foreach (Match match in matchesvideo)

+ 44 - 0
src/Hotline.Application/CallCenter/CallNativeApplication.cs

@@ -0,0 +1,44 @@
+using Hotline.CallCenter.Calls;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.CallCenter;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+
+namespace Hotline.Application.CallCenter;
+public class CallNativeApplication : ICallNativeApplication, IScopeDependency
+{
+    private readonly ICallNativeRepository _callNativeRepository;
+    private readonly ICallDomainService _callDomainService;
+    private readonly IOrderVisitDomainService _orderVisitDomainService;
+
+    public CallNativeApplication(ICallNativeRepository callNativeRepository, IOrderVisitDomainService orderVisitDomainService, ICallDomainService callDomainService)
+    {
+        _callNativeRepository = callNativeRepository;
+        _orderVisitDomainService = orderVisitDomainService;
+        _callDomainService = callDomainService;
+    }
+
+    /// <summary>
+    /// 根据 OrderId 返回用户电话评价枚举
+    /// 默认返回 默认满意
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    public async Task<EVoiceEvaluate> GetReplyVoiceOrDefaultByOrderIdAsync(string orderId)
+    {
+        var callNative = await _callDomainService.GetByOrderIdAsync(orderId);
+        if (callNative is null || callNative.ReplyTxt.IsNullOrEmpty())
+            return EVoiceEvaluate.DefaultSatisfied;
+
+        try
+        {
+            return _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(callNative.ReplyTxt!.Trim());
+        }
+        catch (UserFriendlyException)
+        {
+            return EVoiceEvaluate.DefaultSatisfied;
+        }
+    }
+}

+ 13 - 0
src/Hotline.Application/CallCenter/ICallNativeApplication.cs

@@ -0,0 +1,13 @@
+using Hotline.Share.Enums.Order;
+
+namespace Hotline.Application.CallCenter;
+public interface  ICallNativeApplication
+{
+    /// <summary>
+    /// 根据 OrderId 返回用户电话评价枚举
+    /// 默认返回 默认满意
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    Task<EVoiceEvaluate> GetReplyVoiceOrDefaultByOrderIdAsync(string orderId);
+}

+ 1 - 1
src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs

@@ -158,7 +158,7 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
                     //    expiredTimeChanged = true;
                     //}
                     if (data.FlowDirection is EFlowDirection.CenterToOrg)
-                        order.SendBackAuditEndTime = await _expireTime.WorkDay_ZG(DateTime.Now);
+                        order.SendBackAuditEndTime = await _expireTime.GetWorkDay(DateTime.Now);
 					await _orderRepository.Updateable(order).ExecuteCommandAsync(cancellationToken);
                     //await _orderRepository.UpdateAsync(order, cancellationToken);
 

+ 28 - 0
src/Hotline.Application/Handlers/Order/OrderVisitSmsHandler.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
+using Hotline.Orders.Notifications;
+using Hotline.Push.Notifies;
+using Hotline.Share.Enums.Push;
+using MediatR;
+
+namespace Hotline.Application.Handlers.Order;
+public class OrderVisitSmsHandler : INotificationHandler<ReceiveMessageNotify>
+{
+    private readonly IOrderVisitDomainService _orderVisitDomainService;
+
+    public OrderVisitSmsHandler(IOrderVisitDomainService orderVisitDomainService)
+    {
+        _orderVisitDomainService = orderVisitDomainService;
+    }
+
+    public async Task Handle(ReceiveMessageNotify notification, CancellationToken cancellationToken)
+    {
+        if (notification.NotifyDto.PushBusiness == EPushBusiness.VisitSms)
+            await _orderVisitDomainService.UpdateSmsReplyAsync(notification.NotifyDto);
+    }
+}

+ 1 - 1
src/Hotline.Application/Jobs/CheckAiVisitStateJob.cs

@@ -22,7 +22,7 @@ namespace Hotline.Application.Jobs
 
         public async Task Execute(IJobExecutionContext context)
         {
-            Console.WriteLine($"{nameof(SendOrderJob)} 执行, {DateTime.Now}");
+            Console.WriteLine($"{nameof(CheckAiVisitStateJob)} 执行, {DateTime.Now}");
             await _aiVisitDomainService.OrderVisitStatusService(context.CancellationToken);
         }
     }

+ 98 - 0
src/Hotline.Application/Jobs/XingTangCallSatisfactionSyncJob.cs

@@ -0,0 +1,98 @@
+using DotNetCore.CAP;
+using Hotline.CallCenter.Calls;
+using Hotline.Share.Mq;
+using MapsterMapper;
+using Microsoft.Extensions.Logging;
+using Quartz;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+using XingTang.Sdk;
+
+namespace Hotline.Application.Jobs
+{
+    /// <summary>
+    /// 兴唐评价结果
+    /// </summary>
+    public class XingTangCallSatisfactionSyncJob : IJob, IDisposable
+    {
+        private readonly IRepository<CallNative> _callRepository;
+        private readonly ISqlSugarClient _db;
+        private readonly IRepository<CallSatisfaction> _callSatisfactionRepository;
+        private readonly IMapper _mapper;
+        private readonly ILogger<XingTangCallSatisfactionSyncJob> _logger;
+        private readonly ICapPublisher _capPublisher;
+
+        public XingTangCallSatisfactionSyncJob(IRepository<CallNative> callRepository, ISqlSugarClient db,
+            IRepository<CallSatisfaction> callSatisfactionRepository, IMapper mapper, ILogger<XingTangCallSatisfactionSyncJob> logger,
+            ICapPublisher capPublisher)
+        {
+            _callRepository = callRepository;
+            _db = db;
+            _callSatisfactionRepository = callSatisfactionRepository;
+            _mapper = mapper;
+            _logger = logger;
+            _capPublisher = capPublisher;
+        }
+
+        public void Dispose()
+        {
+        }
+
+        public async Task Execute(IJobExecutionContext context)
+        {
+            var callSatisfactions = await _db.Queryable<XingtangSatisfaction>()
+                .Where(d => (d.IsSync == null || !d.IsSync) && (d.Tries == null || d.Tries <= 50))
+                .OrderBy(d => d.Id)
+                .Take(10)
+                .ToListAsync(context.CancellationToken);
+
+            if (!callSatisfactions.Any()) return;
+            var occupyCallSatisfactions = new List<XingtangSatisfaction>();
+            foreach (var callSatisfaction in callSatisfactions)
+            {
+                callSatisfaction.IsSync = true;
+                callSatisfaction.Tries += 1;
+
+                var rows = await _db.Updateable(callSatisfaction)
+                    .ExecuteCommandWithOptLockAsync();
+                if (rows > 0)
+                    occupyCallSatisfactions.Add(callSatisfaction);
+            }
+
+            if (!occupyCallSatisfactions.Any()) return;
+            try
+            {
+                var callStatisfactions = _mapper.Map<List<CallSatisfaction>>(occupyCallSatisfactions);
+                foreach (var item in callSatisfactions)
+                {
+                    var call = _callRepository.Queryable().Where(x => x.CallNo == item.CallNo).FirstAsync();
+                    if (call != null)
+                    {
+                        item.Id = call.Id;
+                    }
+                }
+
+                await _callSatisfactionRepository.AddRangeAsync(callStatisfactions, context.CancellationToken);
+                await _capPublisher.PublishAsync(EventNames.HotlineCallSatisfactionAdd, callStatisfactions,
+                    cancellationToken: context.CancellationToken);
+            }
+            catch (Exception e)
+            {
+                _logger.LogError($"获取通话记录评价:{e.Message} \n {e.StackTrace}");
+                foreach (var callSatisfaction in occupyCallSatisfactions)
+                {
+                    callSatisfaction.IsSync = false;
+                }
+
+                await _db.Updateable(occupyCallSatisfactions)
+                    .UpdateColumns(d => new { d.IsSync })
+                    .ExecuteCommandAsync(context.CancellationToken);
+            }
+        }
+    }
+}

+ 2 - 0
src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs

@@ -56,6 +56,7 @@ namespace Hotline.Application.Jobs
                 .Take(10)
                 .ToListAsync(context.CancellationToken);
 
+            if(!xingtangCalls.Any()) return;
             var occupyCalls = new List<XingtangCall>();
             foreach (var call in xingtangCalls)
             {
@@ -68,6 +69,7 @@ namespace Hotline.Application.Jobs
                     occupyCalls.Add(call);
             }
 
+            if(!occupyCalls.Any()) return;
             try
             {
                 var calls = _mapper.Map<List<CallNative>>(occupyCalls);

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

@@ -90,6 +90,11 @@ namespace Hotline.Application.Mappers
                         : EEndBy.To;
                 });
 
+            config.ForType<XingtangSatisfaction, CallSatisfaction>()
+                .Map(d => d.CallNo, s => s.CallNo)
+                .Map(d => d.Result, s => s.Result)
+                ;
+
             config.ForType<XingtangSeatOperation, TelOperation>()
                 .Map(d => d.StaffNo, s => s.UserCode)
                 .Map(d => d.TelNo, s => s.Ext)

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

@@ -2,7 +2,9 @@
 using Hotline.JudicialManagement;
 using Hotline.Orders;
 using Hotline.Push.FWMessage;
+using Hotline.Push.Notifies;
 using Hotline.Settings;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Ai;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.JudicialManagement;
@@ -21,6 +23,13 @@ namespace Hotline.Application.Mappers
     {
         public void Register(TypeAdapterConfig config)
         {
+            config.ForType<SystemDicData, Kv>()
+                .Map(s => s.Key, d => d.DicDataValue)
+                .Map(s => s.Value, d => d.DicDataName);
+
+            config.ForType<Message, ReceiveMessageNotify>()
+                .Map(m => m.NotifyDto, d => d);
+
             config.ForType<SystemDicData, SystemDicDataOutDto>()
                 .Map(m => m.Id, n => n.Id)
                 .Map(m => m.DicDataName, n => n.DicDataName)

+ 1 - 0
src/Hotline.Application/Mappers/OrderMapperConfigs.cs

@@ -107,6 +107,7 @@ public class OrderMapperConfigs : IRegister
             .Ignore(d => d.AcceptorId)
             .Ignore(d => d.AcceptorName)
             .Ignore(d => d.AcceptorStaffNo)
+            .Ignore(d=>d.ExternalId)
             //.AfterMapping((s, d) =>
             //{
             //    //d.UpdateHandlingStatus(s.IsInCountersign);

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

@@ -943,6 +943,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .Includes(x => x.OrderScreens)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!)) //标题
             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), d => d.ProvinceNo == dto.ProvinceNo) //省本地编号
+             .WhereIF(!string.IsNullOrEmpty(dto.ReceiveProvinceNo), d => d.ReceiveProvinceNo == dto.ReceiveProvinceNo) //省编号
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No) //工单编码
             .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType)//受理类型
             //.WhereIF(dto.AcceptTypes.Any(), d => dto.AcceptTypes.Contains(d.AcceptTypeCode)) //受理类型

+ 72 - 0
src/Hotline.Application/Subscribers/CallSubscriber.cs

@@ -0,0 +1,72 @@
+using DotNetCore.CAP;
+using Hotline.CallCenter.Calls;
+using Hotline.Orders;
+using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Mq;
+using Hotline.Share.Tools;
+using Polly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Subscribers;
+public class CallSubscriber : ICapSubscribe, ITransientDependency
+{
+    private readonly IRepository<CallNative> _callRepository;
+    private readonly IRepository<Order> _orderRepository;
+    private readonly IRepository<OrderVisit> _orderVisitrepository;
+    private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+    private readonly IOrderVisitRepository _orderVisitRepository;
+
+    public CallSubscriber(IRepository<CallNative> callRepository, IRepository<Order> orderRepository, IRepository<OrderVisit> orderVisitrepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, IOrderVisitRepository orderVisitRepository)
+    {
+        _callRepository = callRepository;
+        _orderRepository = orderRepository;
+        _orderVisitrepository = orderVisitrepository;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+        _orderVisitRepository = orderVisitRepository;
+    }
+
+    /// <summary>
+    /// 挂断电话
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    /// <exception cref="UserFriendlyException"></exception>
+    //[CapSubscribe(EventNames.HotlineCallBye)]
+    //public async Task HotlineCallBye(PublishCallRecrodDto dto, CancellationToken cancellationToken)
+    //{
+    //    var callId = await _orderRepository.Queryable()
+    //        .Where(m => m.Id == dto.Order.Id)
+    //        .Select(m => m.CallId)
+    //        .FirstAsync() ??
+    //        throw new UserFriendlyException($"订单Id:{dto.Order.Id} 的 CallId 为空");
+    //    var replyTxt = string.Empty;
+    //    RetryHelper.Retry(() =>
+    //    {
+    //        return false;
+    //    }, 5, 1000);
+    //}
+
+    /// <summary>
+    /// 如果有新的电话回复记录, 就更新回访记录
+    /// </summary>
+    /// <param name="callSatisfactions"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    //[CapSubscribe(EventNames.HotlineCallSatisfactionAdd)]
+    //public async Task HotlineCallSatisfactionAdd(List<CallSatisfaction> callSatisfactions, CancellationToken cancellationToken)
+    //{
+    //    foreach (var callSatisfaction in callSatisfactions)
+    //    {
+    //        await _orderVisitRepository.UpdateVoiceReplyAsync(callSatisfaction, cancellationToken);
+    //    }
+    //}
+}

+ 18 - 14
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -70,7 +70,7 @@ namespace Hotline.Application.Subscribers
         private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
         private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
 
-		public DataSharingSubscriber(
+        public DataSharingSubscriber(
             IRepository<OrderVisit> orderVisitRepository,
             IMapper mapper,
             IOrderRepository orderRepository,
@@ -132,7 +132,7 @@ namespace Hotline.Application.Subscribers
             _orderPublishRepository = orderPublishRepository;
             _sysDicDataCacheManager = sysDicDataCacheManager;
             _cityBaseConfiguration = cityBaseConfiguration;
-		}
+        }
 
         /// <summary>
         /// 接收工单退回结果
@@ -240,7 +240,8 @@ namespace Hotline.Application.Subscribers
             };
             await _orderRevokeRepository.AddAsync(orderRevoke, cancellationToken);
 
-            var current = SessionContextCreator.CreateSessionContext(dto.Source,_cityBaseConfiguration.Value);
+            //宜宾需求:特提至中心(派单组?),由派单员归档
+            var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
             if (string.IsNullOrEmpty(order?.WorkflowId))
             {
                 var startDto = new StartWorkflowDto
@@ -249,15 +250,18 @@ namespace Hotline.Application.Subscribers
                     Title = order.Title,
                     Opinion = dto.Opinion,
                 };
-                await _workflowApplication.StartToEndAsync(startDto, current, order.Id, order.ExpiredTime,
+                //await _workflowApplication.StartToEndAsync(startDto, current, order.Id, order.ExpiredTime,
+                //    cancellationToken);
+                await _workflowApplication.StartWorkflowAsync(startDto, current, order.Id, order.ExpiredTime,
                     cancellationToken);
             }
             else
             {
                 //await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, dto.Opinion, null,
                 //    cancellationToken: cancellationToken);
-                await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Opinion, null, order.ExpiredTime,
-                    cancellationToken: cancellationToken);
+                //await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Opinion, null, order.ExpiredTime,
+                //    cancellationToken: cancellationToken);
+                await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, dto.Opinion, current, cancellationToken);
             }
         }
 
@@ -417,7 +421,7 @@ namespace Hotline.Application.Subscribers
                             x.Status == EScreenStatus.Approval)
                 .FirstAsync(cancellationToken);
 
-            var current = SessionContextCreator.CreateSessionContext(dto.Source,_cityBaseConfiguration.Value);
+            var current = SessionContextCreator.CreateSessionContext(dto.Source, _cityBaseConfiguration.Value);
             await _workflowApplication.HandleToEndAsync(current,
                 orderScreen.WorkflowId, "省上推送甄别结果", null,
                 dto.ProvinceScreenResult.AuditResult
@@ -550,7 +554,7 @@ namespace Hotline.Application.Subscribers
             {
                 //处理省下行回访
                 var order = await _orderRepository.Queryable().Where(x => x.ReceiveProvinceNo == dto.ProvinceNo).FirstAsync();
-                if (order !=null)
+                if (order != null)
                 {
                     //判断是否有发布数据
                     var orderPublish = await _orderPublishRepository.Queryable()
@@ -606,22 +610,22 @@ namespace Hotline.Application.Subscribers
                     orgDetail.VisitOrgCode = order.ActualHandleOrgCode;
                     orgDetail.VisitOrgName = order.ActualHandleOrgName;
                     orgDetail.VisitTarget = EVisitTarget.Org;
-                    
-                    
+
+
                     orgDetail.OrgProcessingResults = satisfy;
 
                     visitedDetail.Add(orgDetail);
                     //TODO 自贡办件态度
 
 
-                    await _orderVisitedDetailRepository.AddRangeAsync(visitedDetail,cancellationToken);
-                    order.Visited(satisfy.Key,satisfy.Value);
+                    await _orderVisitedDetailRepository.AddRangeAsync(visitedDetail, cancellationToken);
+                    order.Visited(satisfy.Key, satisfy.Value);
                     order.Status = EOrderStatus.Visited;
                     await _orderRepository.UpdateAsync(order, cancellationToken);
 
                 }
-                
-                
+
+
             }
         }
 

+ 30 - 0
src/Hotline.Repository.SqlSugar/CallCenter/CallNativeRepository.cs

@@ -0,0 +1,30 @@
+using Hotline.CallCenter.Calls;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Settings;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Repository.SqlSugar.CallCenter;
+public class CallNativeRepository : BaseRepository<CallNative>, ICallNativeRepository, IScopeDependency
+{
+    public CallNativeRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+
+    public async Task<int> UpdateReplyTxtAsync(string callId, string replyTxt)
+    {
+        return await Updateable()
+            .Where(m => m.Id == callId)
+            .SetColumns(m => m.ReplyTxt, replyTxt)
+            .ExecuteCommandAsync();
+    }
+}

+ 2 - 70
src/Hotline.Repository.SqlSugar/Orders/OrderVisitRepository.cs

@@ -1,5 +1,6 @@
 using Hotline.Caching.Interfaces;
 using Hotline.Caching.Services;
+using Hotline.CallCenter.Calls;
 using Hotline.Orders;
 using Hotline.Push.FWMessage;
 using Hotline.Repository.SqlSugar.DataPermissions;
@@ -12,6 +13,7 @@ using SqlSugar;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
+using static System.Runtime.InteropServices.JavaScript.JSType;
 
 namespace Hotline.Repository.SqlSugar.Orders;
 public class OrderVisitRepository : BaseRepository<OrderVisit>, IOrderVisitRepository, IScopeDependency
@@ -29,74 +31,4 @@ public class OrderVisitRepository : BaseRepository<OrderVisit>, IOrderVisitRepos
         _systemDicDataCacheManager = systemDicDataCacheManager;
     }
 
-    /// <summary>
-    /// 用户回访短信回复更新回访状态
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <param name="data"></param>
-    /// <returns></returns>
-    public async Task UpdateSmsReplyAsync(PushReceiveMessageDto dto, Message data)
-    {
-        if (dto.IsSmsReply == false || dto.SmsReplyContent.IsNullOrEmpty() || dto.ExternalId.IsNullOrEmpty()) return;
-
-        var orderVisit = await GetAsync(dto.ExternalId)
-             ?? throw new UserFriendlyException($"回访单不存在, visitId: {dto.ExternalId} message: {data.ToJson()}");
-
-        Dictionary<string, string> dics = new()
-        {
-            { "1", $"非常满意|{EVisitState.Visited}|{ESeatEvaluate.VerySatisfied}|{EVoiceEvaluate.VerySatisfied}|5" },
-            { "2", $"满意|{EVisitState.Visited}|{ESeatEvaluate.Satisfied}|{EVoiceEvaluate.Satisfied}|4"},
-            { "3", $"一般|{EVisitState.Visited}|{ESeatEvaluate.Satisfied}|{EVoiceEvaluate.Normal}|4"},
-            { "4", $"不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.NoSatisfied}|2"},
-            { "5", $"非常不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.VeryNoSatisfied}|2"},
-        };
-
-        var replyTxt = dto.SmsReplyContent.Trim();
-        var result = dics[replyTxt];
-        if (result.IsNullOrEmpty()) throw new UserFriendlyException($"用户回复短信内容异常; reply: {replyTxt}");
-        var replySplit = result.Split("|");
-        if (new string[] { "4", "5" }.Contains(replyTxt))
-        {
-            // “短信不满意待回访”状态下,由其他方式再次进行回访,回访方式需更新为最新的回访方式
-            // 故在此置为空
-            orderVisit.VisitType = null;
-        }
-        var visitSatisfaction = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction)
-            .First(m => m.DicDataValue == replySplit[4]);
-        orderVisit.NowEvaluate = new Share.Dtos.Kv(visitSatisfaction.DicDataValue, visitSatisfaction.DicDataName);
-
-        orderVisit.VisitTime = DateTime.Now;
-        orderVisit.VisitState = replySplit[1].ToEnum<EVisitState>();
-        await UpdateAsync(orderVisit, ignoreNullColumns: false);
-
-        if (orderVisit.VisitState == EVisitState.Visited)
-        {
-            await _orderRepository.GetAsync(orderVisit.OrderId)
-                .Then(async order =>
-                {
-                    order.Visited(replyTxt, replySplit[0]);
-                    await _orderRepository.UpdateAsync(order);
-                });
-
-        }
-        var detailOrg = await _orderVisitDetailRepository.Queryable()
-            .Where(m => m.VisitId == orderVisit.Id && m.VisitTarget == EVisitTarget.Org)
-            .ToListAsync();
-        foreach (var item in detailOrg)
-        {
-            item.OrgProcessingResults = new Share.Dtos.Kv(visitSatisfaction.DicDataValue, visitSatisfaction.DicDataName);
-            item.OrgHandledAttitude = new Share.Dtos.Kv(visitSatisfaction.DicDataValue, visitSatisfaction.DicDataName);
-            await _orderVisitDetailRepository.UpdateAsync(item);
-        }
-
-        await _orderVisitDetailRepository.Queryable()
-            .Where(m => m.VisitId == orderVisit.Id && m.VisitTarget == EVisitTarget.Seat)
-            .FirstAsync()
-            .Then(async detailSeat =>
-            {
-                detailSeat.SeatEvaluate ??= replySplit[2].ToEnum<ESeatEvaluate>();
-                detailSeat.VoiceEvaluate ??= replySplit[3].ToEnum<EVoiceEvaluate>();
-                await _orderVisitDetailRepository.UpdateAsync(detailSeat);
-            });
-    }
 }

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

@@ -17,6 +17,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public string? ProvinceNo { get; set; }
 
+        /// <summary>
+        /// 省交办工单编号
+        /// </summary>
+        public string? ReceiveProvinceNo { get; set; }
+        
         /// <summary>
         /// 工单标题
         /// </summary>

+ 1 - 1
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -863,7 +863,7 @@ namespace Hotline.Share.Dtos.Order
     /// <summary>
     /// 回访详情的历史记录
     /// </summary>
-    public class OrderVisitDetailHistiryDto
+    public class OrderVisitDetailHistoryDto
     {
         public EVoiceEvaluate? VoiceEvaluate { get; set; }
         public string? VoiceEvaluateTxt => this.VoiceEvaluate?.GetDescription();

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

@@ -258,6 +258,11 @@ public class PublishPublishOrderDto
     /// </summary>
     public string Remark { get; set; }
 
+    /// <summary>
+    /// 答复口径
+    /// </summary>
+    public string AnswerContent { get; set; }
+
     #endregion
 }
 
@@ -300,6 +305,7 @@ public class PublishOrderDto
     /// 省是否公开
     /// </summary>
     public bool? ProPublishState { get; set; }
+
     /// <summary>
     /// 反馈人电话
     /// </summary>
@@ -335,6 +341,11 @@ public class PublishOrderDto
     /// </summary>
     public string? Remark { get; set; }
 
+    /// <summary>
+    /// 答复口径
+    /// </summary>
+    public string? AnswerContent { get; set; }
+
     #endregion
 
     /// <summary>

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

@@ -181,6 +181,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public FiledType? FiledType { get; set; }
 	}
+        /// <summary>
+        /// 省交办编号
+        /// </summary>
+        public string? ReceiveProvinceNo { get; set; }
+    }
 
 
 	public enum FiledType

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

@@ -122,7 +122,10 @@ namespace Hotline.Share.Dtos.Order
 		}
 
 		public bool IsReturnAgainShow => ApplyOrgId != "001" && SendBackOrgId == "001";
-
+		/// <summary>
+		/// 附件列表
+		/// </summary>
+		public List<FileDto> Files { get; set; } = new();
 	}
 	public class SendBackBaseDto
 	{

+ 10 - 0
src/Hotline.Share/Dtos/ProvinceStatistics/QueryProvinceSendBackDto.cs

@@ -19,6 +19,11 @@ namespace Hotline.Share.Dtos.ProvinceStatistics
         /// </summary>
         public string? ProvinceNo { get; set; }
 
+        /// <summary>
+        /// 省交办编号
+        /// </summary>
+        public string? ReceiveProvinceNo { get; set; }
+
         /// <summary>
         /// 申请人
         /// </summary>
@@ -60,6 +65,11 @@ namespace Hotline.Share.Dtos.ProvinceStatistics
         /// </summary>
         public string? ProvinceNo { get; set; }
 
+        /// <summary>
+        /// 省交办编号
+        /// </summary>
+        public string? ReceiveProvinceNo { get; set; }
+
         /// <summary>
         /// 督办标题
         /// </summary>

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

@@ -53,5 +53,7 @@ namespace Hotline.Share.Dtos.Push
         /// 参数
         /// </summary>
         public List<string>? Params { get; set; }
+        public bool IsSmsReply { get; set; }
+        public string? SmsReplyContent { get; set; }
     }
 }

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

@@ -7,7 +7,7 @@
     <GenerateDocumentationFile>True</GenerateDocumentationFile>
     <NoWarn>$(NoWarn);1591;8618;</NoWarn>
     <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-    <Version>1.0.105</Version>
+    <Version>1.0.106</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 6 - 1
src/Hotline.Share/Mq/EventNames.Call.cs

@@ -5,7 +5,7 @@ public partial class EventNames
     /// <summary>
     /// 挂断电话
     /// </summary>
-    public const string HotlineCallBye= "hotline.call.bye";
+    public const string HotlineCallBye = "hotline.call.bye";
 
     /// <summary>
     /// 通话记录与工单关联
@@ -21,4 +21,9 @@ public partial class EventNames
     /// 通话记录关联工单
     /// </summary>
     public const string HotlineCallNativeConnectOrder = "hotline.callnative.connect.order";
+
+    /// <summary>
+    /// 同步电话回复记录 新增事件
+    /// </summary>
+    public const string HotlineCallSatisfactionAdd = "hotline.call.satisfaction.add";
 }

+ 38 - 0
src/Hotline.Share/Tools/RetryHelper.cs

@@ -0,0 +1,38 @@
+namespace Hotline.Share.Tools;
+
+/// <summary>
+/// 重试操作辅助类
+/// </summary>
+public static class RetryHelper
+{
+    /// <summary>
+    /// 重试
+    /// </summary>
+    /// <param name="func"> 重试方法 </param>
+    /// <param name="times"> 重试次数 (1 与 100 之间的整数) </param>
+    /// <param name="millisecond"> 重试等待毫秒 </param>
+    /// <returns> 是否执行成功 </returns>
+    public static bool Retry(Func<bool> func, int times, int millisecond = 0)
+    {
+        if (times <= 1 && times >= 100)
+        {
+            throw new ArgumentOutOfRangeException("times 参数有误。只能是介于 1 与 100(包含1和100)之间的整数。");
+        }
+
+        while (times > 0)
+        {
+            if (func())
+            {
+                return true;
+            }
+
+            times--;
+            if (millisecond > 0)
+            {
+                Thread.Sleep(millisecond);
+            }
+        }
+
+        return false;
+    }
+}

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

@@ -10,7 +10,7 @@ namespace Hotline.Caching.Interfaces
     public interface ISystemDicDataCacheManager
     {
         IReadOnlyList<SystemDicData> GetSysDicDataCache(string code);
-
+        IReadOnlyList<SystemDicData> GetVisitSatisfaction();
         void RemoveSysDicDataCache(string code);
     }
 }

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

@@ -26,6 +26,9 @@ namespace Hotline.Caching.Services
             return sysDicDataList;
         }
 
+        public IReadOnlyList<SystemDicData> GetVisitSatisfaction()
+            => GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
+
         public void RemoveSysDicDataCache(string code)
         {
             _cacheSysDicData.Remove(code);

+ 38 - 7
src/Hotline/CallCenter/Calls/CallDomainService.cs

@@ -1,7 +1,11 @@
 using Hotline.CallCenter.Configs;
 using Hotline.CallCenter.Devices;
 using Hotline.Configurations;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.CallCenter;
+using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
+using Hotline.Share.Tools;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Options;
 using XF.Domain.Dependency;
@@ -15,13 +19,17 @@ namespace Hotline.CallCenter.Calls
         private readonly ICallRepository _callRepository;
         private readonly ICallDetailRepository _callDetailRepository;
         private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
+        private readonly IOrderRepository _orderRepository;
+        private readonly ICallNativeRepository _callNativeRepository;
 
         public CallDomainService(
             IServiceProvider serviceProvider,
-            ICallRepository callRepository, 
+            ICallRepository callRepository,
             ICallDetailRepository callDetailRepository,
             IOptionsSnapshot<AppConfiguration> appOptions,
-            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions)
+            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
+            IOrderRepository orderRepository,
+            ICallNativeRepository callNativeRepository)
         {
             _callRepository = callRepository;
             _callDetailRepository = callDetailRepository;
@@ -30,6 +38,9 @@ namespace Hotline.CallCenter.Calls
             {
                 _newRockDeviceManager = serviceProvider.GetRequiredService<INewRockDeviceManager>();
             }
+
+            _orderRepository = orderRepository;
+            _callNativeRepository = callNativeRepository;
         }
 
         #region 来电
@@ -114,7 +125,7 @@ namespace Hotline.CallCenter.Calls
         /// <param name="request"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task ExtToOuterAsync(ExtToOuterRequest request,CancellationToken cancellationToken)
+        public async Task ExtToOuterAsync(ExtToOuterRequest request, CancellationToken cancellationToken)
         {
             //var callModel = new Call()
             //{
@@ -164,7 +175,7 @@ namespace Hotline.CallCenter.Calls
         /// <param name="request"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task MonitorExt(MonitorExtRequest request,CancellationToken cancellationToken)
+        public async Task MonitorExt(MonitorExtRequest request, CancellationToken cancellationToken)
         {
             await _newRockDeviceManager.MonitorExtAsync(_callcenterOptions.Value.NewRock, request.firstTelNo, request.secondTelNo, cancellationToken);
         }
@@ -178,7 +189,7 @@ namespace Hotline.CallCenter.Calls
         /// <param name="request"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task MonitorExtToTalk(MonitorExtToTalkRequest request,CancellationToken cancellationToken)
+        public async Task MonitorExtToTalk(MonitorExtToTalkRequest request, CancellationToken cancellationToken)
         {
             await _newRockDeviceManager.MonitorExtToTalkAsync(_callcenterOptions.Value.NewRock, request.telNo, cancellationToken);
         }
@@ -192,7 +203,7 @@ namespace Hotline.CallCenter.Calls
         /// <param name="request"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task MonitorExtToListen(MonitorExtToListenRequest request,CancellationToken cancellationToken)
+        public async Task MonitorExtToListen(MonitorExtToListenRequest request, CancellationToken cancellationToken)
         {
             await _newRockDeviceManager.MonitorExtToListenAsync(_callcenterOptions.Value.NewRock, request.telNo, cancellationToken);
         }
@@ -210,12 +221,32 @@ namespace Hotline.CallCenter.Calls
         /// <param name="request"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task BargeinExt(BargeinExtRequest request,CancellationToken cancellationToken)
+        public async Task BargeinExt(BargeinExtRequest request, CancellationToken cancellationToken)
         {
             await _newRockDeviceManager.BargeinExtAsync(_callcenterOptions.Value.NewRock, request.firstTelNo, request.secondTelNo, cancellationToken);
         }
 
         #endregion
 
+
+        #region 电话评价
+
+        /// <summary>
+        /// 根据OrderId 获取电话评价
+        /// </summary>
+        /// <param name="orderId"></param>
+        /// <returns></returns>
+        public async Task<CallNative?> GetByOrderIdAsync(string orderId)
+        {
+            var callId = await _orderRepository.Queryable()
+                .Where(m => m.Id == orderId)
+                .Select(m => m.CallId)
+                .FirstAsync();
+            if (callId.IsNullOrEmpty())
+                return null;
+            return await _callNativeRepository.GetAsync(callId);
+        }
+
+        #endregion
     }
 }

+ 3 - 3
src/Hotline/CallCenter/Calls/CallNative.cs

@@ -17,7 +17,7 @@ namespace Hotline.CallCenter.Calls
     public class CallNative : CreationEntity
     {
         /// <summary>
-        /// 通话记录编号
+        ///  
         /// </summary>
         [SugarColumn(ColumnDescription = "通话记录编号")]
         public string CallNo { get; set; }
@@ -152,9 +152,9 @@ namespace Hotline.CallCenter.Calls
         public string AudioFile { get; set; }
 
         /// <summary>
-        /// 用户回复
+        /// 电话回复内容
         /// </summary>
-        [SugarColumn(ColumnDescription = "用户回复")]
+        [SugarColumn(ColumnDescription = "电话回复内容")]
         public string? ReplyTxt { get; set; }
 
         //public string? ExternalId { get; set; }

+ 25 - 0
src/Hotline/CallCenter/Calls/CallSatisfaction.cs

@@ -0,0 +1,25 @@
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Calls
+{
+    public class CallSatisfaction:CreationEntity
+    {
+        ///Id 和CallNative.Id一致   和Order.CallId 一致
+
+        /// <summary>
+        /// 兴唐通话记录编号(业务不用)
+        /// </summary>
+        public string CallNo { get; set; }
+
+        /// <summary>
+        /// 按键结果值: 1、2、3、4、5
+        /// 自贡:
+        ///     1. 非常满意;
+        ///     2. 满意;
+        ///     3. 一般;
+        ///     4. 不满意;
+        ///     5. 非常不满意;
+        /// </summary>
+        public string Result { get; set; }
+    }
+}

+ 9 - 0
src/Hotline/CallCenter/Calls/ICallDomainService.cs

@@ -110,5 +110,14 @@ namespace Hotline.CallCenter.Calls
         Task BargeinExt(BargeinExtRequest request, CancellationToken cancellationToken);
 
         #endregion
+
+        #region 电话评价
+        /// <summary>
+        /// 根据OrderId 获取电话评价
+        /// </summary>
+        /// <param name="orderId"></param>
+        /// <returns></returns>
+        Task<CallNative?> GetByOrderIdAsync(string orderId);
+        #endregion
     }
 }

+ 14 - 0
src/Hotline/CallCenter/Calls/ICallNativeRepository.cs

@@ -0,0 +1,14 @@
+using Hotline.CallCenter.Calls;
+using Hotline.Share.Enums.Order;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Repository.SqlSugar.CallCenter;
+public interface ICallNativeRepository : IRepository<CallNative>
+{
+    Task<int> UpdateReplyTxtAsync(string callId, string replyTxt);
+}

+ 20 - 0
src/Hotline/Orders/IOrderVisitDomainService.cs

@@ -0,0 +1,20 @@
+using Hotline.Share.Dtos.Push;
+
+namespace Hotline.Orders;
+public interface IOrderVisitDomainService
+{
+    /// <summary>
+    /// 根据用户短信或电话回复的内容转换成系统中对应的评价
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="replyTxt"></param>
+    /// <returns></returns>
+    T GetVisitEvaluateByReplyTxt<T>(string replyTxt);
+
+    /// <summary>
+    /// 收到用户回复的短信后回填回访信息
+    /// </summary>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    Task UpdateSmsReplyAsync(MessageDto data);
+}

+ 2 - 8
src/Hotline/Orders/IOrderVisitRepository.cs

@@ -1,13 +1,7 @@
-using XF.Domain.Repository;
+using Hotline.CallCenter.Calls;
+using XF.Domain.Repository;
 
 namespace Hotline.Orders;
 public interface IOrderVisitRepository : IRepository<OrderVisit>
 {
-    /// <summary>
-    /// 用户回访短信回复更新回访状态
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <param name="data"></param>
-    /// <returns></returns>
-    Task UpdateSmsReplyAsync(Share.Dtos.Push.PushReceiveMessageDto dto, Push.FWMessage.Message data);
 }

+ 6 - 0
src/Hotline/Orders/OrderPublish.cs

@@ -92,6 +92,12 @@ public class OrderPublish : FullStateEntity
     /// 备注
     /// </summary>
     public string? Remark { get; set; }
+
+    /// <summary>
+    /// 答复口径
+    /// </summary>
+    [SugarColumn(ColumnDataType = "varchar(8000)", IsNullable = true)]
+    public string? AnswerContent { get; set; }
     #endregion
 
     public bool? Resolve { get; set; }

+ 5 - 1
src/Hotline/Orders/OrderSendBackAudit.cs

@@ -1,4 +1,5 @@
-using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.File;
+using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Enums.Order;
 using SqlSugar;
 using System;
@@ -144,5 +145,8 @@ namespace Hotline.Orders
 		/// </summary>
 		[SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true, ColumnDescription = "流程退回发起用户角色ID")]
 		public List<string> WorkflowRoleIds { get; set; }
+
+		[SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
+		public List<FileJson>? FileJson { get; set; }
 	}
 }

+ 18 - 0
src/Hotline/Orders/OrderVisitDetail.cs

@@ -95,6 +95,24 @@ namespace Hotline.Orders
         /// </summary>
         public EVisitTarget VisitTarget { get; set; }
 
+        /// <summary>
+        /// 短信回访回填
+        /// </summary>
+        /// <param name="visitSatisfactionKv"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        public void ReplyBackfill(Kv visitSatisfactionKv)
+        {
+            this.OrgProcessingResults = visitSatisfactionKv;
+            this.OrgHandledAttitude = visitSatisfactionKv;
+        }
+
+        public void ReplyBackfill(ESeatEvaluate seatEvaluate, EVoiceEvaluate voiceEvaluate)
+        {
+            this.SeatEvaluate ??= seatEvaluate;
+            this.VoiceEvaluate ??= voiceEvaluate;
+        }
+    }
+
 		/// <summary>
 		/// 截至甄别时间
 		/// </summary>

+ 150 - 0
src/Hotline/Orders/OrderVisitDomainService.cs

@@ -0,0 +1,150 @@
+using Hotline.Caching.Interfaces;
+using Hotline.Push.FWMessage;
+using Hotline.Settings;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Push;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
+using Mapster;
+using Mapster.Utils;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Orders;
+public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependency
+{
+    private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+    private readonly ILogger<OrderVisitDomainService> _logger;
+    private readonly IRepository<Order> _orderRepository;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly IOrderVisitRepository _orderVisitRepository;
+
+
+    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository)
+    {
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+        _logger = logger;
+        _orderRepository = orderRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _orderVisitRepository = orderVisitRepository;
+    }
+
+    /// <summary>
+    /// 用户回访短信回复更新回访状态
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    public async Task UpdateSmsReplyAsync(MessageDto data)
+    {
+        _logger.LogInformation($"UpdateSmsReplyAsync 收到通知: {data.ToJson()}");
+        if (data.IsSmsReply == false || data.SmsReplyContent.IsNullOrEmpty() || data.ExternalId.IsNullOrEmpty()) return;
+
+        var orderVisit = await _orderVisitRepository.GetAsync(data.ExternalId!)
+             ?? throw new UserFriendlyException($"回访单不存在, visitId: {data.ExternalId} message: {data.ToJson()}");
+
+        if (orderVisit.VisitState == EVisitState.Visited)
+            throw new UserFriendlyException($"回访单已回访. visitId: {data.ExternalId}");
+
+        var replyTxt = data.SmsReplyContent!;
+        if (new string[] { "4", "5" }.Contains(replyTxt))
+        {
+            // “短信不满意待回访”状态下,由其他方式再次进行回访,回访方式需更新为最新的回访方式
+            // 故在此置为空
+            orderVisit.VisitType = null;
+        }
+        var visitSatisfactionKv = GetVisitEvaluateByReplyTxt<Kv>(replyTxt);
+        orderVisit.NowEvaluate = visitSatisfactionKv;
+
+        orderVisit.VisitTime = DateTime.Now;
+        orderVisit.VisitState = GetVisitEvaluateByReplyTxt<EVisitState>(replyTxt);
+        await _orderVisitRepository.UpdateAsync(orderVisit, ignoreNullColumns: false);
+
+        if (orderVisit.VisitState == EVisitState.Visited)
+        {
+            await _orderRepository.GetAsync(orderVisit.OrderId)
+                .Then(async order =>
+                {
+                    order!.Visited(replyTxt, GetVisitEvaluateByReplyTxt<string>(replyTxt));
+                    await _orderRepository.UpdateAsync(order);
+                });
+
+        }
+        var detailOrg = await _orderVisitDetailRepository.Queryable()
+            .Where(m => m.VisitId == orderVisit.Id && m.VisitTarget == EVisitTarget.Org)
+            .ToListAsync();
+        foreach (var item in detailOrg)
+        {
+            item.ReplyBackfill(visitSatisfactionKv);
+        }
+        await _orderVisitDetailRepository.UpdateRangeAsync(detailOrg);
+
+        await _orderVisitDetailRepository.Queryable()
+            .Where(m => m.VisitId == orderVisit.Id && m.VisitTarget == EVisitTarget.Seat)
+            .FirstAsync()
+            .Then(async detailSeat =>
+            {
+                detailSeat.ReplyBackfill(
+                    GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt),
+                    GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt)
+                    );
+                await _orderVisitDetailRepository.UpdateAsync(detailSeat);
+            });
+    }
+
+    /// <summary>
+    /// 根据用户短信或电话回复的内容转换成系统中对应的评价
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="replyTxt"></param>
+    /// <returns></returns>
+    /// <exception cref="UserFriendlyException"></exception>
+    public T GetVisitEvaluateByReplyTxt<T>(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"},
+            { "默认满意", $"默认满意|{EVisitState.Visited}|{ESeatEvaluate.DefaultSatisfied}|{EVoiceEvaluate.DefaultSatisfied}|4"},
+        };
+        replyTxt = replyTxt.Trim();
+        if (ReplyToEnumMap.TryGetValue(replyTxt, out var result) == false)
+        {
+            var m = ReplyToEnumMap.FirstOrDefault(item => item.Value.StartsWith(replyTxt));
+            replyTxt = m.Key;
+            result = m.Value;
+        }
+
+        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;
+    }
+}

+ 9 - 4
src/Hotline/Push/FWMessage/PushDomainService.cs

@@ -1,8 +1,11 @@
 using DotNetCore.CAP;
+using Hotline.EventBus;
 using Hotline.Orders;
+using Hotline.Push.Notifies;
 using Hotline.Share.Dtos.Push;
 using Hotline.Share.Dtos.SendSms;
 using Hotline.Share.Enums.Push;
+using Mapster;
 using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Http;
@@ -30,6 +33,8 @@ public class PushDomainService : IPushDomainService, IScopeDependency
     private readonly ICapPublisher _capPublisher;
     private readonly ILogger<PushDomainService> _logger;
     private readonly IOrderVisitRepository _orderVisitRepository;
+    private readonly Publisher _publisher;
+
 
     /// <summary>
     /// 
@@ -45,7 +50,8 @@ public class PushDomainService : IPushDomainService, IScopeDependency
         ICapPublisher capPublisher,
         ILogger<PushDomainService> logger
 ,
-        IOrderVisitRepository orderVisitRepository)
+        IOrderVisitRepository orderVisitRepository,
+        Publisher publisher)
     {
         _messageRepository = messageRepository;
         _mapper = mapper;
@@ -53,6 +59,7 @@ public class PushDomainService : IPushDomainService, IScopeDependency
         _capPublisher = capPublisher;
         _logger = logger;
         _orderVisitRepository = orderVisitRepository;
+        _publisher = publisher;
     }
     #endregion
 
@@ -150,12 +157,10 @@ public class PushDomainService : IPushDomainService, IScopeDependency
                 data.IsSmsReply = dto.IsSmsReply;
                 data.SmsReplyTime = Convert.ToDateTime(dto.SmsReplyTime);
                 data.SmsReplyContent = dto.SmsReplyContent;
-
-                if (data.PushBusiness == EPushBusiness.VisitSms)
-                    await _orderVisitRepository.UpdateSmsReplyAsync(dto, data);
             }
             data.Reason = dto.Reason;
             await _messageRepository.UpdateAsync(data, cancellation);
+            await _publisher.PublishAsync(data.Adapt<ReceiveMessageNotify>(), PublishStrategy.ParallelNoWait, cancellation);
         }
     }
 

+ 6 - 0
src/Hotline/Push/Notifies/PushMessageNotify.cs

@@ -4,4 +4,10 @@ using MediatR;
 namespace Hotline.Push.Notifies
 {
     public record PushMessageNotify(MessageDto NotifyDto) : INotification;
+
+    /// <summary>
+    /// 收到短信
+    /// </summary>
+    /// <param name="NotifyDto"></param>
+    public record ReceiveMessageNotify(MessageDto NotifyDto) : INotification;
 }

+ 12 - 1
src/Hotline/Settings/SettingConstants.cs

@@ -1,4 +1,5 @@
-using Hotline.Users;
+using Hotline.Share.Enums.Order;
+using Hotline.Users;
 
 namespace Hotline.Settings
 {
@@ -524,5 +525,15 @@ namespace Hotline.Settings
 		#endregion
 
         
+
+        /// <summary>
+        /// 定量查询返回数据条数上限
+        /// </summary>
+        public const string FixedQueryCount = "FixedQueryCount";
+
+        /// <summary>
+        /// 旧数据通知公告知识库附件地址	
+        /// </summary>
+        public const string OldFilesUrls = "OldFilesUrls";
     }
 }

+ 4 - 3
src/Hotline/Settings/TimeLimitDomain/ExpireTimeLimitBase.cs

@@ -76,7 +76,7 @@ public abstract class ExpireTimeLimitBase
         return date;
     }
 
-    public virtual async Task<DateTime> WorkDay_ZG(DateTime date)
+    public virtual async Task<DateTime> GetWorkDay(DateTime date)
     {
 	 
 		if (await IsWorkDay(date))
@@ -105,7 +105,7 @@ public abstract class ExpireTimeLimitBase
 		    {
 			    date = date.AddDays(1);
 		    }
-			date = DateTime.Parse(date.ToShortDateString() + "18:00");
+			date = DateTime.Parse(date.ToShortDateString() + " 18:00");
 		}
 	    return date;
     }
@@ -152,7 +152,8 @@ public abstract class ExpireTimeLimitBase
             EFlowDirection.CenterToOrg => GetOrderTimeLimitConfig(order.AcceptTypeCode),
             EFlowDirection.OrgToCenter => GetOrderTimeLimitConfig(),
             EFlowDirection.CenterToCenter => GetOrderTimeLimitConfig(order.AcceptTypeCode),
-            _ => throw new ArgumentOutOfRangeException(nameof(flowDirection), flowDirection, null)
+            EFlowDirection.FiledToOrg => GetOrderTimeLimitConfig(order.AcceptTypeCode),
+			_ => throw new ArgumentOutOfRangeException(nameof(flowDirection), flowDirection, null)
         };
 
         return await CalcEndTime(beginTime, timeConfig, order.AcceptTypeCode);

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

@@ -27,7 +27,7 @@ public interface ICalcExpireTime
     Task<int> CalcWorkTime(DateTime beginTime, DateTime endTime, bool isCenter);
     Task<int> CalcWorkTimeEx(DateTime beginTime, DateTime endTime, bool isCenter);
     Task<DateTime> WorkDay(DateTime now);
-    Task<DateTime> WorkDay_ZG(DateTime now);
+    Task<DateTime> GetWorkDay(DateTime now);
 
 	Task<TimeResult> CalcEndTime(DateTime beginTime, ETimeType timeType, int timeValue, int Percentage, int PercentageOne);
 

+ 39 - 0
src/XingTang.Sdk/XingtangSatisfaction.cs

@@ -0,0 +1,39 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace XingTang.Sdk
+{
+    [SugarTable("call_cti_satisfaction")]
+    public class XingtangSatisfaction
+    {
+        [SugarColumn(IsPrimaryKey = true)]
+        public int Id { get; set; }
+        
+        /// <summary>
+        /// 通话ID
+        /// </summary>
+        [SugarColumn(ColumnName = "callguid")]
+        public string CallNo { get; set; }
+        
+        /// <summary>
+        /// 评价结果
+        /// </summary>
+        public string Result { get; set; }
+
+
+        #region 自建
+
+        public bool IsSync { get; set; }
+
+        public int Tries { get; set; }
+
+        [SugarColumn(IsEnableUpdateVersionValidation = true)]
+        public string Ver { get; set; }
+
+        #endregion
+    }
+}