Эх сурвалжийг харах

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

田爽 7 сар өмнө
parent
commit
6fd9baed2d

+ 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=> SqlFunc.Length(x.Order.Contact.Length)>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;
         }
     }
 }

+ 4 - 3
src/Hotline.Api/Controllers/OrderController.cs

@@ -353,7 +353,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;
@@ -386,7 +386,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;
@@ -918,7 +918,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(),

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

+ 6 - 0
src/Hotline.Application.Tests/Application/OrderApplicationTest.cs

@@ -25,12 +25,18 @@ public class OrderApplicationTest
     [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 }

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

@@ -27,22 +27,28 @@ public class OrderVisitRepositoryTest
     [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 dto = new PushReceiveMessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content };
-        var message = new Message();
+        var message = new Message { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content };
+        var dto = new PushReceiveMessageDto();
         await _orderVisitRepository.UpdateSmsReplyAsync(dto, message);
         visit = _orderVisitRepository.Get(visit.Id);
         visit.VisitState.ShouldBe(visitState.ToEnum<EVisitState>());
-        visit.NowEvaluate.Key.ShouldBe(content);
+        visit.NowEvaluate.Key.ShouldBe(orgResuktKey);
         visit.NowEvaluate.Value.ShouldBe(orgResuktValue);
 
-        if (content == "4" || content == "5")
+        if (content == "4" || content == "5" || content == "不满意" || content == "非常不满意")
             visit.VisitType.ShouldBeNull();
 
         await _orderVisitDetailRepository.Queryable()

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

@@ -0,0 +1,90 @@
+using Hotline.CallCenter.Calls;
+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;
+
+        public XingTangCallSatisfactionSyncJob(IRepository<CallNative> callRepository, ISqlSugarClient db, IRepository<CallSatisfaction> callSatisfactionRepository, IMapper mapper, ILogger<XingTangCallSatisfactionSyncJob> logger)
+        {
+            _callRepository = callRepository;
+            _db = db;
+            _callSatisfactionRepository = callSatisfactionRepository;
+            _mapper = mapper;
+            _logger = logger;
+        }
+
+        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);
+
+            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);
+            }
+
+            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);
+
+            }
+            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);
+            }
+
+
+        }
+    }
+}

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

+ 17 - 7
src/Hotline.Repository.SqlSugar/Orders/OrderVisitRepository.cs

@@ -38,23 +38,33 @@ public class OrderVisitRepository : BaseRepository<OrderVisit>, IOrderVisitRepos
     /// <returns></returns>
     public async Task UpdateSmsReplyAsync(PushReceiveMessageDto dto, Message data)
     {
-        if (dto.IsSmsReply == false || dto.SmsReplyContent.IsNullOrEmpty() || dto.ExternalId.IsNullOrEmpty()) return;
+        _logger.LogInformation($"UpdateSmsReplyAsync 收到通知: {dto.ToJson()}");
+        if (data.IsSmsReply == false || data.SmsReplyContent.IsNullOrEmpty() || data.ExternalId.IsNullOrEmpty()) return;
 
-        var orderVisit = await GetAsync(dto.ExternalId)
-             ?? throw new UserFriendlyException($"回访单不存在, visitId: {dto.ExternalId} message: {data.ToJson()}");
+        var orderVisit = await GetAsync(data.ExternalId)
+             ?? throw new UserFriendlyException($"回访单不存在, visitId: {data.ExternalId} message: {data.ToJson()}");
+
+        if (orderVisit.VisitState == EVisitState.Visited) 
+            throw new UserFriendlyException($"回访单已回访. visitId: {data.ExternalId}");
 
         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"},
+            { "3", $"一般|{EVisitState.Visited}|{ESeatEvaluate.Normal}|{EVoiceEvaluate.Normal}|4"},
             { "4", $"不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.NoSatisfied}|2"},
             { "5", $"非常不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.VeryNoSatisfied}|2"},
         };
+        var replyTxt = data.SmsReplyContent.Trim();
+        var result = string.Empty;
+        if (dics.TryGetValue(replyTxt, out result) == false)
+        {
+            var m = dics.FirstOrDefault(item => item.Value.StartsWith(replyTxt));
+            replyTxt = m.Key;
+            result = m.Value;
+        }
 
-        var replyTxt = dto.SmsReplyContent.Trim();
-        var result = dics[replyTxt];
-        if (result.IsNullOrEmpty()) throw new UserFriendlyException($"用户回复短信内容异常; reply: {replyTxt}");
+        if (result.IsNullOrEmpty()) throw new UserFriendlyException($"用户回复短信内容异常; reply: {data.SmsReplyContent}");
         var replySplit = result.Split("|");
         if (new string[] { "4", "5" }.Contains(replyTxt))
         {

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

@@ -858,7 +858,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();

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

@@ -0,0 +1,25 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+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>
+        /// 按键结果
+        /// </summary>
+        public string Result { get; set; }
+    }
+}

+ 10 - 2
src/Hotline/Push/FWMessage/PushDomainService.cs

@@ -123,6 +123,7 @@ public class PushDomainService : IPushDomainService, IScopeDependency
     /// <returns></returns>
     public async Task PushMsgUpdateStateAsync(PushReceiveMessageDto dto, CancellationToken cancellation)
     {
+        _logger.LogInformation("收到短信通知 PushMsgUpdateStateAsync");
         var data = await _messageRepository.GetAsync(p => p.Id == dto.ExternalId, cancellation);
         if (data != null)
         {
@@ -150,8 +151,15 @@ public class PushDomainService : IPushDomainService, IScopeDependency
                 data.SmsReplyTime = Convert.ToDateTime(dto.SmsReplyTime);
                 data.SmsReplyContent = dto.SmsReplyContent;
 
-                if (data.PushBusiness == EPushBusiness.VisitSms)
-                    await _orderVisitRepository.UpdateSmsReplyAsync(dto, data);
+                try
+                {
+                    if (data.PushBusiness == EPushBusiness.VisitSms)
+                        await _orderVisitRepository.UpdateSmsReplyAsync(dto, data);
+                }
+                catch (Exception e)
+                {
+                    _logger.LogError("_orderVisitRepository.UpdateSmsReplyAsync: " + e.Message);
+                }           
             }
             data.Reason = dto.Reason;
             await _messageRepository.UpdateAsync(data, cancellation);

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

@@ -0,0 +1,37 @@
+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
+    }
+}