Parcourir la source

V.2024-07-22-1
1.【删除】删除12333排队转12345话务组功能;
2.【新增】新增线路根据配置来决定是否排队需要转其他话务组;
3.【修复】修复了分机先签入,话机后注册的情况下,分机不能正常出现在分机组中的情况;
4.【修改】取消企业线路按照工作日的规则进行接听;
5.【新增】如果话机未注册就在系统点签入,系统签入时提示“话机未注册,请先注册话机!”
6.【新增】只要在话机上操作退出分机号,系统就自动签出

Dun.Jason il y a 9 mois
Parent
commit
9e7f84bc39

+ 1 - 1
src/CallCenter.Api/CallCenter.Api.csproj

@@ -14,7 +14,7 @@
     <PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
     <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.10" />
-    <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="6.0.8" />
+    <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.14" />
     <PackageReference Include="NETCore.Encrypt" Version="2.1.0" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
     <PackageReference Include="System.Reactive" Version="5.0.0" />

+ 2 - 2
src/CallCenter.Api/Controllers/CallController.cs

@@ -112,7 +112,7 @@ namespace CallCenter.Api.Controllers
             {
                 OutCallDto outCallDto = new OutCallDto();
                 outCallDto.CallId = item.Id;
-                outCallDto.InfoType = Share.Enums.EInfoType.Call;//TODO目前写死(只有电话)
+                outCallDto.InfoType = Share.Enums.EInfoType.Call;//目前写死(只有电话)
                 outCallDto.Direction = item.CallDirection;
                 outCallDto.Cpn = item.FromNo??"";
                 outCallDto.Cdpn = item.ToNo ?? "";
@@ -125,7 +125,7 @@ namespace CallCenter.Api.Controllers
                 else
                 {
                     outCallDto.Answered = item.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.AnswerNo ?? "";
-                    outCallDto.OnTime = item.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.CreationTime;//TODO 接通时间是否可以为空
+                    outCallDto.OnTime = item.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.CreationTime;//接通时间是否可以为空
                     outCallDto.OnState = item.CallDetails?.Any(x => x.EventName == "ANSWERED") == true ? EOnState.On : EOnState.NoOn;
                 }
                 outCallDto.BeginTime = item.CreationTime;

+ 0 - 1
src/CallCenter.Api/Controllers/IvrController.cs

@@ -168,7 +168,6 @@ public class IvrController : BaseController
     [HttpGet("tree/{categoryId}")]
     public async Task<IvrDto> GetBeginingIvrAsync(string categoryId)
     {
-        ////todo 1.
         ////超过1个根节点则视为未配置完成
         //var count = await _ivrRepository.CountAsync(
         //    d => d.IvrCategoryId == categoryId && string.IsNullOrEmpty(d.PrevIvrNo), HttpContext.RequestAborted);

+ 1 - 1
src/CallCenter.Api/Realtimes/CallCenterHub.cs

@@ -42,7 +42,7 @@ public class CallCenterHub : Hub
     /// <returns>A <see cref="T:System.Threading.Tasks.Task" /> that represents the asynchronous disconnect.</returns>
     public override Task OnDisconnectedAsync(Exception? exception)
     {
-        //todo 1.清理user与connectionId关联关系记录
+        // 1.清理user与connectionId关联关系记录
         return base.OnDisconnectedAsync(exception);
     }
 }

+ 5 - 0
src/CallCenter.Api/Realtimes/RealtimeMethods.cs

@@ -31,5 +31,10 @@
         /// 话机空闲
         /// </summary>
         public static string Idle = "Idle";
+
+        /// <summary>
+        /// 话机自动签出
+        /// </summary>
+        public static string OffDuty = "OffDuty";
     }
 }

+ 15 - 0
src/CallCenter.Api/Realtimes/RealtimeService.cs

@@ -122,5 +122,20 @@ namespace CallCenter.Api.Realtimes
                 throw new UserFriendlyException("无效signalr.connectionId");
             await _hubContext.Clients.Client(work.SignalRId).SendAsync(RealtimeMethods.Idle, new { EventName = "Idle" }, cancellationToken);
         }
