admin 2 년 전
부모
커밋
57e4db70af

+ 8 - 0
src/CallCenter.Api/Controllers/CallController.cs

@@ -104,6 +104,14 @@ namespace CallCenter.Api.Controllers
                 OutCallDto outCallDto = new OutCallDto();
                 outCallDto.CallId = item.Id;
                 outCallDto.InfoType = Share.Enums.EInfoType.Call;//TODO目前写死(只有电话)
+                if (item.CallDetails?.FirstOrDefault(x => x.CallStatus == ECallStatus.Dtmf)?.Remark == "2")
+                {
+                    outCallDto.InfoType = EInfoType.Comments;
+                }
+                if (item.CallType == ECallType.Comments)
+                {
+                    outCallDto.InfoType = EInfoType.Comments;
+                }
                 outCallDto.Direction = item.CallDirection;
                 outCallDto.Cpn = item.FromNo??"";
                 outCallDto.Cdpn = item.ToNo ?? "";

+ 29 - 0
src/CallCenter.Api/Controllers/TelController.cs

@@ -307,6 +307,16 @@ namespace CallCenter.Api.Controllers
             var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
             if (work is null)
                 throw new UserFriendlyException("当前坐席暂未进行工作");
+
+            //获取对方是否在工作
+            var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
+            if (toWork is null)
+                throw new UserFriendlyException("转接分机未进行工作");
+
+            var telState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted);
+            if (telState != Share.Enums.ETelStatus.Ready)
+                throw new UserFriendlyException("被叫分机不在线或正在通话中");
+
             await _deviceManager.ExtToExtAsync(work.TelNo, dto.TelNo, HttpContext.RequestAborted);
         }
 
@@ -335,6 +345,15 @@ namespace CallCenter.Api.Controllers
             var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
             if (work is null)
                 throw new UserFriendlyException("当前坐席暂未进行工作");
+
+            var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
+            if (toWork is null)
+                throw new UserFriendlyException("转接分机未进行工作");
+
+            var telState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted);
+            if (telState != Share.Enums.ETelStatus.Ready)
+                throw new UserFriendlyException("被叫分机不在线或正在通话中");
+
             var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
             if (!string.IsNullOrEmpty(tel.ConversationId))
                 await _deviceManager.VisitorToExtAsync(tel.ConversationId, dto.TelNo, HttpContext.RequestAborted);
@@ -410,6 +429,16 @@ namespace CallCenter.Api.Controllers
             var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
             if (work is null)
                 throw new UserFriendlyException("当前坐席暂未进行工作");
+
+            var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
+            if (toWork is null)
+                throw new UserFriendlyException("转接分机未进行工作");
+
+            var telState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted);
+            if (telState != Share.Enums.ETelStatus.Ready)
+                throw new UserFriendlyException("被叫分机不在线或正在通话中");
+
+
             var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
             if (!string.IsNullOrEmpty(tel.ConversationId))
                 await _deviceManager.OuterToExtAsync(tel.ConversationId, dto.TelNo, HttpContext.RequestAborted);

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

@@ -30,7 +30,7 @@
       //  "Args": {
       //    "path": "logs/log-.txt",
       //    "rollingInterval": "Day"
-      //  }
+      //  }*
       //}
     ],
     "Enrich": [ "FromLogContext", "WithSpan" ]
@@ -54,10 +54,17 @@
   "WorkTimeSettings": {
     "MorningBegin": "08:00",
     "MorningEnd": "12:00",
-    "AfterBegin": "15:00",
+    "AfterBegin": "14:00",
     "AfterEnd": "21:00",
     "WorkDay": [ 1, 2, 3, 4, 5, 0, 6 ],
-    "WorkCategory": "08da9b9f-a35d-4ade-8ea7-55e8abbcdefd",
-    "RestCategory": "08daa5f5-ac7a-4ced-8295-1c78baa15f9e"
+    "WorkCategory": "08db2b5d-50b8-4830-83c5-ebe2c3b4364e",
+    "RestCategory": "08db2b5d-5b37-4e34-8818-ecd1565a53ae"
+  },
+  "SendCallRecord": {
+    "FwUrl": "http://192.168.100.195:8066/api/call/insertcalls"
+  },
+  "RecordSettings": {
+    "Remote": "http://192.168.100.100/mcc/Recorder/",
+    "Local": "http://192.168.100.36:50001/Recorder/"
   }
 }

