Преглед на файлове

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

田爽 преди 5 месеца
родител
ревизия
9795303fc2

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

@@ -1041,7 +1041,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)
+                .Where(x=> !string.IsNullOrEmpty(x.Order.Contact) && SqlFunc.Length(x.Order.Contact)==11 && !x.Order.Contact.Contains("*"))
                 .ToListAsync();
             return _mapper.Map<IReadOnlyList<OrderVisitDto>>(items);
         }

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

@@ -699,6 +699,18 @@ namespace Hotline.Api.Controllers
 
             var dataDtos = _mapper.Map<ICollection<TrCallDto>>(data);
 
+            dto.ColumnInfos.ForEach(x =>
+            {
+                if (x.Prop=="cpn")
+                {
+                    x.Prop ="CPN";
+                }
+                if (x.Prop=="cdpn")
+                {
+                    x.Prop = "CDPN";
+                }
+            });
+
             dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
 
             var dtos = dataDtos

+ 2 - 0
src/Hotline.Application.Tests/Application/SystemSettingCacheManagerTest.cs

@@ -33,5 +33,7 @@ public class SystemSettingCacheManagerTest
         delaySecond.ShouldBe(172800);
         var delaySecondEntity = _systemSettingRepository.GetAsync("08dc0681-a6d2-4ce7-877d-db65f846d523");
         delaySecondEntity.ShouldNotBeNull("DefaultVisitSmsDelaySecond 系统设置为NULL");
+
+        _systemSettingCacheManager.CallSyncUnPushDateTime.ShouldBe(DateTime.Parse("2024/11/19 18:08:00"));
     }
 }

+ 97 - 0
src/Hotline.Application.Tests/Application/XingTangCallsSyncJobTest.cs

