using Consul; using DotNetCore.CAP; using Hotline.Ai.Jths; using Hotline.Ai.Visit; using Hotline.Application.Quality; using Hotline.Caching.Interfaces; using Hotline.Caching.Services; using Hotline.CallCenter.Devices; using Hotline.Orders; using Hotline.Repository.SqlSugar.Extensions; using Hotline.Settings; using Hotline.Share.Dtos; using Hotline.Share.Dtos.Ai; using Hotline.Share.Dtos.Order; using Hotline.Share.Enums.Order; using Hotline.Share.Enums.Quality; using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; using SqlSugar; using System.Threading; using XF.Domain.Authentications; using XF.Domain.Constants; using XF.Domain.Exceptions; using XF.Domain.Repository; namespace Hotline.Api.Controllers { public class AiController: BaseController { private readonly ISystemSettingCacheManager _systemSettingCacheManager; private readonly IRepository _aiOrderVisitRepository; private readonly IRepository _aiOrderVisitDetailRepository; private readonly IRepository _orderVisitRepository; private readonly IRepository _orderVisitDetailRepository; private readonly IMapper _mapper; private readonly IOptionsSnapshot _options; private readonly IAiVisitService _aiVisitService; private readonly ILogger _logger; private readonly ICapPublisher _capPublisher; private readonly IOrderRepository _orderRepository; private readonly IQualityApplication _qualityApplication; private readonly ISystemDicDataCacheManager _sysDicDataCacheManager; public AiController(ISystemSettingCacheManager systemSettingCacheManager,IRepository aiOrderVisitRepository,IRepository aiOrderVisitDetailRepository,IRepository orderVisitRepository,IRepository orderVisitDetailRepository,IMapper mapper, IOptionsSnapshot options,IAiVisitService aiVisitService, ILogger logger,ICapPublisher capPublisher,IOrderRepository orderRepository,IQualityApplication qualityApplication, ISystemDicDataCacheManager sysDicDataCacheManager) { _systemSettingCacheManager = systemSettingCacheManager; _aiOrderVisitRepository = aiOrderVisitRepository; _aiOrderVisitDetailRepository = aiOrderVisitDetailRepository; _orderVisitRepository = orderVisitRepository; _orderVisitDetailRepository = orderVisitDetailRepository; _mapper = mapper; _options = options; _aiVisitService = aiVisitService; _logger = logger; _capPublisher = capPublisher; _orderRepository = orderRepository; _qualityApplication = qualityApplication; _sysDicDataCacheManager = sysDicDataCacheManager; } /// /// 智能语音导航配置查询 /// /// /// [HttpGet("ivr/access-confirm")] [AllowAnonymous] public async Task AccessConfirm([FromQuery]int count) { //获取是否开启智能语音队列 bool isAiIvr = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsAIIVR)?.SettingValue[0]); if (!isAiIvr) return false; //获取智能语音队列数 var queueNum = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.QueueNum)?.SettingValue[0]); if (count < queueNum) return false; //判断是否在时间段内 var aiIVRTimeArr = _systemSettingCacheManager.GetSetting(SettingConstants.AIIVRTime)?.SettingValue; bool IsOk = false; aiIVRTimeArr.ForEach((item) => { List times = JsonConvert.DeserializeObject>(item); var beginTime = DateTime.Parse(DateTime.Now.ToShortDateString() + " " + times[0] + ":00"); var endTime = DateTime.Parse(DateTime.Now.ToShortDateString() + " " + times[1] + ":00"); if (DateTime.Now>beginTime && DateTime.Now< endTime) { IsOk = true; } }); return IsOk; } #region 智能回访 /// /// 智能回访外呼结果回传 /// /// /// [AllowAnonymous] [HttpPost("aivisit/aivisit-back")] public async Task AiVisitBack([FromBody]AiVisitBackDto dto) { _logger.LogInformation($"收到智能外呼结果回传:{JsonConvert.SerializeObject(dto)}"); var aiOrderVisit = await _aiOrderVisitRepository.Queryable() .Includes(x => x.AiOrderVisitDetails,s=>s.OrderVisit,d=>d.Order) .FirstAsync(x => x.BatchUid == dto.BatchUid); if (aiOrderVisit!=null) { //验证记录中是否存在有结果的任务 if (dto.TaskStatus == 6)//执行完 { var aiOrderVisitDetail = aiOrderVisit.AiOrderVisitDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid); if (aiOrderVisitDetail != null) { var callRecord = dto.CallRecordList.OrderBy(x => x.CallStartTime).LastOrDefault(); // 回访结果(ReturnVisit)[1成功、0不涉及、-1失败] if (callRecord != null) //有结果的任务 { aiOrderVisitDetail.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Ended; //更新AI子表 aiOrderVisitDetail.AiVisitTime = DateTime.Now; aiOrderVisit.VisitedCount++; //处理结果 var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id).ToList(); //先处理子表 //处理部门 var orgDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Org).ToList(); //过滤结果 var orgProcessingResults = new Kv(); //var orgHandledAttitude = new Kv(); ESeatEvaluate? seatEvaluate = null; var visitContent = ""; var seatVisitContent = ""; var volveConent = ""; bool? isSolve = null; bool? isContact = null; //通话录音 var recordUrl = callRecord.RecordUrl; foreach (var item in callRecord.QuestionnaireResult) { ////服务过程满意度 //if (item.QuestionId == _options.Value.QuestionIdOne) //{ // if (item.QuestionResult == "满意") // { // orgHandledAttitude = new Kv() { Key="4", Value="满意" }; // } // else // { // orgHandledAttitude = new Kv() { Key = "2", Value = "不满意" }; // } //} //是否联系 if (item.QuestionId == _options.Value.QuestionIdZone) { if (item.QuestionResult == "有联系") { isContact = true; } else if(item.QuestionResult == "没有联系") { isContact = false; } } //是否解决 if (item.QuestionId == _options.Value.QuestionIdOne) { if (item.QuestionResult == "得到解决") { isSolve = true; } else if(item.QuestionResult == "未得到解决") { isSolve = false; } } //办件结果满意度 if (item.QuestionId == _options.Value.QuestionIdTwo) { if (item.QuestionResult == "办件结果满意") { orgProcessingResults = new Kv() { Key = "4", Value = "满意" }; } else if(item.QuestionResult == "办件结果不满意") { orgProcessingResults = new Kv() { Key = "2", Value = "不满意" }; visitContent = callRecord.SceneVariable[_options.Value.VisitContentIdOne]; } } else { aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit; } //坐席是否满意 if (item.QuestionId == _options.Value.QuestionIdThree) { if (item.QuestionResult == "满意接电坐席") { seatEvaluate = ESeatEvaluate.Satisfied; } else if(item.QuestionResult == "不满意接电坐席") { seatEvaluate = ESeatEvaluate.NoSatisfied; seatVisitContent = callRecord.SceneVariable[_options.Value.VisitContentIdTwo]; } } } //先处理坐席(因没有坐席回访,所以默认满意) var seatDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Seat).ToList(); seatDetail.ForEach(x => { x.VoiceEvaluate = Share.Enums.Order.EVoiceEvaluate.Satisfied; x.SeatEvaluate = seatEvaluate; x.VisitContent = seatVisitContent; }); await _orderVisitDetailRepository.UpdateRangeAsync(seatDetail, HttpContext.RequestAborted); //处理结果 orgDetail.ForEach(x => { //x.OrgHandledAttitude = orgHandledAttitude; x.OrgProcessingResults = orgProcessingResults; x.VisitContent = visitContent; x.Volved = isSolve; x.IsContact = isContact; if (orgProcessingResults.Value == "不满意" || (string.IsNullOrEmpty(orgProcessingResults.Key) && seatEvaluate!=null && isSolve!=null && isContact!=null)) { //x.OrgNoSatisfiedReason = new List() { new Kv() { Key = "7", Value = "未回复" } }; //TODO 记录不满意原因到内容中供人工回访甄别选择不满意原因 aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit; } else { aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.Visited; } }); await _orderVisitDetailRepository.UpdateRangeAsync(orgDetail, HttpContext.RequestAborted); //var first = orgProcessingResults; //aiOrderVisitDetail.OrderVisit.OrderVisitDetails.FirstOrDefault(x => x.VisitTarget == EVisitTarget.Org); //处理主表 aiOrderVisitDetail.OrderVisit.AiVisitCount++; aiOrderVisitDetail.OrderVisit.VisitTime = DateTime.Now; aiOrderVisitDetail.OrderVisit.IsPutThrough = true; aiOrderVisitDetail.OrderVisit.VisitType = Share.Enums.Order.EVisitType.ChipVoiceVisit; aiOrderVisitDetail.OrderVisit.AiVisitTime(); aiOrderVisitDetail.IsSuccess = true; if (orgProcessingResults != null) { aiOrderVisitDetail.OrderVisit.NowEvaluate = orgProcessingResults; } //处理是否回访完成TODO await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted); //处理Order表 if (orgProcessingResults != null && aiOrderVisitDetail.OrderVisit.VisitState == EVisitState.Visited) { aiOrderVisitDetail.OrderVisit.Order.Visited(orgProcessingResults.Key, orgProcessingResults.Value); await _orderRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit.Order); //推省上 var orderDto = _mapper.Map(aiOrderVisitDetail.OrderVisit.Order); await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited, new PublishVisitDto() { Order = orderDto, No = aiOrderVisitDetail.OrderVisit.No, VisitType = aiOrderVisitDetail.OrderVisit.VisitType, VisitName = aiOrderVisitDetail.OrderVisit.CreatorName, VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime, VisitRemark = string.IsNullOrEmpty(visitContent) ? aiOrderVisitDetail.OrderVisit.NowEvaluate?.Value : visitContent, AreaCode = aiOrderVisitDetail.OrderVisit.Order.AreaCode!, SubjectResultSatifyCode = orgProcessingResults.Key, FirstSatisfactionCode = aiOrderVisitDetail.OrderVisit.Order.FirstVisitResultCode!, ClientGuid = "" }, cancellationToken: HttpContext.RequestAborted); //推门户 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto() { Id = aiOrderVisitDetail.OrderVisit.Id, Order = orderDto, OrderVisitDetails = _mapper.Map>(aiOrderVisitDetail.OrderVisit.OrderVisitDetails), VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime, VisitType = aiOrderVisitDetail.OrderVisit.VisitType, PublishTime = aiOrderVisitDetail.OrderVisit.PublishTime, }, cancellationToken: HttpContext.RequestAborted); } } else { aiOrderVisitDetail.AiOrderVisitState = (Share.Enums.Ai.EAiOrderVisitState)(dto.TaskStatus); //更新AI子表 aiOrderVisit.VisitedFailCount++; //处理回访主表 aiOrderVisitDetail.OrderVisit.AiVisitTime(); aiOrderVisitDetail.OrderVisit.AiVisitCount++; aiOrderVisitDetail.OrderVisit.IsCanAiVisit = true; aiOrderVisitDetail.OrderVisit.VisitState = EVisitState.WaitForVisit; aiOrderVisitDetail.IsSuccess = false; await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted); } //var callRecord = dto.CallRecordList.OrderByDescending(x => x.CallNo).FirstOrDefault(x => x.ReturnVisit == 1); await _aiOrderVisitDetailRepository.UpdateAsync(aiOrderVisitDetail, HttpContext.RequestAborted); if ((aiOrderVisit.VisitedFailCount+aiOrderVisit.VisitedCount)== aiOrderVisit.HasVisitCount) { aiOrderVisit.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.Ended; } await _aiOrderVisitRepository.UpdateAsync(aiOrderVisit, HttpContext.RequestAborted); #region 注释 //处理不满意结果(如果差评没有不满意原因则不能视为回访完成) --(不满意设置为失效,生成新的人工回访记录) //处理网站通知差评数据 //if (aiOrderVisitDetail.OrderVisit.Order.Source == ESource.Hotline && aiOrderVisitDetail.OrderVisit.OrderVisitDetails.Any(x => x.OrgHandledAttitude?.Key == "1" || x.OrgHandledAttitude?.Key == "2" || x.OrgProcessingResults?.Key == "1" || x.OrgProcessingResults?.Key == "2")) //{ // //处理老数据 // aiOrderVisitDetail.OrderVisit.VisitState = EVisitState.None; // await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit); // //推送老数据变更给门户 // await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto() // { // Id = aiOrderVisitDetail.OrderVisit.Id, // Order = _mapper.Map(aiOrderVisitDetail.OrderVisit.Order), // OrderVisitDetails = _mapper.Map>(aiOrderVisitDetail.OrderVisit.OrderVisitDetails), // VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime, // VisitType = aiOrderVisitDetail.OrderVisit.VisitType, // VisitState = aiOrderVisitDetail.OrderVisit.VisitState, // PublishTime = aiOrderVisitDetail.OrderVisit.PublishTime, // }, cancellationToken: HttpContext.RequestAborted); // //包含不满意数据,重新生成新的回访 // var newOrderVisit = _mapper.Map(aiOrderVisitDetail.OrderVisit); // newOrderVisit.InitId(); // newOrderVisit.VisitState = EVisitState.NoSatisfiedWaitForVisit; // newOrderVisit.VisitTime = null; // newOrderVisit.IsCanHandle = false; // newOrderVisit.IsCanAiVisit = false; // newOrderVisit.AiVisitCount = 0; // await _orderVisitRepository.AddAsync(newOrderVisit, HttpContext.RequestAborted); // var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id); // var list = _mapper.Map>(visitDetail); // list.ForEach(x => // { // x.VisitId = newOrderVisit.Id; // x.VoiceEvaluate = null; // x.VoiceEvaluate = null; // x.OrgHandledAttitude = null; // x.OrgNoSatisfiedReason = null; // x.OrgProcessingResults = null; // x.VisitContent = ""; // }); // await _orderVisitDetailRepository.AddRangeAsync(list, HttpContext.RequestAborted); // //推送新数据给门户 // await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto() // { // Id = newOrderVisit.Id, // Order = _mapper.Map(aiOrderVisitDetail.OrderVisit.Order), // OrderVisitDetails = _mapper.Map>(list), // VisitTime = newOrderVisit.VisitTime, // VisitType = newOrderVisit.VisitType, // VisitState = newOrderVisit.VisitState, // PublishTime = newOrderVisit.PublishTime, // }, cancellationToken: HttpContext.RequestAborted); //} //else //{ #endregion if (aiOrderVisitDetail.OrderVisit.VisitState == EVisitState.Visited) { //写入质检 await _qualityApplication.AddQualityAsync(EQualitySource.Visit, aiOrderVisitDetail.OrderVisit.Order.Id, aiOrderVisitDetail.OrderVisit.Id, HttpContext.RequestAborted); } //} } } } } /// /// 智能回访列表 /// /// /// [HttpGet("aivisit/aivisit-list")] public async Task> AiVisitList([FromQuery]AiVisitListDto dto) { var (total,items) = await _aiOrderVisitRepository.Queryable() .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Name.Contains(dto.Keyword)) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 智能回访明细 /// /// /// [HttpGet("aivisit/aivisitdetail-list")] public async Task> AiVisitDetailList([FromQuery]AiVisitDetailListDto dto) { var (total, items) = await _aiOrderVisitDetailRepository.Queryable() .Includes(x=>x.OrderVisit,x=>x.OrderVisitDetails) .Includes(x=>x.Order) .Where(x => x.AiOrderVisitId == dto.Id) .WhereIF(!string.IsNullOrEmpty(dto.Keyword),x=>x.Order.No.Contains(dto.Keyword) || x.Order.Title.Contains(dto.Keyword)) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 可进行智能回访记录 /// /// [HttpGet("aivisit/canaivisit-list")] public async Task> CanAiVisitList([FromQuery]CanAiVisitListDto dto) { var items= await _orderVisitRepository.Queryable() .Includes(x=>x.Order) .Where(x => x.VisitState == Share.Enums.Order.EVisitState.WaitForVisit && x.IsCanAiVisit == true) .WhereIF(dto.HotspotIds.Any(), x => dto.HotspotIds.Contains(x.Order.HotspotId)) //热点类型 .WhereIF(dto.AcceptTypes.Any(), x => dto.AcceptTypes.Contains(x.Order.AcceptTypeCode)) //受理类型 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No)) //工单编码 .WhereIF(!string.IsNullOrEmpty(dto.Title),x=> x.Order.Title.Contains(dto.Title)) .ToListAsync(); return _mapper.Map>(items); } /// /// 页面基础数据 /// /// [HttpGet("aivisit/base-data")] public async Task BaseData() { var rsp = new { AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType), }; return rsp; } /// /// 新增智能回访任务 /// /// [HttpPost("aivisit/add-aivisit")] public async Task AddAiVisit([FromBody]AddAiVisitDto dto) { //验证是否有重复电话 if(dto.AiOrderVisitDetails.Distinct().Count() != dto.AiOrderVisitDetails.Count) { throw UserFriendlyException.SameMessage("任务中存在重复外呼号码,请检查后重新提交"); } var model = _mapper.Map(dto); var detaillist = _mapper.Map>(dto.AiOrderVisitDetails); model.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.InProgress; model.RuleType = 2; model.HasVisitCount = dto.AiOrderVisitDetails.Count; model.VisitedCount = 0; model.VisitedFailCount = 0; var id = await _aiOrderVisitRepository.AddAsync(model, HttpContext.RequestAborted); detaillist.ForEach(x => { x.AiOrderVisitId = id; x.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.InProgress; }); await _aiOrderVisitDetailRepository.AddRangeAsync(detaillist, HttpContext.RequestAborted); //推送任务 //准备原始数据 var pushModel = await _aiOrderVisitRepository.Queryable() .Includes(x => x.AiOrderVisitDetails, s => s.Order) .Includes(x => x.AiOrderVisitDetails, s => s.OrderVisit, q => q.OrderVisitDetails) .FirstAsync(x => x.Id == id); var newModel = await _aiVisitService.CreateAiOrderVisitTask(pushModel, HttpContext.RequestAborted); if (!string.IsNullOrEmpty(newModel.BatchUid)) { //修改回访主表 await _orderVisitRepository.Updateable() .SetColumns(x => x.IsCanAiVisit == false) .SetColumns(x => x.VisitState == EVisitState.Visiting) .Where(x => detaillist.Select(s => s.OrderVisitId).Contains(x.Id)).ExecuteCommandAsync(HttpContext.RequestAborted); } else { newModel.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.Ended; } await _aiOrderVisitRepository.UpdateAsync(newModel, HttpContext.RequestAborted); await _aiOrderVisitDetailRepository.UpdateRangeAsync(newModel.AiOrderVisitDetails, HttpContext.RequestAborted); } #endregion } }