+ 34 - 1
src/CallCenter.Application/Handlers/CallState/AlertExtToOuterNotificationHandler.cs

@@ -1,6 +1,8 @@
 using CallCenter.Calls;
 using CallCenter.Notifications;
+using CallCenter.Realtimes;
 using CallCenter.Share.Enums;
+using CallCenter.Tools;
 using MediatR;
 
 namespace CallCenter.Application.Handlers
@@ -19,7 +21,8 @@ namespace CallCenter.Application.Handlers
 
         public async Task Handle(AlertExtToOuterNotification notification, CancellationToken cancellationToken)
         {
-            if (!string.IsNullOrEmpty(notification.TelNo))
+            string telNo = notification.Outer.From != "" ? notification.Outer.From : notification.TelNo;
+            if (!string.IsNullOrEmpty(telNo))
             {
                 var model =await _callRepository.GetAsync(x => x.ConversationId==notification.Outer.Id && x.ToNo==notification.Outer.To && x.Trunk==notification.Outer.Trunk && x.CreationTime>=DateTime.Now.AddHours(-2), cancellationToken);
 
@@ -39,6 +42,36 @@ namespace CallCenter.Application.Handlers
                     };
                     await _callDetailRepository.AddAsync(detail, cancellationToken);
                 }
+                else
+                {
+                   
+                        var isp = PhoneIspTool.GetPhoneIsp(notification.Outer.To);
+                        var callModel = new Call()
+                        {
+                            CallStatus = ECallStatus.Alert,
+                            CallDirection = ECallDirection.Out,
+                            CallType = ECallType.ExtToOuter,
+                            ConversationId = notification.Outer.Id,
+                            FromNo = telNo,
+                            ToNo = notification.Outer.To,
+                            Trunk = notification.Outer.Trunk,
+                            PhoneIsp = isp
+                        };
+                        callModel.Modified();
+                        var callId = await _callRepository.AddAsync(callModel, cancellationToken);
+                        //写入明细
+                        var detail = new CallDetail()
+                        {
+                            CallId = callId,
+                            CallStatus = ECallStatus.Alert,
+                            ConversationId = notification.Outer.Id,
+                            OMCallId = notification.Outer.CallId,
+                            EventName = "ALERT",
+                            FromNo = telNo,
+                            ToNo = notification.Outer.To,
+                        };
+                        await _callDetailRepository.AddAsync(detail, cancellationToken);
+                }
 
             }
 

+ 30 - 0
src/CallCenter.Application/Handlers/CallState/RingExtToOuterNotificationHandler.cs

@@ -2,6 +2,7 @@
 using CallCenter.Calls;
 using CallCenter.Share.Enums;
 using MediatR;
+using CallCenter.Tools;
 
 namespace CallCenter.Application.Handlers
 {
@@ -47,6 +48,35 @@ namespace CallCenter.Application.Handlers
 
                 await _callDetailRepository.AddAsync(detail, cancellationToken);
             }
+            else
+            {
+                var isp = PhoneIspTool.GetPhoneIsp(notification.Outer.To);
+                var callModel = new Call()
+                {
+                    CallStatus = ECallStatus.Alert,
+                    CallDirection = ECallDirection.Out,
+                    CallType = ECallType.ExtToOuter,
+                    ConversationId = notification.Outer.Id,
+                    FromNo = notification.Outer.From,
+                    ToNo = notification.Outer.To,
+                    Trunk = notification.Outer.Trunk,
+                    PhoneIsp = isp
+                };
+                callModel.Modified();
+                var callId = await _callRepository.AddAsync(callModel, cancellationToken);
+                //写入明细
+                var detail = new CallDetail()
+                {
+                    CallId = callId,
+                    CallStatus = ECallStatus.Alert,
+                    ConversationId = notification.Outer.Id,
+                    OMCallId = notification.Outer.CallId,
+                    EventName = notification.Attribute,
+                    FromNo = notification.Outer.From,
+                    ToNo = notification.Outer.To,
+                };
+                await _callDetailRepository.AddAsync(detail, cancellationToken);
+            }
         }
     }
 }

+ 103 - 5
src/CallCenter.Application/Handlers/FlowControl/CdrNotificationHandler.cs