+
+        /// <summary>
+        /// 话机自动下线通知
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        /// <exception cref="UserFriendlyException"></exception>
+        public async Task OffDutyAsync(string userId,CancellationToken cancellationToken)
+        {
+            var work = _userCacheManager.GetWorkByUser(userId);
+            if (string.IsNullOrEmpty(work.SignalRId))
+                throw new UserFriendlyException("无效signalr.connectionId");
+            await _hubContext.Clients.Client(work.SignalRId).SendAsync(RealtimeMethods.OffDuty, new { EventName = "OffDuty" }, cancellationToken);
+        }
     }
 }

+ 4 - 4
src/CallCenter.Api/appsettings.Development.json

@@ -37,7 +37,7 @@
   },
   "AllowedHosts": "*",
   "DeviceConfigs": {
-    "Address": "http://192.168.100.100/xml",
+    "Address": "http://10.10.92.100/xml",
     "Authorize": true,
     "ReceiveKey": "E1BBD1BB-A269-44",
     "SendKey": "2A-BA92-160A3B1D",
@@ -50,7 +50,7 @@
   "Cache": {
     "Host": "110.188.24.182",
     "Port": 50179,
-    //"Password": "fengwo22@@",
+    "Password": "fengwo123!$!$",
     "Database": 2
   },
   "Swagger": true,
@@ -58,8 +58,8 @@
     "Origins": [ "http://localhost:8888", "http://callcenter-admin.fengwo.com", "http://admin.call.fengwo.com" ]
   },
   "SendCallRecord": {
-    "FwUrl": "http://192.168.100.195:8066/api/call/insertcalls",
-    "NotReceivedUrl": "http://192.168.100.195:8066/api/call/insertringnotanswered"
+    "FwUrl": "http://10.10.110.195:8066/api/call/insertcalls",
+    "NotReceivedUrl": "http://10.10.110.195:8066/api/call/insertringnotanswered"
   },
   "WorkTimeSettings": {
     "LineSetting": [

+ 1 - 2
src/CallCenter.Application/Handlers/BaseHandler.cs

@@ -115,7 +115,7 @@ namespace CallCenter.Application.Handlers
                             Visitor = new VisitorToGroupQueueVisitor() { Id = model.ConversationId }
                         }, _options.Value.ReceiveKey, _options.Value.Expired, cancellationToken);
 
-                        //处理队列记录 TODO
+                        //处理队列记录
                         _callCacheManager.AddCallCache(model,groupId);
                         await _realtimeService.CallQueueAsync(_callCacheManager.GetCallQueueList(), cancellationToken);
                         // TODO 插播排队语音
@@ -139,7 +139,6 @@ namespace CallCenter.Application.Handlers
                                     Outer = new VisitorToOuterOuter()
                                     {
                                         To = phoneNo,
-                                        //TODO DISPLAY属性待定
                                     }
                                 },
                                     _options.Value.ReceiveKey, _options.Value.Expired, cancellationToken);

+ 0 - 1
src/CallCenter.Application/Handlers/CallState/FailedNotificationHandler.cs

@@ -19,7 +19,6 @@ namespace CallCenter.Application.Handlers
 
         public async Task Handle(FailedNotification notification, CancellationToken cancellationToken)
         {
-             //TODO
         }
     }
 }

+ 1 - 1
src/CallCenter.Application/Handlers/CallState/RingMenuToExtNotificationHandler.cs