@@ -0,0 +1,97 @@
+using AutoFixture;
+using Hotline.Application.Jobs;
+using Hotline.Repository.SqlSugar;
+using Quartz;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XingTang.Sdk;
+
+namespace Hotline.Application.Tests.Application;
+public class XingTangCallsSyncJobTest
+{
+    private readonly XingTangCallsSyncJob xingTangCallsSyncJob;
+    private readonly ISqlSugarClient _db;
+    private readonly IFixture _fixture;
+
+    public XingTangCallsSyncJobTest(XingTangCallsSyncJob xingTangCallsSyncJob, ISugarUnitOfWork<XingTangDbContext> uow)
+    {
+        this.xingTangCallsSyncJob = xingTangCallsSyncJob;
+        _db = uow.Db;
+        _fixture = new Fixture();
+    }
+
+    [Fact]
+    public async Task Handler_Test()
+    {
+        var second = new Random().Next(0, 60);
+        var inDto = _fixture.Create<XingtangCall>();
+        inDto.CallStartTime = DateTime.Parse("2024/11/19 18:07:" + second);
+        inDto.IsSync = false;
+        inDto.Tries = 0;
+        inDto.CallGuid = DateTime.Now.ToString("yyyyMMddhhmmss") + "FLOW";
+        inDto.Ver = string.Empty;
+
+        var inDto2 = _fixture.Create<XingtangCall>();
+        inDto2.CallStartTime = DateTime.Now;
+        inDto2.CallGuid = DateTime.Now.ToString("yyyyMMddhhmmss") + "FLOW";
+        inDto2.IsSync = false;
+        inDto2.Tries = 0;
+        inDto2.Ver = string.Empty;
+
+        await _db.Insertable(inDto).ExecuteCommandAsync();
+        await _db.Insertable(inDto2).ExecuteCommandAsync();
+
+        await xingTangCallsSyncJob.Execute(new JobContext());
+    }
+}
+
+public class JobContext : IJobExecutionContext
+{
+    public IScheduler Scheduler => throw new NotImplementedException();
+
+    public ITrigger Trigger => throw new NotImplementedException();
+
+    public ICalendar? Calendar => throw new NotImplementedException();
+
+    public bool Recovering => throw new NotImplementedException();
+
+    public TriggerKey RecoveringTriggerKey => throw new NotImplementedException();
+
+    public int RefireCount => throw new NotImplementedException();
+
+    public JobDataMap MergedJobDataMap => throw new NotImplementedException();
+
+    public IJobDetail JobDetail => throw new NotImplementedException();
+
+    public IJob JobInstance => throw new NotImplementedException();
+
+    public DateTimeOffset FireTimeUtc => throw new NotImplementedException();
+
+    public DateTimeOffset? ScheduledFireTimeUtc => throw new NotImplementedException();
+
+    public DateTimeOffset? PreviousFireTimeUtc => throw new NotImplementedException();
+
+    public DateTimeOffset? NextFireTimeUtc => throw new NotImplementedException();
+
+    public string FireInstanceId => throw new NotImplementedException();
+
+    public object? Result { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
+
+    public TimeSpan JobRunTime => throw new NotImplementedException();
+
+    public CancellationToken CancellationToken => CancellationToken.None;
+
+    public object? Get(object key)
+    {
+        throw new NotImplementedException();
+    }
+
+    public void Put(object key, object objectValue)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 44 - 7
src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs

@@ -1,4 +1,8 @@
-using Hotline.EventBus;
+using Hotline.Api.Controllers;
+using Hotline.Application.Tests.Mock;
+using Hotline.EventBus;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
 using Hotline.Orders;
 using Hotline.Push.FWMessage;
 using Hotline.Push.Notifies;
@@ -7,29 +11,34 @@ using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Tools;
+using Hotline.Users;
 using Mapster;
+using Microsoft.Extensions.DependencyInjection;
 using Shouldly;
 using XF.Domain.Repository;
 
 namespace Hotline.Application.Tests.Domain;
-public class OrderVisitDomainServiceTest
+public class OrderVisitDomainServiceTest : TestBase
 {
     private readonly IOrderVisitDomainService _orderVisitDomainService;
     private readonly IOrderVisitRepository _orderVisitRepository;
     private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
     private readonly Publisher _publisher;
     private readonly IOrderRepository _orderRepository;
+    private readonly OrderServiceMock _orderServiceMock;
 
-
-    public OrderVisitDomainServiceTest(IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher, IOrderRepository orderRepository)
+    public OrderVisitDomainServiceTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher, IOrderRepository orderRepository, OrderServiceMock orderServiceMock) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository)
     {
         _orderVisitDomainService = orderVisitDomainService;
         _orderVisitRepository = orderVisitRepository;
         _orderVisitDetailRepository = orderVisitDetailRepository;
         _publisher = publisher;
         _orderRepository = orderRepository;
+        _orderServiceMock = orderServiceMock;
     }
 
+
+
     //[Fact]
     public async Task UpdateSmsReplyDefault_Test()
     {
@@ -95,6 +104,17 @@ public class OrderVisitDomainServiceTest
     [InlineData("非常不满意", "SMSUnsatisfied", "2", "不满意")]
     public async Task UpdateSmsReply_Test(string content, string visitState, string orgResuktKey, string orgResuktValue)
     {
+        SetZuoXi();
+        var order = _orderServiceMock.CreateOrder()
+            .办理到一级部门()
+            .办理到二级部门(Set一级部门)
+            .办理一级部门汇总(Set二级部门)
+            .办理到归档(Set一级部门)
+            .发布工单(SetZuoXi)
+            .发送回访短信(SetZuoXi)
+            .GetCreateResult();
+        order.Id.ShouldNotBeNull();
+
         var visit = await _orderVisitRepository.Queryable()
             .Includes(m => m.Order)
             .Where(m => m.VisitState == EVisitState.SMSVisiting)
@@ -108,21 +128,38 @@ public class OrderVisitDomainServiceTest
         await _orderVisitDomainService.UpdateSmsReplyAsync(message);
         visit = _orderVisitRepository.Get(visit.Id);
         visit.VisitState.ShouldBe(visitState.ToEnum<EVisitState>());
+        visit.NowEvaluate.ShouldNotBeNull();
         visit.NowEvaluate.Key.ShouldBe(orgResuktKey);
         visit.NowEvaluate.Value.ShouldBe(orgResuktValue);
-
+        var orderEntity = await _orderRepository.GetAsync(order.Id);
+        orderEntity.ShouldNotBeNull();
         if (content == "4" || content == "5" || content == "不满意" || content == "非常不满意")
+        {
             visit.VisitType.ShouldBeNull();
+            orderEntity.Status.ShouldNotBe(EOrderStatus.Visited);
+        }
+        else 
+        {
+            orderEntity.Status.ShouldBe(EOrderStatus.Visited);
+        }
 
         await _orderVisitDetailRepository.Queryable()
             .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Org)
             .FirstAsync()
-            .Then(async org =>
+            .Then(org =>
             {
+                org.OrgProcessingResults.ShouldNotBeNull();
                 org.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
                 org.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
+
+                orderEntity.OrgProcessingResults.ShouldNotBeNull();
+                orderEntity.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
+                orderEntity.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
+
+                org.OrgHandledAttitude.ShouldNotBeNull();
                 org.OrgHandledAttitude.Key.ShouldBe(orgResuktKey);
                 org.OrgHandledAttitude.Value.ShouldBe(orgResuktValue);
+                return Task.CompletedTask;
             });
     }
 
@@ -132,7 +169,7 @@ public class OrderVisitDomainServiceTest
     [InlineData("3", "一般", "Visited", "Normal", "Normal", "满意|4")]
     [InlineData("4", "不满意", "SMSUnsatisfied", "NoSatisfied", "NoSatisfied", "不满意|2")]
     [InlineData("5", "非常不满意", "SMSUnsatisfied", "NoSatisfied", "VeryNoSatisfied", "不满意|2")]
-    [InlineData("默认满意", "默认满意", "Visited", "DefaultSatisfied", "DefaultSatisfied", "满意|4")]
+    [InlineData("默认满意", "默认满意", "Visited", "DefaultSatisfied", "DefaultSatisfied", "默认满意|0")]
     public void GetVisitEvaluateByReplyTxt_Test(string replyTxt, string assertReplyTxt, string visitState, string seatEvaluate, string voiceEvaluate, string kv)
     {
         var replyString = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<string>(replyTxt);

+ 45 - 1
src/Hotline.Application.Tests/Mock/OrderServiceMock.cs

@@ -6,6 +6,9 @@ using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Tools;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -20,11 +23,17 @@ public class OrderServiceMock
     private CreateOrderOutDto CreateOrderOutDto;
     private AddOrderDto AddOrderDto;
     private readonly IRepository<Order> _orderRepository;
+    private readonly IOrderVisitRepository _orderVisitRepository;
 
-    public OrderServiceMock(OrderController orderController, IRepository<Order> orderRepository)
+    public OrderServiceMock(OrderController orderController, IRepository<Order> orderRepository, IOrderVisitRepository orderVisitRepository)
     {
         _orderController = orderController;
+        _orderController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
         _orderRepository = orderRepository;
+        _orderVisitRepository = orderVisitRepository;
     }
 
     public OrderServiceMock CreateOrder(string callId = "")
@@ -237,4 +246,39 @@ public class OrderServiceMock
         _orderController.Handle(handleDto).GetAwaiter().GetResult();
         return this;
     }
+
+    public OrderServiceMock 发布工单(Action aciton = null)
+    {
+        aciton?.Invoke();
+        var baseData = _orderController.PublishOrderPageBase(CreateOrderOutDto.Id).GetAwaiter().GetResult();
+        var inDto = new PublishOrderDto
+        { 
+            Id = CreateOrderOutDto.Id,
+            PublishState = false,
+            ArrangeTitle = baseData.OrderTitle,
+            ArrangeContent = baseData.Content,
+            ArrangeOpinion = baseData.ActualOpinion,
+            IdNames = [baseData.ActualHandleOrgName],
+            ProPublishState = false,
+            Resolve = true
+        };
+
+        _orderController.PublishOrder(inDto).GetAwaiter().GetResult();
+        return this;
+    }
+
+    public OrderServiceMock 发送回访短信(Action aciton = null)
+    {
+        aciton?.Invoke();
+        var id = _orderVisitRepository.Queryable()
+            .Where(m => m.No == CreateOrderOutDto.No)
+            .Select(m => m.Id)
+            .First();
+        var inDto = new VisitSmsInDto
+        {
+            Ids = [id]
+        };
+        _orderController.VisitPushSMSAsync(inDto).GetAwaiter().GetResult();
+        return this;
+    }
 }

+ 3 - 0
src/Hotline.Application.Tests/Startup.cs

@@ -40,6 +40,7 @@ using Hotline.Application.Tests.Infrastructure;
 using Hotline.Authentications;
 using Hotline.FlowEngine.Notifications;
 using Hotline.Application.Handlers.FlowEngine;
+using Hotline.Application.Jobs;
 
 namespace Hotline.Application.Tests;
 public class Startup
@@ -157,6 +158,8 @@ public class Startup
             services.AddScoped<XingTangCallApplication>();
             services.AddScoped<OrderServiceMock>();
             services.AddScoped<KnowledgeServiceMock>();
+            services.AddScoped<XingTangCallsSyncJob>();
+            services.AddXingTangDb(callCenterConfiguration.XingTang);
             //ServiceLocator.Instance = services.BuildServiceProvider();
         }
 