@@ -1,7 +1,13 @@
 using CallCenter.Calls;
 using CallCenter.Notifications;
+using CallCenter.Share.Dtos;
 using CallCenter.Share.Enums;
 using MediatR;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.Text;
+using System.Text.Json;
+using XF.Domain.Exceptions;
 
 namespace CallCenter.Application.Handlers
 {
@@ -10,18 +16,24 @@ namespace CallCenter.Application.Handlers
         private readonly ICallRecordRepository _callRecordRepository;
         private readonly ICallDetailRepository _callDetailRepository;
         private readonly ICallRepository _callRepository;
+        private readonly IOptionsSnapshot<SendCallRecord> _sendCallRecordOptions;
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly ILogger<CdrNotificationHandler> _logger;
 
-        public CdrNotificationHandler(ICallRecordRepository callRecordRepository, ICallDetailRepository callDetailRepository, ICallRepository callRepository)
+        public CdrNotificationHandler(ICallRecordRepository callRecordRepository, ICallDetailRepository callDetailRepository, ICallRepository callRepository,IOptionsSnapshot<SendCallRecord> sendCallRecordOptions,IHttpClientFactory httpClientFactory,ILogger<CdrNotificationHandler> logger)
         {
             _callRecordRepository = callRecordRepository;
             _callDetailRepository = callDetailRepository;
             _callRepository = callRepository;
+            _sendCallRecordOptions = sendCallRecordOptions;
+            _httpClientFactory = httpClientFactory;
+            _logger = logger;
         }
 
         public async Task Handle(CdrNotification notification, CancellationToken cancellationToken)
         {
             var callDetail = await 
-                _callDetailRepository.GetAsync(x => x.OMCallId == notification.CallId, cancellationToken);
+                _callDetailRepository.GetAsync(x => x.OMCallId == notification.CallId,true,x=>x.CreationTime, cancellationToken);
             
             if (callDetail!=null)
             {
@@ -52,12 +64,98 @@ namespace CallCenter.Application.Handlers
                 await _callRecordRepository.AddAsync(model,cancellationToken);
                 
                 var callModel = await _callRepository.GetAsync(x => x.Id == callDetail.CallId,cancellationToken);
-                if (callModel!=null)
+
+                bool ishave = false;
+
+                if (callModel.CallType == ECallType.ExtToOuter)
+                {
+                    ishave = true;
+                }
+                else
                 {
-                    callModel.Duration = double.Parse(model.Duration);
-                    await _callRepository.UpdateAsync(callModel, cancellationToken);
+                    ishave = await _callDetailRepository.AnyAsync(x => (x.EventName == "RING" || x.EventName == "INCOMING") && x.OMCallId == notification.CallId && x.FromNo == notification.CPN && x.ToNo == notification.CDPN);
                 }
+
+                if (ishave)
+                {
+                    if (callModel!=null)
+                    {
+                        callModel.Duration = double.Parse(model.Duration);
+                        await _callRepository.UpdateAsync(callModel, cancellationToken);
+
+                        var call = await _callRepository.GetExtAsync(callModel.Id, x => x.Includes(d => d.CallDetails));
+                        //TODO 推送通话报告
+                        OutCallDto callDto = new OutCallDto();
+                        callDto.CallId = callDetail.CallId;
+                        callDto.InfoType = EInfoType.Call;
+                        callDto.Direction = callModel.CallDirection;
+                        callDto.Cpn = callModel.FromNo ?? "";
+                        callDto.Cdpn = callModel.ToNo ?? "";
+                        if (callDto.Direction == ECallDirection.In)
+                        {
+                            callDto.Answered = call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWER")?.AnswerNo ?? call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.AnswerNo ?? "";
+                            callDto.OnTime = call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWER")?.CreationTime ?? call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.CreationTime ?? null;
+                            callDto.OnState = call.CallDetails?.Any(x => x.EventName == "ANSWER" || x.EventName == "ANSWERED") == true ? EOnState.On : EOnState.NoOn;
+                        }
+                        else
+                        {
+                            callDto.Answered = call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWER")?.AnswerNo ?? call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.AnswerNo ?? "";
+                            callDto.OnTime = call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWER")?.CreationTime ?? call.CallDetails?.FirstOrDefault(x => x.EventName == "ANSWERED")?.CreationTime ?? null;
+                            callDto.OnState = call.CallDetails?.Any(x => x.EventName == "ANSWER" || x.EventName == "ANSWERED") == true ? EOnState.On : EOnState.NoOn;
+                        }
+                        callDto.BeginTime = call.CreationTime;
+                        callDto.ByeTime = call.CallDetails?.FirstOrDefault(x => x.EventName == "BYE")?.CreationTime ?? DateTime.Now;
+                        callDto.TalkTime = call.Duration;
+                        callDto.SoundFileName = model.Recording;
+                        if (string.IsNullOrEmpty(callDto.SoundFileName))
+                        {
+                            callDto.SoundFileName = call.CallDetails?.FirstOrDefault(x => x.EventName == "BYE" && !string.IsNullOrEmpty(x.Recording))?.Recording;
+                        }
+                        if (string.IsNullOrEmpty(callDto.SoundFileName))
+                        {
+                            callDto.SoundFileName = (await _callRecordRepository.GetAsync(x => x.CallId == callModel.Id && !string.IsNullOrEmpty(x.Recording)))?.Recording;
+                        }
+                        callDto.EvaluateResult = call.CallDetails?.FirstOrDefault(x => x.EventName == "EVALUATE")?.Remark;
+                        callDto.EndBy = call.EndBy;
+                        //callDto.InIvrTime = call.InIvrTime;
+                        //callDto.OutIvrTime = call.OutIvrTime;
+                        //callDto.InGroupTime = call.InGroupTime;
+                        //callDto.OutGroupTime = call.OutGroupTime;
+                        //callDto.InSeaTime = call.InSeaTime;
+                        //callDto.ConnSeaTime = call.ConnSeaTime;
+
+                        var list = new List<OutCallDto>();
+                        list.Add(callDto);
+
+                        try
+                        {
+                            var client = _httpClientFactory.CreateClient();
+                            client.Timeout = TimeSpan.FromSeconds(60);
+                            client.DefaultRequestHeaders.ConnectionClose = true;
+                            var requestContent = JsonSerializer.Serialize(list);
+                            _logger.LogInformation(requestContent);
+                            var content = new StringContent(requestContent, Encoding.UTF8, "application/json");
+                            var responseMessage = await client.PostAsync(_sendCallRecordOptions.Value.FwUrl, content, cancellationToken);
+                            var respContent = responseMessage.Content;
+                            var respContentString = await respContent.ReadAsStringAsync(cancellationToken);
+                            var result = JsonSerializer.Deserialize<FwResult>(respContentString);
+                            _logger.LogInformation("推送报告结果:" + respContentString);
+                        }
+                        catch (Exception ex)
+                        {
+                            throw new UserFriendlyException(ex.Message);
+                        }
+                    }
+                }
+
             }
         }
     }