@@ -5,7 +5,7 @@ namespace CallCenter.Application.Handlers
 {
     public class RingMenuToExtNotificationHandler:INotificationHandler<RingMenuToExtNotification>
     {
-        //TODO 无法定位数据暂无操作
+        //无法定位数据暂无操作
         public async Task Handle(RingMenuToExtNotification notification, CancellationToken cancellationToken)
         {
             

+ 54 - 1
src/CallCenter.Application/Handlers/ExtState/OfflineNotificationHandler.cs

@@ -1,8 +1,14 @@
 
 using CallCenter.Caches;
+using CallCenter.Devices;
 using CallCenter.Notifications;
+using CallCenter.Realtimes;
 using CallCenter.Tels;
+using CallCenter.Users;
 using MediatR;
+using Microsoft.AspNetCore.Http;
+using Polly.Caching;
+using XF.Domain.Authentications;
 using XF.Domain.Cache;
 
 namespace CallCenter.Application.Handlers
@@ -12,11 +18,26 @@ namespace CallCenter.Application.Handlers
         private readonly ITelRepository _telRepository;
         private readonly ITelCacheManager _telCacheManager;
         private readonly ITypedCache<Tel> _typedCache;
-        public OfflineNotificationHandler(ITelRepository telRepository, ITelCacheManager telCacheManager, ITypedCache<Tel> typedCache)
+        private readonly IUserDomainService _userDomainService;
+        private readonly IUserCacheManager _userCacheManager;
+        private readonly ITelRestRepository _telRestRepository;
+        private readonly IWorkRepository _workRepository;
+        private readonly ITypedCache<Work> _cacheWork;
+        private readonly IDeviceManager _deviceManager;
+        private readonly IRealtimeService _realtimeService;
+
+        public OfflineNotificationHandler(ITelRepository telRepository, ITelCacheManager telCacheManager, ITypedCache<Tel> typedCache,IUserDomainService userDomainService,IUserCacheManager userCacheManager, ITelRestRepository telRestRepository, IWorkRepository workRepository,ITypedCache<Work> cacheWork, IDeviceManager deviceManager, IRealtimeService realtimeService)
         {
             _telRepository = telRepository;
             _telCacheManager = telCacheManager;
             _typedCache = typedCache;
+            _userDomainService = userDomainService;
+            _userCacheManager = userCacheManager;
+            _telRestRepository = telRestRepository;
+            _workRepository = workRepository;
+            _cacheWork = cacheWork;
+            _deviceManager = deviceManager;
+            _realtimeService = realtimeService;
         }
 
         public async Task Handle(OfflineNotification notification, CancellationToken cancellationToken)
@@ -25,6 +46,38 @@ namespace CallCenter.Application.Handlers
             telModel.TelStatus = ETelStatus.Offline;
             await _telRepository.UpdateAsync(telModel, cancellationToken);
             _typedCache.Set(notification.TelNo, telModel);
+
+            //下线自动签出(处理小休)
+            var work  =_userCacheManager.GetWorkByTel(telModel.No);
+            if (work!=null)
+            {
+                //处理小休
+                var restList = await _telRestRepository.QueryAsync(x => x.TelNo == telModel.No && !x.EndTime.HasValue);
+                restList.ForEach(x =>
+                {
+                    x.EndRest();
+                });
+                await _telRestRepository.UpdateRangeAsync(restList,cancellationToken);
+
+                await _realtimeService.OffDutyAsync(work.UserId, cancellationToken);
+
+                work.OffDuty();
+                await _workRepository.UpdateAsync(work,cancellationToken);
+
+                _cacheWork.Remove(work.GetKey(KeyMode.UserId));
+                _cacheWork.Remove(work.GetKey(KeyMode.TelNo));
+
+                #region 初始化话机
+                //初始化解除禁音
+                await _deviceManager.UnMuteAsync(telModel.No, cancellationToken);
+
+                foreach (var group in telModel.Groups)
+                {
+                    await _deviceManager.ModifyGroupExtAsync(group.No, group.Distribution, group.Voice, "", false, cancellationToken);
+                }
+                #endregion
+
+            }
         }
 
     }

+ 12 - 1
src/CallCenter.Application/Handlers/ExtState/OnlineNotificationHandler.cs

@@ -1,7 +1,11 @@
 using CallCenter.Caches;
+using CallCenter.Devices;
 using CallCenter.Notifications;
+using CallCenter.Repository.SqlSugar;
 using CallCenter.Tels;
+using CallCenter.Users;
 using MediatR;
+using Polly.Caching;
 using XF.Domain.Cache;
 
 namespace CallCenter.Application.Handlers
@@ -11,11 +15,17 @@ namespace CallCenter.Application.Handlers
         private readonly ITelRepository _telRepository;
         private readonly ITelCacheManager _telCacheManager;
         private readonly ITypedCache<Tel> _typedCache;
-        public OnlineNotificationHandler(ITelRepository telRepository, ITelCacheManager telCacheManager, ITypedCache<Tel> typedCache)
+        private readonly IUserCacheManager _userCacheManager;
+        private readonly IUserRepository _userRepository;
+        private readonly IDeviceManager _deviceManager;
+        public OnlineNotificationHandler(ITelRepository telRepository, ITelCacheManager telCacheManager, ITypedCache<Tel> typedCache, IUserCacheManager userCacheManager, IUserRepository userRepository, IDeviceManager deviceManager)
         {
             _telRepository = telRepository;
             _telCacheManager = telCacheManager;
             _typedCache = typedCache;
+            _userCacheManager = userCacheManager;
+            _userRepository = userRepository;
+            _deviceManager = deviceManager;
         }
 
         /// <summary>Handles a notification</summary>
@@ -28,6 +38,7 @@ namespace CallCenter.Application.Handlers
             telModel.RegisterIP = notification.RegisterIP;
             await _telRepository.UpdateAsync(telModel,cancellationToken);
             _typedCache.Set(notification.TelNo, telModel);
+
         }
     }
 }

