using Hotline.Application.FlowEngine; using Hotline.Caching.Interfaces; using Hotline.CallCenter.Calls; using Hotline.CallCenter.Devices; using Hotline.CallCenter.Ivrs; using Hotline.CallCenter.Tels; using Hotline.FlowEngine.WfModules; using Hotline.Permissions; using Hotline.Settings; using Hotline.Share.Dtos.CallCenter; using Hotline.Share.Dtos.FlowEngine; using Hotline.Share.Dtos.Trunk; using Hotline.Share.Enums.CallCenter; using Hotline.Share.Requests; using Hotline.Users; using MapsterMapper; using Microsoft.AspNetCore.Mvc; using SqlSugar; using Wex.Sdk; using Wex.Sdk.Tel; using XF.Domain.Authentications; using XF.Domain.Cache; using XF.Domain.Constants; using XF.Domain.Exceptions; using XF.Utility.EnumExtensions; using System.Linq; using Hotline.Share.Dtos; using Microsoft.AspNetCore.Authorization; using XF.Domain.Repository; namespace Hotline.Api.Controllers { /// /// 话机设备管理 /// public class PbxController : BaseController { private readonly ITelRepository _telRepository; private readonly ITelRestRepository _telRestRepository; private readonly ITelDomainService _telDomainService; private readonly ITypedCache _cacheTel; private readonly ITypedCache _cacheTelGroup; private readonly ITelGroupRepository _telGroupRepository; private readonly IMapper _mapper; private readonly IWorkRepository _workRepository; private readonly IDeviceManager _deviceManager; private readonly IUserCacheManager _userCacheManager; private readonly ISessionContext _sessionContext; private readonly ICallRepository _callRepository; private readonly IRepository _trunkIvrManagerRepository; private readonly IIvrCategoryRepository _ivrCategoryRepository; private readonly IWorkflowApplication _workflowApplication; private readonly ISystemSettingCacheManager _systemSettingCacheManager; private readonly IIvrDomainService _ivrDomainService; private readonly IIvrCacheManager _ivrCacheManager; private readonly ILogger _logger; private readonly ICallDetailRepository _callDetailRepository; private readonly IRepository _userRepository; private readonly IWexClient _wexClient; private readonly IWexTelGroupRepository _wexTelGroupRepository; public PbxController( ITelRepository telRepository, ITelRestRepository telRestRepository, ITelDomainService telDomainService, ITypedCache cacheTel, ITypedCache cacheTelGroup, ITelGroupRepository telGroupRepository, IMapper mapper, IWorkRepository workRepository, IDeviceManager deviceManager, IUserCacheManager userCacheManager, ISessionContext sessionContext, ICallRepository callRepository, IRepository trunkIvrManagerRepository, IIvrCategoryRepository ivrCategoryRepository, IWorkflowApplication workflowApplication, ISystemSettingCacheManager systemSettingCacheManager, IIvrDomainService ivrDomainService, IIvrCacheManager ivrCacheManager, ILogger logger, ICallDetailRepository callDetailRepository, IRepository userRepository, IWexClient wexClient, IWexTelGroupRepository wexTelGroupRepository) { _telRepository = telRepository; _telRestRepository = telRestRepository; _telDomainService = telDomainService; _cacheTel = cacheTel; _cacheTelGroup = cacheTelGroup; _telGroupRepository = telGroupRepository; _mapper = mapper; _workRepository = workRepository; _deviceManager = deviceManager; _userCacheManager = userCacheManager; _sessionContext = sessionContext; _callRepository = callRepository; _trunkIvrManagerRepository = trunkIvrManagerRepository; _ivrCategoryRepository = ivrCategoryRepository; _workflowApplication = workflowApplication; _systemSettingCacheManager = systemSettingCacheManager; _ivrDomainService = ivrDomainService; _ivrCacheManager = ivrCacheManager; _logger = logger; _callDetailRepository = callDetailRepository; _userRepository = userRepository; _wexClient = wexClient; _wexTelGroupRepository = wexTelGroupRepository; } #region 话机 /// /// 根据设备自动同步分机数据到数据库 /// /// [Permission(EPermission.SyncTelsAsync)] [HttpPut("sync-tels")] public async Task SyncTelsAsync() { var dbTels = await _telRepository.QueryAsync(); foreach (var dbTel in dbTels) { _cacheTel.Remove(dbTel.No); } //await _telRepository.RemoveRangeAsync(dbTels); var tels = await _telDomainService.QueryTelsAsync(HttpContext.RequestAborted); var list = tels.ExceptBy(dbTels.Select(e => e.No), x => x.No).ToList(); var delList = dbTels.ExceptBy(tels.Select(e => e.No), x => x.No).ToList(); for (int i = 0; i < delList.Count; i++) { delList[i].SoftDelete(); } await _telRepository.UpdateRangeAsync(delList); await _telRepository.AddRangeAsync(list); } /// /// 查询所有分机 /// /// [Permission(EPermission.QueryTels)] [HttpGet("query-tels")] public async Task> QueryTels() { var tels = await _telRepository.QueryExtAsync(d => true, d => d.Includes(x => x.Groups)); return _mapper.Map>(tels); } #endregion #region 分机组 /// /// 页面基础信息 /// /// [Permission(EPermission.GetBaseInfoGroup)] [HttpGet("base-info-group")] public async Task GetBaseInfoGroup() { return new { Distributions = EnumExts.GetDescriptions() }; } /// /// 查询所有分机组 /// /// [Permission(EPermission.QueryTelGroups)] [HttpGet("query-telgroups")] public async Task> QueryTelGroups() { var groups = await _telGroupRepository.QueryExtAsync(d => true, d => d.Includes(x => x.Tels)); return _mapper.Map>(groups); } /// /// 新增分机组 /// /// /// [Permission(EPermission.AddTelGroup)] [HttpPost("add-telgroup")] public async Task AddTelGroup([FromBody] AddTelGroupDto dto) { var works = await _workRepository.QueryAsync(d => dto.TelNos.Contains(d.TelNo) && !d.EndTime.HasValue); await _deviceManager.AssginConfigGroupAsync( dto.No, dto.Distribution, ext: works.Select(d => d.TelNo).ToList(), voiceFile: dto.Voice ?? null, cancellationToken: HttpContext.RequestAborted); var group = _mapper.Map(dto); var tels = await _telRepository.QueryAsync(d => dto.TelNos.Contains(d.No)); group.Tels = tels; await _telGroupRepository.AddNavTelsAsync(group, HttpContext.RequestAborted); _cacheTelGroup.Remove(dto.No); } /// /// 更新分机组 /// /// /// [Permission(EPermission.UpdateTelGroup)] [HttpPut("update-telgroup")] public async Task UpdateTelGroup([FromBody] UpdateTelGroupDto dto) { var works = await _workRepository.QueryAsync(d => dto.TelNos.Contains(d.TelNo) && !d.EndTime.HasValue); await _deviceManager.AssginConfigGroupAsync( dto.No, dto.Distribution, ext: works.Select(d => d.TelNo).ToList(), voiceFile: dto.Voice ?? null, cancellationToken: HttpContext.RequestAborted); var group = _mapper.Map(dto); var tels = await _telRepository.QueryAsync(d => dto.TelNos.Contains(d.No)); group.Tels = tels; await _telGroupRepository.UpdateNavTelsAsync(group, HttpContext.RequestAborted); _cacheTelGroup.Remove(dto.No); } #endregion #region 话机设备操作 /// /// 查询小修流程开启参数 /// /// [HttpGet("flow-start")] public async Task GetFlowStartOptionsAsync() { return await _workflowApplication.GetStartOptionsAsync(WorkflowModuleConsts.TelRestApply, HttpContext.RequestAborted); } /// /// 分机休息 /// /// [Permission(EPermission.Rest)] [HttpPost("rest")] public async Task Rest([FromBody] StartRestDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); bool isCanApply = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.RestApproval).SettingValue[0]); if (isCanApply) { var isApply = await _telRepository.IsApplyingAsync(work.TelNo, HttpContext.RequestAborted); if (isApply) throw new UserFriendlyException("分机休息申请正在审核阶段"); } var isResting = await _telRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted); if (isResting) throw new UserFriendlyException("当前坐席正在休息"); var user = _userRepository.Get(work.UserId); var telRest = new TelRest(work.TelId, work.TelNo, work.UserId, work.UserName, dto.Reason, isCanApply,user.StaffNo); await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted); if (!isCanApply) { await _telDomainService.TelRestApplyPassAsync(telRest.Id, HttpContext.RequestAborted); } else { var startWorkflowDto = _mapper.Map(dto); startWorkflowDto.DefinitionModuleCode = WorkflowModuleConsts.TelRestApply; startWorkflowDto.Title = dto.Reason; await _workflowApplication.StartWorkflowAsync(startWorkflowDto, telRest.Id, HttpContext.RequestAborted); } } #region 威尔信 /// /// 分机休息流程开始 /// /// /// [Permission(EPermission.Rest)] [HttpPost("rest-flow")] public async Task RestFlow([FromBody] StartRestDto dto) { var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo },HttpContext.RequestAborted); if (tel.Data == null || tel.Data.Count == 0) throw UserFriendlyException.SameMessage("未找到分机信息"); if (tel.Data[0].Sigin == 0) throw UserFriendlyException.SameMessage("分机未签入,不能休息"); var isApply = await _telRepository.IsApplyingAsync(tel.Data[0].TelNo, HttpContext.RequestAborted); if (isApply) throw UserFriendlyException.SameMessage("分机休息申请正在审核阶段"); var isResting = await _telRepository.IsRestingAsync(tel.Data[0].TelNo, HttpContext.RequestAborted); if (isResting) throw new UserFriendlyException("当前坐席正在休息"); var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, true, _sessionContext.StaffNo); await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted); var startWorkflowDto = _mapper.Map(dto); startWorkflowDto.DefinitionModuleCode = WorkflowModuleConsts.TelRestApply; startWorkflowDto.Title = dto.Reason; await _workflowApplication.StartWorkflowAsync(startWorkflowDto, telRest.Id, HttpContext.RequestAborted); } /// /// 开始分机休息 /// /// /// [Permission(EPermission.Rest)] [HttpGet("begin-rest")] public async Task BeginRest() { var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted); if (tel.Data == null || tel.Data.Count == 0) throw UserFriendlyException.SameMessage("未找到分机信息"); if (tel.Data[0].Sigin == 0) throw UserFriendlyException.SameMessage("分机未签入,不能休息"); var telRest = await _telRestRepository.GetAsync(x => !x.EndTime.HasValue && !x.StartTime.HasValue && x.TelNo == tel.Data[0].TelNo, HttpContext.RequestAborted); if (telRest == null) throw new UserFriendlyException($"错误"); //if (string.IsNullOrEmpty(id)) // throw UserFriendlyException.SameMessage("无效分机休息编号"); //var telRest = await _telRestRepository.GetAsync(id, HttpContext.RequestAborted); //if (telRest == null) // throw new UserFriendlyException($"无效分机休息编号, telRestId: {id}", "无效分机休息编号"); telRest.ApplyStatus = ETelRestApplyStatus.Resting; telRest.StartTime = DateTime.Now; await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted); } /// /// 分机休息 /// /// /// [Permission(EPermission.Rest)] [HttpPost("rest-add")] public async Task Rest([FromBody] ResstDto dto) { var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted); if (tel.Data == null || tel.Data.Count == 0) throw UserFriendlyException.SameMessage("未找到分机信息"); if (tel.Data[0].Sigin == 0) throw UserFriendlyException.SameMessage("分机未签入,不能休息"); var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, false, _sessionContext.StaffNo); await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted); } /// /// 删除分机休息 /// /// /// [Permission(EPermission.Rest)] [HttpGet("rest-del")] public async Task DelRest() { var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted); if (tel.Data == null || tel.Data.Count == 0) throw UserFriendlyException.SameMessage("未找到分机信息"); if (tel.Data[0].Sigin == 0) throw UserFriendlyException.SameMessage("分机未签入,不能休息"); var restingTel = await _telRestRepository.GetAsync(d => d.TelId == tel.Data[0].TelNo && !d.EndTime.HasValue, HttpContext.RequestAborted); if (restingTel is null) throw new UserFriendlyException("未查询到分机休息信息"); await _telRestRepository.RemoveAsync(restingTel.Id, false, HttpContext.RequestAborted); } /// /// 分机结束休息(威尔信) /// /// [Permission(EPermission.UnRest)] [HttpGet("un-rest-wex")] public async Task UnRestWEX() { var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted); if (tel.Data == null || tel.Data.Count == 0) throw UserFriendlyException.SameMessage("未找到分机信息"); if (tel.Data[0].Sigin == 0) throw UserFriendlyException.SameMessage("分机未签入,不能休息"); var telRest = await _telDomainService.UnRestWexAsync(tel.Data[0].TelNo, HttpContext.RequestAborted); return _mapper.Map(telRest); } #endregion /// /// 分机结束休息 /// [Permission(EPermission.UnRest)] [HttpPut("un-rest")] public async Task UnRest() { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var telRest = await _telDomainService.UnRestAsync(work.TelId, HttpContext.RequestAborted); return _mapper.Map(telRest); } /// /// 保持通话 /// [Permission(EPermission.Hold)] [HttpPut("hold")] public async Task Hold(string callId) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); await _telDomainService.HoldAsync(work.TelId, _sessionContext.RequiredUserId, _sessionContext.UserName, callId, HttpContext.RequestAborted); } /// /// 恢复通话(解除hold状态) /// [Permission(EPermission.UnHold)] [HttpPut("un-hold")] public async Task UnHold(string callId) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); await _telDomainService.UnHoldAsync(work.TelId, _sessionContext.RequiredUserId, callId, HttpContext.RequestAborted); } /// /// 分机呼分机 /// /// [Permission(EPermission.TelToTel)] [HttpPost("tel-to-tel")] public async Task TelToTel([FromBody] TelToTelDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); //获取对方是否在工作 var toWork = _userCacheManager.GetWorkByTel(dto.TelNo); if (toWork is null) throw UserFriendlyException.SameMessage("转接分机未进行工作"); //判断分机状态 var telState =await _deviceManager.QueryTelState(dto.TelNo,HttpContext.RequestAborted); if (telState != ETelStatus.Ready) throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中"); bool isRest = await _telRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted); if (isRest) throw new UserFriendlyException("被叫分机正在休息不能转接"); await _deviceManager.ExtToExtAsync(work.TelNo, dto.TelNo, HttpContext.RequestAborted); } /// /// 分机拨打外部电话 /// /// /// [Permission(EPermission.TelToOuter)] [HttpPost("tel-to-outer")] public async Task TelToOuter([FromBody] TelToOuterDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); await _deviceManager.ExtToOuterAsync(work.TelNo, dto.OuterNo, HttpContext.RequestAborted); } /// /// 指定模拟外线外呼(分机拨打外部电话) /// OM设备多个外线时调用此接口 /// /// /// [Permission(EPermission.TelToOuterByLine)] [HttpPost("tel-to-outer-line")] public async Task TelToOuterByLine([FromBody] TelToOuterByLineDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); string newOuter = dto.LineId + "," + dto.OuterNo; await _deviceManager.ExtToOuterAsync(work.TelNo, newOuter, HttpContext.RequestAborted); } /// /// 来电转分机 /// /// /// [Permission(EPermission.VisitorToTel)] [HttpPost("visitor-to-tel")] public async Task VisitorToTel([FromBody] VisitorToTelDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var toWork = _userCacheManager.GetWorkByTel(dto.TelNo); if (toWork is null) throw UserFriendlyException.SameMessage("转接分机未进行工作"); var totelState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted); if (totelState != ETelStatus.Ready) throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中"); bool isRest = await _telRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted); if (isRest) 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); else throw UserFriendlyException.SameMessage("当前分机没有通话"); } /// /// 来电转外部电话 /// /// /// [Permission(EPermission.VisitorToOuter)] [HttpPost("visitor-to-outer")] public async Task VisitorToOuter([FromBody] VisitorToOuterDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted); if (!string.IsNullOrEmpty(tel.ConversationId)) await _deviceManager.VisitorToOuterAsync(tel.ConversationId, dto.OuterNo, HttpContext.RequestAborted); else throw UserFriendlyException.SameMessage("当前分机没有通话"); } /// /// 来电转分机组队列 /// /// /// [Permission(EPermission.VisitorToGroup)] [HttpPost("visitor-to-group")] public async Task VisitorToGroup([FromBody] VisitorToGroupDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted); if (!string.IsNullOrEmpty(tel.ConversationId)) await _deviceManager.VisitorToGroupAsync(tel.ConversationId, dto.groupid, HttpContext.RequestAborted); else throw UserFriendlyException.SameMessage("当前分机没有通话"); } /// /// 去电转外部电话 /// /// /// /// [Permission(EPermission.OuterToOuter)] [HttpPost("outer-to-outer")] public async Task OuterToOuter([FromBody] OuterToOuterDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted); if (!string.IsNullOrEmpty(tel.ConversationId)) await _deviceManager.OuterToOuterAsync(tel.ConversationId, dto.OuterNo, HttpContext.RequestAborted); else throw UserFriendlyException.SameMessage("当前分机没有通话"); } /// /// 去电转分机 /// /// /// /// [Permission(EPermission.OuterToTel)] [HttpPost("outer-to-tel")] public async Task OuterToTel([FromBody] OuterToTelDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var toWork = _userCacheManager.GetWorkByTel(dto.TelNo); if (toWork is null) throw UserFriendlyException.SameMessage("转接分机未进行工作"); var totelState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted); if (totelState != ETelStatus.Ready) throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中"); bool isRest = await _telRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted); if (isRest) 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); else throw UserFriendlyException.SameMessage("当前分机没有通话"); } /// /// 三方会议 /// 先建立两方通话,然后调用保持通话接口,拨通第三方分机,然后再调用三方会议接口 /// 1. 分机 A 正在和 B 通话; /// 2. 分机 A 把原通话呼叫保持; /// 3. 分机 A 向 C 发起新的呼叫,并建立通话; /// 4. 此时,使用该 API 能够实现以分机 A 为主持方建立 A、B、C 的三方会议。 /// /// TelNo:会议发起方分机号 /// /// [Permission(EPermission.Conference)] [HttpPost("meeting")] public async Task Conference([FromBody] ConferenceDto dto) { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted); if (!string.IsNullOrEmpty(tel.ConversationId)) await _deviceManager.ConferenceMeetingAsync(dto.TelNo, HttpContext.RequestAborted); else throw UserFriendlyException.SameMessage("当前分机没有通话"); } #region 监听和强插 /// /// 监听分机 /// /// /// [Permission(EPermission.MonitorExt)] [HttpPost("monitor-ext")] public async Task MonitorExt([FromBody] MonitorExtRequest request) { await _deviceManager.MonitorExtAsync(request.firstTelNo, request.secondTelNo, HttpContext.RequestAborted); } /// /// 从监听到插播状态变换 /// 1. 已知:分机 A 在监听分机 B 与其通话方的通话; /// 2. 执行分机 A 的从监听到插播状态变换的 API; /// 3. 执行成功时,分机 A 与分机 B 建立通话,分机 B 的原通话方听保持音。 /// /// /// [Permission(EPermission.MonitorExtToTalk)] [HttpPost("monitor-ext-to-talk")] public async Task MonitorExtToTalk([FromBody] MonitorExtToTalkRequest request) { await _deviceManager.MonitorExtToTalkAsync(request.telNo, HttpContext.RequestAborted); } /// /// 从插播到监听状态变换 /// 1. 已知:分机 A 在插播分机 B 的通话; /// 2. 执行分机 A 的从插播到监听状态变换的 API; /// 3. 执行成功时,分机 A 监听分机 B 及其原通话方的通话。 /// /// /// [Permission(EPermission.MonitorExtToListen)] [HttpPost("monitor-ext-to-listen")] public async Task MonitorExtToListen([FromBody] MonitorExtToListenRequest request) { await _deviceManager.MonitorExtToListenAsync(request.telNo, HttpContext.RequestAborted); } #endregion #region 强插 /// /// 强插 /// 1. 已知:分机 A 当前空闲,分机 B 正在通话中; /// 2. 执行分机 A 强插分机 B 的 API; /// 3. 执行成功时,分机 A 振铃,摘机后即可形成三方通话。 /// /// /// [Permission(EPermission.BargeinExt)] [HttpPost("bargein-ext")] public async Task BargeinExt([FromBody] BargeinExtRequest request) { await _deviceManager.BargeinExtAsync(request.firstTelNo, request.secondTelNo, HttpContext.RequestAborted); } #endregion #region 强拆 /// /// 强拆分机 /// /// /// [Permission(EPermission.ClearExt)] [HttpPost("clear-ext")] public async Task ClearExt([FromBody] ClearExtRequest request) { //查询当前通话记录 //var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted); //if (call is null) // throw UserFriendlyException.SameMessage("无效通话,无法挂断"); //if (call.CallStatus == ECallStatus.Bye) // throw UserFriendlyException.SameMessage("通话已结束"); await _deviceManager.ClearExtAsync(request.CallId, HttpContext.RequestAborted); } /// /// 强拆来电 /// /// /// [Permission(EPermission.ClearVisitor)] [HttpPost("clear-visitor")] public async Task ClearVisitor([FromBody] ClearVisitorRequest request) { //查询当前通话记录 var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted); if (call is null) throw UserFriendlyException.SameMessage("无效通话,无法挂断"); if (call.CallStatus == ECallStatus.Bye) throw UserFriendlyException.SameMessage("通话已结束"); await _deviceManager.ClearVisitorAsync(call.ConversationId, HttpContext.RequestAborted); } /// /// 强拆去电 /// /// /// [Permission(EPermission.ClearOuter)] [HttpPost("clear-outer")] public async Task ClearOuter([FromBody] ClearOuterRequest request) { //查询当前通话记录 var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted); if (call is null) throw UserFriendlyException.SameMessage("无效通话,无法挂断"); if (call.CallStatus == ECallStatus.Bye) throw UserFriendlyException.SameMessage("通话已结束"); await _deviceManager.ClearOuterAsync(call.ConversationId, HttpContext.RequestAborted); } #endregion #region 静音和取消静音 /// /// 静音 /// /// [Permission(EPermission.Mute)] [HttpGet("mute")] public async Task Mute() { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); await _deviceManager.MuteAsync(work.TelNo, HttpContext.RequestAborted); } /// /// 取消静音 /// /// [Permission(EPermission.UnMute)] [HttpGet("unmute")] public async Task UnMute() { var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId); if (work is null) throw UserFriendlyException.SameMessage("当前坐席暂未进行工作"); await _deviceManager.UnMuteAsync(work.TelNo, HttpContext.RequestAborted); } #endregion #endregion #region 线路管理 /// /// 获取线路列表 /// /// [Permission(EPermission.GetTrunkList)] [HttpGet("trunk-list")] public async Task> GetTrunkList() { return await _trunkIvrManagerRepository.Queryable() .Includes(d => d.WorkCategoryModel) .Includes(d => d.RestCategoryModel) .Includes(d => d.WorkToGroupModel.MappingField(z => z.No, () => d.WorkToGroup)) .Includes(d => d.RestToGroupModel.MappingField(z => z.No, () => d.RestToGroup)) .ToListAsync(); } /// /// 获取线路对象 /// /// /// [Permission(EPermission.GetTrunk)] [HttpGet("trunk/{id}")] public async Task GetTrunk(string id) { var trunk = await _trunkIvrManagerRepository.GetAsync(id, HttpContext.RequestAborted); if (trunk is null) throw UserFriendlyException.SameMessage("无效线路"); return trunk; } /// /// 新增线路 /// /// /// [Permission(EPermission.AddTrunk)] [HttpPost("addtrunk")] public async Task AddTrunk([FromBody] AddTrunkDto dto) { var trunk = _mapper.Map(dto); await _trunkIvrManagerRepository.AddAsync(trunk, HttpContext.RequestAborted); } /// /// 修改线路 /// /// /// [Permission(EPermission.UpdateTrunk)] [HttpPost("updatetrunk")] public async Task UpdateTrunk([FromBody] UpdateTrunkDto dto) { var trunk = await _trunkIvrManagerRepository.GetAsync(dto.Id, HttpContext.RequestAborted); if (trunk is null) throw UserFriendlyException.SameMessage("无效线路"); _mapper.Map(dto, trunk); await _trunkIvrManagerRepository.UpdateAsync(trunk, HttpContext.RequestAborted); } /// /// 删除线路 /// /// /// [Permission(EPermission.RemoveTrunk)] [HttpGet("removetrunk/{id}")] public async Task RemoveTrunk(string id) { var trunk = await _trunkIvrManagerRepository.GetAsync(id, HttpContext.RequestAborted); if (trunk is null) throw UserFriendlyException.SameMessage("无效线路"); await _trunkIvrManagerRepository.RemoveAsync(id, false, HttpContext.RequestAborted); } /// /// 线路页面基础信息 /// /// [Permission(EPermission.TrunkPageInfo)] [HttpGet("trunk-page-info")] public async Task TrunkPageInfo() { var ivr = await _ivrCategoryRepository.QueryAsync(); var group = await _telGroupRepository.QueryAsync(); var workDay = new List(); workDay.Add(new WorkDayModel() { weekName = "星期日", weekValue = "0" }); workDay.Add(new WorkDayModel() { weekName = "星期一", weekValue = "1" }); workDay.Add(new WorkDayModel() { weekName = "星期二", weekValue = "2" }); workDay.Add(new WorkDayModel() { weekName = "星期三", weekValue = "3" }); workDay.Add(new WorkDayModel() { weekName = "星期四", weekValue = "4" }); workDay.Add(new WorkDayModel() { weekName = "星期五", weekValue = "5" }); workDay.Add(new WorkDayModel() { weekName = "星期六", weekValue = "6" }); return new { WorkCategorys = ivr, RestCategory = ivr, WorkToGroup = group, RestToGroup = group, WorkDay = workDay }; } #endregion #region 话机业务 /// /// 语音评价 /// /// /// [Permission(EPermission.Evaluate)] [HttpGet("evaluate/{callId}")] public async Task Evaluate(string callId) { //检查通话是否存在 var call = await _callRepository.GetAsync(callId, HttpContext.RequestAborted); if (call is null) { throw new UserFriendlyException("未找到当前通话"); } if (call.CallDirection != ECallDirection.In) { throw new UserFriendlyException("当前通话不是来电,不能发送评价邀请"); } if (call.CallStatus == ECallStatus.Bye) { throw new UserFriendlyException("当前通话已结束"); } if (call.CallStatus != ECallStatus.Answered && call.CallStatus != ECallStatus.Answer) { throw new UserFriendlyException("当前未进行通话,不能发送评价邀请"); } //获取配置 var correct = _ivrDomainService.GetCorrectIvr(call.ToNo, true); if (correct is null) throw new UserFriendlyException("系统未配置评价,请联系管理员"); //检查是否有评价录音配置 var ivrList = await _ivrCacheManager.GetIvrsAsync(HttpContext.RequestAborted); if (!ivrList.Any()) throw new UserFriendlyException("未查到任何ivr配置"); var ivr = ivrList.First(x => x.IvrCategoryId == correct.ReturnValue && x.IsRoot); _logger.LogInformation("transfer to ivr.no:{ivrNo}", ivr.No); //写入子表 var detail = new CallDetail() { CallId = call.Id, CallStatus = ECallStatus.Evaluate, ConversationId = call.ConversationId, EventName = "EVALUATE", FromNo = call.FromNo, ToNo = call.ToNo, }; await _callDetailRepository.AddAsync(detail, HttpContext.RequestAborted); await _deviceManager.VisitorToMenuAsync(call.ConversationId, ivr.No, HttpContext.RequestAborted); } #endregion #region 威尔信分机和分机组操作 /// /// 查询分机 /// /// /// [Permission(EPermission.QueryTels)] [HttpGet("query-tel-list")] public async Task> QueryTelList([FromQuery]QueryTelListDto dto) { var rsp = await _wexClient.QueryTelsPageAsync(new QueryTelPageRequest() { PageIndex = dto.PageIndex, PageSize = dto.PageSize,input = new Input() { TelNo = dto.TelNo } }, HttpContext.RequestAborted); var telList = rsp?.Data; var count = rsp.Count; if (telList!=null) { var telGroup = _wexTelGroupRepository.Queryable().ToList(); var list = (from a in telList join b in telGroup on a.TelNo equals b.TelNo into output from j in output.DefaultIfEmpty() select new TelListPageDto { Id = (j==null ? "": j.Id), TelNo = a.TelNo, GroupName = (j==null ? "": j.GroupName), GroupId = (j==null ? 0: j.GroupId) }).ToList(); return new PagedDto(count, list); } return new PagedDto(0, null); } /// /// 新增或修改分机关联分机组 /// /// /// [Permission(EPermission.AddTelGroup)] [HttpPost("add-update-telgroup")] public async Task AddOrUpdateTelGroup([FromBody]AddOrUpdateTelGroupDto dto) { if(string.IsNullOrEmpty(dto.Id)) { //新增 var telGroup = new WexTelGroup(); telGroup.TelNo = dto.TelNo; telGroup.GroupId = dto.GroupId; telGroup.GroupName = dto.GroupName; telGroup.ZuoGroupName = dto.ZuoGroupName; await _wexTelGroupRepository.AddAsync(telGroup, HttpContext.RequestAborted); } else { //修改 var telGroup = await _wexTelGroupRepository.GetAsync(dto.Id, HttpContext.RequestAborted); if (telGroup is null) { throw UserFriendlyException.SameMessage("未找到对应数据,无法修改"); } if (dto.GroupId == 0) { await _wexTelGroupRepository.RemoveAsync(dto.Id, false, HttpContext.RequestAborted); } else { telGroup.GroupId = dto.GroupId; telGroup.GroupName = dto.GroupName; telGroup.ZuoGroupName = dto.ZuoGroupName; await _wexTelGroupRepository.UpdateAsync(telGroup); } } } /// /// 分机组列表 /// /// [Permission(EPermission.QueryTelGroups)] [HttpGet("telgroup-list")] public async Task> TelGroupList() { var rsp = await _wexClient.QueryGroupAsync(new QueryGroupRequest() { }, HttpContext.RequestAborted); var groupList = rsp.Data; var list = _mapper.Map>(groupList); return list; } /// /// 根据分机号查询分机关联坐席组 /// /// /// [Permission(EPermission.QueryTelGroups)] [HttpGet("telgroup/{telno}")] public async Task TelGroup(string telno) { var telGroup = await _wexTelGroupRepository.GetAsync(x => x.TelNo == telno, HttpContext.RequestAborted); if (telGroup is null) { return new WexTelGroup() { TelNo = telno, }; } return telGroup; } #endregion } }