+
+    public class FwResult
+    {
+        public int code { get; set; }
+
+        public string msg { get; set; }
+    }
 }

+ 37 - 26
src/CallCenter.Application/Handlers/FlowControl/IncomingNotificationHandler.cs

@@ -66,51 +66,62 @@ namespace CallCenter.Application.Handlers
                     ToNo = notification.Visitor.To,
                 };
                 await _callDetailRepository.AddAsync(detail, cancellationToken);
-            }
-            //TODO IVR处理
-            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.IVRConfig);
-            if (bool.Parse(setting.SettingValue))
-            {
-                //TODO 获取工作或休息时间(接听策略)
-                //var ivrList = _ivrCacheManager.GetIvrs();
-                //var ivr = ivrList.First(x => x.IvrCategoryId == "08da9b9f-a35d-4ade-8ea7-55e8abbcdefd" && x.IsRoot);
 
-                var ivr = GetCorrectIvr();
-                _logger.LogInformation("transfer to ivr.no: {ivrNo}", ivr.No);
-                await _newRockClient.VisitorToMenu(
-                    new VisitorToMenuRequest()
+                //TODO IVR处理
+                var setting = _systemSettingCacheManager.GetSetting(SettingConstants.IVRConfig);
+                if (bool.Parse(setting.SettingValue))
+                {
+                    //TODO 获取工作或休息时间(接听策略)
+                    //var ivrList = _ivrCacheManager.GetIvrs();
+                    //var ivr = ivrList.First(x => x.IvrCategoryId == "08da9b9f-a35d-4ade-8ea7-55e8abbcdefd" && x.IsRoot);
+
+                    var ivr = GetCorrectIvr();
+                    _logger.LogInformation("transfer to ivr.no: {ivrNo}", ivr.No);
+                    //工作时间
+                    if (ivr.IsRest)
                     {
-                        Attribute = "Connect",
-                        Menu = new VisitorToMenuMenu() { Id = ivr.No },
-                        Visitor = new VisitorToMenuVisitor() { Id = notification.Visitor.Id }
-                    },
-                    _options.Value.ReceiveKey, _options.Value.Expired, cancellationToken);
-            }
-            else
-            {
-                //TODO 跳转默认分机组
+                        //非工作时间
+                        model.CallType = ECallType.Comments;
+                        await _callRepository.UpdateAsync(model, cancellationToken);
+                    }
+                    await _newRockClient.VisitorToMenu(
+                        new VisitorToMenuRequest()
+                        {
+                            Attribute = "Connect",
+                            Menu = new VisitorToMenuMenu() { Id = ivr.No },
+                            Visitor = new VisitorToMenuVisitor() { Id = notification.Visitor.Id }
+                        },
+                        _options.Value.ReceiveKey, _options.Value.Expired, cancellationToken);
+
+                }
+                else
+                {
+                    //TODO 跳转默认分机组
+                }
             }