+ 8 - 17
src/CallCenter.Application/Handlers/FlowControl/IncomingNotificationHandler.cs

@@ -122,29 +122,20 @@ namespace CallCenter.Application.Handlers
                         case ECorrectIvr.Group:
 
                             //判断12333是否在排队
-#if DEBUG
-                        if(notification.TrunkId == "3496")
-#else
-                        if (notification.TrunkId == "12333")
-#endif
-                        {
-                            int count = _callCacheManager.GetCallQueueListByTrunk(notification.TrunkId);
-                            if (count>=1)
+//#if DEBUG
+//                        if(notification.TrunkId == "3496")
+//#else
+//                        if (notification.TrunkId == "12333")
+//#endif
+                            if(!string.IsNullOrEmpty(correct.BusyGroup))
                             {
-                                if (!string.IsNullOrEmpty(correct.BusyGroup))
+                                int count = _callCacheManager.GetCallQueueListByTrunk(notification.TrunkId);
+                                if (count>=1)
                                 {
                                     correct.ReturnValue = correct.BusyGroup;
                                 }
                             }
-                        }
 
-                            #region 工作日逻辑
-                            
-
-
-
-
-                            #endregion
 
                             _logger.LogInformation("transfer to group.no:{groupNo}", correct.ReturnValue);
 

+ 2 - 2
src/CallCenter.Caching/CallCenter.Caching.csproj

@@ -7,10 +7,10 @@
   </PropertyGroup>
   
       <ItemGroup>
-    <PackageReference Include="EasyCaching.Bus.Redis" Version="1.9.0" />
+    <PackageReference Include="EasyCaching.Bus.Redis" Version="1.9.2" />
     <PackageReference Include="EasyCaching.HybridCache" Version="1.9.0" />
     <PackageReference Include="EasyCaching.InMemory" Version="1.9.0" />
-    <PackageReference Include="EasyCaching.Redis" Version="1.9.0" />
+    <PackageReference Include="EasyCaching.Redis" Version="1.9.2" />
     <PackageReference Include="EasyCaching.Serialization.SystemTextJson" Version="1.9.0" />
   </ItemGroup>  
 

+ 1 - 1
src/CallCenter.Caching/StartupExtensions.cs

@@ -42,7 +42,7 @@ namespace CallCenter.Caching
                     .WithRedisBus(busConf =>
                     {
                         busConf.Endpoints.Add(new ServerEndPoint(options.Host, options.Port));
-
+                        busConf.Password = options.Password;
                         // do not forget to set the SerializerName for the bus here !!
                         busConf.SerializerName = "xjson";
                     });

+ 1 - 0
src/CallCenter.NewRock/DeviceManager.cs

@@ -21,6 +21,7 @@ using CallCenter.Share.Enums;
 using CallCenter.Caches;
 using Microsoft.AspNetCore.Http;
 using CallCenter.Tools;
