IPPbxController.cs 44 KB

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