IPPbxController.cs 30 KB

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