+using System.Reflection.Metadata.Ecma335;
 
 namespace CallCenter.NewRock
 {

+ 0 - 3
src/CallCenter/Calls/CallDomainService.cs

@@ -65,7 +65,6 @@ namespace CallCenter.Calls
         {
             //调用设备接口转外线
             await _deviceManager.VisitorToOuterAsync(request.VisitorId, request.OuterPhoneNum, cancellationToken);
-            //TODO 写入数据库通话记录
         }
 
         /// <summary>
@@ -78,7 +77,6 @@ namespace CallCenter.Calls
         {
             //调用设备接口转语音菜单
             await _deviceManager.VisitorToMenuAsync(request.VisitorId, request.MenuId, cancellationToken);
-            //TODO 写入数据库通话记录
         }
 
         /// <summary>
@@ -90,7 +88,6 @@ namespace CallCenter.Calls
         public async Task VisitorToTelGroupAsync(VisitorToTelGroupRequest request, CancellationToken cancellationToken)
         {
             await _deviceManager.VisitorToGroupAsync(request.VisitorId, request.GroupId, cancellationToken);
-            //TODO 写入数据库通话记录
         }
 
         #endregion

+ 0 - 2
src/CallCenter/Ivrs/IvrDomainService.cs

@@ -132,8 +132,6 @@ public class IvrDomainService : IIvrDomainService, IScopeDependency
         }
         else if (ivr.IvrType == EIvrType.Score)//score
         {
-            //todo 评分
-
             //评分成功返回第一个策略,否则直接挂机
             return ivr.IvrStrategies.FirstOrDefault()?.Answer ?? new IvrAnswer { IvrAnswerType = EIvrAnswerType.HangUp };
         }

+ 4 - 0
src/CallCenter/Realtimes/IRealtimeService.cs

@@ -1,5 +1,6 @@
 using CallCenter.Calls;
 using CallCenter.Share.Enums;
+using SqlSugar;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -22,6 +23,8 @@ namespace CallCenter.Realtimes
         Task AlertAsync(string userId, AlertDto dto, CancellationToken cancellationToken);
 
         Task IdleAsync(string userId, CancellationToken cancellationToken);
+
+        Task OffDutyAsync(string userId, CancellationToken cancellationToken);
     }
 
     public record AlertDto
@@ -67,4 +70,5 @@ namespace CallCenter.Realtimes
 
         public string ConversationId { get; set; }
     }
+
 }

+ 6 - 1
src/CallCenter/Users/UserDomainService.cs

@@ -58,7 +58,12 @@ namespace CallCenter.Users
 
             var telIsWorking = await _workRepository.AnyAsync(d => d.TelId == tel.Id && !d.EndTime.HasValue, cancellationToken);
             if (telIsWorking)
-                throw new UserFriendlyException("当前分机已被占用");
+                throw UserFriendlyException.SameMessage("当前分机已被占用");
+
+            var telModel = await _deviceManager.QueryTelState(tel.No, cancellationToken);
+
+            if (telModel != Share.Enums.ETelStatus.Ready)
+                throw UserFriendlyException.SameMessage("话机未注册或不是空闲状态,请先确认话机状态!");
 
             var work = new Work(userId, user.Name, tel.Id, tel.No);
             await _workRepository.AddAsync(work, cancellationToken);

+ 22 - 0
src/XF.Domain/Exceptions/UserFriendlyException.cs

@@ -11,6 +11,8 @@ namespace XF.Domain.Exceptions
     {
         public int Code { get; set; }
 
+        public string? FriendlyMessage { get; set; }
+
         public UserFriendlyException()
         {
 
@@ -31,6 +33,26 @@ namespace XF.Domain.Exceptions
             Code = code;
         }
 
+        public UserFriendlyException(string error, string friendlyMessage) : base(error)
+        {
+            Code = 500;
+            FriendlyMessage = friendlyMessage;
+        }
+
+        public UserFriendlyException(int code, string error, string friendlyMessage) : base(error)
+        {
+            Code = code;
+            FriendlyMessage = friendlyMessage;
+        }
+
+        public UserFriendlyException(int code, string error, string friendlyMessage, Exception inner) : base(error, inner)
+        {
+            Code = code;
+            FriendlyMessage = friendlyMessage;
+        }
+
+        public static UserFriendlyException SameMessage(string error) => new(error, error);
+
         protected UserFriendlyException(
             SerializationInfo info,
             StreamingContext context) : base(info, context)