+ 26 - 8
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -435,7 +435,6 @@ public abstract class DefaultCallApplication : ICallApplication
         }
     }
 
-
     /// <summary>
     /// 保存回访详情时发送延迟消息同步通话记录
     /// 如果回访通话记录有多条, 需要关联通话时长最长的那条
@@ -449,9 +448,14 @@ public abstract class DefaultCallApplication : ICallApplication
             .Where(m => m.CallNo == dto.CallId && string.IsNullOrEmpty(m.AudioFile) == false)
             .OrderByDescending(m => m.Duration)
             .Select(m => m.Id)
-            .FirstAsync();
+            .FirstAsync(cancellationToken);
         if (callId == null) return;
         visit.CallId = callId;
+
+        await _callIdRelationRepository.Updateable()
+            .SetColumns(m => m.CallId == callId)
+            .Where(m => m.Id == dto.CallId)
+            .ExecuteCommandAsync(cancellationToken);
         await _orderVisitRepository.UpdateAsync(visit);
     }
 
@@ -512,24 +516,38 @@ public abstract class DefaultCallApplication : ICallApplication
             .Where(m => m.CallNo == callNo)
             .ToListAsync(cancellationToken);
 
-        // 只有一条通话记录, 不处理
-        if (callNative.Count < 2) return;
-
         var call = callNative.Where(m => m.Direction == ECallDirection.In)
             .OrderByDescending(m => m.Duration)
             .First();
 