+            
         }
 
         private Ivr GetCorrectIvr()
         {
             var worktimeSettings = _worktimeCache.GetOrAdd("worktimesettings", d => _worktimeOptions.Value, ExpireMode.Absolute, TimeSpan.FromDays(1));
-            var categoryId = GetCorrectCategory(worktimeSettings);
+            var (categoryId,isRest) = GetCorrectCategory(worktimeSettings);
             var ivrList = _ivrCacheManager.GetIvrs();
             var ivr = ivrList.First(x => x.IvrCategoryId == categoryId && x.IsRoot);
+            ivr.IsRest = isRest;
             return ivr;
         }
 
-        private string GetCorrectCategory(WorkTimeSettings settings)
+        private (string,bool) GetCorrectCategory(WorkTimeSettings settings)
         {
             if (!settings.WorkDay.Contains((int)DateTime.Now.DayOfWeek))
-                return settings.RestCategory;
+                return (settings.RestCategory,true);
             var time = TimeOnly.FromDateTime(DateTime.Now);
             if ((time >= TimeOnly.Parse(settings.MorningBegin) && time <= TimeOnly.Parse(settings.MorningEnd))
                 || (time >= TimeOnly.Parse(settings.AfterBegin) && time <= TimeOnly.Parse(settings.AfterEnd))
                 )
-                return settings.WorkCategory;
-            return settings.RestCategory;
+                return (settings.WorkCategory,false);
+            return (settings.RestCategory,true);
         }
     }
 }

+ 79 - 19
src/CallCenter.NewRock/DeviceManager.cs

@@ -18,6 +18,7 @@ using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using Group = NewRock.Sdk.Control.Request.Group;
 using CallCenter.Share.Enums;
+using CallCenter.Caches;
 
 namespace CallCenter.NewRock
 {
@@ -27,13 +28,19 @@ namespace CallCenter.NewRock
         private readonly ICallRepository _callRepository;
         private readonly IOptionsSnapshot<DeviceConfigs> _options;
         private readonly IMapper _mapper;
+        private readonly ITelGroupRepository _telGroupRepository;
+        private readonly IUserCacheManager _userCacheManager;
+        private readonly ITelRestRepository _telRestRepository;
 
-        public DeviceManager(INewRockClient newRockClient, IOptionsSnapshot<DeviceConfigs> options, IMapper mapper, ICallRepository callRepository)
+        public DeviceManager(INewRockClient newRockClient, IOptionsSnapshot<DeviceConfigs> options, IMapper mapper, ICallRepository callRepository,ITelGroupRepository telGroupRepository,IUserCacheManager userCacheManager,ITelRestRepository telRestRepository)
         {
             _newRockClient = newRockClient;
             _options = options;
             _mapper = mapper;
             _callRepository = callRepository;
+            _telGroupRepository = telGroupRepository;
+            _userCacheManager = userCacheManager;
+            _telRestRepository = telRestRepository;
         }
 
         #region 查询
@@ -108,6 +115,33 @@ namespace CallCenter.NewRock
         }
 
 
