IPPbxController.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. using DotNetCore.CAP;
  2. using Hotline.Ai.Quality;
  3. using Hotline.Application.CallCenter.Calls;
  4. using Hotline.Application.Tels;
  5. using Hotline.Caching.Interfaces;
  6. using Hotline.CallCenter.Calls;
  7. using Hotline.CallCenter.Tels;
  8. using Hotline.Orders;
  9. using Hotline.Permissions;
  10. using Hotline.Quality;
  11. using Hotline.Repository.SqlSugar.Extensions;
  12. using Hotline.Settings;
  13. using Hotline.Share.Dtos;
  14. using Hotline.Share.Dtos.Order;
  15. using Hotline.Share.Dtos.TrCallCenter;
  16. using Hotline.Share.Enums.CallCenter;
  17. using Hotline.Share.Enums.Quality;
  18. using Hotline.Users;
  19. using MapsterMapper;
  20. using Microsoft.AspNetCore.Authorization;
  21. using Microsoft.AspNetCore.Mvc;
  22. using Microsoft.IdentityModel.Tokens;
  23. using Newtonsoft.Json;
  24. using Tr.Sdk;
  25. using Tr.Sdk.Blacklist;
  26. using Tr.Sdk.Tels;
  27. using XF.Domain.Authentications;
  28. using XF.Domain.Constants;
  29. using XF.Domain.Exceptions;
  30. using XF.Domain.Filters;
  31. using XF.Domain.Repository;
  32. using XF.Utility.EnumExtensions;
  33. using static Grpc.Core.ChannelOption;
  34. namespace Hotline.Api.Controllers
  35. {
  36. public class IPPbxController : BaseController
  37. {
  38. private readonly ITrClient _trClient;
  39. private readonly IMapper _mapper;
  40. private readonly IUserDomainService _userDomainService;
  41. private readonly ISessionContext _sessionContext;
  42. private readonly IRepository<TrCallRecord> _trCallRecordRepository;
  43. private readonly ITrApplication _trApplication;
  44. private readonly IRepository<TrCallEvaluate> _trCallEvaluate;
  45. private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
  46. private readonly ILogger<IPPbxController> _logger;
  47. private readonly IOrderRepository _orderRepository;
  48. private readonly IRepository<OrderVisit> _orderVisitRepository;
  49. private readonly IUserCacheManager _userCacheManager;
  50. private readonly ICapPublisher _capPublisher;
  51. private readonly ITelRestRepository _telRestRepository;
  52. private readonly IRepository<User> _userRepository;
  53. private readonly ITelApplication _telApplication;
  54. private readonly IRepository<Quality.Quality> _qualiteyRepository;
  55. private readonly IAiQualityService _aiQualityService;
  56. private readonly IRepository<QualityTemplate> _qualityTemplate;
  57. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  58. private readonly IRepository<TelActionRecord> _telActionRecordRepository;
  59. public IPPbxController(ITrClient trClient, IMapper mapper, IUserDomainService userDomainService,
  60. ISessionContext sessionContext, IRepository<TrCallRecord> trCallRecordRepository,
  61. ITrApplication trApplication, IRepository<TrCallEvaluate> trCallRecord,
  62. ISystemDicDataCacheManager systemDicDataCacheManager, ILogger<IPPbxController> logger,
  63. IOrderRepository orderRepository, IRepository<OrderVisit> orderVisitRepository,
  64. IUserCacheManager userCacheManager, ICapPublisher capPublisher,
  65. ITelRestRepository telRestRepository, IRepository<User> userRepository,
  66. ITelApplication telApplication, IRepository<Quality.Quality> qualiteyRepository,
  67. IAiQualityService aiQualityService, IRepository<QualityTemplate> qualityTemplate,
  68. ISystemSettingCacheManager systemSettingCacheManager,IRepository<TelActionRecord> telActionRecordRepository)
  69. {
  70. _trClient = trClient;
  71. _mapper = mapper;
  72. _userDomainService = userDomainService;
  73. _sessionContext = sessionContext;
  74. _trCallRecordRepository = trCallRecordRepository;
  75. _trApplication = trApplication;
  76. _trCallEvaluate = trCallRecord;
  77. _systemDicDataCacheManager = systemDicDataCacheManager;
  78. _logger = logger;
  79. _orderRepository = orderRepository;
  80. _orderVisitRepository = orderVisitRepository;
  81. _userCacheManager = userCacheManager;
  82. _capPublisher = capPublisher;
  83. _telRestRepository = telRestRepository;
  84. _userRepository = userRepository;
  85. _telApplication = telApplication;
  86. _qualiteyRepository = qualiteyRepository;
  87. _aiQualityService = aiQualityService;
  88. _qualityTemplate = qualityTemplate;
  89. _systemSettingCacheManager = systemSettingCacheManager;
  90. _telActionRecordRepository = telActionRecordRepository;
  91. }
  92. #region 添添呼
  93. #region 分机
  94. /// <summary>
  95. /// 查询所有分机
  96. /// </summary>
  97. /// <returns></returns>
  98. [HttpGet("query-tels")]
  99. public async Task<IReadOnlyList<TrTelDto>> TrQueryTels()
  100. {
  101. var tels = await _trClient.QueryTelsAsync(new QueryTelRequest() { }, HttpContext.RequestAborted);
  102. return _mapper.Map<IReadOnlyList<TrTelDto>>(tels);
  103. }
  104. /// <summary>
  105. /// 查询所有分机状态
  106. /// </summary>
  107. /// <returns></returns>
  108. [HttpGet("query-telstate")]
  109. [AllowAnonymous]
  110. public async Task<IReadOnlyList<TrTelStateDto>> TrQueryTelState([FromQuery] string? state)
  111. {
  112. var tels = await _trClient.QueryTelStateAsync(new QueryTelStateRequest() { State = state }, HttpContext.RequestAborted);
  113. return _mapper.Map<IReadOnlyList<TrTelStateDto>>(tels.AgentList);
  114. }
  115. #endregion
  116. #region 黑白名单
  117. /// <summary>
  118. /// 新增黑白名单
  119. /// </summary>
  120. /// <param name="dto"></param>
  121. /// <returns></returns>
  122. [Permission(EPermission.AddBlackList)]
  123. [HttpPost("add-blacklist")]
  124. public async Task AddBlacklist([FromBody] TrAddBlacklistDto dto)
  125. {
  126. await _trClient.AddBlacklistAsync(new AddBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
  127. }
  128. /// <summary>
  129. /// 删除黑白名单
  130. /// </summary>
  131. /// <param name="dto"></param>
  132. /// <returns></returns>
  133. [Permission(EPermission.RemoveBlacklist)]
  134. [HttpPost("remove-blacklist")]
  135. public async Task DelBlacklist([FromBody] TrDelBlacklistDto dto)
  136. {
  137. await _trClient.DelBlacklistAsync(new DelBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
  138. }
  139. /// <summary>
  140. /// 查询黑白名单
  141. /// </summary>
  142. /// <returns></returns>
  143. [Permission(EPermission.QueryPagedBlack)]
  144. [HttpGet("query-blacklist")]
  145. public async Task<IReadOnlyList<TrQueryBlacklistResponseDto>> QueryBlacklist([FromQuery] TrQueryBlacklistDto dto)
  146. {
  147. var blacklist = await _trClient.QueryBlacklistAsync(new QueryBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
  148. return _mapper.Map<IReadOnlyList<TrQueryBlacklistResponseDto>>(blacklist);
  149. }
  150. #endregion
  151. #region 签入后调用
  152. /// <summary>
  153. /// 上班
  154. /// </summary>
  155. /// <param name="dto"></param>
  156. /// <returns></returns>
  157. [HttpPost("on-duty")]
  158. public async Task<TrOnDutyResponseDto> OnDuty([FromBody] TrOnDutyDto dto)
  159. {
  160. //return await _userDomainService.TrOnDutyAsync(_sessionContext.RequiredUserId,dto.TelNo, HttpContext.RequestAborted);
  161. return await _trApplication.OnSign(_sessionContext.RequiredUserId, dto.TelNo, HttpContext.RequestAborted);
  162. }
  163. /// <summary>
  164. /// 下班
  165. /// </summary>
  166. /// <returns></returns>
  167. [HttpPost("off-duty")]
  168. public async Task OffDuty()
  169. {
  170. //await _userDomainService.TrOffDutyAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  171. await _telApplication.SignOutAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  172. }
  173. /// <summary>
  174. /// 查询当前用户的分机状态
  175. /// </summary>
  176. /// <returns></returns>
  177. [HttpGet("tel-state")]
  178. public async Task<TrOnDutyResponseDto> TelState()
  179. {
  180. return await _trApplication.TelState(_sessionContext.RequiredUserId,HttpContext.RequestAborted);
  181. }
  182. /// <summary>
  183. /// 切换状态
  184. /// </summary>
  185. /// <param name="userId"></param>
  186. /// <param name="isCallOut"></param>
  187. /// <returns></returns>
  188. [HttpPost("change-telmodel")]
  189. public async Task ChangeTelModel([FromBody] ChangeTelModelDto dto)
  190. {
  191. await _trApplication.ChangeTelModel(_sessionContext.RequiredUserId, dto.isCallOut,HttpContext.RequestAborted);
  192. }
  193. /// <summary>
  194. /// 下班-管理手动操作
  195. /// </summary>
  196. /// <param name="telNo"></param>
  197. /// <returns></returns>
  198. [HttpGet("off-duty-manage")]
  199. public async Task OffDuty([FromQuery]string telNo)
  200. {
  201. await _telApplication.SignOutByTelNoAsync(telNo, HttpContext.RequestAborted);
  202. }
  203. //提供关闭浏览器事件触发调用
  204. [HttpPost("off-duty-no-auth")]
  205. [AllowAnonymous]
  206. public async Task OffDutyWithNoAuthorize([FromQuery] string userId)
  207. {
  208. if (string.IsNullOrEmpty(userId))
  209. throw new UserFriendlyException("无效用户编号");
  210. await _telApplication.SignOutByTelNoAsync(userId, HttpContext.RequestAborted);
  211. }
  212. #endregion
  213. #region 添添呼小休
  214. /// <summary>
  215. /// 小休页面基础数据
  216. /// </summary>
  217. /// <returns></returns>
  218. [HttpGet("rest/basedata")]
  219. public async Task<object> RestBasePaegData()
  220. {
  221. var rsp = new
  222. {
  223. RestReason = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.RestReason)
  224. };
  225. return rsp;
  226. }
  227. /// <summary>
  228. /// 小休
  229. /// </summary>
  230. /// <returns></returns>
  231. [HttpPost("rest")]
  232. public async Task Rest([FromBody] TrRestDto dto)
  233. {
  234. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  235. if (work == null)
  236. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  237. var isResting = await _telRestRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted);
  238. if (isResting)
  239. throw UserFriendlyException.SameMessage("当前坐席正在休息");
  240. var user = await _userRepository.GetAsync(work.UserId, HttpContext.RequestAborted);
  241. var telRest = new TelRest(work.TelNo, work.TelNo, work.UserId, work.UserName, dto.Reason, false, user.StaffNo);
  242. await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
  243. var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, EActionType.TelRest);
  244. await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted);
  245. }
  246. /// <summary>
  247. /// 取消小休
  248. /// </summary>
  249. /// <returns></returns>
  250. [HttpPost("unrest")]
  251. public async Task UnRest()
  252. {
  253. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  254. if (work is null)
  255. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  256. var telRest = await _telRestRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, HttpContext.RequestAborted);
  257. if (telRest is null)
  258. throw UserFriendlyException.SameMessage("未查询到分机休息信息");
  259. telRest.EndRest();
  260. await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted);
  261. var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == EActionType.TelRest && !x.EndTime.HasValue, HttpContext.RequestAborted);
  262. if (telAction!=null)
  263. {
  264. telAction.EndAction();
  265. await _telActionRecordRepository.UpdateAsync(telAction);
  266. }
  267. }
  268. #endregion
  269. #region 添添呼话后整理
  270. /// <summary>
  271. /// 话后整理开始
  272. /// </summary>
  273. /// <returns></returns>
  274. [HttpGet("callennd-arrange-begin")]
  275. public async Task CallEndArrangeBegin()
  276. {
  277. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  278. if (work is null)
  279. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  280. var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, EActionType.CallEndArrange);
  281. await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted);
  282. }
  283. /// <summary>
  284. /// 话后整理结束
  285. /// </summary>
  286. /// <returns></returns>
  287. [HttpGet("callend-arrange-end")]
  288. public async Task CallEndArrangeEnd()
  289. {
  290. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  291. if (work is null)
  292. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  293. var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, HttpContext.RequestAborted);
  294. if (telAction != null)
  295. {
  296. telAction.EndAction();
  297. await _telActionRecordRepository.UpdateAsync(telAction);
  298. }
  299. }
  300. #endregion
  301. #region 通话记录(对外)
  302. /// <summary>
  303. /// 接收通话记录
  304. /// </summary>
  305. /// <param name="dto"></param>
  306. /// <returns></returns>
  307. [AllowAnonymous]
  308. [HttpPost("receivecallrecord")]
  309. public async Task<OpenResponse> ReceiveCallRecord([FromBody] ReceiveCallRecordDto dto)
  310. {
  311. _logger.LogInformation($"收到通话记录结果回传:{JsonConvert.SerializeObject(dto)}");
  312. var model = _mapper.Map<TrCallRecord>(dto);
  313. model.Duration = 0;
  314. model.RingTimes = 0;
  315. model.QueueTims = 0;
  316. model.OnState = Share.Enums.CallCenter.EOnState.NoOn;
  317. //计算通话时长
  318. if (model.AnsweredTime != null)
  319. {
  320. model.OnState = Share.Enums.CallCenter.EOnState.On; //是否接通
  321. TimeSpan tsend = new TimeSpan(model.OverTime.Ticks);
  322. TimeSpan tsbegin = new TimeSpan(model.AnsweredTime.Value.Ticks);
  323. model.Duration = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  324. }
  325. //计算振铃时长
  326. if (model.BeginRingTime != null)
  327. {
  328. TimeSpan tsbegin = new TimeSpan(model.BeginRingTime.Value.Ticks);
  329. if (model.EndRingTimg != null)
  330. {
  331. TimeSpan tsend = new TimeSpan(model.EndRingTimg.Value.Ticks);
  332. model.RingTimes = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  333. }
  334. else
  335. {
  336. TimeSpan tsend = new TimeSpan(model.OverTime.Ticks);
  337. model.RingTimes = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  338. }
  339. }
  340. //计算队列时长
  341. if (model.BeginQueueTime != null && model.EndQueueTime != null)
  342. {
  343. TimeSpan tsend = new TimeSpan(model.EndQueueTime.Value.Ticks);
  344. TimeSpan tsbegin = new TimeSpan(model.BeginQueueTime.Value.Ticks);
  345. model.QueueTims = Convert.ToInt32(tsend.Subtract(tsbegin).TotalSeconds);
  346. }
  347. //获取分机号
  348. if (model.CallDirection == ECallDirection.In)
  349. {
  350. model.TelNo = model.CDPN;
  351. }
  352. else
  353. {
  354. model.TelNo = model.CPN;
  355. }
  356. //获取关联 工单或是回访
  357. var order = await _orderRepository.GetAsync(x => x.CallId == model.CallAccept, HttpContext.RequestAborted);
  358. if (order != null)
  359. {
  360. model.CallOrderType = ECallOrderType.Order;
  361. model.ExternalId = order.Id;
  362. // 写入智能质检
  363. var teAny = await _qualityTemplate.Queryable()
  364. .LeftJoin<QualityTemplateDetail>((x, d) => x.Id == d.TemplateId)
  365. .LeftJoin<QualityItem>((x, d, i) => d.ItemId == i.Id)
  366. .Where((x, d, i) => i.IsIntelligent == 1)
  367. .Where((x, d, i) => x.Grouping == ETemplateGrouping.Accepted).AnyAsync();
  368. if (teAny)
  369. {
  370. var quality = await _qualiteyRepository.Queryable().Where(x => x.OrderId == order.Id && x.Source == Share.Enums.Quality.EQualitySource.Accepted).FirstAsync();
  371. await _aiQualityService.CreateAiOrderQualityTask(quality, model, order, HttpContext.RequestAborted);
  372. }
  373. }
  374. else
  375. {
  376. var orderVisit = await _orderVisitRepository.GetAsync(x => x.CallId == model.CallAccept, HttpContext.RequestAborted);
  377. if (orderVisit != null)
  378. {
  379. model.CallOrderType = ECallOrderType.Visit;
  380. model.ExternalId = orderVisit.Id;
  381. }
  382. }
  383. //获取用户
  384. var work = await _userCacheManager.GetWorkByTelNoLast(model.TelNo);
  385. if (!string.IsNullOrEmpty(model.AgentTransferNumber))
  386. {
  387. work = await _userCacheManager.GetWorkByTelNoLast(model.AgentTransferNumber);
  388. }
  389. if (work != null)
  390. {
  391. model.UserId = work.UserId;
  392. model.UserName = work.UserName;
  393. model.StaffNo = work.StaffNo;
  394. }
  395. await _trCallRecordRepository.AddAsync(model, HttpContext.RequestAborted);
  396. if (model.CallDirection == ECallDirection.In)
  397. {
  398. var publishCallRecordDto = new PublishCallRecrodDto() { };
  399. if (order != null)
  400. {
  401. var orderDto = _mapper.Map<OrderDto>(order);
  402. publishCallRecordDto.Order = orderDto;
  403. }
  404. var trCallDto = _mapper.Map<TrCallDto>(model);
  405. publishCallRecordDto.TrCallRecordDto = trCallDto;
  406. if (string.IsNullOrEmpty(model.AgentTransferNumber))
  407. {
  408. //推省上
  409. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallBye, publishCallRecordDto);
  410. }
  411. }
  412. return OpenResponse.Ok("success");
  413. }
  414. /// <summary>
  415. /// 接受通话语音评价
  416. /// </summary>
  417. /// <param name="dto"></param>
  418. /// <returns></returns>
  419. [AllowAnonymous]
  420. [HttpPost("receivecallevaluate")]
  421. public async Task<OpenResponse> ReceiveCallEvaluate([FromBody] ReceiveCallEvaluateDto dto)
  422. {
  423. var model = _mapper.Map<TrCallEvaluate>(dto);
  424. await _trCallEvaluate.AddAsync(model, HttpContext.RequestAborted);
  425. return OpenResponse.Ok("success");
  426. }
  427. #endregion
  428. #region 通话记录(对内)
  429. /// <summary>
  430. /// 获取通话记录列表
  431. /// </summary>
  432. /// <param name="dto"></param>
  433. /// <returns></returns>
  434. [HttpGet("calls/call-list")]
  435. public async Task<PagedDto<TrCallDto>> GetCallList([FromQuery] GetCallListDto dto)
  436. {
  437. var (total, items) = await _trCallRecordRepository.Queryable()
  438. .Includes(x => x.Order)
  439. .WhereIF(!string.IsNullOrEmpty(dto.CPN), x => x.CPN.Contains(dto.CPN))
  440. .WhereIF(!string.IsNullOrEmpty(dto.CDPN), x => x.CDPN.Contains(dto.CDPN))
  441. .WhereIF(!string.IsNullOrEmpty(dto.TelNo), x => x.TelNo.Contains(dto.TelNo))
  442. .WhereIF(!string.IsNullOrEmpty(dto.UserName), x => x.UserName.Contains(dto.UserName))
  443. .WhereIF(dto.CallDirection != null, x => x.CallDirection == dto.CallDirection)
  444. .WhereIF(dto.OnState != null, x => x.OnState == dto.OnState)
  445. .WhereIF(dto.EndBy != null, x => x.EndBy == dto.EndBy)
  446. .WhereIF(dto.BeginIvrTimeStart.HasValue, x => x.BeginIvrTime >= dto.BeginIvrTimeStart)
  447. .WhereIF(dto.BeginIvrTimeEnd.HasValue,x=> x.BeginIvrTime<= dto.BeginIvrTimeEnd)
  448. .WhereIF(dto.EndIvrTimeStart.HasValue, x => x.EndIvrTime >= dto.EndIvrTimeStart)
  449. .WhereIF(dto.EndIvrTimeEnd.HasValue,x=> x.EndIvrTime <= dto.EndIvrTimeEnd)
  450. .WhereIF(dto.BeginQueueTimeStart.HasValue, x => x.BeginQueueTime >= dto.BeginQueueTimeEnd)
  451. .WhereIF(dto.BeginQueueTimeEnd.HasValue,x=> x.BeginQueueTime <= dto.BeginQueueTimeEnd)
  452. .WhereIF(dto.EndQueueTimeStart.HasValue, x => x.EndQueueTime >= dto.EndQueueTimeStart)
  453. .WhereIF(dto.EndQueueTimeEnd.HasValue,x=> x.EndQueueTime <= dto.EndQueueTimeEnd)
  454. .WhereIF(dto.AnsweredTimeStart.HasValue, x => x.AnsweredTime >= dto.AnsweredTimeStart)
  455. .WhereIF(dto.AnsweredTimeEnd.HasValue,x=> x.AnsweredTime<= dto.AnsweredTimeEnd)
  456. .WhereIF(dto.OverTimeStart.HasValue, x => x.OverTime >= dto.OverTimeStart)
  457. .WhereIF(dto.OverTimeEnd.HasValue,x=> x.OverTime <= dto.OverTimeEnd)
  458. .WhereIF(dto.BeginRingTimeStart.HasValue,x=> x.BeginRingTime>= dto.BeginRingTimeStart)
  459. .WhereIF(dto.BeginRingTimeEnd.HasValue,x=> x.BeginRingTime<= dto.BeginRingTimeEnd)
  460. .WhereIF(dto.EndRingTimeStart.HasValue,x=> x.EndRingTimg >= dto.EndRingTimeStart)
  461. .WhereIF(dto.EndRingTimeEnd.HasValue, x=>x.EndRingTimg <= dto.EndRingTimeEnd)
  462. .WhereIF(dto.CallTimeStart.HasValue, x=>x.CreatedTime>= dto.CallTimeStart)
  463. .WhereIF(dto.CallTimeEnd.HasValue, x=>x.CreatedTime <= dto.CallTimeEnd)
  464. .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), x => x.Order.No.Contains(dto.OrderNo))
  465. .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Order.Title.Contains(dto.Title))
  466. .WhereIF(!string.IsNullOrEmpty(dto.Gateway), x => x.Gateway.Contains(dto.Gateway))
  467. // .WhereIF(dto.StartTime.HasValue, x => x.CreatedTime >= dto.StartTime)
  468. //.WhereIF(dto.EndTime.HasValue, x => x.CreatedTime <= dto.EndTime)
  469. .OrderByDescending(x => x.CreatedTime)
  470. .ToPagedListAsync(dto.PageIndex, dto.PageSize);
  471. return new PagedDto<TrCallDto>(total, _mapper.Map<IReadOnlyList<TrCallDto>>(items));
  472. }
  473. /// <summary>
  474. /// 通话记录基础数据
  475. /// </summary>
  476. /// <returns></returns>
  477. [HttpGet("calls/basedata")]
  478. public async Task<object> CallBaseData()
  479. {
  480. return new
  481. {
  482. OnState = EnumExts.GetDescriptions<EOnState>(),
  483. CallDirection = EnumExts.GetDescriptions<ECallDirection>(),
  484. EndBy = EnumExts.GetDescriptions<EEndBy>(),
  485. };
  486. }
  487. #endregion
  488. #region 关联
  489. /// <summary>
  490. /// 可关联工单
  491. /// </summary>
  492. /// <param name="dto"></param>
  493. /// <returns></returns>
  494. [HttpGet("canlink-order")]
  495. public async Task<PagedDto<OrderDto>> CanLinkCallRecordOrder([FromQuery] CanLinkCallRecordOrderDto dto)
  496. {
  497. var (total, items) = await _orderRepository.Queryable()
  498. .Where(x => string.IsNullOrEmpty(x.CallId) && x.Source == Share.Enums.Order.ESource.Hotline)
  499. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.No.Contains(dto.Keyword) || x.Title.Contains(dto.Keyword))
  500. .OrderByDescending(x => x.CreationTime)
  501. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  502. return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
  503. }
  504. /// <summary>
  505. /// 可关联回访
  506. /// </summary>
  507. /// <param name="dto"></param>
  508. /// <returns></returns>
  509. [HttpGet("canlink-ordervisit")]
  510. public async Task<PagedDto<OrderVisitDto>> CanLinkCallRecordOrderVisit([FromQuery] CanLinkCallRecordOrderVisitDto dto)
  511. {
  512. var (total, items) = await _orderVisitRepository.Queryable()
  513. .Includes(x => x.Order)
  514. .Where(x => string.IsNullOrEmpty(x.CallId) && x.VisitType == Share.Enums.Order.EVisitType.ArtificialVisit)
  515. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => x.No.Contains(dto.Keyword))
  516. .OrderByDescending(x => x.CreationTime)
  517. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  518. return new PagedDto<OrderVisitDto>(total, _mapper.Map<IReadOnlyList<OrderVisitDto>>(items));
  519. }
  520. /// <summary>
  521. /// 关联工单或者回访
  522. /// </summary>
  523. /// <param name="dto"></param>
  524. /// <returns></returns>
  525. [HttpPost("link-callrecord")]
  526. public async Task LinkCallRecord([FromBody] LinkCallRecordDto dto)
  527. {
  528. var trRecord = await _trCallRecordRepository.GetAsync(x => x.CallAccept == dto.CallId, HttpContext.RequestAborted);
  529. if (dto.IsOrder)
  530. {
  531. if (trRecord.CallOrderType == ECallOrderType.Order && !string.IsNullOrEmpty(trRecord.ExternalId))
  532. throw UserFriendlyException.SameMessage("通话记录已经关联工单");
  533. //工单
  534. var order = await _orderRepository.GetAsync(x => x.Id == dto.Id, HttpContext.RequestAborted);
  535. if (!string.IsNullOrEmpty(order.CallId))
  536. throw UserFriendlyException.SameMessage("通话记录已经关联工单");
  537. order.CallId = dto.CallId;
  538. order.FromPhone = trRecord.CPN;
  539. order.TransferPhone = trRecord.Gateway;
  540. await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted);
  541. trRecord.CallOrderType = ECallOrderType.Order;
  542. trRecord.ExternalId = order.Id;
  543. //推省上
  544. await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto() { Order = _mapper.Map<OrderDto>(order), TrCallRecordDto = _mapper.Map<TrCallDto>(trRecord) });
  545. }
  546. else
  547. {
  548. //回访
  549. var visit = await _orderVisitRepository.GetAsync(x => x.Id == dto.Id, HttpContext.RequestAborted);
  550. visit.CallId = dto.CallId;
  551. await _orderVisitRepository.UpdateAsync(visit, HttpContext.RequestAborted);
  552. trRecord.CallOrderType = ECallOrderType.Visit;
  553. trRecord.ExternalId = visit.Id;
  554. }
  555. await _trCallRecordRepository.UpdateAsync(trRecord, HttpContext.RequestAborted);
  556. }
  557. #endregion
  558. #region 坐席动作记录
  559. /// <summary>
  560. /// 坐席动作列表
  561. /// </summary>
  562. /// <param name="dto"></param>
  563. /// <returns></returns>
  564. [HttpGet("telaction-list")]
  565. public async Task<PagedDto<TelActionListRep>> TelActionList([FromQuery] TelActionListDto dto)
  566. {
  567. if (!dto.EndTime.HasValue)
  568. {
  569. dto.EndTime = DateTime.Now.Date;
  570. }
  571. dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
  572. var (total,items) = await _telActionRecordRepository.Queryable()
  573. .WhereIF(string.IsNullOrEmpty(dto.TelNo) == false, x => x.TelNo.Contains(dto.TelNo))
  574. .WhereIF(dto.ActionTtype != null, x => x.ActionType == dto.ActionTtype)
  575. .WhereIF(string.IsNullOrEmpty(dto.UserName) == false, x => x.UserName.Contains(dto.UserName))
  576. .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
  577. .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
  578. .OrderByDescending(x => x.CreationTime)
  579. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  580. return new PagedDto<TelActionListRep>(total, _mapper.Map<IReadOnlyList<TelActionListRep>>(items));
  581. }
  582. /// <summary>
  583. /// 坐席动作列表基础数据
  584. /// </summary>
  585. /// <returns></returns>
  586. [HttpGet("telaction-basedata")]
  587. public async Task<object> TelActionBaseData()
  588. {
  589. return new
  590. {
  591. ActionType = EnumExts.GetDescriptions<EActionType>(),
  592. };
  593. }
  594. #endregion
  595. #endregion
  596. }
  597. }