+        // 只有一条通话记录, 不处理
         // 需要更新的callId 和 order.callId 相同, 不处理
-        if (callId == call.Id) return;
+        if (callNative.Count < 2 || callId == call.Id)
+        {
+            // 推省上
+            await _capPublisher.PublishAsync(EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto()
+            {
+                Order = _orderRepository.GetAsync(orderId, cancellationToken).Adapt<OrderDto>(),
+                TrCallRecordDto = call.Adapt<TrCallDto>()
+            }, cancellationToken: cancellationToken);
+            return;
+        }
 
         await _orderRepository.Updateable()
             .SetColumns(m => m.CallId == call.Id)
             .Where(m => m.Id == orderId)
             .ExecuteCommandAsync(cancellationToken);
+
+        await _callIdRelationRepository.Updateable()
+            .SetColumns(m => m.CallId == call.Id)
+            .Where(m => m.Id == callNo)
+            .ExecuteCommandAsync(cancellationToken);
+
         // 推省上
-        await _capPublisher.PublishAsync(EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto() {
+        await _capPublisher.PublishAsync(EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto()
+        {
             Order = _orderRepository.GetAsync(orderId, cancellationToken).Adapt<OrderDto>(),
-            TrCallRecordDto = call.Adapt<TrCallDto>() 
+            TrCallRecordDto = call.Adapt<TrCallDto>()
         }, cancellationToken: cancellationToken);
         _systemLogRepository.Add("延迟更新工单通话", orderId, $"原CallId: {callId}, 更新CallId: {call.Id}", status: 1);
     }

+ 13 - 5
src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs

@@ -15,6 +15,9 @@ using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Enums.CallCenter;
 using System.Dynamic;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Caching.Interfaces;
+using Mapster;
+using Hotline.Share.Mq;
 
 namespace Hotline.Application.Jobs
 {
@@ -30,6 +33,7 @@ namespace Hotline.Application.Jobs
         private readonly IMapper _mapper;
         private readonly ILogger<XingTangCallsSyncJob> _logger;
         private readonly ISqlSugarClient _db;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
 
         public XingTangCallsSyncJob(
             ISugarUnitOfWork<XingTangDbContext> uow,
@@ -38,7 +42,8 @@ namespace Hotline.Application.Jobs
             ICallApplication callApplication,
             ICapPublisher capPublisher,
             IMapper mapper,
-            ILogger<XingTangCallsSyncJob> logger)
+            ILogger<XingTangCallsSyncJob> logger,
+            ISystemSettingCacheManager systemSettingCacheManager)
         {
             _callRepository = callRepository;
             _userRepository = userRepository;
@@ -47,6 +52,7 @@ namespace Hotline.Application.Jobs
             _mapper = mapper;
             _logger = logger;
             _db = uow.Db;
+            _systemSettingCacheManager = systemSettingCacheManager;
         }
 
         public async Task Execute(IJobExecutionContext context)
@@ -116,11 +122,13 @@ namespace Hotline.Application.Jobs
 
                 await _callRepository.AddRangeAsync(calls, context.CancellationToken);
 
-                //推省上
+                // 开关
+                var unPushTime = _systemSettingCacheManager.CallSyncUnPushDateTime;
+                calls = calls.Where(m => m.BeginIvrTime >= unPushTime).ToList();
                 if (calls.Any())
                 {
-                    var callIns = _mapper.Map<List<CallNativeDto>>(calls);
-                    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallAdd, callIns);
+                    //推省上
+                    await _capPublisher.PublishAsync(EventNames.HotlineCallAdd, calls.Adapt<List<CallNativeDto>>());
                 }
                 ////todo
                 //var callIns = calls.Where(d => d.Direction == ECallDirection.In).ToList();
@@ -178,4 +186,4 @@ namespace Hotline.Application.Jobs
             //_logger.LogInformation($"{nameof(XingTangCallsSyncJob)} disposed");
         }
     }
-}
+}

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

@@ -942,6 +942,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         await _orderVisitedDetailRepository.UpdateRangeAsync(visit.OrderVisitDetails, cancellationToken);
         await _orderRepository.UpdateAsync(visit.Order, cancellationToken);
         var orderDto = _mapper.Map<OrderDto>(visit.Order);