+
+        public async Task<Share.Enums.ETelStatus> QueryTelState(string TelNo,CancellationToken cancellationToken)
+        {
+            var result = await _newRockClient.QueryExt(
+                new QueryExtRequest() { Attribute = "Query", Ext = new Ext { Id = TelNo } },
+                _options.Value.ReceiveKey, _options.Value.Expired, cancellationToken);
+
+            switch (result.Ext.State)
+            {
+                case "ready":
+                    return Share.Enums.ETelStatus.Ready;
+                case "active":
+                    return Share.Enums.ETelStatus.Active;
+                case "progress":
+                    return Share.Enums.ETelStatus.Progress;
+                case "offline":
+                    return Share.Enums.ETelStatus.Offline;
+                case "offhook":
+                    return Share.Enums.ETelStatus.Offhook;
+                default:
+                    break;
+            }
+            
+            return Share.Enums.ETelStatus.Offline;
+        }
+
+
         /// <summary>
         /// 查询所有分机
         /// </summary>
@@ -328,7 +362,7 @@ namespace CallCenter.NewRock
         /// <param name="cancellationToken"></param>
         /// <param name="isAdd"></param>
         /// <returns></returns>
-        public async Task ModifyGroupExtAsync(string groupId,string ext,string voicefile="",bool isAdd=true,CancellationToken cancellationToken = default)
+        public async Task ModifyGroupExtAsync(string groupId,EDistribution distribution,string voicefile="", string extId="", bool isAdd=true,CancellationToken cancellationToken = default)
         {
             if (!int.TryParse(groupId, out int mId))
                 throw new UserFriendlyException("请输入数字");
@@ -336,37 +370,63 @@ namespace CallCenter.NewRock
             if (mId < 1 || mId > 50)
                 throw new UserFriendlyException("分机组只允许在1-50范围内");
 
-            //查询原设备数据
-            var result = await _newRockClient.QueryExtGroup(
-                new QueryExtGroupRequest() { Attribute = "Query", Group = new QueryExtGroup() { Id = groupId } },
-                _options.Value.ReceiveKey,
-                _options.Value.Expired,
-                cancellationToken
-            );
+            #region 清除分机组设置
+
+            await _newRockClient.ConfigExtGroup(new AssginConfigGroupRequest() { 
+                 Attribute="Assign", Group=new Group() { Id = groupId }
+            },_options.Value.ReceiveKey,_options.Value.Expired,cancellationToken);
+
+            #endregion
+
+            var list = await _telGroupRepository.QueryExtAsync(d => d.No == groupId, d => d.Includes(x => x.Tels));
+
+            List<string> exts = new List<string>();
+            foreach (var ext in list[0].Tels)
+            {
+                var iswork = await _userCacheManager.IsWorkingByTelAsync(ext.No, cancellationToken);
+                if (iswork)
+                    exts.Add(ext.No);
+            }
+
+            //查询所有正在休息的分机
+            List<string> restexts = (await _telRestRepository.QueryAsync(x => x.EndTime == null)).Select(x => x.TelNo).ToList();
+            if (restexts!=null && restexts.Count>0)
+            {
+                foreach (var item in restexts)
+                {
+                    exts.Remove(item);
+                }
+            }
+
             //更新
-            var exts = result.Group[0].Ext;
             var groupModel = new Group()
             {
                 Id = groupId,
                 Voicefile = voicefile
             };
-
-            if (isAdd)
+            switch (distribution)
             {
-                exts.Add(new QueryExtGroupExt(){ Id = ext });
-            }
-            else
-            {
-                exts.Remove(exts.First(x=>x.Id == ext));
+                case EDistribution.Sequential:
+                    groupModel.Distribution = "sequential";
+                    break;
+                case EDistribution.Group:
+                    groupModel.Distribution = "group";
+                    break;
+                case EDistribution.Circular:
+                    groupModel.Distribution = "circular";
+                    break;
+                default:
+                    break;
             }
 
-            groupModel.Ext = exts.Select(x=>x.Id).ToList();
+            groupModel.Ext = exts;
             await _newRockClient.ConfigExtGroup(
-                new AssginConfigGroupRequest() { Attribute = "Assign", Group = groupModel, },
+                new AssginConfigGroupRequest() { Attribute="Assign",Group=groupModel },
                 _options.Value.ReceiveKey,
                 _options.Value.Expired,
                 cancellationToken
                 );
+
         }
 
         /// <summary>

