Prechádzať zdrojové kódy

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

xf 7 mesiacov pred
rodič
commit
a51347696e

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

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

+ 32 - 29
src/Hotline.Api/Controllers/OrderController.cs

@@ -135,7 +135,7 @@ public class OrderController : BaseController
     private readonly IRepository<OrderPushType> _orderPushTypeRepository;
     private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
 
-	public OrderController(
+    public OrderController(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowApplication workflowApplication,
@@ -257,7 +257,7 @@ public class OrderController : BaseController
         _expireTime = expireTime;
         _orderPushTypeRepository = orderPushTypeRepository;
         _cityBaseConfiguration = cityBaseConfiguration;
-	}
+    }
     #endregion 
 
     #region 工单发布
@@ -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;
@@ -548,7 +548,7 @@ public class OrderController : BaseController
             visitedDetail.Add(orgDetail);
         }
 
-        if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null,IsProvince:false })
+        if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null, IsProvince: false })
         {
             seatDetail.VoiceEvaluate = EVoiceEvaluate.Satisfied;
             seatDetail.SeatEvaluate = ESeatEvaluate.Satisfied;
@@ -1019,14 +1019,14 @@ public class OrderController : BaseController
                     IsAgain = false,
                     VisitDetails = new List<VisitDetailDto>
                     {
-                new()
-                {
-                Id = seatDetail.Id,
-                VisitId = visit.VisitId,
-                VisitContent = dto.SeatVisitContent,
-                SeatEvaluate = dto.SeatEvaluate,
-                                VisitTarget = EVisitTarget.Seat
-                }
+			new()
+			{
+			Id = seatDetail.Id,
+			VisitId = visit.VisitId,
+			VisitContent = dto.SeatVisitContent,
+			SeatEvaluate = dto.SeatEvaluate,
+			VisitTarget = EVisitTarget.Seat
+			}
                     }
                 };
 
@@ -1034,16 +1034,19 @@ public class OrderController : BaseController
                 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;
@@ -3686,7 +3689,7 @@ public class OrderController : BaseController
     public async Task<PagedDto<OrderDto>> QueryWaited([FromQuery] QueryOrderWaitedDto dto)
     {
         var isHandled = dto.IsHandled.HasValue && dto.IsHandled.Value;
-       
+
         var isAdmin = _orderDomainService.IsCheckAdmin();
 
         var (total, items) = await _orderRepository
@@ -4804,12 +4807,12 @@ public class OrderController : BaseController
             var orderStatus = EOrderStatus.SendBack;
             var orderStartStatus = EOrderStatus.BackToUnAccept;
 
-			if (_appOptions.Value.IsZiGong)
+            if (_appOptions.Value.IsZiGong)
             {
-	            orderStatus = model.SpecialType == ESpecialType.SendBack ? EOrderStatus.SendBack : EOrderStatus.Special;
-	            orderStartStatus = model.SpecialType == ESpecialType.SendBack ? EOrderStatus.BackToUnAccept : EOrderStatus.SpecialToUnAccept;
-			}
-			var Status = model.StepType == EStepType.Start ? orderStartStatus : orderStatus;
+                orderStatus = model.SpecialType == ESpecialType.SendBack ? EOrderStatus.SendBack : EOrderStatus.Special;
+                orderStartStatus = model.SpecialType == ESpecialType.SendBack ? EOrderStatus.BackToUnAccept : EOrderStatus.SpecialToUnAccept;
+            }
+            var Status = model.StepType == EStepType.Start ? orderStartStatus : orderStatus;
             await _orderRepository.Updateable().SetColumns(o => new Orders.Order() { ProcessType = processType, ReTransactNum = reTransactNum, Status = Status }).Where(o => o.Id == order.Id)
                 .ExecuteCommandAsync(HttpContext.RequestAborted);
 

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

+ 15 - 6
src/Hotline.Repository.SqlSugar/Orders/OrderVisitRepository.cs

@@ -39,23 +39,32 @@ public class OrderVisitRepository : BaseRepository<OrderVisit>, IOrderVisitRepos
     public async Task UpdateSmsReplyAsync(PushReceiveMessageDto dto, Message data)
     {
         _logger.LogInformation($"UpdateSmsReplyAsync 收到通知: {dto.ToJson()}");
-        if (dto.IsSmsReply == false || dto.SmsReplyContent.IsNullOrEmpty() || dto.ExternalId.IsNullOrEmpty()) return;
+        if (data.IsSmsReply == false || data.SmsReplyContent.IsNullOrEmpty() || data.ExternalId.IsNullOrEmpty()) return;
 
         var orderVisit = await GetAsync(data.ExternalId)
-             ?? throw new UserFriendlyException($"回访单不存在, visitId: {dto.ExternalId} message: {data.ToJson()}");
+             ?? 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))
         {

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

+ 2 - 1
src/Hotline/Settings/TimeLimitDomain/ExpireTimeLimitBase.cs

@@ -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);

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