+        await _publisher.PublishAsync(new UpdateOrderVisitNotify(visit.Adapt<OrderVisitNotifyDto>()), cancellationToken);
         if (first != null)
         {
             //推省上
@@ -1074,11 +1075,13 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
 
         query = query.Includes(x => x.OrderScreens);
-        if (!_appOptions.Value.IsYiBin)
-        {
-            query = query.Includes(x => x.OrderVisits.Where(m => m.VisitState == EVisitState.Visited).ToList())
-         .Includes(x => x.OrderVisits.Where(m => m.VisitState == EVisitState.Visited).ToList(), ovd => ovd.OrderVisitDetails);
-        }
+
+        //if (!_appOptions.Value.IsYiBin)
+        //{
+        //    query = query.Includes(x => x.OrderVisits.Where(m => m.VisitState == EVisitState.Visited).ToList())
+        // .Includes(x => x.OrderVisits.Where(m => m.VisitState == EVisitState.Visited).ToList(), ovd => ovd.OrderVisitDetails);
+        //}
+
         query = query
          .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!)) //标题
          .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), d => d.ProvinceNo.Contains(dto.ProvinceNo)) //省本地编号

+ 40 - 0
src/Hotline.Application/Orders/OrderVisitHandler/OrderVisitUpdateHandler.cs

@@ -0,0 +1,40 @@
+using DotNetCore.CAP;
+using Hotline.Application.Quality;
+using Hotline.Orders;
+using Hotline.Orders.Notifications;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Quality;
+using Hotline.Users;
+using Mapster;
+using MediatR;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Orders.OrderVisitHandler;
+public class OrderVisitUpdateHandler : INotificationHandler<UpdateOrderVisitNotify>
+{
+    private readonly IOrderRepository _orderRepository;
+    private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+
+    public OrderVisitUpdateHandler(IOrderRepository orderRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository)
+    {
+        _orderRepository = orderRepository;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+    }
+
+    public async Task Handle(UpdateOrderVisitNotify notification, CancellationToken cancellationToken)
+    {
+        var visit = notification.orderVisit;
+
+        var orgDetail = await _orderVisitDetailRepository.Queryable()
+            .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Org).FirstAsync(cancellationToken);
+        var seatDetail = await _orderVisitDetailRepository.Queryable()
+            .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Seat).FirstAsync(cancellationToken);
+
+        await _orderRepository.Updateable()
+            .SetColumns(m => m.OrgProcessingResults == orgDetail.OrgProcessingResults)
+            .SetColumns(m => m.SeatEvaluate == seatDetail.SeatEvaluate)
+            .Where(m => m.Id == visit.OrderId)
+            .ExecuteCommandAsync(cancellationToken);
+    }
+}

+ 50 - 41
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -166,6 +166,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public double? CenterToOrgHandleDurationWorkday { get; set; }
 
+        /// <summary>
+        /// 第一次评价结果代码
+        /// </summary>
+        public string? FirstVisitResultCode { get; set; }        
+
         #region 实际办理信息(节点,部门,意见)
 
         /// <summary>
@@ -857,57 +862,61 @@ namespace Hotline.Share.Dtos.Order
 
         #region 回访信息
 
+        ///// <summary>
+        ///// 部门满意度
+        ///// </summary>
+        //public Kv? OrgEvaluate { get; set; }
+        //{
+        //    get
+        //    {
+        //        if (this.OrderVisits == null) return null;
+        //        var visitEntity = this.OrderVisits
+        //            .OrderByDescending(m => m.CreationTime)
+        //            .FirstOrDefault();
+        //        if (visitEntity == null) return null;
+        //        return visitEntity.NowEvaluate;
+        //    }
+        //}
+
         /// <summary>
         /// 部门满意度
         /// </summary>
-        public Kv? OrgEvaluate
-        {
-            get
-            {
-                if (this.OrderVisits == null) return null;
-                var visitEntity = this.OrderVisits
-                    .OrderByDescending(m => m.CreationTime)
-                    .FirstOrDefault();
-                if (visitEntity == null) return null;
-                return visitEntity.NowEvaluate;
-            }
-        }
-
+        public Kv? OrgProcessingResults { get; set; }
 
         /// <summary>
         /// 部门满意度
         /// </summary>
-        public string? OrgEvaluateValue
-        {
-            get
-            {
-                if (this.OrderVisits == null) return null;
-                var visitEntity = this.OrderVisits
-                    .OrderByDescending(m => m.CreationTime)
-                    .FirstOrDefault();
-                if (visitEntity == null) return null;
-                var now = visitEntity.NowEvaluate;
-                if (now == null) return null;
-                return now.Value;
-            }
-        }
+        public string? OrgEvaluateValue => OrgProcessingResults != null ? OrgProcessingResults.Value : "";
+        //{
+        //    get
+        //    {
+        //        if (this.OrderVisits == null) return null;
+        //        var visitEntity = this.OrderVisits
+        //            .OrderByDescending(m => m.CreationTime)
+        //            .FirstOrDefault();
+        //        if (visitEntity == null) return null;
+        //        var now = visitEntity.NowEvaluate;
+        //        if (now == null) return null;
+        //        return now.Value;
+        //    }
+        //}
 
         /// <summary>
         /// 坐席满意度
         /// </summary>