+ 14 - 0
src/CallCenter.Repository.SqlSugar/BaseRepository.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Linq.Expressions;
 using System.Text;
 using System.Threading.Tasks;
+using Org.BouncyCastle.Utilities.IO;
 using SqlSugar;
 using XF.Domain;
 using XF.Domain.Repository;
@@ -104,6 +105,19 @@ namespace CallCenter.Repository.SqlSugar
             return await Db.Queryable<TEntity>().FirstAsync(predicate);
         }
 
+        public async Task<TEntity?> GetAsync(Expression<Func<TEntity,bool>> predicate,bool isDesc,Expression<Func<TEntity,object>> orderBy,CancellationToken cancellationToken = default)
+        {
+            if (isDesc)
+            {
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Desc).FirstAsync(predicate);
+            }
+            else
+            {
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Asc).FirstAsync(predicate);
+            }
+        }
+
+
         public async Task<List<TEntity>> QueryAsync(Expression<Func<TEntity, bool>>? predicate = null, params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
         {
             var query = Db.Queryable<TEntity>().Where(predicate ??= d => true);

+ 5 - 1
src/CallCenter.Share/Enums/ECallType.cs

@@ -28,6 +28,10 @@ namespace CallCenter.Share.Enums
         [Description("来电")]
         VisitorCallIn= 3,
 
-        
+        /// <summary>
+        /// 来电留言
+        /// </summary>
+        [Description("来电留言")]
+        Comments = 4,
     }
 }

+ 13 - 0
src/CallCenter/Calls/SendCallRecord.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CallCenter.Calls
+{
+    public class SendCallRecord
+    {
+        public string FwUrl { get; set; }
+    }
+}

+ 11 - 1
src/CallCenter/Devices/IDeviceManager.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using CallCenter.Calls;
 using CallCenter.Share.Dtos;
+using CallCenter.Share.Enums;
 using CallCenter.Tels;
 using Org.BouncyCastle.Operators;
 using SqlSugar;
@@ -22,6 +23,15 @@ namespace CallCenter.Devices
         /// <returns></returns>
         Task<TelDto> QueryTelAsync(string TelNo,CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 查询分机状态
+        /// </summary>
+        /// <param name="TelNo"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<Share.Enums.ETelStatus> QueryTelState(string TelNo, CancellationToken cancellationToken);
+
+
         /// <summary>
         /// 查询所有分机
         /// </summary>
@@ -112,7 +122,7 @@ namespace CallCenter.Devices
         /// <param name="cancellationToken"></param>
         /// <param name="isAdd"></param>
         /// <returns></returns>
-        Task ModifyGroupExtAsync(string groupId, string ext, string voicefile = "",bool isAdd= true, CancellationToken cancellationToken = default);
+        Task ModifyGroupExtAsync(string groupId, EDistribution distribution, string voicefile = "", string extId = "", bool isAdd = true, CancellationToken cancellationToken = default);
         #endregion
 
         #region 通话控制

+ 4 - 0
src/CallCenter/Ivrs/Ivr.cs

@@ -1,5 +1,6 @@
 using CallCenter.Share.Dtos;
 using CallCenter.Share.Enums;
+using Org.BouncyCastle.Utilities.IO;
 using SqlSugar;
 using XF.Domain;
 
@@ -74,4 +75,7 @@ public class Ivr : CreationEntity
 
     [SugarColumn(IsIgnore = true)]
     public static string Key => $"Ivrs";
+
+    [SugarColumn(IsIgnore = true)]
+    public bool IsRest { get; set; }
 }

+ 31 - 5
src/CallCenter/Tels/TelDomainService.cs

@@ -1,4 +1,5 @@
-using CallCenter.Devices;
+using CallCenter.Caches;
+using CallCenter.Devices;
 using CallCenter.Share.Dtos;
 using CallCenter.Users;
 using MapsterMapper;
@@ -12,15 +13,18 @@ public class TelDomainService : ITelDomainService, IScopeDependency
     private readonly IDeviceManager _deviceManager;
     private readonly ITelRepository _telRepository;
     private readonly ITelRestRepository _telRestRepository;
