123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038 |
- 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<TrCallRecord> _trCallRecordRepository;
- private readonly ICallApplication _callApplication;
- private readonly IRepository<TrCallEvaluate> _trCallEvaluate;
- private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
- private readonly ILogger<IPPbxController> _logger;
- private readonly IOrderRepository _orderRepository;
- private readonly IRepository<OrderVisit> _orderVisitRepository;
- private readonly IUserCacheManager _userCacheManager;
- private readonly ICapPublisher _capPublisher;
- private readonly ITelRestRepository _telRestRepository;
- private readonly IRepository<User> _userRepository;
- private readonly ITelApplication _telApplication;
- private readonly IRepository<Quality.Quality> _qualiteyRepository;
- private readonly IRepository<QualityTemplate> _qualityTemplate;
- private readonly ISystemSettingCacheManager _systemSettingCacheManager;
- private readonly IRepository<TelActionRecord> _telActionRecordRepository;
- private readonly ITypedCache<Work> _cacheWork;
- private readonly ISystemMobilAreaApplication _systemMobilAreaApplication;
- private readonly IRepository<Work> _workRepository;
- private readonly Publisher _publisher;
- private readonly ITrCallRecordRepository _callRecordRepository;
- private readonly IIPPbxApplication _iPPbxApplication;
- private readonly ICallTelClient _callTelClient;
- private readonly IRepository<TelOperation> _telOperationRepository;
- private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
- public IPPbxController(IMapper mapper, IUserDomainService userDomainService,
- ISessionContext sessionContext, IRepository<TrCallRecord> trCallRecordRepository,
- ICallApplication callApplication, IRepository<TrCallEvaluate> trCallRecord,
- ISystemDicDataCacheManager systemDicDataCacheManager, ILogger<IPPbxController> logger,
- IOrderRepository orderRepository, IRepository<OrderVisit> orderVisitRepository,
- IUserCacheManager userCacheManager, ICapPublisher capPublisher,
- ITelRestRepository telRestRepository, IRepository<User> userRepository,
- ITelApplication telApplication, IRepository<Quality.Quality> qualiteyRepository,
- IRepository<QualityTemplate> qualityTemplate,
- ISystemSettingCacheManager systemSettingCacheManager, IRepository<TelActionRecord> telActionRecordRepository,
- ISystemMobilAreaApplication systemMobilAreaApplication,
- IRepository<Work> workRepository,
- Publisher publisher,
- ITrCallRecordRepository callRecordRepository,
- ITypedCache<Work> cacheWork,
- IIPPbxApplication iPPbxApplication,
- ICallTelClient callTelClient,
- IRepository<TelOperation> telOperationRepository,
- IOptionsSnapshot<AppConfiguration> 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 分机
- /// <summary>
- /// 查询所有分机
- /// </summary>
- /// <returns></returns>
- [HttpGet("query-tels")]
- public async Task<List<TrTelDto>> 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<List<TrTelDto>>(tels);
- string callOutQueueId = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutQueueId).SettingValue[0];
- returnlist.ForEach(x =>
- {
- x.CallOutQueue = callOutQueueId;
- });
- return returnlist;
- }
- /// <summary>
- /// 查询所有分机状态
- /// </summary>
- /// <returns></returns>
- [HttpGet("query-telstate")]
- [AllowAnonymous]
- public async Task<IReadOnlyList<TrTelStateDto>> 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<IReadOnlyList<TrTelStateDto>>(tels.AgentList);
- }
- /// <summary>
- /// 查询坐席分机状态及通话信息
- /// </summary>
- /// <param name="telno"></param>
- /// <returns></returns>
- [HttpGet("query-telstatebyno")]
- [AllowAnonymous]
- public async Task<TrTelStateDto> 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 黑白名单
- /// <summary>
- /// 新增黑白名单
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [LogFilter("新增黑白名单")]
- [HttpPost("add-blacklist")]
- public async Task AddBlacklist([FromBody] TrAddBlacklistDto dto)
- {
- await _callTelClient.AddBlacklistAsync(new AddBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
- }
- /// <summary>
- /// 删除黑白名单
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [LogFilter("删除黑白名单")]
- [HttpPost("remove-blacklist")]
- public async Task DelBlacklist([FromBody] TrDelBlacklistDto dto)
- {
- await _callTelClient.DelBlacklistAsync(new DelBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
- }
- /// <summary>
- /// 查询黑白名单
- /// </summary>
- /// <returns></returns>
- [HttpGet("query-blacklist")]
- public async Task<IReadOnlyList<TrQueryBlacklistResponseDto>> QueryBlacklist([FromQuery] TrQueryBlacklistDto dto)
- {
- var blacklist = await _callTelClient.QueryBlacklistAsync(new QueryBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
- return _mapper.Map<IReadOnlyList<TrQueryBlacklistResponseDto>>(blacklist);
- }
- #endregion
- #region 签入后调用
- /// <summary>
- /// 上班
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpPost("on-duty")]
- public async Task<TrOnDutyResponseDto> OnDuty([FromBody] TrOnDutyDto dto)
- {
- return await _callApplication.SignInAsync(new SignInDto() { TelNo = dto.TelNo, TelModelState = dto.TelModelState }, HttpContext.RequestAborted);
- }
- /// <summary>
- /// 下班
- /// </summary>
- /// <returns></returns>
- [HttpPost("off-duty")]
- public async Task OffDuty()
- {
- //await _userDomainService.TrOffDutyAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
- await _telApplication.SignOutAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
- }
- /// <summary>
- /// 查询当前用户的分机状态
- /// </summary>
- /// <returns></returns>
- [HttpGet("tel-state")]
- public async Task<TrOnDutyResponseDto> TelState()
- {
- return await _callApplication.GetTelStateAsync(HttpContext.RequestAborted);
- }
- /// <summary>
- /// 切换状态
- /// </summary>
- /// <param name="userId"></param>
- /// <param name="isCallOut"></param>
- /// <returns></returns>
- [HttpPost("change-telmodel")]
- public async Task ChangeTelModel([FromBody] ChangeTelModelDto dto)
- {
- await _callApplication.ChangeTelModel(dto.isCallOut, HttpContext.RequestAborted);
- }
- /// <summary>
- /// 下班-管理手动操作
- /// </summary>
- /// <param name="telNo"></param>
- /// <returns></returns>
- [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 添添呼小休
- /// <summary>
- /// 小休页面基础数据
- /// </summary>
- /// <returns></returns>
- [HttpGet("rest/basedata")]
- public async Task<object> RestBasePaegData()
- {
- var rsp = new
- {
- RestReason = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.RestReason)
- };
- return rsp;
- }
- /// <summary>
- /// 小休
- /// </summary>
- /// <returns></returns>
- [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);
- }
- /// <summary>
- /// 取消小休
- /// </summary>
- /// <returns></returns>
- [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 添添呼话后整理
- /// <summary>
- /// 分机动作开始
- /// </summary>
- /// <returns></returns>
- [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);
- }
- }
- /// <summary>
- /// 分机动作结束
- /// </summary>
- /// <returns></returns>
- [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 通话记录(对外)
- /// <summary>
- /// 接收通话记录
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [AllowAnonymous]
- [HttpPost("receivecallrecord")]
- public async Task<OpenResponse> 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<TrCallRecord>(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<QualityTemplateDetail>((x, d) => x.Id == d.TemplateId)
- .LeftJoin<QualityItem>((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<OrderDto>(order);
- publishCallRecordDto.Order = orderDto;
- }
- var trCallDto = _mapper.Map<TrCallDto>(model);
- publishCallRecordDto.TrCallRecordDto = trCallDto;
- if (string.IsNullOrEmpty(model.AgentTransferNumber))
- {
- //推省上
- await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallBye, publishCallRecordDto);
- }
- return OpenResponse.Ok("success");
- }
- /// <summary>
- /// 接受通话语音评价
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [AllowAnonymous]
- [HttpPost("receivecallevaluate")]
- public async Task<OpenResponse> ReceiveCallEvaluate([FromBody] ReceiveCallEvaluateDto dto)
- {
- var model = _mapper.Map<TrCallEvaluate>(dto);
- await _trCallEvaluate.AddAsync(model, HttpContext.RequestAborted);
- return OpenResponse.Ok("success");
- }
- /// <summary>
- /// 话机状态通知
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [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<EndActionInDto>());
- }
- #endregion
- #region 通话记录(对内)
- /// <summary>
- /// 获取通话记录列表
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpGet("calls/call-list")]
- public async Task<PagedDto<TrCallDto>> 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<List<TrCallDto>>(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<TrCallDto>(total, itemsResult);
- //return new PagedDto<TrCallDto>(total, _mapper.Map<IReadOnlyList<TrCallDto>>(items));
- }
- /// <summary>
- /// 通话记录导出
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpPost("calls/call-list/export")]
- [LogFilterAlpha("导出日志")]
- public async Task<FileStreamResult> GetCallListExport([FromBody] ExportExcelDto<GetCallListDto> dto)
- {
- var query = _callRecordRepository.GetCallList(dto.QueryDto);
- List<TrCallRecord> 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<ICollection<TrCallDto>>(data);
- dto.ColumnInfos.ForEach(x =>
- {
- if (x.Prop == "cpn")
- {
- x.Prop = "CPN";
- }
- if (x.Prop == "cdpn")
- {
- x.Prop = "CDPN";
- }
- });
- dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass<TrCallDto>(dto.ColumnInfos);
- var dtos = dataDtos
- .Select(stu => _mapper.Map(stu, typeof(TrCallDto), dynamicClass))
- .Cast<object>()
- .ToList();
- var stream = ExcelHelper.CreateStream(dtos);
- return ExcelStreamResult(stream, "通话记录");
- }
- /// <summary>
- /// 通话记录基础数据
- /// </summary>
- /// <returns></returns>
- [HttpGet("calls/basedata")]
- public async Task<object> CallBaseData()
- {
- return new
- {
- OnState = EnumExts.GetDescriptions<EOnState>(),
- CallDirection = EnumExts.GetDescriptions<ECallDirection>(),
- EndBy = EnumExts.GetDescriptions<EEndBy>(),
- PhoneTypes = EnumExts.GetDescriptions<EPhoneTypes>()
- };
- }
- #endregion
- #region 关联
- /// <summary>
- /// 可关联工单
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpGet("canlink-order")]
- public async Task<PagedDto<OrderDto>> 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<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
- }
- /// <summary>
- /// 可关联回访
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpGet("canlink-ordervisit")]
- public async Task<PagedDto<OrderVisitDto>> 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<OrderVisitDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDto>>(items));
- }
- /// <summary>
- /// 关联工单或者回访
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [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<OrderDto>(order), TrCallRecordDto = _mapper.Map<TrCallDto>(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 坐席动作记录
- /// <summary>
- /// 坐席动作列表
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpGet("telaction-list")]
- public async Task<PagedDto<TelActionListRep>> 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<TelActionListRep>(total, _mapper.Map<IReadOnlyList<TelActionListRep>>(items));
- }
- /// <summary>
- /// 坐席动作列表导出
- /// </summary>
- /// <param name="dto"></param>
- /// <returns></returns>
- [HttpPost("telaction-list/export")]
- [LogFilterAlpha("导出日志")]
- public async Task<FileStreamResult> TelActionListExport([FromBody] ExportExcelDto<TelActionListDto> dto)
- {
- var total = 0;
- var items = new List<TelActionRecord>();
- 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<TelActionListRep>(dto.ColumnInfos);
- var dtos = _mapper.Map<ICollection<TelActionListRep>>(items)
- .Select(stu => _mapper.Map(stu, typeof(TelActionListRep), dynamicClass))
- .Cast<object>()
- .ToList();
- var stream = ExcelHelper.CreateStream(dtos);
- return ExcelStreamResult(stream, "坐席动作列表");
- }
- /// <summary>
- /// 坐席动作列表基础数据
- /// </summary>
- /// <returns></returns>
- [HttpGet("telaction-basedata")]
- public async Task<object> TelActionBaseData()
- {
- var actionType = EnumExts.GetDescriptions<EActionType>();
- 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 话务队列信息
- /// <summary>
- /// 今日等待数
- /// </summary>
- /// <returns></returns>
- [HttpGet("query-todaywaitnum")]
- public async Task<int> 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 话机状态
- /// <summary>
- /// 重置话机状态
- /// </summary>
- /// <returns></returns>
- [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
- }
- }
|