-        public ESeatEvaluate? SeatEvaluate
-        {
-            get
-            {
-                if (this.OrderVisits == null) return null;
-                var visitEntity = this.OrderVisits
-                    .OrderByDescending(m => m.CreationTime)
-                    .FirstOrDefault();
-                if (visitEntity == null) return null;
-                return visitEntity.OrderVisitDetails.Where(m => m.VisitTarget == EVisitTarget.Seat)
-                    .FirstOrDefault()?.SeatEvaluate;
-            }
-        }
+        public ESeatEvaluate? SeatEvaluate { get; set; }
+        //{
+        //    get
+        //    {
+        //        if (this.OrderVisits == null) return null;
+        //        var visitEntity = this.OrderVisits
+        //            .OrderByDescending(m => m.CreationTime)
+        //            .FirstOrDefault();
+        //        if (visitEntity == null) return null;
+        //        return visitEntity.OrderVisitDetails.Where(m => m.VisitTarget == EVisitTarget.Seat)
+        //            .FirstOrDefault()?.SeatEvaluate;
+        //    }
+        //}
 
         public string? SeatEvaluateTxt => SeatEvaluate?.GetDescription();
 
@@ -1621,4 +1630,4 @@ namespace Hotline.Share.Dtos.Order
                                (Status is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept
                                    or EOrderStatus.HandOverToUnAccept);
     }
-}
+}

+ 54 - 8
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -295,13 +295,14 @@ namespace Hotline.Share.Dtos.Order
         public string UserId { get; set; }
     }
 
-    public record VisitPutThroughDto { 
-    
+    public record VisitPutThroughDto
+    {
+
         public string id { get; set; }
-	}
+    }
 
 
-	public class VisitSmsInDto
+    public class VisitSmsInDto
     {
         [Required]
         public List<string> Ids { get; set; }
@@ -661,8 +662,8 @@ namespace Hotline.Share.Dtos.Order
         SMSUnsatisfied = 41,
 
         [Description("未接通")]
-		NoPutThrough =51,
-	}
+        NoPutThrough = 51,
+    }
 
 
 
@@ -934,7 +935,7 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public DateTime? ScreenByEndTime { get; set; }
 
-	}
+    }
 
     /// <summary>
     /// 回访详情的历史记录
@@ -1097,7 +1098,6 @@ namespace Hotline.Share.Dtos.Order
 
     }
 
-
     public class OrderVisitProvinceDto
     {
         public string ProvinceNo { get; set; }
@@ -1110,7 +1110,53 @@ namespace Hotline.Share.Dtos.Order
         public string VisitRemark { get; set; }
 
         public Kv VisitResult { get; set; }
+    }
+
+    /// <summary>
+    /// 回访更新事件载荷
+    /// </summary>
+    public class OrderVisitNotifyDto
+    {
+        /// <summary>
+        /// Id
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string OrderId { get; set; }
+
+        /// <summary>
+        /// 回访详情
+        /// </summary>
+        public List<OrderVisitDetailDto> OrderVisitDetails { get; set; }
 
+        /// <summary>
+        /// 回访状态
+        /// </summary>
+        public EVisitState VisitState { get; set; }
 
+        /// <summary>
+        /// 回访方式
+        /// </summary>
+        public EVisitType? VisitType { get; set; }
+
+        /// <summary>
+        /// 回访人
+        /// </summary>
+        public string? EmployeeId { get; set; }
+
+        /// <summary>
+        /// 回访时间
+        /// </summary>
+        public DateTime? VisitTime { get; set; }
+
+        public string CreatorName { get; set; }
+
+        /// <summary>
+        /// 发布时间
+        /// </summary>
+        public DateTime PublishTime { get; set; }
     }
 }

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

@@ -38,5 +38,10 @@ namespace Hotline.Caching.Interfaces
         /// 如果超过这个时间就更新回访为默认满意
         /// </summary>
         int DefaultVisitSmsDelaySecond { get; }
+
+        /// <summary>
+        /// 通话记录同步时不推送省上的时间(默认:2024/11/19 18:08)
+        /// </summary>
+        DateTime CallSyncUnPushDateTime { get; }
     }
 }

+ 20 - 6
src/Hotline/Caching/Services/SystemSettingCacheManager.cs

@@ -58,7 +58,8 @@ namespace Hotline.Caching.Services
                         Code = code,
                         SettingName = name,
                         SettingValue = [defaultValue],
-                        Remark = remark
+                        Remark = remark,
+                        CreatorName = "自动创建",
                     };
                     if (id.NotNullOrEmpty())
                         entity.Id = id;