+    private readonly ITelCacheManager _telCacheManager;
 
     public TelDomainService(
         IDeviceManager deviceManager,
         ITelRepository telRepository,
-        ITelRestRepository telRestRepository)
+        ITelRestRepository telRestRepository,
+        ITelCacheManager telCacheManager)
     {
         _deviceManager = deviceManager;
         _telRepository = telRepository;
         _telRestRepository = telRestRepository;
+        _telCacheManager = telCacheManager;
     }
 
 
@@ -51,9 +55,19 @@ public class TelDomainService : ITelDomainService, IScopeDependency
         var isResting = await _telRepository.IsRestingAsync(currentWork.TelNo, cancellationToken);
         if (!isResting)
         {
-            await _deviceManager.TelRestAsync(currentWork.TelNo, cancellationToken);
+            
             await _telRestRepository.AddAsync(new TelRest(currentWork.TelId, currentWork.TelNo, currentWork.UserId, currentWork.UserName),
                 cancellationToken);
+
+            var tel = _telCacheManager.GetTel(currentWork.TelNo);
+            #region 处理设备
+
+            foreach (var group in tel.Groups)
+            {
+                await _deviceManager.ModifyGroupExtAsync(group.No, group.Distribution, group.Voice, tel.No, true, cancellationToken);
+            }
+
+            #endregion
         }
     }
 
@@ -66,14 +80,26 @@ public class TelDomainService : ITelDomainService, IScopeDependency
     public async Task<TelRest> UnRestAsync(string telId, CancellationToken cancellationToken)
     {
         var tel = await _telRepository.GetAsync(telId, cancellationToken);
-        if (tel is null)
+        var telCache = _telCacheManager.GetTel(tel.No);
+        if (telCache is null)
             throw new UserFriendlyException("无效分机编号");
-        await _deviceManager.TelEndRestAsync(tel.No, cancellationToken);
+        //await _deviceManager.TelEndRestAsync(tel.No, cancellationToken);
         var restingTel = await _telRestRepository.GetAsync(d => d.TelId == telId && !d.EndTime.HasValue, cancellationToken);
         if (restingTel is null)
             throw new UserFriendlyException("未查询到分机休息信息");
         restingTel.EndRest();
         await _telRestRepository.UpdateAsync(restingTel, cancellationToken);
+
+        //更新分机组
+        #region 处理设备
+
+        foreach (var group in telCache.Groups)
+        {
+            await _deviceManager.ModifyGroupExtAsync(group.No, group.Distribution, group.Voice, "", true, cancellationToken);
+        }
+
+        #endregion
+
         return restingTel;
     }
 

+ 5 - 5
src/CallCenter/Users/UserDomainService.cs

@@ -67,7 +67,7 @@ namespace CallCenter.Users
             //更新分机组
             foreach (var group in tel.Groups)
             {
-                await _deviceManager.ModifyGroupExtAsync(group.No,tel.No,group.Voice,true, cancellationToken);
+                await _deviceManager.ModifyGroupExtAsync(group.No,group.Distribution,group.Voice, "", true, cancellationToken);
             }
         }
 
@@ -87,10 +87,10 @@ namespace CallCenter.Users
             _cacheWork.Remove(work.GetKey(KeyMode.UserId));
             _cacheWork.Remove(work.GetKey(KeyMode.TelNo));
 
-            //foreach (var group in tel.Groups)
-            //{
-            //    await _deviceManager.ModifyGroupExtAsync(group.No, tel.No, group.Voice,false, cancellationToken);
-            //}
+            foreach (var group in tel.Groups)
+            {
+                await _deviceManager.ModifyGroupExtAsync(group.No,group.Distribution, group.Voice,"", false, cancellationToken);
+            }
 
             return _mapper.Map<WorkDto>(work);
         }

+ 2 - 0
src/XF.Domain.Repository/IRepositoryWithTKey.cs

@@ -22,6 +22,8 @@ namespace XF.Domain.Repository
         Task UpdateRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default);
         Task<TEntity?> GetAsync(TKey id, CancellationToken cancellationToken = default);
         Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default);
+
+        Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, bool isDesc, Expression<Func<TEntity, object>> orderBy, CancellationToken cancellationToken = default);
         Task<List<TEntity>> QueryAsync(
             Expression<Func<TEntity, bool>>? predicate = null,
             params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs);