AiController.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. 
  2. using Consul;
  3. using DotNetCore.CAP;
  4. using Hotline.Ai.Jths;
  5. using Hotline.Ai.Visit;
  6. using Hotline.Application.Quality;
  7. using Hotline.Caching.Interfaces;
  8. using Hotline.Caching.Services;
  9. using Hotline.CallCenter.Devices;
  10. using Hotline.Orders;
  11. using Hotline.Repository.SqlSugar.Extensions;
  12. using Hotline.Settings;
  13. using Hotline.Share.Dtos;
  14. using Hotline.Share.Dtos.Ai;
  15. using Hotline.Share.Dtos.Order;
  16. using Hotline.Share.Enums.Order;
  17. using Hotline.Share.Enums.Quality;
  18. using MapsterMapper;
  19. using Microsoft.AspNetCore.Authorization;
  20. using Microsoft.AspNetCore.Mvc;
  21. using Microsoft.Extensions.Options;
  22. using Newtonsoft.Json;
  23. using SqlSugar;
  24. using System.Threading;
  25. using XF.Domain.Authentications;
  26. using XF.Domain.Constants;
  27. using XF.Domain.Exceptions;
  28. using XF.Domain.Repository;
  29. namespace Hotline.Api.Controllers
  30. {
  31. public class AiController: BaseController
  32. {
  33. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  34. private readonly IRepository<AiOrderVisit> _aiOrderVisitRepository;
  35. private readonly IRepository<AiOrderVisitDetail> _aiOrderVisitDetailRepository;
  36. private readonly IRepository<OrderVisit> _orderVisitRepository;
  37. private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
  38. private readonly IMapper _mapper;
  39. private readonly IOptionsSnapshot<AiVisitConfig> _options;
  40. private readonly IAiVisitService _aiVisitService;
  41. private readonly ILogger<AiController> _logger;
  42. private readonly ICapPublisher _capPublisher;
  43. private readonly IOrderRepository _orderRepository;
  44. private readonly IQualityApplication _qualityApplication;
  45. private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
  46. public AiController(ISystemSettingCacheManager systemSettingCacheManager,IRepository<AiOrderVisit> aiOrderVisitRepository,IRepository<AiOrderVisitDetail> aiOrderVisitDetailRepository,IRepository<OrderVisit> orderVisitRepository,IRepository<OrderVisitDetail> orderVisitDetailRepository,IMapper mapper, IOptionsSnapshot<AiVisitConfig> options,IAiVisitService aiVisitService, ILogger<AiController> logger,ICapPublisher capPublisher,IOrderRepository orderRepository,IQualityApplication qualityApplication, ISystemDicDataCacheManager sysDicDataCacheManager)
  47. {
  48. _systemSettingCacheManager = systemSettingCacheManager;
  49. _aiOrderVisitRepository = aiOrderVisitRepository;
  50. _aiOrderVisitDetailRepository = aiOrderVisitDetailRepository;
  51. _orderVisitRepository = orderVisitRepository;
  52. _orderVisitDetailRepository = orderVisitDetailRepository;
  53. _mapper = mapper;
  54. _options = options;
  55. _aiVisitService = aiVisitService;
  56. _logger = logger;
  57. _capPublisher = capPublisher;
  58. _orderRepository = orderRepository;
  59. _qualityApplication = qualityApplication;
  60. _sysDicDataCacheManager = sysDicDataCacheManager;
  61. }
  62. /// <summary>
  63. /// 智能语音导航配置查询
  64. /// </summary>
  65. /// <param name="count"></param>
  66. /// <returns></returns>
  67. [HttpGet("ivr/access-confirm")]
  68. [AllowAnonymous]
  69. public async Task<bool> AccessConfirm([FromQuery]int count)
  70. {
  71. //获取是否开启智能语音队列
  72. bool isAiIvr = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsAIIVR)?.SettingValue[0]);
  73. if (!isAiIvr)
  74. return false;
  75. //获取智能语音队列数
  76. var queueNum = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.QueueNum)?.SettingValue[0]);
  77. if (count < queueNum)
  78. return false;
  79. //判断是否在时间段内
  80. var aiIVRTimeArr = _systemSettingCacheManager.GetSetting(SettingConstants.AIIVRTime)?.SettingValue;
  81. bool IsOk = false;
  82. aiIVRTimeArr.ForEach((item) => {
  83. List<string> times = JsonConvert.DeserializeObject<List<string>>(item);
  84. var beginTime = DateTime.Parse(DateTime.Now.ToShortDateString() + " " + times[0] + ":00");
  85. var endTime = DateTime.Parse(DateTime.Now.ToShortDateString() + " " + times[1] + ":00");
  86. if (DateTime.Now>beginTime && DateTime.Now< endTime)
  87. {
  88. IsOk = true;
  89. }
  90. });
  91. return IsOk;
  92. }
  93. #region 智能回访
  94. /// <summary>
  95. /// 智能回访外呼结果回传
  96. /// </summary>
  97. /// <param name="dto"></param>
  98. /// <returns></returns>
  99. [AllowAnonymous]
  100. [HttpPost("aivisit/aivisit-back")]
  101. public async Task AiVisitBack([FromBody]AiVisitBackDto dto)
  102. {
  103. _logger.LogInformation($"收到智能外呼结果回传:{JsonConvert.SerializeObject(dto)}");
  104. var aiOrderVisit = await _aiOrderVisitRepository.Queryable()
  105. .Includes(x => x.AiOrderVisitDetails,s=>s.OrderVisit,d=>d.Order)
  106. .FirstAsync(x => x.BatchUid == dto.BatchUid);
  107. if (aiOrderVisit!=null)
  108. {
  109. //验证记录中是否存在有结果的任务
  110. if (dto.TaskStatus == 6)//执行完
  111. {
  112. var aiOrderVisitDetail = aiOrderVisit.AiOrderVisitDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid);
  113. if (aiOrderVisitDetail != null)
  114. {
  115. var callRecord = dto.CallRecordList.OrderBy(x => x.CallStartTime).LastOrDefault();
  116. // 回访结果(ReturnVisit)[1成功、0不涉及、-1失败]
  117. if (callRecord != null) //有结果的任务
  118. {
  119. aiOrderVisitDetail.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Ended; //更新AI子表
  120. aiOrderVisitDetail.AiVisitTime = DateTime.Now;
  121. aiOrderVisit.VisitedCount++;
  122. //处理结果
  123. var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id).ToList();
  124. //先处理子表
  125. //处理部门
  126. var orgDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Org).ToList();
  127. //过滤结果
  128. var orgProcessingResults = new Kv();
  129. //var orgHandledAttitude = new Kv();
  130. ESeatEvaluate? seatEvaluate = null;
  131. var visitContent = "";
  132. var seatVisitContent = "";
  133. var volveConent = "";
  134. bool? isSolve = null;
  135. bool? isContact = null;
  136. //通话录音
  137. var recordUrl = callRecord.RecordUrl;
  138. foreach (var item in callRecord.QuestionnaireResult)
  139. {
  140. ////服务过程满意度
  141. //if (item.QuestionId == _options.Value.QuestionIdOne)
  142. //{
  143. // if (item.QuestionResult == "满意")
  144. // {
  145. // orgHandledAttitude = new Kv() { Key="4", Value="满意" };
  146. // }
  147. // else
  148. // {
  149. // orgHandledAttitude = new Kv() { Key = "2", Value = "不满意" };
  150. // }
  151. //}
  152. //是否联系
  153. if (item.QuestionId == _options.Value.QuestionIdZone)
  154. {
  155. if (item.QuestionResult == "有联系")
  156. {
  157. isContact = true;
  158. }
  159. else if(item.QuestionResult == "没有联系")
  160. {
  161. isContact = false;
  162. }
  163. }
  164. //是否解决
  165. if (item.QuestionId == _options.Value.QuestionIdOne)
  166. {
  167. if (item.QuestionResult == "得到解决")
  168. {
  169. isSolve = true;
  170. }
  171. else if(item.QuestionResult == "未得到解决")
  172. {
  173. isSolve = false;
  174. }
  175. }
  176. //办件结果满意度
  177. if (item.QuestionId == _options.Value.QuestionIdTwo)
  178. {
  179. if (item.QuestionResult == "办件结果满意")
  180. {
  181. orgProcessingResults = new Kv() { Key = "4", Value = "满意" };
  182. }
  183. else if(item.QuestionResult == "办件结果不满意")
  184. {
  185. orgProcessingResults = new Kv() { Key = "2", Value = "不满意" };
  186. visitContent = callRecord.SceneVariable[_options.Value.VisitContentIdOne];
  187. }
  188. }
  189. else
  190. {
  191. aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit;
  192. }
  193. //坐席是否满意
  194. if (item.QuestionId == _options.Value.QuestionIdThree)
  195. {
  196. if (item.QuestionResult == "满意接电坐席")
  197. {
  198. seatEvaluate = ESeatEvaluate.Satisfied;
  199. }
  200. else if(item.QuestionResult == "不满意接电坐席")
  201. {
  202. seatEvaluate = ESeatEvaluate.NoSatisfied;
  203. seatVisitContent = callRecord.SceneVariable[_options.Value.VisitContentIdTwo];
  204. }
  205. }
  206. }
  207. //先处理坐席(因没有坐席回访,所以默认满意)
  208. var seatDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Seat).ToList();
  209. seatDetail.ForEach(x =>
  210. {
  211. x.VoiceEvaluate = Share.Enums.Order.EVoiceEvaluate.Satisfied;
  212. x.SeatEvaluate = seatEvaluate;
  213. x.VisitContent = seatVisitContent;
  214. });
  215. await _orderVisitDetailRepository.UpdateRangeAsync(seatDetail, HttpContext.RequestAborted);
  216. //处理结果
  217. orgDetail.ForEach(x =>
  218. {
  219. //x.OrgHandledAttitude = orgHandledAttitude;
  220. x.OrgProcessingResults = orgProcessingResults;
  221. x.VisitContent = visitContent;
  222. x.Volved = isSolve;
  223. x.IsContact = isContact;
  224. if (orgProcessingResults.Value == "不满意" || (string.IsNullOrEmpty(orgProcessingResults.Key) && seatEvaluate!=null && isSolve!=null && isContact!=null))
  225. {
  226. //x.OrgNoSatisfiedReason = new List<Kv>() { new Kv() { Key = "7", Value = "未回复" } };
  227. //TODO 记录不满意原因到内容中供人工回访甄别选择不满意原因
  228. aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit;
  229. }
  230. else
  231. {
  232. aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.Visited;
  233. }
  234. });
  235. await _orderVisitDetailRepository.UpdateRangeAsync(orgDetail, HttpContext.RequestAborted);
  236. //var first = orgProcessingResults; //aiOrderVisitDetail.OrderVisit.OrderVisitDetails.FirstOrDefault(x => x.VisitTarget == EVisitTarget.Org);
  237. //处理主表
  238. aiOrderVisitDetail.OrderVisit.AiVisitCount++;
  239. aiOrderVisitDetail.OrderVisit.VisitTime = DateTime.Now;
  240. aiOrderVisitDetail.OrderVisit.IsPutThrough = true;
  241. aiOrderVisitDetail.OrderVisit.VisitType = Share.Enums.Order.EVisitType.ChipVoiceVisit;
  242. aiOrderVisitDetail.OrderVisit.AiVisitTime();
  243. aiOrderVisitDetail.IsSuccess = true;
  244. if (orgProcessingResults != null)
  245. {
  246. aiOrderVisitDetail.OrderVisit.NowEvaluate = orgProcessingResults;
  247. }
  248. //处理是否回访完成TODO
  249. await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted);
  250. //处理Order表
  251. if (orgProcessingResults != null && aiOrderVisitDetail.OrderVisit.VisitState == EVisitState.Visited)
  252. {
  253. aiOrderVisitDetail.OrderVisit.Order.Visited(orgProcessingResults.Key, orgProcessingResults.Value);
  254. await _orderRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit.Order);
  255. //推省上
  256. var orderDto = _mapper.Map<OrderDto>(aiOrderVisitDetail.OrderVisit.Order);
  257. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
  258. new PublishVisitDto()
  259. {
  260. Order = orderDto,
  261. No = aiOrderVisitDetail.OrderVisit.No,
  262. VisitType = aiOrderVisitDetail.OrderVisit.VisitType,
  263. VisitName = aiOrderVisitDetail.OrderVisit.CreatorName,
  264. VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime,
  265. VisitRemark = string.IsNullOrEmpty(visitContent) ? aiOrderVisitDetail.OrderVisit.NowEvaluate?.Value : visitContent,
  266. AreaCode = aiOrderVisitDetail.OrderVisit.Order.AreaCode!,
  267. SubjectResultSatifyCode = orgProcessingResults.Key,
  268. FirstSatisfactionCode = aiOrderVisitDetail.OrderVisit.Order.FirstVisitResultCode!,
  269. ClientGuid = ""
  270. }, cancellationToken: HttpContext.RequestAborted);
  271. //推门户
  272. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
  273. {
  274. Id = aiOrderVisitDetail.OrderVisit.Id,
  275. Order = orderDto,
  276. OrderVisitDetails = _mapper.Map<List<VisitDetailDto>>(aiOrderVisitDetail.OrderVisit.OrderVisitDetails),
  277. VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime,
  278. VisitType = aiOrderVisitDetail.OrderVisit.VisitType,
  279. PublishTime = aiOrderVisitDetail.OrderVisit.PublishTime,
  280. }, cancellationToken: HttpContext.RequestAborted);
  281. }
  282. }
  283. else
  284. {
  285. aiOrderVisitDetail.AiOrderVisitState = (Share.Enums.Ai.EAiOrderVisitState)(dto.TaskStatus); //更新AI子表
  286. aiOrderVisit.VisitedFailCount++;
  287. //处理回访主表
  288. aiOrderVisitDetail.OrderVisit.AiVisitTime();
  289. aiOrderVisitDetail.OrderVisit.AiVisitCount++;
  290. aiOrderVisitDetail.OrderVisit.IsCanAiVisit = true;
  291. aiOrderVisitDetail.OrderVisit.VisitState = EVisitState.WaitForVisit;
  292. aiOrderVisitDetail.IsSuccess = false;
  293. await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted);
  294. }
  295. //var callRecord = dto.CallRecordList.OrderByDescending(x => x.CallNo).FirstOrDefault(x => x.ReturnVisit == 1);
  296. await _aiOrderVisitDetailRepository.UpdateAsync(aiOrderVisitDetail, HttpContext.RequestAborted);
  297. if ((aiOrderVisit.VisitedFailCount+aiOrderVisit.VisitedCount)== aiOrderVisit.HasVisitCount)
  298. {
  299. aiOrderVisit.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.Ended;
  300. }
  301. await _aiOrderVisitRepository.UpdateAsync(aiOrderVisit, HttpContext.RequestAborted);
  302. #region 注释
  303. //处理不满意结果(如果差评没有不满意原因则不能视为回访完成) --(不满意设置为失效,生成新的人工回访记录)
  304. //处理网站通知差评数据
  305. //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"))
  306. //{
  307. // //处理老数据
  308. // aiOrderVisitDetail.OrderVisit.VisitState = EVisitState.None;
  309. // await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit);
  310. // //推送老数据变更给门户
  311. // await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
  312. // {
  313. // Id = aiOrderVisitDetail.OrderVisit.Id,
  314. // Order = _mapper.Map<OrderDto>(aiOrderVisitDetail.OrderVisit.Order),
  315. // OrderVisitDetails = _mapper.Map<List<VisitDetailDto>>(aiOrderVisitDetail.OrderVisit.OrderVisitDetails),
  316. // VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime,
  317. // VisitType = aiOrderVisitDetail.OrderVisit.VisitType,
  318. // VisitState = aiOrderVisitDetail.OrderVisit.VisitState,
  319. // PublishTime = aiOrderVisitDetail.OrderVisit.PublishTime,
  320. // }, cancellationToken: HttpContext.RequestAborted);
  321. // //包含不满意数据,重新生成新的回访
  322. // var newOrderVisit = _mapper.Map<OrderVisit>(aiOrderVisitDetail.OrderVisit);
  323. // newOrderVisit.InitId();
  324. // newOrderVisit.VisitState = EVisitState.NoSatisfiedWaitForVisit;
  325. // newOrderVisit.VisitTime = null;
  326. // newOrderVisit.IsCanHandle = false;
  327. // newOrderVisit.IsCanAiVisit = false;
  328. // newOrderVisit.AiVisitCount = 0;
  329. // await _orderVisitRepository.AddAsync(newOrderVisit, HttpContext.RequestAborted);
  330. // var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id);
  331. // var list = _mapper.Map<List<OrderVisitDetail>>(visitDetail);
  332. // list.ForEach(x =>
  333. // {
  334. // x.VisitId = newOrderVisit.Id;
  335. // x.VoiceEvaluate = null;
  336. // x.VoiceEvaluate = null;
  337. // x.OrgHandledAttitude = null;
  338. // x.OrgNoSatisfiedReason = null;
  339. // x.OrgProcessingResults = null;
  340. // x.VisitContent = "";
  341. // });
  342. // await _orderVisitDetailRepository.AddRangeAsync(list, HttpContext.RequestAborted);
  343. // //推送新数据给门户
  344. // await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
  345. // {
  346. // Id = newOrderVisit.Id,
  347. // Order = _mapper.Map<OrderDto>(aiOrderVisitDetail.OrderVisit.Order),
  348. // OrderVisitDetails = _mapper.Map<List<VisitDetailDto>>(list),
  349. // VisitTime = newOrderVisit.VisitTime,
  350. // VisitType = newOrderVisit.VisitType,
  351. // VisitState = newOrderVisit.VisitState,
  352. // PublishTime = newOrderVisit.PublishTime,
  353. // }, cancellationToken: HttpContext.RequestAborted);
  354. //}
  355. //else
  356. //{
  357. #endregion
  358. if (aiOrderVisitDetail.OrderVisit.VisitState == EVisitState.Visited)
  359. {
  360. //写入质检
  361. await _qualityApplication.AddQualityAsync(EQualitySource.Visit, aiOrderVisitDetail.OrderVisit.Order.Id, aiOrderVisitDetail.OrderVisit.Id,
  362. HttpContext.RequestAborted);
  363. }
  364. //}
  365. }
  366. }
  367. }
  368. }
  369. /// <summary>
  370. /// 智能回访列表
  371. /// </summary>
  372. /// <param name="dto"></param>
  373. /// <returns></returns>
  374. [HttpGet("aivisit/aivisit-list")]
  375. public async Task<PagedDto<AiOrderVisitDto>> AiVisitList([FromQuery]AiVisitListDto dto)
  376. {
  377. var (total,items) = await _aiOrderVisitRepository.Queryable()
  378. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.Name.Contains(dto.Keyword))
  379. .OrderByDescending(x => x.CreationTime)
  380. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  381. return new PagedDto<AiOrderVisitDto>(total, _mapper.Map<IReadOnlyList<AiOrderVisitDto>>(items));
  382. }
  383. /// <summary>
  384. /// 智能回访明细
  385. /// </summary>
  386. /// <param name="dto"></param>
  387. /// <returns></returns>
  388. [HttpGet("aivisit/aivisitdetail-list")]
  389. public async Task<PagedDto<AiOrderVisitDetailDto>> AiVisitDetailList([FromQuery]AiVisitDetailListDto dto)
  390. {
  391. var (total, items) = await _aiOrderVisitDetailRepository.Queryable()
  392. .Includes(x=>x.OrderVisit,x=>x.OrderVisitDetails)
  393. .Includes(x=>x.Order)
  394. .Where(x => x.AiOrderVisitId == dto.Id)
  395. .WhereIF(!string.IsNullOrEmpty(dto.Keyword),x=>x.Order.No.Contains(dto.Keyword) || x.Order.Title.Contains(dto.Keyword))
  396. .OrderByDescending(x => x.CreationTime)
  397. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  398. return new PagedDto<AiOrderVisitDetailDto>(total, _mapper.Map<IReadOnlyList<AiOrderVisitDetailDto>>(items));
  399. }
  400. /// <summary>
  401. /// 可进行智能回访记录
  402. /// </summary>
  403. /// <returns></returns>
  404. [HttpGet("aivisit/canaivisit-list")]
  405. public async Task<IReadOnlyList<OrderVisitDto>> CanAiVisitList([FromQuery]CanAiVisitListDto dto)
  406. {
  407. var items= await _orderVisitRepository.Queryable()
  408. .Includes(x=>x.Order)
  409. .Where(x => x.VisitState == Share.Enums.Order.EVisitState.WaitForVisit && x.IsCanAiVisit == true)
  410. .WhereIF(dto.HotspotIds.Any(), x => dto.HotspotIds.Contains(x.Order.HotspotId)) //热点类型
  411. .WhereIF(dto.AcceptTypes.Any(), x => dto.AcceptTypes.Contains(x.Order.AcceptTypeCode)) //受理类型
  412. .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No)) //工单编码
  413. .WhereIF(!string.IsNullOrEmpty(dto.Title),x=> x.Order.Title.Contains(dto.Title))
  414. .ToListAsync();
  415. return _mapper.Map<IReadOnlyList<OrderVisitDto>>(items);
  416. }
  417. /// <summary>
  418. /// 页面基础数据
  419. /// </summary>
  420. /// <returns></returns>
  421. [HttpGet("aivisit/base-data")]
  422. public async Task<object> BaseData()
  423. {
  424. var rsp = new
  425. {
  426. AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
  427. };
  428. return rsp;
  429. }
  430. /// <summary>
  431. /// 新增智能回访任务
  432. /// </summary>
  433. /// <returns></returns>
  434. [HttpPost("aivisit/add-aivisit")]
  435. public async Task AddAiVisit([FromBody]AddAiVisitDto dto)
  436. {
  437. //验证是否有重复电话
  438. if(dto.AiOrderVisitDetails.Distinct().Count() != dto.AiOrderVisitDetails.Count)
  439. {
  440. throw UserFriendlyException.SameMessage("任务中存在重复外呼号码,请检查后重新提交");
  441. }
  442. var model = _mapper.Map<AiOrderVisit>(dto);
  443. var detaillist = _mapper.Map<List<AiOrderVisitDetail>>(dto.AiOrderVisitDetails);
  444. model.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.InProgress;
  445. model.RuleType = 2;
  446. model.HasVisitCount = dto.AiOrderVisitDetails.Count;
  447. model.VisitedCount = 0;
  448. model.VisitedFailCount = 0;
  449. var id = await _aiOrderVisitRepository.AddAsync(model, HttpContext.RequestAborted);
  450. detaillist.ForEach(x =>
  451. {
  452. x.AiOrderVisitId = id;
  453. x.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.InProgress;
  454. });
  455. await _aiOrderVisitDetailRepository.AddRangeAsync(detaillist, HttpContext.RequestAborted);
  456. //推送任务
  457. //准备原始数据
  458. var pushModel = await _aiOrderVisitRepository.Queryable()
  459. .Includes(x => x.AiOrderVisitDetails, s => s.Order)
  460. .Includes(x => x.AiOrderVisitDetails, s => s.OrderVisit, q => q.OrderVisitDetails)
  461. .FirstAsync(x => x.Id == id);
  462. var newModel = await _aiVisitService.CreateAiOrderVisitTask(pushModel, HttpContext.RequestAborted);
  463. if (!string.IsNullOrEmpty(newModel.BatchUid))
  464. {
  465. //修改回访主表
  466. await _orderVisitRepository.Updateable()
  467. .SetColumns(x => x.IsCanAiVisit == false)
  468. .SetColumns(x => x.VisitState == EVisitState.Visiting)
  469. .Where(x => detaillist.Select(s => s.OrderVisitId).Contains(x.Id)).ExecuteCommandAsync(HttpContext.RequestAborted);
  470. }
  471. else
  472. {
  473. newModel.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.Ended;
  474. }
  475. await _aiOrderVisitRepository.UpdateAsync(newModel, HttpContext.RequestAborted);
  476. await _aiOrderVisitDetailRepository.UpdateRangeAsync(newModel.AiOrderVisitDetails, HttpContext.RequestAborted);
  477. }
  478. #endregion
  479. }
  480. }