@@ -70,15 +71,22 @@ namespace Hotline.Caching.Services
 
         public T GetOrDefault<T>(string id, string code, string name, T defaultValue, string remark) where T : struct
         {
-            var value = GetOrSetDefault(code, name, defaultValue.ToString(), remark, id);
-
             try
             {
-                return (T)Convert.ChangeType(value, typeof(T));
+                var value = GetOrSetDefault(code, name, defaultValue.ToString(), remark, id);
+
+                try
+                {
+                    return (T)Convert.ChangeType(value, typeof(T));
+                }
+                catch (InvalidCastException e)
+                {
+                    _logger.LogError($"无法将 '{value}' 转换为类型 {typeof(T).Name}!已返回默认值 {defaultValue}. {e.Message}");
+                }
             }
-            catch (InvalidCastException e)
+            catch (Exception e)
             {
-                _logger.LogError($"无法将 '{value}' 转换为类型 {typeof(T).Name}!已返回默认值 {defaultValue}. {e.Message}");
+                _logger.LogError(e.Message + e.StackTrace);
             }
             return defaultValue;
         }
@@ -153,5 +161,11 @@ namespace Hotline.Caching.Services
             GetOrDefault<int>("08dc0681-a6d2-4ce7-877d-db65f846d523", SettingConstants.DefaultVisitSmsDelaySecond, "发送回访短信后等候市民回复短信的时间(秒)", 172800,
                 "发送回访短信后等候市民回复短信的时间(秒), 如果超过这个时间就更新回访为默认满意. 默认 172800.");
 
+        /// <summary>
+        /// 通话记录同步时不推送省上的时间(默认:2024/11/19 18:08)
+        /// </summary>
+        public DateTime CallSyncUnPushDateTime =>
+            GetOrDefault("08dc9bed-41da-4074-8289-e753e8fa8ea4", SettingConstants.CallSyncUnPushDateTime, "通话记录同步时不推送省上的时间(默认:2024/11/19 18:08)",
+                DateTime.Parse("2024/11/19 18:08:00"), "通话记录同步时不推送省上的时间(默认:2024/11/19 18:08), 在兴唐数据库 callstarttime 字段时间小于设置 (默认:2024/11/19 18:08) 的时间都不会推送给省上");
     }
 }

+ 10 - 0
src/Hotline/Orders/Notifications/UpdateOrderVisitNotify.cs

@@ -0,0 +1,10 @@
+using Hotline.Share.Dtos.Order;
+using MediatR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Orders.Notifications;
+public record UpdateOrderVisitNotify(OrderVisitNotifyDto orderVisit) : INotification;

+ 14 - 0
src/Hotline/Orders/Order.cs

@@ -2,6 +2,7 @@
 using Hotline.FlowEngine.Workflows;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
@@ -1059,6 +1060,19 @@ namespace Hotline.Orders
 		[SugarColumn(ColumnDescription = "话务提醒是否转办")]
 		public bool? IsForwarded { get; set; }
 
+        #region 回访信息
+        /// <summary>
+        /// 话务员评价(话务评价)
+        /// </summary>
+		[SugarColumn(ColumnDescription = "话务员评价")]
+        public ESeatEvaluate? SeatEvaluate { get; set; }
+
+        /// <summary>
+        /// 部门办件结果
+        /// </summary>
+        [SugarColumn(ColumnDataType = "json", ColumnDescription = "部门办件结果", IsJson = true, IsNullable = true)]
+        public Kv? OrgProcessingResults { get; set; }
+        #endregion
     }
 
     public partial class Order

+ 2 - 0
src/Hotline/Orders/OrderDomainService.cs

@@ -272,6 +272,8 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
 
         visitedDetail.Add(seatDetail);
         await _orderVisitDetailRepository.AddRangeAsync(visitedDetail, cancellationToken);
+        await _publisher.PublishAsync(new ContingencyManagementNotify(order, order.Title, order.Content, order.ActualOpinion),
+    PublishStrategy.ParallelWhenAll, cancellationToken);
 
 
         if (order.IsProvince == false && orderVisit.VisitState == EVisitState.Visited)

+ 4 - 1
src/Hotline/Orders/OrderVisitDetail.cs

