using Consul; using DocumentFormat.OpenXml.Wordprocessing; using DotNetCore.CAP; using Hotline.Ai.CallOut; using Hotline.Ai.Jths; using Hotline.Ai.Visit; using Hotline.Application.Orders; using Hotline.Application.Quality; using Hotline.Caching.Interfaces; using Hotline.Caching.Services; 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.Ai; using Hotline.Share.Enums.Order; using Hotline.Share.Enums.Quality; using Hotline.Tools; using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Novacode.NETCorePort; using Org.BouncyCastle.Utilities; using SqlSugar; using System.Runtime.CompilerServices; using System.Threading; using XF.Domain.Authentications; using XF.Domain.Constants; using XF.Domain.Exceptions; using XF.Domain.Repository; using XF.Utility.EnumExtensions; 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; private readonly IRepository _callOutTemplateRepository; private readonly IRepository _callOutTaskRepository; private readonly IRepository _callOutTaskDetailRepository; private readonly ISessionContext _sessionContext; private readonly IAiOrderVisitApplication _aiOrderVisitApplication; 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,IRepository callOutTemplateRepository, IRepository callOutTaskRepository,IRepository callOutTaskDetailRepository,ISessionContext sessionContext,IAiOrderVisitApplication aiOrderVisitApplication) { _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; _callOutTemplateRepository = callOutTemplateRepository; _callOutTaskRepository = callOutTaskRepository; _callOutTaskDetailRepository = callOutTaskDetailRepository; _sessionContext = sessionContext; _aiOrderVisitApplication = aiOrderVisitApplication; } /// /// 智能语音导航配置查询 /// /// /// [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 批量外呼 #region 批量外呼模板 /// /// 外呼模板列表 /// /// /// [HttpGet("callout-template-list")] public async Task> AiCallOutTemplateQuery([FromQuery]AiCallOutTemplateQueryRequest dto) { var (total,items) =await _callOutTemplateRepository.Queryable() .Includes(x=>x.CallOutTasks) .WhereIF(!string.IsNullOrEmpty(dto.TemplateName), x => x.TemplateName.Contains(dto.TemplateName)) .WhereIF(dto.StartTime != null, x => x.CreationTime >= dto.StartTime) .WhereIF(dto.EndTime != null, x => x.CreationTime <= dto.EndTime) .Select(x => new AiCallOutTemplateQueryRep() { Id = x.Id, TemplateName = x.TemplateName, TemplateContent = x.TemplateContent, CallOutTaskCount = x.CallOutTasks.Count(), CreationTime = x.CreationTime, IsEnable = x.IsEnable, CreatorName = x.CreatorName, CreatorOrgName = x.CreatorOrgName, }) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total,items); } /// /// 新增外呼模板 /// /// /// [HttpPost("add-callouttemplate")] public async Task AddCallOutTemplate([FromBody]CallOutTemplateDto dto) { var callOutTemplate = _mapper.Map(dto); callOutTemplate.IsEnable = true; await _callOutTemplateRepository.AddAsync(callOutTemplate, HttpContext.RequestAborted); } /// /// 修改外呼模板 /// /// /// [HttpPost("update-callouttemplate")] public async Task UpdateCallOutTemplate([FromBody] UpdateCallOutTemplateDto dto) { var model = await _callOutTemplateRepository.Queryable().Includes(x=>x.CallOutTasks).FirstAsync(x => x.Id == dto.Id, HttpContext.RequestAborted); if (model == null) throw UserFriendlyException.SameMessage("无效模板"); //验证是否有待执行的外呼任务 if (model.CallOutTasks.Any(x=>x.AiCallOutTaskState!= EAiCallOutTaskState.Ended)) { throw UserFriendlyException.SameMessage("当前模板有待执行的外呼任务,暂时无法修改!"); } model.TemplateName = dto.TemplateName; model.TemplateContent = dto.TemplateContent; await _callOutTemplateRepository.UpdateAsync(model, HttpContext.RequestAborted); } /// /// 删除外呼模板 /// /// /// [HttpDelete("del-callouttemplate")] public async Task DelCallOutTemplate([FromQuery]string id) { var model = await _callOutTemplateRepository.Queryable().Includes(x => x.CallOutTasks).FirstAsync(x => x.Id == id, HttpContext.RequestAborted); if (model == null) throw UserFriendlyException.SameMessage("无效模板"); //验证是否有待执行的外呼任务 if (model.CallOutTasks.Any(x => x.AiCallOutTaskState != EAiCallOutTaskState.Ended)) { throw UserFriendlyException.SameMessage("当前模板有待执行的外呼任务,暂时无法删除!"); } await _callOutTemplateRepository.RemoveAsync(id, true, HttpContext.RequestAborted); } /// /// 外呼模板启用禁用 /// /// /// [HttpGet("callouttemplate-enable-unenable")] public async Task CallOutTemplateEnableAndUnEnable([FromQuery]string id) { var model = await _callOutTemplateRepository.Queryable().Includes(x => x.CallOutTasks).FirstAsync(x => x.Id == id, HttpContext.RequestAborted); if (model == null) throw UserFriendlyException.SameMessage("无效模板"); //验证是否有待执行的外呼任务 if (model.CallOutTasks.Any(x => x.AiCallOutTaskState != EAiCallOutTaskState.Ended)) { throw UserFriendlyException.SameMessage("当前模板有待执行的外呼任务,暂时无法操作!"); } model.IsEnable = !model.IsEnable; await _callOutTemplateRepository.UpdateAsync(model, HttpContext.RequestAborted); } #endregion #region 批量外呼任务 /// /// 批量外呼任务列表 /// /// /// [HttpGet("callout/callouttask-list")] public async Task> AiCallOutList([FromQuery]AiCallOutListRequest dto) { var (total,items) = await _callOutTaskRepository.Queryable() .Includes(x => x.CallOutTemplate) .WhereIF(!string.IsNullOrEmpty(dto.TaskName), x => x.TaskName.Contains(dto.TaskName)) .WhereIF(dto.AiCallOutTaskState != null, x => x.AiCallOutTaskState == dto.AiCallOutTaskState) .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)); } /// /// 外呼任务列表基础数据 /// /// [HttpGet("callout/list-basedata")] public async Task AiCallOuttListBaseData() { return new { AiCallOutTaskState = EnumExts.GetDescriptions(), AiCallOutState = EnumExts.GetDescriptions() }; } /// /// 批量外呼任务明细列表 /// /// /// [HttpGet("callout/callouttaskdetail")] public async Task> AiCallOutDetailList([FromQuery] AiCallOutDetailListRequest dto) { var (total, items) = await _callOutTaskDetailRepository.Queryable() .Where(x => x.CallOutTaskId == dto.Id) .WhereIF(!string.IsNullOrEmpty(dto.OuterNo),x=>x.OuterNo.Contains(dto.OuterNo)) .WhereIF(!string.IsNullOrEmpty(dto.Name),x=>x.Name.Contains(dto.Name)) .WhereIF(dto.AiCallOutState!=null,x=>x.AiCallOutState== dto.AiCallOutState) .OrderByDescending(x => x.CreationTime) .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total,_mapper.Map>(items)); } /// /// 新增批量外呼任务 /// /// /// [HttpPost("callout/add-callout")] public async Task AddAiCallOut([FromBody] AddAiCallOutRequest dto) { //验证是否有重复电话 if (dto.AddAiCallOutTaskDetailDtos.DistinctBy(x => x.OuterNo).Count() != dto.AddAiCallOutTaskDetailDtos.Count) { throw UserFriendlyException.SameMessage("任务中存在重复外呼号码,请检查后重新提交"); } var model = _mapper.Map(dto); var detaillist = _mapper.Map>(dto.AddAiCallOutTaskDetailDtos); model.AiCallOutTaskState = EAiCallOutTaskState.NoStarted; model.HasVisitCount = dto.AddAiCallOutTaskDetailDtos.Count; model.VisitedCount = 0; model.VisitedFailCount = 0; var id = await _callOutTaskRepository.AddAsync(model, HttpContext.RequestAborted); detaillist.ForEach(x => { x.CallOutTaskId = id; x.AiCallOutState = EAiCallOutState.InProgress; }); await _callOutTaskDetailRepository.AddRangeAsync(detaillist, HttpContext.RequestAborted); #region 获取系统设置 var callOutSceneUid = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutSceneUid)?.SettingValue[0]; var callOutRuleUid = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutRuleUid)?.SettingValue[0]; var CallOutContentKey = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutContentKey)?.SettingValue[0]; #endregion var pushModel = await _callOutTaskRepository.Queryable() .Includes(x => x.CallOutTemplate) .Includes(x => x.CallOutTaskDetails) .FirstAsync(x => x.Id == id); var newModel = await _aiVisitService.CreateAiCallOutTask(pushModel, callOutSceneUid, callOutRuleUid, CallOutContentKey, HttpContext.RequestAborted); if (string.IsNullOrEmpty(newModel.BatchUid)) { newModel.AiCallOutTaskState = EAiCallOutTaskState.Ended; } await _callOutTaskRepository.UpdateAsync(newModel, HttpContext.RequestAborted); await _callOutTaskDetailRepository.UpdateRangeAsync(newModel.CallOutTaskDetails, HttpContext.RequestAborted); } /// /// 可用外呼模板 /// /// [HttpGet("callout/canuse-template")] public async Task> CanUseCallOutTemplateList() { var list = await _callOutTemplateRepository.Queryable().Where(x => x.IsEnable == true).ToListAsync(HttpContext.RequestAborted); return _mapper.Map>(list); } /// /// 终止外呼任务 /// /// /// [HttpPost("callout/closecallouttask")] public async Task CloseCalloutTask([FromBody] CloseCalloutTaskReq request) { switch (request.TypeId) { case 1: var aiVisit = await _aiOrderVisitRepository.Queryable() .Includes(x => x.AiOrderVisitDetails,s=>s.OrderVisit) .FirstAsync(x => x.Id == request.Id,HttpContext.RequestAborted); if (aiVisit!=null && !string.IsNullOrEmpty(aiVisit.BatchUid)) { if (aiVisit.TaskState != EAiOrderVisitTaskState.Pause) throw UserFriendlyException.SameMessage("请先暂停任务,再进行终止;"); bool isOk = await _aiVisitService.ChangeStatusAsync(aiVisit.BatchUid, "cancel", HttpContext.RequestAborted); if (!isOk) throw UserFriendlyException.SameMessage("终止失败"); aiVisit.TaskState = EAiOrderVisitTaskState.Close; aiVisit.AiOrderVisitDetails.ForEach(async x => { if (x.AiOrderVisitState == EAiOrderVisitState.NoStarted) { x.AiOrderVisitState = EAiOrderVisitState.LoseEfficacy; x.OrderVisit.VisitState = EVisitState.WaitForVisit; } await _orderVisitRepository.UpdateAsync(x.OrderVisit); }); await _aiOrderVisitRepository.UpdateAsync(aiVisit, HttpContext.RequestAborted); await _aiOrderVisitDetailRepository.UpdateRangeAsync(aiVisit.AiOrderVisitDetails, HttpContext.RequestAborted); } break; case 2: var callOut = await _callOutTaskRepository.Queryable() .Includes(x => x.CallOutTaskDetails) .FirstAsync(x => x.Id == request.Id); if (callOut != null && !string.IsNullOrEmpty(callOut.BatchUid)) { if (callOut.AiCallOutTaskState != EAiCallOutTaskState.Pause) throw UserFriendlyException.SameMessage("请先暂停任务,再进行终止;"); bool isOk = await _aiVisitService.ChangeStatusAsync(callOut.BatchUid, "cancel", HttpContext.RequestAborted); if (!isOk) throw UserFriendlyException.SameMessage("终止失败"); //处理业务数据 callOut.AiCallOutTaskState = EAiCallOutTaskState.Close; callOut.CallOutTaskDetails.ForEach(x => { if (x.AiCallOutState == EAiCallOutState.NoStarted) x.AiCallOutState = EAiCallOutState.LoseEfficacy; }); await _callOutTaskRepository.UpdateAsync(callOut, HttpContext.RequestAborted); await _callOutTaskDetailRepository.UpdateRangeAsync(callOut.CallOutTaskDetails, HttpContext.RequestAborted); } break; default: throw UserFriendlyException.SameMessage("未知业务"); } } /// /// 暂停外呼任务 /// /// /// [HttpPost("callout/pausecallouttask")] public async Task PauseCalloutTask([FromBody] CloseCalloutTaskReq request) { switch (request.TypeId) { case 1: var aiVisit = await _aiOrderVisitRepository.Queryable() .FirstAsync(x => x.Id == request.Id,HttpContext.RequestAborted); if (aiVisit != null && !string.IsNullOrEmpty(aiVisit.BatchUid)) { if (aiVisit.TaskState != EAiOrderVisitTaskState.NoStarted && aiVisit.TaskState != EAiOrderVisitTaskState.InProgress) throw UserFriendlyException.SameMessage("当前状态不能终止"); bool isOk = await _aiVisitService.ChangeStatusAsync(aiVisit.BatchUid, "stop", HttpContext.RequestAborted); if (!isOk) throw UserFriendlyException.SameMessage("终止失败"); aiVisit.TaskState = EAiOrderVisitTaskState.Pause; await _aiOrderVisitRepository.UpdateAsync(aiVisit, HttpContext.RequestAborted); } break; case 2: var callOut = await _callOutTaskRepository.Queryable() .FirstAsync(x => x.Id == request.Id); if (callOut != null && !string.IsNullOrEmpty(callOut.BatchUid)) { if (callOut.AiCallOutTaskState != EAiCallOutTaskState.NoStarted && callOut.AiCallOutTaskState != EAiCallOutTaskState.InProgress) throw UserFriendlyException.SameMessage("当前状态不能终止"); bool isOk = await _aiVisitService.ChangeStatusAsync(callOut.BatchUid, "stop", HttpContext.RequestAborted); if (!isOk) throw UserFriendlyException.SameMessage("终止失败"); //处理业务数据 callOut.AiCallOutTaskState = EAiCallOutTaskState.Pause; await _callOutTaskRepository.UpdateAsync(callOut, HttpContext.RequestAborted); } break; default: throw UserFriendlyException.SameMessage("未知业务"); } } /// /// 启动外呼任务 /// /// /// [HttpPost("callout/startcallouttask")] public async Task StartCalloutTask([FromBody] CloseCalloutTaskReq request) { switch (request.TypeId) { case 1: var aiVisit = await _aiOrderVisitRepository.Queryable() .FirstAsync(x => x.Id == request.Id, HttpContext.RequestAborted); if (aiVisit != null && !string.IsNullOrEmpty(aiVisit.BatchUid)) { if (aiVisit.TaskState != EAiOrderVisitTaskState.Pause) throw UserFriendlyException.SameMessage("当前状态不能启动"); bool isOk = await _aiVisitService.ChangeStatusAsync(aiVisit.BatchUid, "start", HttpContext.RequestAborted); if (!isOk) throw UserFriendlyException.SameMessage("启动失败"); aiVisit.TaskState = EAiOrderVisitTaskState.InProgress; await _aiOrderVisitRepository.UpdateAsync(aiVisit, HttpContext.RequestAborted); } break; case 2: var callOut = await _callOutTaskRepository.Queryable() .FirstAsync(x => x.Id == request.Id); if (callOut != null && !string.IsNullOrEmpty(callOut.BatchUid)) { if (callOut.AiCallOutTaskState != EAiCallOutTaskState.Pause) throw UserFriendlyException.SameMessage("当前状态不能启动"); bool isOk = await _aiVisitService.ChangeStatusAsync(callOut.BatchUid, "start", HttpContext.RequestAborted); if (!isOk) throw UserFriendlyException.SameMessage("启动失败"); //处理业务数据 callOut.AiCallOutTaskState = EAiCallOutTaskState.InProgress; await _callOutTaskRepository.UpdateAsync(callOut, HttpContext.RequestAborted); } break; default: break; } } #endregion #endregion #region 智能回访 /// /// 智能回访外呼结果回传 /// /// /// [AllowAnonymous] [HttpPost("aivisit/aivisit-back")] public async Task AiVisitBack([FromBody]AiVisitBackDto dto) { _logger.LogInformation($"收到智能外呼结果回传:{JsonConvert.SerializeObject(dto)}"); var SceneUid = _systemSettingCacheManager.GetSetting(SettingConstants.VisitSceneUid)?.SettingValue[0];//场景ID var CallOutSceneUid = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutSceneUid)?.SettingValue[0];//批量外呼场景ID //回访 if (dto.SceneUid== SceneUid) { 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)//执行完 { #region 获取所有配置参数 var QuestionIdZero = _systemSettingCacheManager.GetSetting(SettingConstants.QuestionIdZero)?.SettingValue[0]; //是否联系 var QuestionIdOne = _systemSettingCacheManager.GetSetting(SettingConstants.QuestionIdOne)?.SettingValue[0];//是否解决 var QuestionIdTwo = _systemSettingCacheManager.GetSetting(SettingConstants.QuestionIdTwo)?.SettingValue[0];//办件结果满意度 //var QuestionIdThree = _systemSettingCacheManager.GetSetting(SettingConstants.QuestionIdThree)?.SettingValue[0];//坐席是否满意 var VisitContentIdOne = _systemSettingCacheManager.GetSetting(SettingConstants.VisitContentIdOne)?.SettingValue[0];//办件结果不满意原因 //var VisitContentIdTwo = _systemSettingCacheManager.GetSetting(SettingConstants.VisitContentIdTwo)?.SettingValue[0];//坐席不满意原因 #endregion var aiOrderVisitDetail = aiOrderVisit.AiOrderVisitDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid); if (aiOrderVisitDetail != null) { var callRecord = dto.CallRecordList.OrderBy(x => x.CallNo).LastOrDefault(); // 回访结果(ReturnVisit)[1成功、0不涉及、-1失败] if (callRecord != null) //有结果的任务 { aiOrderVisitDetail.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Ended; //更新AI子表 aiOrderVisitDetail.AiVisitTime = DateTime.Now; aiOrderVisitDetail.CallTime = string.IsNullOrEmpty(callRecord.CallTime)? null : DateTime.Parse(callRecord.CallTime); 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(); //过滤结果 Kv? orgProcessingResults = null; //var orgHandledAttitude = new Kv(); ESeatEvaluate? seatEvaluate = ESeatEvaluate.DefaultSatisfied; var visitContent = ""; var seatVisitContent = "默认满意"; var volveConent = ""; bool? isSolve = null; bool? isContact = null; //通话录音 var recordUrl = callRecord.RecordUrl; if (callRecord.QuestionnaireResult!=null) { 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 == QuestionIdZero) { if (item.QuestionResult == "有联系") { isContact = true; } else if (item.QuestionResult == "没有联系") { isContact = false; } } //是否解决 if (item.QuestionId == QuestionIdOne) { if (item.QuestionResult == "得到解决") { isSolve = true; } else if (item.QuestionResult == "未得到解决") { isSolve = false; } } //办件结果满意度 if (item.QuestionId == QuestionIdTwo) { if (item.QuestionResult == "办件结果满意") { orgProcessingResults = new Kv() { Key = "4", Value = "满意" }; } else if (item.QuestionResult == "办件结果不满意") { orgProcessingResults = new Kv() { Key = "2", Value = "不满意" }; visitContent = callRecord.SceneVariable != null ? callRecord.SceneVariable[VisitContentIdOne] : ""; } } else { aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit; } ////坐席是否满意 //if (item.QuestionId == QuestionIdThree) //{ // if (item.QuestionResult == "满意接电坐席") // { // seatEvaluate = ESeatEvaluate.Satisfied; // } // else if (item.QuestionResult == "不满意接电坐席") // { // seatEvaluate = ESeatEvaluate.NoSatisfied; // seatVisitContent = callRecord.SceneVariable != null ? callRecord.SceneVariable[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; }); //处理结果 orgDetail.ForEach(x => { //x.OrgHandledAttitude = orgHandledAttitude; if (orgProcessingResults!=null) { x.OrgProcessingResults = orgProcessingResults; } x.VisitContent = visitContent; x.Volved = isSolve; x.IsContact = isContact; if (string.IsNullOrEmpty(orgProcessingResults?.Key) || isSolve == null || isContact == null || orgProcessingResults?.Value == "不满意") { //x.OrgNoSatisfiedReason = new List() { new Kv() { Key = "7", Value = "未回复" } }; //TODO 记录不满意原因到内容中供人工回访甄别选择不满意原因 if (dto.CallTimes.Value>=1 && orgProcessingResults is null) { orgProcessingResults = new Kv() { Key = "0", Value = "默认满意" }; aiOrderVisitDetail.OrderVisit.VisitState = EVisitState.Visited; aiOrderVisitDetail.OrderVisit.NowEvaluate = orgProcessingResults; x.VisitContent = "智能回访两次未接默认已回访"; x.Volved = true; x.IsContact = true; x.OrgProcessingResults = orgProcessingResults; seatDetail.ForEach(x => { x.VoiceEvaluate = Share.Enums.Order.EVoiceEvaluate.Satisfied; x.SeatEvaluate = ESeatEvaluate.DefaultSatisfied; x.VisitContent = "智能回访两次未接默认已回访"; }); } else { aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit; aiOrderVisitDetail.OrderVisit.IsEffectiveAiVisit = false; } } else { aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.Visited; aiOrderVisitDetail.OrderVisit.IsEffectiveAiVisit = true; } }); await _orderVisitDetailRepository.UpdateRangeAsync(seatDetail, HttpContext.RequestAborted); 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.RecordUrl = recordUrl; aiOrderVisitDetail.OrderVisit.IsPutThrough = true; aiOrderVisitDetail.OrderVisit.RecordUrl = recordUrl; aiOrderVisitDetail.OrderVisit.VisitType = Share.Enums.Order.EVisitType.ChipVoiceVisit; aiOrderVisitDetail.OrderVisit.AiVisitTime(); aiOrderVisitDetail.IsSuccess = true; aiOrderVisitDetail.AiVolved = isSolve; aiOrderVisitDetail.AiIsContact = isContact; aiOrderVisitDetail.AiVolveConent = visitContent; aiOrderVisitDetail.AiSeatEvaluate = seatEvaluate; aiOrderVisitDetail.AiOrgProcessingResults = orgProcessingResults; aiOrderVisitDetail.AiOrgNoSatisfiedReason = visitContent; aiOrderVisitDetail.AiSeatNoSatisfiedReason = seatVisitContent; aiOrderVisitDetail.CallTimes = dto.CallTimes.Value; if (orgProcessingResults != null) { aiOrderVisitDetail.OrderVisit.NowEvaluate = orgProcessingResults; } //处理是否回访完成 await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted); //处理Order表 if (orgProcessingResults != null && !string.IsNullOrEmpty(orgProcessingResults.Key) && 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; } else { aiOrderVisit.TaskState = EAiOrderVisitTaskState.InProgress; } 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); } //} } } else { //失败 var aiOrderVisitDetail = aiOrderVisit.AiOrderVisitDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid); if (aiOrderVisitDetail!=null) { aiOrderVisitDetail.AiOrderVisitState = (EAiOrderVisitState)dto.TaskStatus; await _aiOrderVisitDetailRepository.UpdateAsync(aiOrderVisitDetail, HttpContext.RequestAborted); } } } } //批量外呼 else if(dto.SceneUid == CallOutSceneUid) { var callOut = await _callOutTaskRepository.Queryable() .Includes(x => x.CallOutTaskDetails) .FirstAsync(x => x.BatchUid == dto.BatchUid); if (callOut != null) { var aicallOutDetail = callOut.CallOutTaskDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid); if (aicallOutDetail != null) { if (dto.TaskStatus == 6) { var callRecord = dto.CallRecordList.OrderBy(x => x.CallNo).LastOrDefault(); if (callRecord!=null) { aicallOutDetail.IsSuccess = true; aicallOutDetail.AiCallOutState = Share.Enums.Ai.EAiCallOutState.Ended; //更新AI子表 aicallOutDetail.CallOutTime = DateTime.Now; callOut.VisitedCount++; } else { aicallOutDetail.IsSuccess = false; aicallOutDetail.AiCallOutState = (EAiCallOutState)dto.TaskStatus; aicallOutDetail.CallOutTime = DateTime.Now; callOut.VisitedFailCount++; } } else { aicallOutDetail.IsSuccess = false; aicallOutDetail.AiCallOutState = (EAiCallOutState)dto.TaskStatus; aicallOutDetail.CallOutTime = DateTime.Now; callOut.VisitedFailCount++; } if ((callOut.VisitedFailCount + callOut.VisitedCount) == callOut.HasVisitCount) { callOut.AiCallOutTaskState = EAiCallOutTaskState.Ended; } else { callOut.AiCallOutTaskState = EAiCallOutTaskState.InProgress; } await _callOutTaskRepository.UpdateAsync(callOut,HttpContext.RequestAborted); await _callOutTaskDetailRepository.UpdateAsync(aicallOutDetail,HttpContext.RequestAborted); } } } else { } } /// /// 智能回访列表 /// /// /// [HttpGet("aivisit/aivisit-list")] public async Task> AiVisitList([FromQuery]AiVisitListDto dto) { var query = _aiOrderVisitApplication.QuerysAiOrderVisit(dto); var (total, items) = await query .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 智能回访任务导出 /// /// /// [HttpPost("aivisit/taskexport")] public async Task ExportAiOrderVisit([FromBody]ExportExcelDto dto) { var query = _aiOrderVisitApplication.QuerysAiOrderVisit(dto.QueryDto); List aiOrderVisits; if (dto.IsExportAll) { aiOrderVisits = await query.ToListAsync(HttpContext.RequestAborted); } else { var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted); aiOrderVisits = items; } var aiOrderVisitDtos = _mapper.Map>(aiOrderVisits); dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos); var dtos = aiOrderVisitDtos .Select(stu => _mapper.Map(stu, typeof(AiOrderVisitDto), dynamicClass)) .Cast() .ToList(); var stream = ExcelHelper.CreateStream(dtos); return ExcelStreamResult(stream, "智能回访任务"); } /// /// 智能回访明细 /// /// /// [HttpGet("aivisit/aivisitdetail-list")] public async Task> AiVisitDetailList([FromQuery]AiVisitDetailListDto dto) { var query = _aiOrderVisitApplication.QueryAiOrderVisitDetail(dto); var (total,items) =await query.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted); return new PagedDto(total, _mapper.Map>(items)); } /// /// 智能回访明细导出 /// /// /// [HttpPost("aivisit/taskdetailexport")] public async Task ExportAiOrderVisitDetail([FromBody]ExportExcelDto dto) { var query = _aiOrderVisitApplication.QueryAiOrderVisitDetail(dto.QueryDto); List aiOrderVisits; if (dto.IsExportAll) { aiOrderVisits = await query.ToListAsync(HttpContext.RequestAborted); } else { var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted); aiOrderVisits = items; } var aiOrderVisitDetailDtos = _mapper.Map>(aiOrderVisits); dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos); var dtos = aiOrderVisitDetailDtos .Select(stu => _mapper.Map(stu, typeof(AiOrderVisitDetailDto), dynamicClass)) .Cast() .ToList(); var stream = ExcelHelper.CreateStream(dtos); return ExcelStreamResult(stream, "智能回访明细"); } /// /// 可进行智能回访记录 /// /// [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 && x.EmployeeId == _sessionContext.RequiredUserId) .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.Order.HotspotSpliceName != null && d.Order.HotspotSpliceName.Contains(dto.Hotspot))//热点类型 .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.Order.AcceptTypeCode == dto.AcceptType)//受理类型 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No)) //工单编码 .WhereIF(!string.IsNullOrEmpty(dto.Title),x=> x.Order.Title.Contains(dto.Title)) .Where(x=> !string.IsNullOrEmpty(x.Order.Contact) && SqlFunc.Length(x.Order.Contact)>6) .ToListAsync(); return _mapper.Map>(items); } /// /// 任务页面基础数据 /// /// [HttpGet("aivisit/taskbase-data")] public async Task AiVisitTaskBaseData() { var rsp = new { AiOrderVisitTaskState = EnumExts.GetDescriptions(), AiOrderVisitState = EnumExts.GetDescriptions() }; return rsp; } /// /// 页面基础数据 /// /// [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) { //验证是否有重复电话 //有重复电话自动排除 dto.AiOrderVisitDetails = dto.AiOrderVisitDetails.DistinctBy(x => x.OuterNo.Trim()).ToList() ; //if (dto.AiOrderVisitDetails.DistinctBy(x=>x.OuterNo.Trim()).Count() != dto.AiOrderVisitDetails.Count) //{ // throw UserFriendlyException.SameMessage("任务中存在重复外呼号码,请检查后重新提交"); //} var model = _mapper.Map(dto); var detaillist = _mapper.Map>(dto.AiOrderVisitDetails); model.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.NoStarted; 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); #region 获取系统设置 var sceneuid = _systemSettingCacheManager.GetSetting(SettingConstants.VisitSceneUid)?.SettingValue[0]; var ruleuId = _systemSettingCacheManager.GetSetting(SettingConstants.VisitRuleUid)?.SettingValue[0]; var visitFromNameKey = _systemSettingCacheManager.GetSetting(SettingConstants.VisitFromNameKey)?.SettingValue[0]; var visitFromGenderKey = _systemSettingCacheManager.GetSetting(SettingConstants.VisitFromGenderKey)?.SettingValue[0]; var visitCreationTimeKey = _systemSettingCacheManager.GetSetting(SettingConstants.VisitCreationTimeKey)?.SettingValue[0]; var visitOrderTitleKey = _systemSettingCacheManager.GetSetting(SettingConstants.VisitOrderTitleKey)?.SettingValue[0]; var visitIsCallOrderKey = _systemSettingCacheManager.GetSetting(SettingConstants.VisitIsCallOrder)?.SettingValue[0]; #endregion var newModel = await _aiVisitService.CreateAiOrderVisitTask(pushModel, sceneuid, ruleuId, visitFromNameKey, visitFromGenderKey, visitCreationTimeKey, visitOrderTitleKey, visitIsCallOrderKey, 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 } }