using DocumentFormat.OpenXml.Office2010.Excel; using DocumentFormat.OpenXml.Spreadsheet; using DotNetCore.CAP; using Hotline.Api.Filter; using Hotline.Application.CallCenter; using Hotline.Application.Systems; using Hotline.Application.Tels; using Hotline.Caching.Interfaces; using Hotline.CallCenter.Calls; using Hotline.CallCenter.Tels; using Hotline.CallCenter.Tels.CallTelDomain; using Hotline.Configurations; using Hotline.EventBus; using Hotline.Orders; using Hotline.Quality; using Hotline.Quality.Notifications; using Hotline.Repository.SqlSugar.Extensions; using Hotline.Settings; using Hotline.Share.Dtos; using Hotline.Share.Dtos.CallCenter; using Hotline.Share.Dtos.Order; using Hotline.Share.Dtos.Quality; using Hotline.Share.Dtos.TrCallCenter; using Hotline.Share.Enums.CallCenter; using Hotline.Share.Enums.Quality; using Hotline.Share.Requests; using Hotline.Share.Tools; using Hotline.Tools; using Hotline.Users; using Mapster; using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; using SqlSugar; using XF.Domain.Authentications; using XF.Domain.Cache; using XF.Domain.Exceptions; using XF.Domain.Filters; using XF.Domain.Repository; using XF.Utility.EnumExtensions; namespace Hotline.Api.Controllers { public class IPPbxController : BaseController { private readonly IMapper _mapper; private readonly IUserDomainService _userDomainService; private readonly ISessionContext _sessionContext; private readonly IRepository _trCallRecordRepository; private readonly ICallApplication _callApplication; private readonly IRepository _trCallEvaluate; private readonly ISystemDicDataCacheManager _systemDicDataCacheManager; private readonly ILogger _logger; private readonly IOrderRepository _orderRepository; private readonly IRepository _orderVisitRepository; private readonly IUserCacheManager _userCacheManager; private readonly ICapPublisher _capPublisher; private readonly ITelRestRepository _telRestRepository; private readonly IRepository _userRepository; private readonly ITelApplication _telApplication; private readonly IRepository _qualiteyRepository; private readonly IRepository _qualityTemplate; private readonly ISystemSettingCacheManager _systemSettingCacheManager; private readonly IRepository _telActionRecordRepository; private readonly ITypedCache _cacheWork; private readonly ISystemMobilAreaApplication _systemMobilAreaApplication; private readonly IRepository _workRepository; private readonly Publisher _publisher; private readonly ITrCallRecordRepository _callRecordRepository; private readonly IIPPbxApplication _iPPbxApplication; private readonly ICallTelClient _callTelClient; private readonly IRepository _telOperationRepository; private readonly IOptionsSnapshot _appOptions; public IPPbxController(IMapper mapper, IUserDomainService userDomainService, ISessionContext sessionContext, IRepository trCallRecordRepository, ICallApplication callApplication, IRepository trCallRecord, ISystemDicDataCacheManager systemDicDataCacheManager, ILogger logger, IOrderRepository orderRepository, IRepository orderVisitRepository, IUserCacheManager userCacheManager, ICapPublisher capPublisher, ITelRestRepository telRestRepository, IRepository userRepository, ITelApplication telApplication, IRepository qualiteyRepository, IRepository qualityTemplate, ISystemSettingCacheManager systemSettingCacheManager, IRepository telActionRecordRepository, ISystemMobilAreaApplication systemMobilAreaApplication, IRepository workRepository, Publisher publisher, ITrCallRecordRepository callRecordRepository, ITypedCache cacheWork, IIPPbxApplication iPPbxApplication, ICallTelClient callTelClient, IRepository telOperationRepository, IOptionsSnapshot appOptions) { _mapper = mapper; _userDomainService = userDomainService; _sessionContext = sessionContext; _trCallRecordRepository = trCallRecordRepository; _callApplication = callApplication; _trCallEvaluate = trCallRecord; _systemDicDataCacheManager = systemDicDataCacheManager; _logger = logger; _orderRepository = orderRepository; _orderVisitRepository = orderVisitRepository; _userCacheManager = userCacheManager; _capPublisher = capPublisher; _telRestRepository = telRestRepository; _userRepository = userRepository; _telApplication = telApplication; _qualiteyRepository = qualiteyRepository; _qualityTemplate = qualityTemplate; _systemSettingCacheManager = systemSettingCacheManager; _telActionRecordRepository = telActionRecordRepository; _systemMobilAreaApplication = systemMobilAreaApplication; _workRepository = workRepository; _publisher = publisher; _callRecordRepository = callRecordRepository; _cacheWork = cacheWork; _iPPbxApplication = iPPbxApplication; _callTelClient = callTelClient; _telOperationRepository = telOperationRepository; _appOptions = appOptions; } #region 添添呼 #region 分机 /// /// 查询所有分机 /// /// [HttpGet("query-tels")] public async Task> TrQueryTels() { var tels = await _callTelClient.QueryTelsAsync(new QueryTelRequest() { }, HttpContext.RequestAborted); var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue; tels = tels.Where(m => listenTels.Contains(m.TelNo) == false).ToList(); var returnlist = _mapper.Map>(tels); string callOutQueueId = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutQueueId).SettingValue[0]; returnlist.ForEach(x => { x.CallOutQueue = callOutQueueId; }); return returnlist; } /// /// 查询所有分机状态 /// /// [HttpGet("query-telstate")] [AllowAnonymous] public async Task> TrQueryTelState([FromQuery] string? state, bool hasListen) { var tels = await _callTelClient.QueryTelStateAsync(new QueryTelStateRequest() { State = state }, HttpContext.RequestAborted); var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue; var workList = await _workRepository.Queryable().Where(d => 1 == 1 && !d.EndTime.HasValue).ToListAsync(); var query = from tel in tels.AgentList join works in workList on tel.TelNo equals works.TelNo into workD from work in workD.DefaultIfEmpty() select new TrTelStateDto { Id = tel.Id, TelNo = tel.TelNo, ChannelUUid = tel.ChannelUUid, TelName = tel.TelName, Type = tel.Type, Weight = tel.Weight, Queue = tel.Queue, State = tel.State, OldState = tel.OldState, Device = tel.Device, SipIp = tel.SipIp, PrivateData = tel.PrivateData, SipState = tel.SipState, CreatedAt = tel.CreatedAt, UpdatedAt = tel.UpdatedAt, CallDirection = tel.CallDirection, OtherNumber = tel.OtherNumber, GateWay = tel.GateWay, AnsweredAt = tel.AnsweredAt, WorkUserId = (work != null) ? work.UserId : "", WorkUserName = (work != null) ? work.UserName : "", }; //if (hasListen == false) //{ // query = query.Where(m => listenTels.Contains(m.TelNo) == false); //} var list = query.OrderBy(x => x.TelNo).OrderByDescending(x => x.CreatedAt).ToList(); return list;// _mapper.Map>(tels.AgentList); } /// /// 查询坐席分机状态及通话信息 /// /// /// [HttpGet("query-telstatebyno")] [AllowAnonymous] public async Task TrQueryTelStateByTelNo([FromQuery] string? telno) { var tels = await _callTelClient.QueryTelStateAsync(new QueryTelStateRequest() { TelNo = telno }, HttpContext.RequestAborted); var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue; tels.Agents = tels.Agents?.Where(m => listenTels.Contains(m.TelNo) == false).ToList(); var tel = tels.Agents?.FirstOrDefault(); if (tel != null) { var work = _userCacheManager.GetWorkByTel(tel.TelNo); var result = new TrTelStateDto { Id = tel.Id, TelNo = tel.TelNo, ChannelUUid = tel.ChannelUUid, TelName = tel.TelName, Type = tel.Type, Weight = tel.Weight, Queue = tel.Queue, State = tel.State, OldState = tel.OldState, Device = tel.Device, SipIp = tel.SipIp, PrivateData = tel.PrivateData, SipState = tel.SipState, CreatedAt = tel.CreatedAt, UpdatedAt = tel.UpdatedAt, CallDirection = tel.CallDirection, OtherNumber = tel.OtherNumber, GateWay = tel.GateWay, AnsweredAt = tel.AnsweredAt, WorkUserId = (work != null) ? work.UserId : "", WorkUserName = (work != null) ? work.UserName : "", }; result.OnStateCount = await _trCallRecordRepository.Queryable().Where(x => x.CDPN == telno && x.CallDirection == ECallDirection.In && x.OnState == EOnState.On && x.CreatedTime.Date == DateTime.Now.Date).CountAsync(); return result; } return null; } #endregion #region 黑白名单 /// /// 新增黑白名单 /// /// /// [LogFilter("新增黑白名单")] [HttpPost("add-blacklist")] public async Task AddBlacklist([FromBody] TrAddBlacklistDto dto) { await _callTelClient.AddBlacklistAsync(new AddBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted); } /// /// 删除黑白名单 /// /// /// [LogFilter("删除黑白名单")] [HttpPost("remove-blacklist")] public async Task DelBlacklist([FromBody] TrDelBlacklistDto dto) { await _callTelClient.DelBlacklistAsync(new DelBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted); } /// /// 查询黑白名单 /// /// [HttpGet("query-blacklist")] public async Task> QueryBlacklist([FromQuery] TrQueryBlacklistDto dto) { var blacklist = await _callTelClient.QueryBlacklistAsync(new QueryBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted); return _mapper.Map>(blacklist); } #endregion #region 签入后调用 /// /// 上班 /// /// /// [HttpPost("on-duty")] public async Task OnDuty([FromBody] TrOnDutyDto dto) { return await _callApplication.SignInAsync(new SignInDto() { TelNo = dto.TelNo, TelModelState = dto.TelModelState }, HttpContext.RequestAborted); } /// /// 下班 /// /// [HttpPost("off-duty")] public async Task OffDuty() { //await _userDomainService.TrOffDutyAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted); await _telApplication.SignOutAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted); } /// /// 查询当前用户的分机状态 /// /// [HttpGet("tel-state")] public async Task TelState() { return await _callApplication.GetTelStateAsync(HttpContext.RequestAborted); } /// /// 切换状态 /// /// /// /// [HttpPost("change-telmodel")] public async Task ChangeTelModel([FromBody] ChangeTelModelDto dto) { await _callApplication.ChangeTelModel(dto.isCallOut, HttpContext.RequestAborted); } /// /// 下班-管理手动操作 /// /// /// [HttpGet("off-duty-manage")] public async Task OffDuty([FromQuery] string telNo) { await _telApplication.SignOutByTelNoAsync(telNo, HttpContext.RequestAborted); } //提供关闭浏览器事件触发调用 [HttpPost("off-duty-no-auth")] [AllowAnonymous] public async Task OffDutyWithNoAuthorize([FromQuery] string userId) { if (string.IsNullOrEmpty(userId)) throw new UserFriendlyException("无效用户编号"); await _telApplication.SignOutByTelNoAsync(userId, HttpContext.RequestAborted); } #endregion #region 添添呼小休 /// /// 小休页面基础数据 /// /// [HttpGet("rest/basedata")] public async Task RestBasePaegData() { var rsp = new { RestReason = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.RestReason) }; return rsp; } /// /// 小休 /// /// [HttpPost("rest")] [LogFilter("分机小休")] public async Task Rest([FromBody] TrRestDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work == null) throw UserFriendlyException.SameMessage("分机未签入,不能休息"); var isResting = await _telRestRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted); if (isResting) throw UserFriendlyException.SameMessage("当前坐席正在休息"); var user = await _userRepository.GetAsync(work.UserId, HttpContext.RequestAborted); var telRest = new TelRest(work.TelNo, work.TelNo, work.UserId, work.UserName, dto.Reason, false, user.StaffNo); await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted); var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, EActionType.TelRest); await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted); } /// /// 取消小休 /// /// [HttpPost("unrest")] [LogFilter("分机取消小休")] public async Task UnRest() { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("分机未签入,不能操作"); var telRest = await _telRestRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, HttpContext.RequestAborted); if (telRest is null) throw UserFriendlyException.SameMessage("未查询到分机休息信息"); telRest.EndRest(); await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted); var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == EActionType.TelRest && !x.EndTime.HasValue, HttpContext.RequestAborted); if (telAction != null) { telAction.EndAction(); await _telActionRecordRepository.UpdateAsync(telAction); } } #endregion #region 添添呼话后整理 /// /// 分机动作开始 /// /// [HttpGet("callennd-arrange-begin")] public async Task CallEndArrangeBegin(int actionType) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("分机未签入,不能操作"); //判断如果已经存在未结束的该动作就不新增 var ishas = await _telActionRecordRepository.AnyAsync(x => x.TelNo == work.TelNo && x.ActionType == (EActionType)actionType && !x.EndTime.HasValue, HttpContext.RequestAborted); if (!ishas) { var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, (EActionType)actionType); await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted); } } /// /// 分机动作结束 /// /// [HttpGet("callend-arrange-end")] public async Task CallEndArrangeEnd(int actionType) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("分机未签入,不能操作"); var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == (EActionType)actionType && !x.EndTime.HasValue, HttpContext.RequestAborted); if (telAction != null) { telAction.EndAction(); await _telActionRecordRepository.UpdateAsync(telAction); } } #endregion #region 通话记录(对外) /// /// 接收通话记录 /// /// /// [AllowAnonymous] [HttpPost("receivecallrecord")] public async Task ReceiveCallRecord([FromBody] ReceiveCallRecordDto dto) { _logger.LogInformation($"收到通话记录结果回传:{JsonConvert.SerializeObject(dto)}"); //bool isHas = await _trCallRecordRepository.AnyAsync(x => x.OtherAccept == dto.other_accept, HttpContext.RequestAborted); //if (isHas) //{ // return OpenResponse.Ok("success"); //} var model = _mapper.Map(dto); model.Duration = 0; model.RingTimes = 0; model.QueueTims = 0; model.OnState = Share.Enums.CallCenter.EOnState.NoOn; //计算通话时长 if (model.AnsweredTime != null) { model.OnState = Share.Enums.CallCenter.EOnState.On; //是否接通 TimeSpan tsend = new TimeSpan(model.OverTime.Ticks); TimeSpan tsbegin = new TimeSpan(model.AnsweredTime.Value.Ticks); model.Duration = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds); } //计算振铃时长 if (model.BeginRingTime != null) { TimeSpan tsbegin = new TimeSpan(model.BeginRingTime.Value.Ticks); if (model.EndRingTimg != null) { TimeSpan tsend = new TimeSpan(model.EndRingTimg.Value.Ticks); model.RingTimes = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds); } else { TimeSpan tsend = new TimeSpan(model.OverTime.Ticks); model.RingTimes = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds); } } //计算队列时长 if (model.BeginQueueTime != null && model.EndQueueTime != null) { TimeSpan tsend = new TimeSpan(model.EndQueueTime.Value.Ticks); TimeSpan tsbegin = new TimeSpan(model.BeginQueueTime.Value.Ticks); model.QueueTims = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds); } //获取分机号 if (model.CallDirection == ECallDirection.In) { model.TelNo = model.CDPN; try { var areaModel = await _systemMobilAreaApplication.GetPhoneCardArea(model.CPN, HttpContext.RequestAborted); if (areaModel != null) { model.MobileAreaName = areaModel.MobileAreaName; model.OFlag = areaModel.OFlag; model.OperatorName = areaModel.OperatorName; } } catch { } } else { model.TelNo = model.CPN; try { var areaModel = await _systemMobilAreaApplication.GetPhoneCardArea(model.CDPN, HttpContext.RequestAborted); if (areaModel != null) { model.MobileAreaName = areaModel.MobileAreaName; model.OFlag = areaModel.OFlag; model.OperatorName = areaModel.OperatorName; } } catch { } } //判断是否是内部通话(目前分机都为4位) if (model.CPN.Length == 4 && model.CDPN.Length == 4) //是内部通话 { model.TelNo = model.CDPN;//如果是内部通话 响应分机为被叫号码 } if (!string.IsNullOrEmpty(dto.phoneTypes)) { model.PhoneTypes = (EPhoneTypes)Convert.ToInt32(dto.phoneTypes); } //获取关联 工单或是回访 //var order = await _orderRepository.GetAsync(x => x.CallId == model.CallAccept, HttpContext.RequestAborted);//由CallAccept改为OtherAccept var order = await _orderRepository.GetAsync(x => x.CallId == model.OtherAccept && string.IsNullOrEmpty(x.CallId) == false, HttpContext.RequestAborted); if (order != null) { model.CallOrderType = ECallOrderType.Order; model.ExternalId = order.Id; try { // 写入智能质检 var teAny = await _qualityTemplate.Queryable() .LeftJoin((x, d) => x.Id == d.TemplateId) .LeftJoin((x, d, i) => d.ItemId == i.Id) .Where((x, d, i) => i.IsIntelligent == 1) .Where((x, d, i) => x.Grouping == ETemplateGrouping.Accepted).AnyAsync(); if (teAny) { var quality = await _qualiteyRepository.Queryable().Where(x => x.OrderId == order.Id && x.Source == Share.Enums.Quality.EQualitySource.Accepted).FirstAsync(); if (quality != null) { var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix); //await _aiQualityService.CreateAiOrderQualityTask(quality, model, order, setting?.SettingValue[0], HttpContext.RequestAborted); try { //_aiQualityService.CreateAiOrderQualityTask( //quality, //model.RecordingAbsolutePath, //model.CPN, //model.CreatedTime, //order, setting?.SettingValue[0], HttpContext.RequestAborted); var handler = new AiQualityHandler() { Id = quality.Id, Source = quality.Source.ToString(), AudioFile = model.RecordingAbsolutePath, FromNo = model.CPN, CallStartTime = model.CreatedTime, ViteRecordPrefix = setting?.SettingValue[0], }; await _publisher.PublishAsync(new AiOrderQualityNotify(handler), PublishStrategy.ParallelNoWait, HttpContext.RequestAborted); } catch (Exception e) { _logger.LogError($"写入智能质检异常2!, \r\n{e.Message}"); } } } } catch { } } else { //var orderVisit = await _orderVisitRepository.GetAsync(x => x.CallId == model.CallAccept, HttpContext.RequestAborted);//由CallAccept改为OtherAccept var orderVisit = await _orderVisitRepository.GetAsync(x => x.CallId == model.OtherAccept && string.IsNullOrEmpty(x.CallId) == false, HttpContext.RequestAborted); if (orderVisit != null) { model.CallOrderType = ECallOrderType.Visit; model.ExternalId = orderVisit.Id; } } //获取用户 var work = await _userCacheManager.GetWorkByTelNoLast(model.TelNo); if (!string.IsNullOrEmpty(model.AgentTransferNumber)) { work = await _userCacheManager.GetWorkByTelNoLast(model.AgentTransferNumber); } if (work != null) { model.UserId = work.UserId; model.UserName = work.UserName; model.StaffNo = work.StaffNo; } await _trCallRecordRepository.AddAsync(model, HttpContext.RequestAborted); var publishCallRecordDto = new PublishCallRecrodDto() { }; if (order != null) { var orderDto = _mapper.Map(order); publishCallRecordDto.Order = orderDto; } var trCallDto = _mapper.Map(model); publishCallRecordDto.TrCallRecordDto = trCallDto; if (string.IsNullOrEmpty(model.AgentTransferNumber)) { //推省上 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallBye, publishCallRecordDto); } return OpenResponse.Ok("success"); } /// /// 接受通话语音评价 /// /// /// [AllowAnonymous] [HttpPost("receivecallevaluate")] public async Task ReceiveCallEvaluate([FromBody] ReceiveCallEvaluateDto dto) { var model = _mapper.Map(dto); await _trCallEvaluate.AddAsync(model, HttpContext.RequestAborted); return OpenResponse.Ok("success"); } /// /// 话机状态通知 /// /// /// [AllowAnonymous] [StaffNoSessionContextFilter()] [HttpPost("notify/status")] [LogFilterAlpha("话机状态通知")] public async Task NotifyStatus(NotifyInDto dto) { var userName = _sessionContext.UserName; if (dto.Status == 0) // 签出 { //await _iPPbxApplication.ResetTelStatus(null, dto.TelNo, CancellationToken.None); } //dto.Status = await _callTelClient.GetStatusAsync(dto.Status); //await _callApplication.EndActionAsync(dto.Adapt()); } #endregion #region 通话记录(对内) /// /// 获取通话记录列表 /// /// /// [HttpGet("calls/call-list")] public async Task> GetCallList([FromQuery] GetCallListDto dto) { var (total, items) = await _callRecordRepository.GetCallList(dto) .ToPagedListAsync(dto.PageIndex, dto.PageSize); var isCallBindOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsCallBindOrder).SettingValue[0]); var itemsResult = _mapper.Map>(items); itemsResult.ForEach(x => { if (isCallBindOrder) { if (x.Order == null && !string.IsNullOrEmpty(x.OtherAccept)) x.IsBindOrder = true; else x.IsBindOrder = false; } else { if (string.IsNullOrEmpty(x.UserId) && x.Order == null && !string.IsNullOrEmpty(x.OtherAccept)) x.IsBindOrder = true; else x.IsBindOrder = false; } }); return new PagedDto(total, itemsResult); //return new PagedDto(total, _mapper.Map>(items)); } /// /// 通话记录导出 /// /// /// [HttpPost("calls/call-list/export")] [LogFilterAlpha("导出日志")] public async Task GetCallListExport([FromBody] ExportExcelDto dto) { var query = _callRecordRepository.GetCallList(dto.QueryDto); List data; if (dto.IsExportAll) { data = await query.ToListAsync(HttpContext.RequestAborted); } else { var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted); data = items; } var dataDtos = _mapper.Map>(data); dto.ColumnInfos.ForEach(x => { if (x.Prop == "cpn") { x.Prop = "CPN"; } if (x.Prop == "cdpn") { x.Prop = "CDPN"; } }); dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos); var dtos = dataDtos .Select(stu => _mapper.Map(stu, typeof(TrCallDto), dynamicClass)) .Cast() .ToList(); var stream = ExcelHelper.CreateStream(dtos); return ExcelStreamResult(stream, "通话记录"); } /// /// 通话记录基础数据 /// /// [HttpGet("calls/basedata")] public async Task CallBaseData() { return new { OnState = EnumExts.GetDescriptions(), CallDirection = EnumExts.GetDescriptions(), EndBy = EnumExts.GetDescriptions(), PhoneTypes = EnumExts.GetDescriptions() }; } #endregion #region 关联 /// /// 可关联工单 /// /// /// [HttpGet("canlink-order")] public async Task> CanLinkCallRecordOrder([FromQuery] CanLinkCallRecordOrderDto dto) { var (total, items) = await _orderRepository.Queryable() .Where(x => string.IsNullOrEmpty(x.CallId) && x.Source == Share.Enums.Order.ESource.Hotline) .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.No.Contains(dto.Keyword) || x.Title.Contains(dto.Keyword)) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 可关联回访 /// /// /// [HttpGet("canlink-ordervisit")] public async Task> CanLinkCallRecordOrderVisit([FromQuery] CanLinkCallRecordOrderVisitDto dto) { var (total, items) = await _orderVisitRepository.Queryable() .Includes(x => x.Order) .Where(x => string.IsNullOrEmpty(x.CallId) && x.VisitType == Share.Enums.Order.EVisitType.ArtificialVisit) .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.No.Contains(dto.Keyword)) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 关联工单或者回访 /// /// /// [HttpPost("link-callrecord")] public async Task LinkCallRecord([FromBody] LinkCallRecordDto dto) { //var trRecord = await _trCallRecordRepository.GetAsync(x => x.CallAccept == dto.CallId, HttpContext.RequestAborted);//由CallAccept改为OtherAccept if (dto.IsOrder) { var trRecords = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == dto.CallId).ToListAsync(HttpContext.RequestAborted); foreach (var trRecord in trRecords) { if (trRecord.CallOrderType == ECallOrderType.Order && !string.IsNullOrEmpty(trRecord.ExternalId)) throw UserFriendlyException.SameMessage("通话记录已经关联工单"); //工单 var order = await _orderRepository.GetAsync(x => x.Id == dto.Id, HttpContext.RequestAborted); //if (!string.IsNullOrEmpty(order.CallId)) // throw UserFriendlyException.SameMessage("通话记录已经关联工单"); order.CallId = dto.CallId; order.FromPhone = trRecord.CPN; order.TransferPhone = trRecord.Gateway; await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted); trRecord.CallOrderType = ECallOrderType.Order; trRecord.ExternalId = order.Id; await _trCallRecordRepository.UpdateAsync(trRecord, HttpContext.RequestAborted); //推省上 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto() { Order = _mapper.Map(order), TrCallRecordDto = _mapper.Map(trRecord) }); } } else { var trRecord = await _trCallRecordRepository.GetAsync(x => x.OtherAccept == dto.CallId, HttpContext.RequestAborted); //回访 var visit = await _orderVisitRepository.GetAsync(x => x.Id == dto.Id, HttpContext.RequestAborted); visit.CallId = dto.CallId; await _orderVisitRepository.UpdateAsync(visit, HttpContext.RequestAborted); trRecord.CallOrderType = ECallOrderType.Visit; trRecord.ExternalId = visit.Id; await _trCallRecordRepository.UpdateAsync(trRecord, HttpContext.RequestAborted); } } #endregion #region 坐席动作记录 /// /// 坐席动作列表 /// /// /// [HttpGet("telaction-list")] public async Task> TelActionList([FromQuery] TelActionListDto dto) { var (total, items) = await _telActionRecordRepository.Queryable() .WhereIF(string.IsNullOrEmpty(dto.TelNo) == false, x => x.TelNo.Contains(dto.TelNo)) .WhereIF(dto.ActionTtype != null, x => x.ActionType == dto.ActionTtype) .WhereIF(string.IsNullOrEmpty(dto.UserName) == false, x => x.UserName.Contains(dto.UserName)) .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime) .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 坐席动作列表导出 /// /// /// [HttpPost("telaction-list/export")] [LogFilterAlpha("导出日志")] public async Task TelActionListExport([FromBody] ExportExcelDto dto) { var total = 0; var items = new List(); var query = _telActionRecordRepository.Queryable() .WhereIF(string.IsNullOrEmpty(dto.QueryDto.TelNo) == false, x => x.TelNo.Contains(dto.QueryDto.TelNo)) .WhereIF(dto.QueryDto.ActionTtype != null, x => x.ActionType == dto.QueryDto.ActionTtype) .WhereIF(string.IsNullOrEmpty(dto.QueryDto.UserName) == false, x => x.UserName.Contains(dto.QueryDto.UserName)) .WhereIF(dto.QueryDto.StartTime.HasValue, x => x.CreationTime >= dto.QueryDto.StartTime) .WhereIF(dto.QueryDto.EndTime.HasValue, x => x.CreationTime <= dto.QueryDto.EndTime) .OrderByDescending(x => x.CreationTime); if (dto.IsExportAll) { items = await query.ToListAsync(); } else { (total, items) = await query.ToPagedListAsync(dto.QueryDto.PageIndex, dto.QueryDto.PageSize); } dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos); var dtos = _mapper.Map>(items) .Select(stu => _mapper.Map(stu, typeof(TelActionListRep), dynamicClass)) .Cast() .ToList(); var stream = ExcelHelper.CreateStream(dtos); return ExcelStreamResult(stream, "坐席动作列表"); } /// /// 坐席动作列表基础数据 /// /// [HttpGet("telaction-basedata")] public async Task TelActionBaseData() { var actionType = EnumExts.GetDescriptions(); if (_appOptions.Value.GetDefaultAppScopeConfiguration().CallCenterType == AppDefaults.CallCenterType.XingTang) { actionType = actionType.Where(m => new int[] { 4, 5 }.Contains(m.Key) == false).ToList(); } return new { ActionType = actionType, }; } #endregion #region 话务队列信息 /// /// 今日等待数 /// /// [HttpGet("query-todaywaitnum")] public async Task QueryToDayWaitNum() { int count = await _trCallRecordRepository.Queryable() .Where(x => x.CreatedTime.Date == DateTime.Now.Date) .Where(x => x.QueueTims > 0 && x.Duration == 0) .CountAsync(); return count; } #endregion #region 话机状态 /// /// 重置话机状态 /// /// [HttpPost("tels-status-refreshservice")] [AllowAnonymous] public async Task TelsStatusRefreshService() { try { var list = await _workRepository.Queryable().Where(x => 1 == 1 && !x.EndTime.HasValue).ToListAsync(); var tellist = await _callTelClient.QueryTelStateAsync(new QueryTelStateRequest { }, HttpContext.RequestAborted); foreach (var item in list) { var telmodel = tellist.AgentList.First(x => x.TelNo == item.TelNo); if (telmodel != null) { if (telmodel.State == "logout") { await _iPPbxApplication.ResetTelStatus(item.Id, null, HttpContext.RequestAborted); //var telRest = await _telRestRepository.GetAsync(x => x.TelNo == item.TelNo && !x.EndTime.HasValue, HttpContext.RequestAborted); //if (telRest is not null) //{ // telRest.EndRest(); // await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted); //} //item.OffDuty(); //await _workRepository.UpdateAsync(item, HttpContext.RequestAborted); //_cacheWork.Remove(item.GetKey(KeyMode.UserId)); //_cacheWork.Remove(item.GetKey(KeyMode.TelNo)); //var listx = await _telActionRecordRepository.Queryable().Where(x => x.TelNo == item.TelNo && !x.EndTime.HasValue).ToListAsync(); //foreach (var itemx in listx) //{ // itemx.EndAction(); // await _telActionRecordRepository.UpdateAsync(itemx); //} } } } } catch { } } #endregion #endregion } }