IPPbxController.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. using DotNetCore.CAP;
  2. using ExtendedNumerics.Exceptions;
  3. using Hotline.Ai.Quality;
  4. using Hotline.Application.CallCenter.Calls;
  5. using Hotline.Application.Systems;
  6. using Hotline.Application.Tels;
  7. using Hotline.Caching.Interfaces;
  8. using Hotline.CallCenter.Calls;
  9. using Hotline.CallCenter.Configs;
  10. using Hotline.CallCenter.Tels;
  11. using Hotline.Orders;
  12. using Hotline.Permissions;
  13. using Hotline.Quality;
  14. using Hotline.Repository.SqlSugar.Extensions;
  15. using Hotline.Settings;
  16. using Hotline.Share.Dtos;
  17. using Hotline.Share.Dtos.CallCenter;
  18. using Hotline.Share.Dtos.Order;
  19. using Hotline.Share.Dtos.TrCallCenter;
  20. using Hotline.Share.Enums.CallCenter;
  21. using Hotline.Share.Enums.Quality;
  22. using Hotline.Users;
  23. using MapsterMapper;
  24. using Microsoft.AspNetCore.Authorization;
  25. using Microsoft.AspNetCore.Mvc;
  26. using Microsoft.Extensions.Options;
  27. using Newtonsoft.Json;
  28. using System.Threading;
  29. using Hotline.EventBus;
  30. using Tr.Sdk;
  31. using Tr.Sdk.Blacklist;
  32. using Tr.Sdk.Tels;
  33. using XF.Domain.Authentications;
  34. using XF.Domain.Exceptions;
  35. using XF.Domain.Filters;
  36. using XF.Domain.Repository;
  37. using XF.Utility.EnumExtensions;
  38. using Hotline.FlowEngine.Notifications;
  39. using Hotline.Quality.Notifications;
  40. using Hotline.Share.Dtos.Quality;
  41. using MongoDB.Driver;
  42. using Hotline.Repository.SqlSugar.CallCenter;
  43. using SqlSugar;
  44. namespace Hotline.Api.Controllers
  45. {
  46. public class IPPbxController : BaseController
  47. {
  48. private readonly ITrClient _trClient;
  49. private readonly IMapper _mapper;
  50. private readonly IUserDomainService _userDomainService;
  51. private readonly ISessionContext _sessionContext;
  52. private readonly IRepository<TrCallRecord> _trCallRecordRepository;
  53. private readonly ITrApplication _trApplication;
  54. private readonly IRepository<TrCallEvaluate> _trCallEvaluate;
  55. private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
  56. private readonly ILogger<IPPbxController> _logger;
  57. private readonly IOrderRepository _orderRepository;
  58. private readonly IRepository<OrderVisit> _orderVisitRepository;
  59. private readonly IUserCacheManager _userCacheManager;
  60. private readonly ICapPublisher _capPublisher;
  61. private readonly ITelRestRepository _telRestRepository;
  62. private readonly IRepository<User> _userRepository;
  63. private readonly ITelApplication _telApplication;
  64. private readonly IRepository<Quality.Quality> _qualiteyRepository;
  65. private readonly IAiQualityService _aiQualityService;
  66. private readonly IRepository<QualityTemplate> _qualityTemplate;
  67. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  68. private readonly IRepository<TelActionRecord> _telActionRecordRepository;
  69. private readonly ISystemMobilAreaApplication _systemMobilAreaApplication;
  70. private readonly IRepository<Work> _workRepository;
  71. private readonly Publisher _publisher;
  72. public IPPbxController(ITrClient trClient, IMapper mapper, IUserDomainService userDomainService,
  73. ISessionContext sessionContext, IRepository<TrCallRecord> trCallRecordRepository,
  74. ITrApplication trApplication, IRepository<TrCallEvaluate> trCallRecord,
  75. ISystemDicDataCacheManager systemDicDataCacheManager, ILogger<IPPbxController> logger,
  76. IOrderRepository orderRepository, IRepository<OrderVisit> orderVisitRepository,
  77. IUserCacheManager userCacheManager, ICapPublisher capPublisher,
  78. ITelRestRepository telRestRepository, IRepository<User> userRepository,
  79. ITelApplication telApplication, IRepository<Quality.Quality> qualiteyRepository,
  80. IAiQualityService aiQualityService, IRepository<QualityTemplate> qualityTemplate,
  81. ISystemSettingCacheManager systemSettingCacheManager,IRepository<TelActionRecord> telActionRecordRepository,
  82. ISystemMobilAreaApplication systemMobilAreaApplication,IRepository<Work> workRepository ,Publisher publisher)
  83. {
  84. _trClient = trClient;
  85. _mapper = mapper;
  86. _userDomainService = userDomainService;
  87. _sessionContext = sessionContext;
  88. _trCallRecordRepository = trCallRecordRepository;
  89. _trApplication = trApplication;
  90. _trCallEvaluate = trCallRecord;
  91. _systemDicDataCacheManager = systemDicDataCacheManager;
  92. _logger = logger;
  93. _orderRepository = orderRepository;
  94. _orderVisitRepository = orderVisitRepository;
  95. _userCacheManager = userCacheManager;
  96. _capPublisher = capPublisher;
  97. _telRestRepository = telRestRepository;
  98. _userRepository = userRepository;
  99. _telApplication = telApplication;
  100. _qualiteyRepository = qualiteyRepository;
  101. _aiQualityService = aiQualityService;
  102. _qualityTemplate = qualityTemplate;
  103. _systemSettingCacheManager = systemSettingCacheManager;
  104. _telActionRecordRepository = telActionRecordRepository;
  105. _systemMobilAreaApplication = systemMobilAreaApplication;
  106. _workRepository = workRepository;
  107. _publisher = publisher;
  108. }
  109. #region 添添呼
  110. #region 分机
  111. /// <summary>
  112. /// 查询所有分机
  113. /// </summary>
  114. /// <returns></returns>
  115. [HttpGet("query-tels")]
  116. public async Task<List<TrTelDto>> TrQueryTels()
  117. {
  118. var tels = await _trClient.QueryTelsAsync(new QueryTelRequest() { }, HttpContext.RequestAborted);
  119. var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue;
  120. tels = tels.Where(m => listenTels.Contains(m.TelNo) == false).ToList();
  121. var returnlist = _mapper.Map<List<TrTelDto>>(tels);
  122. string callOutQueueId = _systemSettingCacheManager.GetSetting(SettingConstants.CallOutQueueId).SettingValue[0];
  123. returnlist.ForEach(x =>
  124. {
  125. x.CallOutQueue = callOutQueueId;
  126. });
  127. return returnlist;
  128. }
  129. /// <summary>
  130. /// 查询所有分机状态
  131. /// </summary>
  132. /// <returns></returns>
  133. [HttpGet("query-telstate")]
  134. [AllowAnonymous]
  135. public async Task<IReadOnlyList<TrTelStateDto>> TrQueryTelState([FromQuery] string? state, bool hasListen)
  136. {
  137. var tels = await _trClient.QueryTelStateAsync(new QueryTelStateRequest() { State = state }, HttpContext.RequestAborted);
  138. var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue;
  139. var workList = await _workRepository.Queryable().Where(d=> 1 == 1 && !d.EndTime.HasValue).ToListAsync();
  140. var query = from tel in tels.AgentList
  141. join works in workList on tel.TelNo equals works.TelNo into workD
  142. from work in workD.DefaultIfEmpty()
  143. select new TrTelStateDto
  144. {
  145. Id = tel.Id,
  146. TelNo =tel.TelNo,
  147. ChannelUUid =tel.ChannelUUid,
  148. TelName =tel.TelName,
  149. Type = tel.Type,
  150. Weight =tel.Weight,
  151. Queue = tel.Queue,
  152. State = tel.State,
  153. OldState = tel.OldState,
  154. Device = tel.Device,
  155. SipIp = tel.SipIp,
  156. PrivateData = tel.PrivateData,
  157. SipState = tel.SipState,
  158. CreatedAt = tel.CreatedAt,
  159. UpdatedAt = tel.UpdatedAt,
  160. CallDirection = tel.CallDirection,
  161. OtherNumber = tel.OtherNumber,
  162. GateWay = tel.GateWay,
  163. AnsweredAt = tel.AnsweredAt,
  164. WorkUserId = (work != null) ? work.UserId: "",
  165. WorkUserName = (work != null) ? work.UserName: "",
  166. };
  167. if (hasListen == false)
  168. {
  169. query = query.Where(m => listenTels.Contains(m.TelNo) == false);
  170. }
  171. var list = query.OrderBy(x=>x.TelNo).OrderByDescending(x=>x.CreatedAt).ToList();
  172. return list;// _mapper.Map<IReadOnlyList<TrTelStateDto>>(tels.AgentList);
  173. }
  174. /// <summary>
  175. /// 查询坐席分机状态及通话信息
  176. /// </summary>
  177. /// <param name="telno"></param>
  178. /// <returns></returns>
  179. [HttpGet("query-telstatebyno")]
  180. [AllowAnonymous]
  181. public async Task<TrTelStateDto> TrQueryTelStateByTelNo([FromQuery]string? telno)
  182. {
  183. var tels = await _trClient.QueryTelStateAsync(new QueryTelStateRequest() { TelNo = telno }, HttpContext.RequestAborted);
  184. var listenTels = _systemSettingCacheManager.GetSetting(SettingConstants.ListenTels)?.SettingValue;
  185. tels.Agents = tels.Agents?.Where(m => listenTels.Contains(m.TelNo) == false).ToList();
  186. var tel = tels.Agents?.FirstOrDefault();
  187. if (tel != null)
  188. {
  189. var work = _userCacheManager.GetWorkByTel(tel.TelNo);
  190. var result = new TrTelStateDto
  191. {
  192. Id = tel.Id,
  193. TelNo = tel.TelNo,
  194. ChannelUUid = tel.ChannelUUid,
  195. TelName = tel.TelName,
  196. Type = tel.Type,
  197. Weight = tel.Weight,
  198. Queue = tel.Queue,
  199. State = tel.State,
  200. OldState = tel.OldState,
  201. Device = tel.Device,
  202. SipIp = tel.SipIp,
  203. PrivateData = tel.PrivateData,
  204. SipState = tel.SipState,
  205. CreatedAt = tel.CreatedAt,
  206. UpdatedAt = tel.UpdatedAt,
  207. CallDirection = tel.CallDirection,
  208. OtherNumber = tel.OtherNumber,
  209. GateWay = tel.GateWay,
  210. AnsweredAt = tel.AnsweredAt,
  211. WorkUserId = (work != null) ? work.UserId : "",
  212. WorkUserName = (work != null) ? work.UserName : "",
  213. };
  214. 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();
  215. return result;
  216. }
  217. return null;
  218. }
  219. #endregion
  220. #region 黑白名单
  221. /// <summary>
  222. /// 新增黑白名单
  223. /// </summary>
  224. /// <param name="dto"></param>
  225. /// <returns></returns>
  226. [HttpPost("add-blacklist")]
  227. public async Task AddBlacklist([FromBody] TrAddBlacklistDto dto)
  228. {
  229. await _trClient.AddBlacklistAsync(new AddBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
  230. }
  231. /// <summary>
  232. /// 删除黑白名单
  233. /// </summary>
  234. /// <param name="dto"></param>
  235. /// <returns></returns>
  236. [HttpPost("remove-blacklist")]
  237. public async Task DelBlacklist([FromBody] TrDelBlacklistDto dto)
  238. {
  239. await _trClient.DelBlacklistAsync(new DelBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
  240. }
  241. /// <summary>
  242. /// 查询黑白名单
  243. /// </summary>
  244. /// <returns></returns>
  245. [HttpGet("query-blacklist")]
  246. public async Task<IReadOnlyList<TrQueryBlacklistResponseDto>> QueryBlacklist([FromQuery] TrQueryBlacklistDto dto)
  247. {
  248. var blacklist = await _trClient.QueryBlacklistAsync(new QueryBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
  249. return _mapper.Map<IReadOnlyList<TrQueryBlacklistResponseDto>>(blacklist);
  250. }
  251. #endregion
  252. #region 签入后调用
  253. /// <summary>
  254. /// 上班
  255. /// </summary>
  256. /// <param name="dto"></param>
  257. /// <returns></returns>
  258. [HttpPost("on-duty")]
  259. public async Task<TrOnDutyResponseDto> OnDuty([FromBody] TrOnDutyDto dto)
  260. {
  261. //return await _userDomainService.TrOnDutyAsync(_sessionContext.RequiredUserId,dto.TelNo, HttpContext.RequestAborted);
  262. return await _trApplication.OnSign(_sessionContext.RequiredUserId, dto.TelNo,(ETelModel)dto.TelModelState, HttpContext.RequestAborted);
  263. }
  264. /// <summary>
  265. /// 下班
  266. /// </summary>
  267. /// <returns></returns>
  268. [HttpPost("off-duty")]
  269. public async Task OffDuty()
  270. {
  271. //await _userDomainService.TrOffDutyAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  272. await _telApplication.SignOutAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  273. }
  274. /// <summary>
  275. /// 查询当前用户的分机状态
  276. /// </summary>
  277. /// <returns></returns>
  278. [HttpGet("tel-state")]
  279. public async Task<TrOnDutyResponseDto> TelState()
  280. {
  281. return await _trApplication.TelState(_sessionContext.RequiredUserId,HttpContext.RequestAborted);
  282. }
  283. /// <summary>
  284. /// 切换状态
  285. /// </summary>
  286. /// <param name="userId"></param>
  287. /// <param name="isCallOut"></param>
  288. /// <returns></returns>
  289. [HttpPost("change-telmodel")]
  290. public async Task ChangeTelModel([FromBody] ChangeTelModelDto dto)
  291. {
  292. await _trApplication.ChangeTelModel(_sessionContext.RequiredUserId, dto.isCallOut,HttpContext.RequestAborted);
  293. }
  294. /// <summary>
  295. /// 下班-管理手动操作
  296. /// </summary>
  297. /// <param name="telNo"></param>
  298. /// <returns></returns>
  299. [HttpGet("off-duty-manage")]
  300. public async Task OffDuty([FromQuery]string telNo)
  301. {
  302. await _telApplication.SignOutByTelNoAsync(telNo, HttpContext.RequestAborted);
  303. }
  304. //提供关闭浏览器事件触发调用
  305. [HttpPost("off-duty-no-auth")]
  306. [AllowAnonymous]
  307. public async Task OffDutyWithNoAuthorize([FromQuery] string userId)
  308. {
  309. if (string.IsNullOrEmpty(userId))
  310. throw new UserFriendlyException("无效用户编号");
  311. await _telApplication.SignOutByTelNoAsync(userId, HttpContext.RequestAborted);
  312. }
  313. #endregion
  314. #region 添添呼小休
  315. /// <summary>
  316. /// 小休页面基础数据
  317. /// </summary>
  318. /// <returns></returns>
  319. [HttpGet("rest/basedata")]
  320. public async Task<object> RestBasePaegData()
  321. {
  322. var rsp = new
  323. {
  324. RestReason = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.RestReason)
  325. };
  326. return rsp;
  327. }
  328. /// <summary>
  329. /// 小休
  330. /// </summary>
  331. /// <returns></returns>
  332. [HttpPost("rest")]
  333. public async Task Rest([FromBody] TrRestDto dto)
  334. {
  335. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  336. if (work == null)
  337. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  338. var isResting = await _telRestRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted);
  339. if (isResting)
  340. throw UserFriendlyException.SameMessage("当前坐席正在休息");
  341. var user = await _userRepository.GetAsync(work.UserId, HttpContext.RequestAborted);
  342. var telRest = new TelRest(work.TelNo, work.TelNo, work.UserId, work.UserName, dto.Reason, false, user.StaffNo);
  343. await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
  344. var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, EActionType.TelRest);
  345. await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted);
  346. }
  347. /// <summary>
  348. /// 取消小休
  349. /// </summary>
  350. /// <returns></returns>
  351. [HttpPost("unrest")]
  352. public async Task UnRest()
  353. {
  354. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  355. if (work is null)
  356. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  357. var telRest = await _telRestRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, HttpContext.RequestAborted);
  358. if (telRest is null)
  359. throw UserFriendlyException.SameMessage("未查询到分机休息信息");
  360. telRest.EndRest();
  361. await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted);
  362. var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == EActionType.TelRest && !x.EndTime.HasValue, HttpContext.RequestAborted);
  363. if (telAction!=null)
  364. {
  365. telAction.EndAction();
  366. await _telActionRecordRepository.UpdateAsync(telAction);
  367. }
  368. }
  369. #endregion
  370. #region 添添呼话后整理
  371. /// <summary>
  372. /// 分机动作开始
  373. /// </summary>
  374. /// <returns></returns>
  375. [HttpGet("callennd-arrange-begin")]
  376. public async Task CallEndArrangeBegin(int actionType)
  377. {
  378. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  379. if (work is null)
  380. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  381. //判断如果已经存在未结束的该动作就不新增
  382. var ishas = await _telActionRecordRepository.AnyAsync(x => x.TelNo == work.TelNo && x.ActionType == (EActionType)actionType && !x.EndTime.HasValue, HttpContext.RequestAborted);
  383. if (!ishas)
  384. {
  385. var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, (EActionType)actionType);
  386. await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted);
  387. }
  388. }
  389. /// <summary>
  390. /// 分机动作结束
  391. /// </summary>
  392. /// <returns></returns>
  393. [HttpGet("callend-arrange-end")]
  394. public async Task CallEndArrangeEnd(int actionType)
  395. {
  396. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  397. if (work is null)
  398. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  399. var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == (EActionType)actionType && !x.EndTime.HasValue, HttpContext.RequestAborted);
  400. if (telAction != null)
  401. {
  402. telAction.EndAction();
  403. await _telActionRecordRepository.UpdateAsync(telAction);
  404. }
  405. }
  406. #endregion
  407. #region 通话记录(对外)
  408. /// <summary>
  409. /// 接收通话记录
  410. /// </summary>
  411. /// <param name="dto"></param>
  412. /// <returns></returns>
  413. [AllowAnonymous]
  414. [HttpPost("receivecallrecord")]
  415. public async Task<OpenResponse> ReceiveCallRecord([FromBody] ReceiveCallRecordDto dto)
  416. {
  417. _logger.LogInformation($"收到通话记录结果回传:{JsonConvert.SerializeObject(dto)}");
  418. //bool isHas = await _trCallRecordRepository.AnyAsync(x => x.OtherAccept == dto.other_accept, HttpContext.RequestAborted);
  419. //if (isHas)
  420. //{
  421. // return OpenResponse.Ok("success");
  422. //}
  423. var model = _mapper.Map<TrCallRecord>(dto);
  424. model.Duration = 0;
  425. model.RingTimes = 0;
  426. model.QueueTims = 0;
  427. model.OnState = Share.Enums.CallCenter.EOnState.NoOn;
  428. //计算通话时长
  429. if (model.AnsweredTime != null)
  430. {
  431. model.OnState = Share.Enums.CallCenter.EOnState.On; //是否接通
  432. TimeSpan tsend = new TimeSpan(model.OverTime.Ticks);
  433. TimeSpan tsbegin = new TimeSpan(model.AnsweredTime.Value.Ticks);
  434. model.Duration = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  435. }
  436. //计算振铃时长
  437. if (model.BeginRingTime != null)
  438. {
  439. TimeSpan tsbegin = new TimeSpan(model.BeginRingTime.Value.Ticks);
  440. if (model.EndRingTimg != null)
  441. {
  442. TimeSpan tsend = new TimeSpan(model.EndRingTimg.Value.Ticks);
  443. model.RingTimes = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  444. }
  445. else
  446. {
  447. TimeSpan tsend = new TimeSpan(model.OverTime.Ticks);
  448. model.RingTimes = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  449. }
  450. }
  451. //计算队列时长
  452. if (model.BeginQueueTime != null && model.EndQueueTime != null)
  453. {
  454. TimeSpan tsend = new TimeSpan(model.EndQueueTime.Value.Ticks);
  455. TimeSpan tsbegin = new TimeSpan(model.BeginQueueTime.Value.Ticks);
  456. model.QueueTims = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  457. }
  458. //获取分机号
  459. if (model.CallDirection == ECallDirection.In)
  460. {
  461. model.TelNo = model.CDPN;
  462. try
  463. {
  464. var areaModel = await _systemMobilAreaApplication.GetPhoneCardArea(model.CPN, HttpContext.RequestAborted);
  465. if (areaModel != null)
  466. {
  467. model.MobileAreaName = areaModel.MobileAreaName;
  468. model.OFlag = areaModel.OFlag;
  469. model.OperatorName = areaModel.OperatorName;
  470. }
  471. }
  472. catch{}
  473. }
  474. else
  475. {
  476. model.TelNo = model.CPN;
  477. try
  478. {
  479. var areaModel = await _systemMobilAreaApplication.GetPhoneCardArea(model.CDPN, HttpContext.RequestAborted);
  480. if (areaModel != null)
  481. {
  482. model.MobileAreaName = areaModel.MobileAreaName;
  483. model.OFlag = areaModel.OFlag;
  484. model.OperatorName = areaModel.OperatorName;
  485. }
  486. }
  487. catch{}
  488. }
  489. //判断是否是内部通话(目前分机都为4位)
  490. if (model.CPN.Length==4 && model.CDPN.Length ==4) //是内部通话
  491. {
  492. model.TelNo = model.CDPN;//如果是内部通话 响应分机为被叫号码
  493. }
  494. if (!string.IsNullOrEmpty(dto.phoneTypes))
  495. {
  496. model.PhoneTypes = (EPhoneTypes)Convert.ToInt32(dto.phoneTypes);
  497. }
  498. //获取关联 工单或是回访
  499. //var order = await _orderRepository.GetAsync(x => x.CallId == model.CallAccept, HttpContext.RequestAborted);//由CallAccept改为OtherAccept
  500. var order = await _orderRepository.GetAsync(x => x.CallId == model.OtherAccept && string.IsNullOrEmpty(x.CallId) == false, HttpContext.RequestAborted);
  501. if (order != null)
  502. {
  503. model.CallOrderType = ECallOrderType.Order;
  504. model.ExternalId = order.Id;
  505. try
  506. {
  507. // 写入智能质检
  508. var teAny = await _qualityTemplate.Queryable()
  509. .LeftJoin<QualityTemplateDetail>((x, d) => x.Id == d.TemplateId)
  510. .LeftJoin<QualityItem>((x, d, i) => d.ItemId == i.Id)
  511. .Where((x, d, i) => i.IsIntelligent == 1)
  512. .Where((x, d, i) => x.Grouping == ETemplateGrouping.Accepted).AnyAsync();
  513. if (teAny)
  514. {
  515. var quality = await _qualiteyRepository.Queryable().Where(x => x.OrderId == order.Id && x.Source == Share.Enums.Quality.EQualitySource.Accepted).FirstAsync();
  516. if (quality !=null)
  517. {
  518. var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
  519. //await _aiQualityService.CreateAiOrderQualityTask(quality, model, order, setting?.SettingValue[0], HttpContext.RequestAborted);
  520. try
  521. {
  522. //_aiQualityService.CreateAiOrderQualityTask(
  523. //quality,
  524. //model.RecordingAbsolutePath,
  525. //model.CPN,
  526. //model.CreatedTime,
  527. //order, setting?.SettingValue[0], HttpContext.RequestAborted);
  528. var handler = new AiQualityHandler()
  529. {
  530. Id = quality.Id,
  531. Source = quality.Source.ToString(),
  532. AudioFile = model.RecordingAbsolutePath,
  533. FromNo = model.CPN,
  534. CallStartTime = model.CreatedTime,
  535. ViteRecordPrefix = setting?.SettingValue[0],
  536. };
  537. await _publisher.PublishAsync(new AiOrderQualityNotify(handler), PublishStrategy.ParallelNoWait, HttpContext.RequestAborted);
  538. }
  539. catch (Exception e)
  540. {
  541. _logger.LogError($"写入智能质检异常!, \r\n{e.Message}");
  542. }
  543. }
  544. }
  545. }
  546. catch{}
  547. }
  548. else
  549. {
  550. //var orderVisit = await _orderVisitRepository.GetAsync(x => x.CallId == model.CallAccept, HttpContext.RequestAborted);//由CallAccept改为OtherAccept
  551. var orderVisit = await _orderVisitRepository.GetAsync(x => x.CallId == model.OtherAccept && string.IsNullOrEmpty(x.CallId) == false, HttpContext.RequestAborted);
  552. if (orderVisit != null)
  553. {
  554. model.CallOrderType = ECallOrderType.Visit;
  555. model.ExternalId = orderVisit.Id;
  556. }
  557. }
  558. //获取用户
  559. var work = await _userCacheManager.GetWorkByTelNoLast(model.TelNo);
  560. if (!string.IsNullOrEmpty(model.AgentTransferNumber))
  561. {
  562. work = await _userCacheManager.GetWorkByTelNoLast(model.AgentTransferNumber);
  563. }
  564. if (work != null)
  565. {
  566. model.UserId = work.UserId;
  567. model.UserName = work.UserName;
  568. model.StaffNo = work.StaffNo;
  569. }
  570. await _trCallRecordRepository.AddAsync(model, HttpContext.RequestAborted);
  571. var publishCallRecordDto = new PublishCallRecrodDto() { };
  572. if (order != null)
  573. {
  574. var orderDto = _mapper.Map<OrderDto>(order);
  575. publishCallRecordDto.Order = orderDto;
  576. }
  577. var trCallDto = _mapper.Map<TrCallDto>(model);
  578. publishCallRecordDto.TrCallRecordDto = trCallDto;
  579. if (string.IsNullOrEmpty(model.AgentTransferNumber))
  580. {
  581. //推省上
  582. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallBye, publishCallRecordDto);
  583. }
  584. return OpenResponse.Ok("success");
  585. }
  586. /// <summary>
  587. /// 接受通话语音评价
  588. /// </summary>
  589. /// <param name="dto"></param>
  590. /// <returns></returns>
  591. [AllowAnonymous]
  592. [HttpPost("receivecallevaluate")]
  593. public async Task<OpenResponse> ReceiveCallEvaluate([FromBody] ReceiveCallEvaluateDto dto)
  594. {
  595. var model = _mapper.Map<TrCallEvaluate>(dto);
  596. await _trCallEvaluate.AddAsync(model, HttpContext.RequestAborted);
  597. return OpenResponse.Ok("success");
  598. }
  599. #endregion
  600. #region 通话记录(对内)
  601. /// <summary>
  602. /// 获取通话记录列表
  603. /// </summary>
  604. /// <param name="dto"></param>
  605. /// <returns></returns>
  606. [HttpGet("calls/call-list")]
  607. public async Task<PagedDto<TrCallDto>> GetCallList([FromQuery] GetCallListDto dto)
  608. {
  609. var (total, items) = await _trCallRecordRepository.Queryable()
  610. .Includes(x => x.Order)
  611. .WhereIF(!string.IsNullOrEmpty(dto.CPN), x => x.CPN.Contains(dto.CPN))
  612. .WhereIF(!string.IsNullOrEmpty(dto.CDPN), x => x.CDPN.Contains(dto.CDPN))
  613. .WhereIF(!string.IsNullOrEmpty(dto.TelNo), x => x.TelNo.Contains(dto.TelNo))
  614. .WhereIF(!string.IsNullOrEmpty(dto.UserName), x => x.UserName.Contains(dto.UserName))
  615. .WhereIF(dto.CallDirection != null, x => x.CallDirection == dto.CallDirection)
  616. .WhereIF(dto.OnState != null, x => x.OnState == dto.OnState)
  617. .WhereIF(dto.EndBy != null, x => x.EndBy == dto.EndBy)
  618. .WhereIF(dto.BeginIvrTimeStart.HasValue, x => x.BeginIvrTime >= dto.BeginIvrTimeStart)
  619. .WhereIF(dto.BeginIvrTimeEnd.HasValue,x=> x.BeginIvrTime<= dto.BeginIvrTimeEnd)
  620. .WhereIF(dto.EndIvrTimeStart.HasValue, x => x.EndIvrTime >= dto.EndIvrTimeStart)
  621. .WhereIF(dto.EndIvrTimeEnd.HasValue,x=> x.EndIvrTime <= dto.EndIvrTimeEnd)
  622. .WhereIF(dto.BeginQueueTimeStart.HasValue, x => x.BeginQueueTime >= dto.BeginQueueTimeEnd)
  623. .WhereIF(dto.BeginQueueTimeEnd.HasValue,x=> x.BeginQueueTime <= dto.BeginQueueTimeEnd)
  624. .WhereIF(dto.EndQueueTimeStart.HasValue, x => x.EndQueueTime >= dto.EndQueueTimeStart)
  625. .WhereIF(dto.EndQueueTimeEnd.HasValue,x=> x.EndQueueTime <= dto.EndQueueTimeEnd)
  626. .WhereIF(dto.AnsweredTimeStart.HasValue, x => x.AnsweredTime >= dto.AnsweredTimeStart)
  627. .WhereIF(dto.AnsweredTimeEnd.HasValue,x=> x.AnsweredTime<= dto.AnsweredTimeEnd)
  628. .WhereIF(dto.OverTimeStart.HasValue, x => x.OverTime >= dto.OverTimeStart)
  629. .WhereIF(dto.OverTimeEnd.HasValue,x=> x.OverTime <= dto.OverTimeEnd)
  630. .WhereIF(dto.BeginRingTimeStart.HasValue,x=> x.BeginRingTime>= dto.BeginRingTimeStart)
  631. .WhereIF(dto.BeginRingTimeEnd.HasValue,x=> x.BeginRingTime<= dto.BeginRingTimeEnd)
  632. .WhereIF(dto.EndRingTimeStart.HasValue,x=> x.EndRingTimg >= dto.EndRingTimeStart)
  633. .WhereIF(dto.EndRingTimeEnd.HasValue, x=>x.EndRingTimg <= dto.EndRingTimeEnd)
  634. .WhereIF(dto.CallTimeStart.HasValue, x=>x.CreatedTime>= dto.CallTimeStart)
  635. .WhereIF(dto.CallTimeEnd.HasValue, x=>x.CreatedTime <= dto.CallTimeEnd)
  636. .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.Order.No.Contains(dto.OrderNo))
  637. .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Order.Title.Contains(dto.Title))
  638. .WhereIF(!string.IsNullOrEmpty(dto.Gateway), x => x.Gateway.Contains(dto.Gateway))
  639. .WhereIF(dto.IsSensitiveWord.HasValue && dto.IsSensitiveWord == true, d => d.Sensitive != null && SqlFunc.JsonArrayLength(d.Sensitive) > 0)
  640. .WhereIF(!string.IsNullOrEmpty(dto.SensitiveWord), d => SqlFunc.JsonArrayAny(d.Sensitive, dto.SensitiveWord))
  641. .WhereIF(dto.IsAiAnswered == true,x=>string.IsNullOrEmpty(x.UserId) == true && x.BeginIvrTime.HasValue && x.EndIvrTime.HasValue)
  642. .WhereIF(dto.PhoneTypes!=null,x=>x.PhoneTypes == dto.PhoneTypes)
  643. .OrderByDescending(x => x.CreatedTime)
  644. .ToPagedListAsync(dto.PageIndex, dto.PageSize);
  645. return new PagedDto<TrCallDto>(total, _mapper.Map<IReadOnlyList<TrCallDto>>(items));
  646. }
  647. /// <summary>
  648. /// 通话记录基础数据
  649. /// </summary>
  650. /// <returns></returns>
  651. [HttpGet("calls/basedata")]
  652. public async Task<object> CallBaseData()
  653. {
  654. return new
  655. {
  656. OnState = EnumExts.GetDescriptions<EOnState>(),
  657. CallDirection = EnumExts.GetDescriptions<ECallDirection>(),
  658. EndBy = EnumExts.GetDescriptions<EEndBy>(),
  659. PhoneTypes = EnumExts.GetDescriptions<EPhoneTypes>()
  660. };
  661. }
  662. #endregion
  663. #region 关联
  664. /// <summary>
  665. /// 可关联工单
  666. /// </summary>
  667. /// <param name="dto"></param>
  668. /// <returns></returns>
  669. [HttpGet("canlink-order")]
  670. public async Task<PagedDto<OrderDto>> CanLinkCallRecordOrder([FromQuery] CanLinkCallRecordOrderDto dto)
  671. {
  672. var (total, items) = await _orderRepository.Queryable()
  673. .Where(x => string.IsNullOrEmpty(x.CallId) && x.Source == Share.Enums.Order.ESource.Hotline)
  674. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.No.Contains(dto.Keyword) || x.Title.Contains(dto.Keyword))
  675. .OrderByDescending(x => x.CreationTime)
  676. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  677. return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
  678. }
  679. /// <summary>
  680. /// 可关联回访
  681. /// </summary>
  682. /// <param name="dto"></param>
  683. /// <returns></returns>
  684. [HttpGet("canlink-ordervisit")]
  685. public async Task<PagedDto<OrderVisitDto>> CanLinkCallRecordOrderVisit([FromQuery] CanLinkCallRecordOrderVisitDto dto)
  686. {
  687. var (total, items) = await _orderVisitRepository.Queryable()
  688. .Includes(x => x.Order)
  689. .Where(x => string.IsNullOrEmpty(x.CallId) && x.VisitType == Share.Enums.Order.EVisitType.ArtificialVisit)
  690. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.No.Contains(dto.Keyword))
  691. .OrderByDescending(x => x.CreationTime)
  692. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  693. return new PagedDto<OrderVisitDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDto>>(items));
  694. }
  695. /// <summary>
  696. /// 关联工单或者回访
  697. /// </summary>
  698. /// <param name="dto"></param>
  699. /// <returns></returns>
  700. [HttpPost("link-callrecord")]
  701. public async Task LinkCallRecord([FromBody] LinkCallRecordDto dto)
  702. {
  703. //var trRecord = await _trCallRecordRepository.GetAsync(x => x.CallAccept == dto.CallId, HttpContext.RequestAborted);//由CallAccept改为OtherAccept
  704. if (dto.IsOrder)
  705. {
  706. var trRecords = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == dto.CallId).ToListAsync(HttpContext.RequestAborted);
  707. foreach (var trRecord in trRecords)
  708. {
  709. if (trRecord.CallOrderType == ECallOrderType.Order && !string.IsNullOrEmpty(trRecord.ExternalId))
  710. throw UserFriendlyException.SameMessage("通话记录已经关联工单");
  711. //工单
  712. var order = await _orderRepository.GetAsync(x => x.Id == dto.Id, HttpContext.RequestAborted);
  713. //if (!string.IsNullOrEmpty(order.CallId))
  714. // throw UserFriendlyException.SameMessage("通话记录已经关联工单");
  715. order.CallId = dto.CallId;
  716. order.FromPhone = trRecord.CPN;
  717. order.TransferPhone = trRecord.Gateway;
  718. await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted);
  719. trRecord.CallOrderType = ECallOrderType.Order;
  720. trRecord.ExternalId = order.Id;
  721. await _trCallRecordRepository.UpdateAsync(trRecord, HttpContext.RequestAborted);
  722. //推省上
  723. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto() { Order = _mapper.Map<OrderDto>(order), TrCallRecordDto = _mapper.Map<TrCallDto>(trRecord) });
  724. }
  725. }
  726. else
  727. {
  728. var trRecord = await _trCallRecordRepository.GetAsync(x => x.OtherAccept == dto.CallId, HttpContext.RequestAborted);
  729. //回访
  730. var visit = await _orderVisitRepository.GetAsync(x => x.Id == dto.Id, HttpContext.RequestAborted);
  731. visit.CallId = dto.CallId;
  732. await _orderVisitRepository.UpdateAsync(visit, HttpContext.RequestAborted);
  733. trRecord.CallOrderType = ECallOrderType.Visit;
  734. trRecord.ExternalId = visit.Id;
  735. await _trCallRecordRepository.UpdateAsync(trRecord, HttpContext.RequestAborted);
  736. }
  737. }
  738. #endregion
  739. #region 坐席动作记录
  740. /// <summary>
  741. /// 坐席动作列表
  742. /// </summary>
  743. /// <param name="dto"></param>
  744. /// <returns></returns>
  745. [HttpGet("telaction-list")]
  746. public async Task<PagedDto<TelActionListRep>> TelActionList([FromQuery] TelActionListDto dto)
  747. {
  748. var (total,items) = await _telActionRecordRepository.Queryable()
  749. .WhereIF(string.IsNullOrEmpty(dto.TelNo) == false, x => x.TelNo.Contains(dto.TelNo))
  750. .WhereIF(dto.ActionTtype != null, x => x.ActionType == dto.ActionTtype)
  751. .WhereIF(string.IsNullOrEmpty(dto.UserName) == false, x => x.UserName.Contains(dto.UserName))
  752. .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
  753. .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
  754. .OrderByDescending(x => x.CreationTime)
  755. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  756. return new PagedDto<TelActionListRep>(total, _mapper.Map<IReadOnlyList<TelActionListRep>>(items));
  757. }
  758. /// <summary>
  759. /// 坐席动作列表基础数据
  760. /// </summary>
  761. /// <returns></returns>
  762. [HttpGet("telaction-basedata")]
  763. public async Task<object> TelActionBaseData()
  764. {
  765. return new
  766. {
  767. ActionType = EnumExts.GetDescriptions<EActionType>(),
  768. };
  769. }
  770. #endregion
  771. #region 话务队列信息
  772. /// <summary>
  773. /// 今日等待数
  774. /// </summary>
  775. /// <returns></returns>
  776. [HttpGet("query-todaywaitnum")]
  777. public async Task<int> QueryToDayWaitNum()
  778. {
  779. int count = await _trCallRecordRepository.Queryable()
  780. .Where(x => x.CreatedTime.Date == DateTime.Now.Date)
  781. .Where(x => x.QueueTims > 0 && x.Duration == 0)
  782. .CountAsync();
  783. return count;
  784. }
  785. #endregion
  786. #endregion
  787. }
  788. }