@@ -4,6 +4,7 @@ using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
 using SqlSugar;
 using XF.Domain.Repository;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Orders
 {
@@ -104,6 +105,7 @@ namespace Hotline.Orders
         {
             this.OrgProcessingResults = visitSatisfactionKv;
             this.OrgHandledAttitude = visitSatisfactionKv;
+            this.VisitContent = visitSatisfactionKv.Value;
         }
 
         /// <summary>
@@ -111,9 +113,10 @@ namespace Hotline.Orders
         /// </summary>
         /// <param name="visitSatisfactionKv"></param>
         /// <exception cref="NotImplementedException"></exception>
-        public void ReplyBackfill(ESeatEvaluate seatEvaluate, EVoiceEvaluate voiceEvaluate)
+        public void ReplyBackfill(ESeatEvaluate seatEvaluate)
         {
             this.SeatEvaluate ??= seatEvaluate;
+            VisitContent = seatEvaluate.GetDescription();
         }
 
 		/// <summary>

+ 50 - 19
src/Hotline/Orders/OrderVisitDomainService.cs

@@ -1,23 +1,17 @@
-using Hotline.Caching.Interfaces;
-using Hotline.Push.FWMessage;
-using Hotline.Settings;
+using DotNetCore.CAP;
+using Hotline.Caching.Interfaces;
 using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Order;
 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;
-using static System.Runtime.InteropServices.JavaScript.JSType;
+using Hotline.EventBus;
+using Hotline.Orders.Notifications;
 
 namespace Hotline.Orders;
 public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependency
@@ -27,15 +21,18 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     private readonly IRepository<Order> _orderRepository;
     private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
     private readonly IOrderVisitRepository _orderVisitRepository;
+    private readonly ICapPublisher _capPublisher;
+    private readonly Publisher _publisher;
 
-
-    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository)
+    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository, ICapPublisher capPublisher, Publisher publisher)
     {
         _orderVisitDetailRepository = orderVisitDetailRepository;
         _logger = logger;
         _orderRepository = orderRepository;
         _systemDicDataCacheManager = systemDicDataCacheManager;
         _orderVisitRepository = orderVisitRepository;
+        _capPublisher = capPublisher;
+        _publisher = publisher;
     }
 
     /// <summary>
@@ -117,19 +114,28 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     public async Task UpdateSmsReplyDefaultAsync(MessageDto dto)
     {
         await _orderVisitRepository.Queryable()
+            .Includes(m => m.Order)
             .Where(m => m.Id == dto.ExternalId)
             .Where(m => m.VisitState == EVisitState.SMSVisiting)
             .FirstAsync()
-            .Then(async orderVisit => 
+            .Then(async orderVisit =>
             {
                 await UpdateSmsReplyAsync(orderVisit, "默认满意");
             });
     }
 
+    /// <summary>
+    /// 超过48小时未回复短信,修改短信回访记录为默认满意
+    /// 用户回访短信回复更新回访状态
+    /// </summary>
+    /// <param name="orderVisit"></param>
+    /// <param name="replyTxt"></param>
+    /// <returns></returns>
     private async Task UpdateSmsReplyAsync(OrderVisit orderVisit, string replyTxt)
     {
+        orderVisit.Order ??= await _orderRepository.GetAsync(orderVisit.OrderId);
         orderVisit.VisitType = EVisitType.SmsVisit;
-        if (new string[] { "4", "5" }.Contains(replyTxt))
+        if (new string[] { "4", "5" , "不满意", "非常不满意" }.Contains(replyTxt))
         {
             // “短信不满意待回访”状态下,由其他方式再次进行回访,回访方式需更新为最新的回访方式
             // 故在此置为空
@@ -166,11 +172,36 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .FirstAsync()
             .Then(async detailSeat =>
             {
-                detailSeat.ReplyBackfill(
-                    GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt),
-                    GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt)
-                    );
+                detailSeat.ReplyBackfill(GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt));
                 await _orderVisitDetailRepository.UpdateAsync(detailSeat);
             });
+
+        await _publisher.PublishAsync(new UpdateOrderVisitNotify(orderVisit.Adapt<OrderVisitNotifyDto>()), cancellationToken: CancellationToken.None);
+
+        if (orderVisit.VisitState != EVisitState.Visited) return;
+        orderVisit.Order.Visited(visitSatisfactionKv.Key, visitSatisfactionKv.Value);
+        await _orderRepository.UpdateAsync(orderVisit.Order);
+        var orderDto = orderVisit.Order.Adapt<OrderDto>();
+        var first = detailOrg.First();
+        if (visitSatisfactionKv.Key != null && visitSatisfactionKv.Value != null)
+        {
+            //推省上
+            await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
+                new PublishVisitDto()
+                {
+                    Order = orderDto,
+                    No = orderVisit.No,
+                    VisitType = orderVisit.VisitType,
+                    VisitName = orderVisit.CreatorName,
+                    VisitTime = orderVisit.VisitTime,
+                    VisitRemark = string.IsNullOrEmpty(first.VisitContent) ? first.OrgProcessingResults?.Value : first.VisitContent,
+                    AreaCode = orderVisit.Order.AreaCode!,
+                    SubjectResultSatifyCode = first.OrgProcessingResults.Key,
+                    FirstSatisfactionCode = orderVisit.Order.FirstVisitResultCode!,
+                    ClientGuid = ""
+                }, cancellationToken: CancellationToken.None);
+
+        }
+
     }
 }

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

@@ -607,5 +607,10 @@ namespace Hotline.Settings
         /// 如果超过这个时间就更新回访为默认满意
         /// </summary>
         public const string DefaultVisitSmsDelaySecond = "DefaultVisitSmsDelaySecond";
+
+        /// <summary>
+        /// 通话记录同步时不推送省上的时间(默认:2024/11/19 18:08)
+        /// </summary>
+        public const string CallSyncUnPushDateTime = "CallSyncUnPushDateTime";
     }
 }