PbxController.cs 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. using Hotline.Application.FlowEngine;
  2. using Hotline.Caching.Interfaces;
  3. using Hotline.CallCenter.Calls;
  4. using Hotline.CallCenter.Devices;
  5. using Hotline.CallCenter.Ivrs;
  6. using Hotline.CallCenter.Tels;
  7. using Hotline.Permissions;
  8. using Hotline.Settings;
  9. using Hotline.Share.Dtos.CallCenter;
  10. using Hotline.Share.Dtos.FlowEngine;
  11. using Hotline.Share.Dtos.Trunk;
  12. using Hotline.Share.Enums.CallCenter;
  13. using Hotline.Share.Requests;
  14. using Hotline.Users;
  15. using MapsterMapper;
  16. using Microsoft.AspNetCore.Mvc;
  17. using SqlSugar;
  18. using Wex.Sdk;
  19. using Wex.Sdk.Tel;
  20. using XF.Domain.Authentications;
  21. using XF.Domain.Cache;
  22. using XF.Domain.Constants;
  23. using XF.Domain.Exceptions;
  24. using XF.Utility.EnumExtensions;
  25. using System.Linq;
  26. using Hotline.FlowEngine.WorkflowModules;
  27. using Hotline.Share.Dtos;
  28. using Microsoft.AspNetCore.Authorization;
  29. using XF.Domain.Repository;
  30. using Tr.Sdk;
  31. namespace Hotline.Api.Controllers
  32. {
  33. /// <summary>
  34. /// 话机设备管理
  35. /// </summary>
  36. public class PbxController : BaseController
  37. {
  38. private readonly ITelRepository _telRepository;
  39. private readonly ITelRestRepository _telRestRepository;
  40. private readonly ITelDomainService _telDomainService;
  41. private readonly ITypedCache<Tel> _cacheTel;
  42. private readonly ITypedCache<TelGroup> _cacheTelGroup;
  43. private readonly ITelGroupRepository _telGroupRepository;
  44. private readonly IMapper _mapper;
  45. private readonly IWorkRepository _workRepository;
  46. private readonly IDeviceManager _deviceManager;
  47. private readonly IUserCacheManager _userCacheManager;
  48. private readonly ISessionContext _sessionContext;
  49. private readonly ICallRepository _callRepository;
  50. private readonly IRepository<TrunkIvrManager> _trunkIvrManagerRepository;
  51. private readonly IIvrCategoryRepository _ivrCategoryRepository;
  52. private readonly IWorkflowApplication _workflowApplication;
  53. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  54. private readonly IIvrDomainService _ivrDomainService;
  55. private readonly IIvrCacheManager _ivrCacheManager;
  56. private readonly ILogger<TelController> _logger;
  57. private readonly ICallDetailRepository _callDetailRepository;
  58. private readonly IRepository<User> _userRepository;
  59. private readonly IWexClient _wexClient;
  60. private readonly IWexTelGroupRepository _wexTelGroupRepository;
  61. private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
  62. public PbxController(
  63. ITelRepository telRepository,
  64. ITelRestRepository telRestRepository,
  65. ITelDomainService telDomainService,
  66. ITypedCache<Tel> cacheTel,
  67. ITypedCache<TelGroup> cacheTelGroup,
  68. ITelGroupRepository telGroupRepository,
  69. IMapper mapper,
  70. IWorkRepository workRepository,
  71. IDeviceManager deviceManager,
  72. IUserCacheManager userCacheManager,
  73. ISessionContext sessionContext,
  74. ICallRepository callRepository,
  75. IRepository<TrunkIvrManager> trunkIvrManagerRepository,
  76. IIvrCategoryRepository ivrCategoryRepository,
  77. IWorkflowApplication workflowApplication,
  78. ISystemSettingCacheManager systemSettingCacheManager,
  79. IIvrDomainService ivrDomainService,
  80. IIvrCacheManager ivrCacheManager,
  81. ILogger<TelController> logger,
  82. ICallDetailRepository callDetailRepository,
  83. IRepository<User> userRepository,
  84. IWexClient wexClient,
  85. IWexTelGroupRepository wexTelGroupRepository,
  86. ISystemDicDataCacheManager systemDicDataCacheManager)
  87. {
  88. _telRepository = telRepository;
  89. _telRestRepository = telRestRepository;
  90. _telDomainService = telDomainService;
  91. _cacheTel = cacheTel;
  92. _cacheTelGroup = cacheTelGroup;
  93. _telGroupRepository = telGroupRepository;
  94. _mapper = mapper;
  95. _workRepository = workRepository;
  96. _deviceManager = deviceManager;
  97. _userCacheManager = userCacheManager;
  98. _sessionContext = sessionContext;
  99. _callRepository = callRepository;
  100. _trunkIvrManagerRepository = trunkIvrManagerRepository;
  101. _ivrCategoryRepository = ivrCategoryRepository;
  102. _workflowApplication = workflowApplication;
  103. _systemSettingCacheManager = systemSettingCacheManager;
  104. _ivrDomainService = ivrDomainService;
  105. _ivrCacheManager = ivrCacheManager;
  106. _logger = logger;
  107. _callDetailRepository = callDetailRepository;
  108. _userRepository = userRepository;
  109. _wexClient = wexClient;
  110. _wexTelGroupRepository = wexTelGroupRepository;
  111. _systemDicDataCacheManager = systemDicDataCacheManager;
  112. }
  113. #region 话机
  114. /// <summary>
  115. /// 根据设备自动同步分机数据到数据库
  116. /// </summary>
  117. /// <returns></returns>
  118. [HttpPut("sync-tels")]
  119. public async Task SyncTelsAsync()
  120. {
  121. var dbTels = await _telRepository.QueryAsync();
  122. foreach (var dbTel in dbTels)
  123. {
  124. _cacheTel.Remove(dbTel.No);
  125. }
  126. //await _telRepository.RemoveRangeAsync(dbTels);
  127. var tels = await _telDomainService.QueryTelsAsync(HttpContext.RequestAborted);
  128. var list = tels.ExceptBy(dbTels.Select(e => e.No), x => x.No).ToList();
  129. var delList = dbTels.ExceptBy(tels.Select(e => e.No), x => x.No).ToList();
  130. for (int i = 0; i < delList.Count; i++)
  131. {
  132. delList[i].SoftDelete();
  133. }
  134. await _telRepository.UpdateRangeAsync(delList);
  135. await _telRepository.AddRangeAsync(list);
  136. }
  137. /// <summary>
  138. /// 查询所有分机
  139. /// </summary>
  140. /// <returns></returns>
  141. [HttpGet("query-tels")]
  142. public async Task<IReadOnlyList<TelDto>> QueryTels()
  143. {
  144. var tels = await _telRepository.QueryExtAsync(d => true, d => d.Includes(x => x.Groups));
  145. return _mapper.Map<IReadOnlyList<TelDto>>(tels);
  146. }
  147. #endregion
  148. #region 分机组
  149. /// <summary>
  150. /// 页面基础信息
  151. /// </summary>
  152. /// <returns></returns>
  153. [HttpGet("base-info-group")]
  154. public async Task<dynamic> GetBaseInfoGroup()
  155. {
  156. return new
  157. {
  158. Distributions = EnumExts.GetDescriptions<EDistribution>()
  159. };
  160. }
  161. /// <summary>
  162. /// 查询所有分机组
  163. /// </summary>
  164. /// <returns></returns>
  165. [HttpGet("query-telgroups")]
  166. public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroups()
  167. {
  168. var groups = await _telGroupRepository.QueryExtAsync(d => true, d => d.Includes(x => x.Tels));
  169. return _mapper.Map<IReadOnlyList<TelGroupDto>>(groups);
  170. }
  171. /// <summary>
  172. /// 新增分机组
  173. /// </summary>
  174. /// <param name="dto"></param>
  175. /// <returns></returns>
  176. [HttpPost("add-telgroup")]
  177. public async Task AddTelGroup([FromBody] AddTelGroupDto dto)
  178. {
  179. var works = await _workRepository.QueryAsync(d => dto.TelNos.Contains(d.TelNo) && !d.EndTime.HasValue);
  180. await _deviceManager.AssginConfigGroupAsync(
  181. dto.No,
  182. dto.Distribution,
  183. ext: works.Select(d => d.TelNo).ToList(),
  184. voiceFile: dto.Voice ?? null,
  185. cancellationToken: HttpContext.RequestAborted);
  186. var group = _mapper.Map<TelGroup>(dto);
  187. var tels = await _telRepository.QueryAsync(d => dto.TelNos.Contains(d.No));
  188. group.Tels = tels;
  189. await _telGroupRepository.AddNavTelsAsync(group, HttpContext.RequestAborted);
  190. _cacheTelGroup.Remove(dto.No);
  191. }
  192. /// <summary>
  193. /// 更新分机组
  194. /// </summary>
  195. /// <param name="dto"></param>
  196. /// <returns></returns>
  197. [HttpPut("update-telgroup")]
  198. public async Task UpdateTelGroup([FromBody] UpdateTelGroupDto dto)
  199. {
  200. var works = await _workRepository.QueryAsync(d => dto.TelNos.Contains(d.TelNo) && !d.EndTime.HasValue);
  201. await _deviceManager.AssginConfigGroupAsync(
  202. dto.No,
  203. dto.Distribution,
  204. ext: works.Select(d => d.TelNo).ToList(),
  205. voiceFile: dto.Voice ?? null,
  206. cancellationToken: HttpContext.RequestAborted);
  207. var group = _mapper.Map<TelGroup>(dto);
  208. var tels = await _telRepository.QueryAsync(d => dto.TelNos.Contains(d.No));
  209. group.Tels = tels;
  210. await _telGroupRepository.UpdateNavTelsAsync(group, HttpContext.RequestAborted);
  211. _cacheTelGroup.Remove(dto.No);
  212. }
  213. #endregion
  214. #region 话机设备操作
  215. /// <summary>
  216. /// 查询小休流程开启参数
  217. /// </summary>
  218. /// <returns></returns>
  219. [HttpGet("flow-start")]
  220. public async Task<NextStepsDto> GetFlowStartOptionsAsync()
  221. {
  222. //return await _workflowApplication.GetStartOptionsAsync(WorkflowModuleConsts.TelRestApply,
  223. // HttpContext.RequestAborted);
  224. return await _workflowApplication.GetStartStepsAsync(WorkflowModuleConsts.TelRestApply,
  225. HttpContext.RequestAborted);
  226. }
  227. /// <summary>
  228. /// 分机休息
  229. /// </summary>
  230. /// <returns></returns>
  231. [HttpPost("rest")]
  232. public async Task Rest([FromBody] StartRestDto dto)
  233. {
  234. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  235. if (work is null)
  236. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  237. bool isCanApply = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsRestApproval).SettingValue[0]);
  238. if (isCanApply)
  239. {
  240. var isApply = await _telRestRepository.IsApplyingAsync(work.TelNo, HttpContext.RequestAborted);
  241. if (isApply)
  242. throw new UserFriendlyException("分机休息申请正在审核阶段");
  243. }
  244. var isResting = await _telRestRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted);
  245. if (isResting)
  246. throw new UserFriendlyException("当前坐席正在休息");
  247. var user = _userRepository.Get(work.UserId);
  248. var telRest = new TelRest(work.TelId, work.TelNo, work.UserId, work.UserName, dto.Reason, isCanApply, user.StaffNo);
  249. await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
  250. if (!isCanApply)
  251. {
  252. await _telDomainService.TelRestApplyPassAsync(telRest.Id, HttpContext.RequestAborted);
  253. }
  254. else
  255. {
  256. var startWorkflowDto = _mapper.Map<StartWorkflowDto>(dto);
  257. startWorkflowDto.DefinitionModuleCode = WorkflowModuleConsts.TelRestApply;
  258. startWorkflowDto.Title = dto.Reason;
  259. await _workflowApplication.StartWorkflowAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
  260. }
  261. }
  262. #region 威尔信小休
  263. /// <summary>
  264. /// 分机休息流程开始
  265. /// </summary>
  266. /// <param name="dto"></param>
  267. /// <returns></returns>
  268. [HttpPost("rest-flow")]
  269. public async Task RestFlow([FromBody] StartRestDto dto)
  270. {
  271. var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);
  272. if (tel.Data == null || tel.Data.Count == 0)
  273. throw UserFriendlyException.SameMessage("未找到分机信息");
  274. if (tel.Data[0].Sigin == 0)
  275. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  276. var isApply = await _telRestRepository.IsApplyingAsync(tel.Data[0].TelNo, HttpContext.RequestAborted);
  277. if (isApply)
  278. throw UserFriendlyException.SameMessage("分机休息申请正在审核阶段");
  279. var isResting = await _telRestRepository.IsRestingAsync(tel.Data[0].TelNo, HttpContext.RequestAborted);
  280. if (isResting)
  281. throw new UserFriendlyException("当前坐席正在休息");
  282. var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, true, _sessionContext.StaffNo);
  283. await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
  284. var startWorkflowDto = _mapper.Map<StartWorkflowDto>(dto);
  285. startWorkflowDto.DefinitionModuleCode = WorkflowModuleConsts.TelRestApply;
  286. startWorkflowDto.Title = dto.Reason;
  287. await _workflowApplication.StartWorkflowAsync(startWorkflowDto, telRest.Id, cancellationToken: HttpContext.RequestAborted);
  288. }
  289. /// <summary>
  290. /// 开始分机休息
  291. /// </summary>
  292. /// <returns></returns>
  293. [HttpGet("begin-rest")]
  294. public async Task BeginRest()
  295. {
  296. var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);
  297. if (tel.Data == null || tel.Data.Count == 0)
  298. throw UserFriendlyException.SameMessage("未找到分机信息");
  299. if (tel.Data[0].Sigin == 0)
  300. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  301. var telRest = await _telRestRepository.GetAsync(x => !x.EndTime.HasValue && !x.StartTime.HasValue && x.TelNo == tel.Data[0].TelNo, HttpContext.RequestAborted);
  302. if (telRest == null)
  303. throw new UserFriendlyException($"错误");
  304. //if (string.IsNullOrEmpty(id))
  305. // throw UserFriendlyException.SameMessage("无效分机休息编号");
  306. //var telRest = await _telRestRepository.GetAsync(id, HttpContext.RequestAborted);
  307. //if (telRest == null)
  308. // throw new UserFriendlyException($"无效分机休息编号, telRestId: {id}", "无效分机休息编号");
  309. telRest.ApplyStatus = ETelRestApplyStatus.Resting;
  310. telRest.StartTime = DateTime.Now;
  311. await _telRestRepository.UpdateAsync(telRest, HttpContext.RequestAborted);
  312. }
  313. /// <summary>
  314. /// 分机休息
  315. /// </summary>
  316. /// <param name="dto"></param>
  317. /// <returns></returns>
  318. [HttpPost("rest-add")]
  319. public async Task Rest([FromBody] ResstDto dto)
  320. {
  321. var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);
  322. if (tel.Data == null || tel.Data.Count == 0)
  323. throw UserFriendlyException.SameMessage("未找到分机信息");
  324. if (tel.Data[0].Sigin == 0)
  325. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  326. var telRest = new TelRest(tel.Data[0].TelNo, tel.Data[0].TelNo, _sessionContext.RequiredUserId, _sessionContext.UserName, dto.Reason, false, _sessionContext.StaffNo);
  327. await _telRestRepository.AddAsync(telRest, HttpContext.RequestAborted);
  328. }
  329. /// <summary>
  330. /// 删除分机休息
  331. /// </summary>
  332. /// <returns></returns>
  333. /// <exception cref="UserFriendlyException"></exception>
  334. [HttpGet("rest-del")]
  335. public async Task DelRest()
  336. {
  337. var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);
  338. if (tel.Data == null || tel.Data.Count == 0)
  339. throw UserFriendlyException.SameMessage("未找到分机信息");
  340. if (tel.Data[0].Sigin == 0)
  341. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  342. var restingTel = await _telRestRepository.GetAsync(d => d.TelId == tel.Data[0].TelNo && !d.EndTime.HasValue, HttpContext.RequestAborted);
  343. if (restingTel is null)
  344. throw new UserFriendlyException("未查询到分机休息信息");
  345. await _telRestRepository.RemoveAsync(restingTel.Id, false, HttpContext.RequestAborted);
  346. }
  347. /// <summary>
  348. /// 分机结束休息(威尔信)
  349. /// </summary>
  350. /// <returns></returns>
  351. [HttpGet("un-rest-wex")]
  352. public async Task<TelRestDto> UnRestWEX()
  353. {
  354. var tel = await _wexClient.QueryTelsAsync(new QueryTelRequest() { StaffNo = _sessionContext.StaffNo }, HttpContext.RequestAborted);
  355. if (tel.Data == null || tel.Data.Count == 0)
  356. throw UserFriendlyException.SameMessage("未找到分机信息");
  357. if (tel.Data[0].Sigin == 0)
  358. throw UserFriendlyException.SameMessage("分机未签入,不能休息");
  359. var telRest = await _telDomainService.UnRestWexAsync(tel.Data[0].TelNo, HttpContext.RequestAborted);
  360. return _mapper.Map<TelRestDto>(telRest);
  361. }
  362. #endregion
  363. /// <summary>
  364. /// 分机结束休息
  365. /// </summary>
  366. [HttpPut("un-rest")]
  367. public async Task<TelRestDto> UnRest()
  368. {
  369. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  370. if (work is null)
  371. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  372. var telRest = await _telDomainService.UnRestAsync(work.TelId, HttpContext.RequestAborted);
  373. return _mapper.Map<TelRestDto>(telRest);
  374. }
  375. /// <summary>
  376. /// 保持通话
  377. /// </summary>
  378. [HttpPut("hold")]
  379. public async Task Hold(string callId)
  380. {
  381. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  382. if (work is null)
  383. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  384. await _telDomainService.HoldAsync(work.TelId, _sessionContext.RequiredUserId, _sessionContext.UserName, callId, HttpContext.RequestAborted);
  385. }
  386. /// <summary>
  387. /// 恢复通话(解除hold状态)
  388. /// </summary>
  389. [HttpPut("un-hold")]
  390. public async Task UnHold(string callId)
  391. {
  392. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  393. if (work is null)
  394. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  395. await _telDomainService.UnHoldAsync(work.TelId, _sessionContext.RequiredUserId, callId, HttpContext.RequestAborted);
  396. }
  397. /// <summary>
  398. /// 分机呼分机
  399. /// </summary>
  400. /// <returns></returns>
  401. [HttpPost("tel-to-tel")]
  402. public async Task TelToTel([FromBody] TelToTelDto dto)
  403. {
  404. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  405. if (work is null)
  406. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  407. //获取对方是否在工作
  408. var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
  409. if (toWork is null)
  410. throw UserFriendlyException.SameMessage("转接分机未进行工作");
  411. //判断分机状态
  412. var telState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted);
  413. if (telState != ETelStatus.Ready)
  414. throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中");
  415. bool isRest = await _telRestRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted);
  416. if (isRest)
  417. throw new UserFriendlyException("被叫分机正在休息不能转接");
  418. await _deviceManager.ExtToExtAsync(work.TelNo, dto.TelNo, HttpContext.RequestAborted);
  419. }
  420. /// <summary>
  421. /// 分机拨打外部电话
  422. /// </summary>
  423. /// <param name="dto"></param>
  424. /// <returns></returns>
  425. [HttpPost("tel-to-outer")]
  426. public async Task TelToOuter([FromBody] TelToOuterDto dto)
  427. {
  428. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  429. if (work is null)
  430. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  431. await _deviceManager.ExtToOuterAsync(work.TelNo, dto.OuterNo, HttpContext.RequestAborted);
  432. }
  433. /// <summary>
  434. /// 指定模拟外线外呼(分机拨打外部电话)
  435. /// OM设备多个外线时调用此接口
  436. /// </summary>
  437. /// <param name="dto"></param>
  438. /// <returns></returns>
  439. [HttpPost("tel-to-outer-line")]
  440. public async Task TelToOuterByLine([FromBody] TelToOuterByLineDto dto)
  441. {
  442. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  443. if (work is null)
  444. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  445. string newOuter = dto.LineId + "," + dto.OuterNo;
  446. await _deviceManager.ExtToOuterAsync(work.TelNo, newOuter, HttpContext.RequestAborted);
  447. }
  448. /// <summary>
  449. /// 来电转分机
  450. /// </summary>
  451. /// <param name="dto"></param>
  452. /// <returns></returns>
  453. [HttpPost("visitor-to-tel")]
  454. public async Task VisitorToTel([FromBody] VisitorToTelDto dto)
  455. {
  456. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  457. if (work is null)
  458. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  459. var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
  460. if (toWork is null)
  461. throw UserFriendlyException.SameMessage("转接分机未进行工作");
  462. var totelState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted);
  463. if (totelState != ETelStatus.Ready)
  464. throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中");
  465. bool isRest = await _telRestRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted);
  466. if (isRest)
  467. throw new UserFriendlyException("被叫分机正在休息不能转接");
  468. var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
  469. if (!string.IsNullOrEmpty(tel.ConversationId))
  470. await _deviceManager.VisitorToExtAsync(tel.ConversationId, dto.TelNo, HttpContext.RequestAborted);
  471. else
  472. throw UserFriendlyException.SameMessage("当前分机没有通话");
  473. }
  474. /// <summary>
  475. /// 来电转外部电话
  476. /// </summary>
  477. /// <param name="dto"></param>
  478. /// <returns></returns>
  479. [HttpPost("visitor-to-outer")]
  480. public async Task VisitorToOuter([FromBody] VisitorToOuterDto dto)
  481. {
  482. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  483. if (work is null)
  484. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  485. var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
  486. if (!string.IsNullOrEmpty(tel.ConversationId))
  487. await _deviceManager.VisitorToOuterAsync(tel.ConversationId, dto.OuterNo, HttpContext.RequestAborted);
  488. else
  489. throw UserFriendlyException.SameMessage("当前分机没有通话");
  490. }
  491. /// <summary>
  492. /// 来电转分机组队列
  493. /// </summary>
  494. /// <param name="dto"></param>
  495. /// <returns></returns>
  496. [HttpPost("visitor-to-group")]
  497. public async Task VisitorToGroup([FromBody] VisitorToGroupDto dto)
  498. {
  499. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  500. if (work is null)
  501. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  502. var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
  503. if (!string.IsNullOrEmpty(tel.ConversationId))
  504. await _deviceManager.VisitorToGroupAsync(tel.ConversationId, dto.groupid, HttpContext.RequestAborted);
  505. else
  506. throw UserFriendlyException.SameMessage("当前分机没有通话");
  507. }
  508. /// <summary>
  509. /// 去电转外部电话
  510. /// </summary>
  511. /// <param name="dto"></param>
  512. /// <returns></returns>
  513. /// <exception cref="UserFriendlyException"></exception>
  514. [HttpPost("outer-to-outer")]
  515. public async Task OuterToOuter([FromBody] OuterToOuterDto dto)
  516. {
  517. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  518. if (work is null)
  519. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  520. var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
  521. if (!string.IsNullOrEmpty(tel.ConversationId))
  522. await _deviceManager.OuterToOuterAsync(tel.ConversationId, dto.OuterNo, HttpContext.RequestAborted);
  523. else
  524. throw UserFriendlyException.SameMessage("当前分机没有通话");
  525. }
  526. /// <summary>
  527. /// 去电转分机
  528. /// </summary>
  529. /// <param name="dto"></param>
  530. /// <returns></returns>
  531. /// <exception cref="UserFriendlyException"></exception>
  532. [HttpPost("outer-to-tel")]
  533. public async Task OuterToTel([FromBody] OuterToTelDto dto)
  534. {
  535. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  536. if (work is null)
  537. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  538. var toWork = _userCacheManager.GetWorkByTel(dto.TelNo);
  539. if (toWork is null)
  540. throw UserFriendlyException.SameMessage("转接分机未进行工作");
  541. var totelState = await _deviceManager.QueryTelState(dto.TelNo, HttpContext.RequestAborted);
  542. if (totelState != ETelStatus.Ready)
  543. throw UserFriendlyException.SameMessage("被叫分机不在线或正在通话中");
  544. bool isRest = await _telRestRepository.IsRestingAsync(dto.TelNo, HttpContext.RequestAborted);
  545. if (isRest)
  546. throw new UserFriendlyException("被叫分机正在休息不能转接");
  547. var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
  548. if (!string.IsNullOrEmpty(tel.ConversationId))
  549. await _deviceManager.OuterToExtAsync(tel.ConversationId, dto.TelNo, HttpContext.RequestAborted);
  550. else
  551. throw UserFriendlyException.SameMessage("当前分机没有通话");
  552. }
  553. /// <summary>
  554. /// 三方会议
  555. /// 先建立两方通话,然后调用保持通话接口,拨通第三方分机,然后再调用三方会议接口
  556. /// 1. 分机 A 正在和 B 通话;
  557. /// 2. 分机 A 把原通话呼叫保持;
  558. /// 3. 分机 A 向 C 发起新的呼叫,并建立通话;
  559. /// 4. 此时,使用该 API 能够实现以分机 A 为主持方建立 A、B、C 的三方会议。
  560. /// </summary>
  561. /// <param name="dto">TelNo:会议发起方分机号</param>
  562. /// <returns></returns>
  563. /// <exception cref="UserFriendlyException"></exception>
  564. [HttpPost("meeting")]
  565. public async Task Conference([FromBody] ConferenceDto dto)
  566. {
  567. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  568. if (work is null)
  569. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  570. var tel = await _deviceManager.QueryTelAsync(work.TelNo, HttpContext.RequestAborted);
  571. if (!string.IsNullOrEmpty(tel.ConversationId))
  572. await _deviceManager.ConferenceMeetingAsync(dto.TelNo, HttpContext.RequestAborted);
  573. else
  574. throw UserFriendlyException.SameMessage("当前分机没有通话");
  575. }
  576. #region 监听和强插
  577. /// <summary>
  578. /// 监听分机
  579. /// </summary>
  580. /// <param name="request"></param>
  581. /// <returns></returns>
  582. [HttpPost("monitor-ext")]
  583. public async Task MonitorExt([FromBody] MonitorExtRequest request)
  584. {
  585. await _deviceManager.MonitorExtAsync(request.firstTelNo, request.secondTelNo, HttpContext.RequestAborted);
  586. }
  587. /// <summary>
  588. /// 从监听到插播状态变换
  589. /// 1. 已知:分机 A 在监听分机 B 与其通话方的通话;
  590. /// 2. 执行分机 A 的从监听到插播状态变换的 API;
  591. /// 3. 执行成功时,分机 A 与分机 B 建立通话,分机 B 的原通话方听保持音。
  592. /// </summary>
  593. /// <param name="request"></param>
  594. /// <returns></returns>
  595. [HttpPost("monitor-ext-to-talk")]
  596. public async Task MonitorExtToTalk([FromBody] MonitorExtToTalkRequest request)
  597. {
  598. await _deviceManager.MonitorExtToTalkAsync(request.telNo, HttpContext.RequestAborted);
  599. }
  600. /// <summary>
  601. /// 从插播到监听状态变换
  602. /// 1. 已知:分机 A 在插播分机 B 的通话;
  603. /// 2. 执行分机 A 的从插播到监听状态变换的 API;
  604. /// 3. 执行成功时,分机 A 监听分机 B 及其原通话方的通话。
  605. /// </summary>
  606. /// <param name="request"></param>
  607. /// <returns></returns>
  608. [HttpPost("monitor-ext-to-listen")]
  609. public async Task MonitorExtToListen([FromBody] MonitorExtToListenRequest request)
  610. {
  611. await _deviceManager.MonitorExtToListenAsync(request.telNo, HttpContext.RequestAborted);
  612. }
  613. #endregion
  614. #region 强插
  615. /// <summary>
  616. /// 强插
  617. /// 1. 已知:分机 A 当前空闲,分机 B 正在通话中;
  618. /// 2. 执行分机 A 强插分机 B 的 API;
  619. /// 3. 执行成功时,分机 A 振铃,摘机后即可形成三方通话。
  620. /// </summary>
  621. /// <param name="request"></param>
  622. /// <returns></returns>
  623. [HttpPost("bargein-ext")]
  624. public async Task BargeinExt([FromBody] BargeinExtRequest request)
  625. {
  626. await _deviceManager.BargeinExtAsync(request.firstTelNo, request.secondTelNo, HttpContext.RequestAborted);
  627. }
  628. #endregion
  629. #region 强拆
  630. /// <summary>
  631. /// 强拆分机
  632. /// </summary>
  633. /// <param name="request"></param>
  634. /// <returns></returns>
  635. [HttpPost("clear-ext")]
  636. public async Task ClearExt([FromBody] ClearExtRequest request)
  637. {
  638. //查询当前通话记录
  639. //var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted);
  640. //if (call is null)
  641. // throw UserFriendlyException.SameMessage("无效通话,无法挂断");
  642. //if (call.CallStatus == ECallStatus.Bye)
  643. // throw UserFriendlyException.SameMessage("通话已结束");
  644. await _deviceManager.ClearExtAsync(request.CallId, HttpContext.RequestAborted);
  645. }
  646. /// <summary>
  647. /// 强拆来电
  648. /// </summary>
  649. /// <param name="request"></param>
  650. /// <returns></returns>
  651. [HttpPost("clear-visitor")]
  652. public async Task ClearVisitor([FromBody] ClearVisitorRequest request)
  653. {
  654. //查询当前通话记录
  655. var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted);
  656. if (call is null)
  657. throw UserFriendlyException.SameMessage("无效通话,无法挂断");
  658. if (call.CallStatus == ECallStatus.Bye)
  659. throw UserFriendlyException.SameMessage("通话已结束");
  660. await _deviceManager.ClearVisitorAsync(call.ConversationId, HttpContext.RequestAborted);
  661. }
  662. /// <summary>
  663. /// 强拆去电
  664. /// </summary>
  665. /// <param name="request"></param>
  666. /// <returns></returns>
  667. [HttpPost("clear-outer")]
  668. public async Task ClearOuter([FromBody] ClearOuterRequest request)
  669. {
  670. //查询当前通话记录
  671. var call = await _callRepository.GetAsync(request.CallId, HttpContext.RequestAborted);
  672. if (call is null)
  673. throw UserFriendlyException.SameMessage("无效通话,无法挂断");
  674. if (call.CallStatus == ECallStatus.Bye)
  675. throw UserFriendlyException.SameMessage("通话已结束");
  676. await _deviceManager.ClearOuterAsync(call.ConversationId, HttpContext.RequestAborted);
  677. }
  678. #endregion
  679. #region 静音和取消静音
  680. /// <summary>
  681. /// 静音
  682. /// </summary>
  683. /// <returns></returns>
  684. [HttpGet("mute")]
  685. public async Task Mute()
  686. {
  687. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  688. if (work is null)
  689. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  690. await _deviceManager.MuteAsync(work.TelNo, HttpContext.RequestAborted);
  691. }
  692. /// <summary>
  693. /// 取消静音
  694. /// </summary>
  695. /// <returns></returns>
  696. [HttpGet("unmute")]
  697. public async Task UnMute()
  698. {
  699. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  700. if (work is null)
  701. throw UserFriendlyException.SameMessage("当前坐席暂未进行工作");
  702. await _deviceManager.UnMuteAsync(work.TelNo, HttpContext.RequestAborted);
  703. }
  704. #endregion
  705. #endregion
  706. #region 线路管理
  707. /// <summary>
  708. /// 获取线路列表
  709. /// </summary>
  710. /// <returns></returns>
  711. [HttpGet("trunk-list")]
  712. public async Task<IReadOnlyList<TrunkIvrManager>> GetTrunkList()
  713. {
  714. return await _trunkIvrManagerRepository.Queryable()
  715. .Includes(d => d.WorkCategoryModel)
  716. .Includes(d => d.RestCategoryModel)
  717. .Includes(d => d.WorkToGroupModel.MappingField(z => z.No, () => d.WorkToGroup))
  718. .Includes(d => d.RestToGroupModel.MappingField(z => z.No, () => d.RestToGroup))
  719. .ToListAsync();
  720. }
  721. /// <summary>
  722. /// 获取线路对象
  723. /// </summary>
  724. /// <param name="id"></param>
  725. /// <returns></returns>
  726. [HttpGet("trunk/{id}")]
  727. public async Task<TrunkIvrManager> GetTrunk(string id)
  728. {
  729. var trunk = await _trunkIvrManagerRepository.GetAsync(id, HttpContext.RequestAborted);
  730. if (trunk is null)
  731. throw UserFriendlyException.SameMessage("无效线路");
  732. return trunk;
  733. }
  734. /// <summary>
  735. /// 新增线路
  736. /// </summary>
  737. /// <param name="dto"></param>
  738. /// <returns></returns>
  739. [HttpPost("addtrunk")]
  740. public async Task AddTrunk([FromBody] AddTrunkDto dto)
  741. {
  742. var trunk = _mapper.Map<TrunkIvrManager>(dto);
  743. await _trunkIvrManagerRepository.AddAsync(trunk, HttpContext.RequestAborted);
  744. }
  745. /// <summary>
  746. /// 修改线路
  747. /// </summary>
  748. /// <param name="dto"></param>
  749. /// <returns></returns>
  750. [HttpPost("updatetrunk")]
  751. public async Task UpdateTrunk([FromBody] UpdateTrunkDto dto)
  752. {
  753. var trunk = await _trunkIvrManagerRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
  754. if (trunk is null)
  755. throw UserFriendlyException.SameMessage("无效线路");
  756. _mapper.Map(dto, trunk);
  757. await _trunkIvrManagerRepository.UpdateAsync(trunk, HttpContext.RequestAborted);
  758. }
  759. /// <summary>
  760. /// 删除线路
  761. /// </summary>
  762. /// <param name="id"></param>
  763. /// <returns></returns>
  764. [HttpGet("removetrunk/{id}")]
  765. public async Task RemoveTrunk(string id)
  766. {
  767. var trunk = await _trunkIvrManagerRepository.GetAsync(id, HttpContext.RequestAborted);
  768. if (trunk is null)
  769. throw UserFriendlyException.SameMessage("无效线路");
  770. await _trunkIvrManagerRepository.RemoveAsync(id, false, HttpContext.RequestAborted);
  771. }
  772. /// <summary>
  773. /// 线路页面基础信息
  774. /// </summary>
  775. /// <returns></returns>
  776. [HttpGet("trunk-page-info")]
  777. public async Task<object> TrunkPageInfo()
  778. {
  779. var ivr = await _ivrCategoryRepository.QueryAsync();
  780. var group = await _telGroupRepository.QueryAsync();
  781. var workDay = new List<WorkDayModel>();
  782. workDay.Add(new WorkDayModel() { weekName = "星期日", weekValue = "0" });
  783. workDay.Add(new WorkDayModel() { weekName = "星期一", weekValue = "1" });
  784. workDay.Add(new WorkDayModel() { weekName = "星期二", weekValue = "2" });
  785. workDay.Add(new WorkDayModel() { weekName = "星期三", weekValue = "3" });
  786. workDay.Add(new WorkDayModel() { weekName = "星期四", weekValue = "4" });
  787. workDay.Add(new WorkDayModel() { weekName = "星期五", weekValue = "5" });
  788. workDay.Add(new WorkDayModel() { weekName = "星期六", weekValue = "6" });
  789. return new { WorkCategorys = ivr, RestCategory = ivr, WorkToGroup = group, RestToGroup = group, WorkDay = workDay };
  790. }
  791. #endregion
  792. #region 话机业务
  793. /// <summary>
  794. /// 语音评价
  795. /// </summary>
  796. /// <param name="callId"></param>
  797. /// <returns></returns>
  798. [HttpGet("evaluate/{callId}")]
  799. public async Task Evaluate(string callId)
  800. {
  801. //检查通话是否存在
  802. var call = await _callRepository.GetAsync(callId, HttpContext.RequestAborted);
  803. if (call is null)
  804. {
  805. throw new UserFriendlyException("未找到当前通话");
  806. }
  807. if (call.CallDirection != ECallDirection.In)
  808. {
  809. throw new UserFriendlyException("当前通话不是来电,不能发送评价邀请");
  810. }
  811. if (call.CallStatus == ECallStatus.Bye)
  812. {
  813. throw new UserFriendlyException("当前通话已结束");
  814. }
  815. if (call.CallStatus != ECallStatus.Answered && call.CallStatus != ECallStatus.Answer)
  816. {
  817. throw new UserFriendlyException("当前未进行通话,不能发送评价邀请");
  818. }
  819. //获取配置
  820. var correct = _ivrDomainService.GetCorrectIvr(call.ToNo, true);
  821. if (correct is null)
  822. throw new UserFriendlyException("系统未配置评价,请联系管理员");
  823. //检查是否有评价录音配置
  824. var ivrList = await _ivrCacheManager.GetIvrsAsync(HttpContext.RequestAborted);
  825. if (!ivrList.Any())
  826. throw new UserFriendlyException("未查到任何ivr配置");
  827. var ivr = ivrList.First(x => x.IvrCategoryId == correct.ReturnValue && x.IsRoot);
  828. _logger.LogInformation("transfer to ivr.no:{ivrNo}", ivr.No);
  829. //写入子表
  830. var detail = new CallDetail()
  831. {
  832. CallId = call.Id,
  833. CallStatus = ECallStatus.Evaluate,
  834. ConversationId = call.ConversationId,
  835. EventName = "EVALUATE",
  836. FromNo = call.FromNo,
  837. ToNo = call.ToNo,
  838. };
  839. await _callDetailRepository.AddAsync(detail, HttpContext.RequestAborted);
  840. await _deviceManager.VisitorToMenuAsync(call.ConversationId, ivr.No, HttpContext.RequestAborted);
  841. }
  842. #endregion
  843. #region 威尔信分机和分机组操作
  844. /// <summary>
  845. /// 查询分机
  846. /// </summary>
  847. /// <param name="dto"></param>
  848. /// <returns></returns>
  849. [HttpGet("query-tel-list")]
  850. public async Task<PagedDto<TelListPageDto>> QueryTelList([FromQuery] QueryTelListDto dto)
  851. {
  852. var rsp = await _wexClient.QueryTelsPageAsync(new QueryTelPageRequest() { PageIndex = dto.PageIndex, PageSize = dto.PageSize, input = new Input() { TelNo = dto.TelNo } }, HttpContext.RequestAborted);
  853. var telList = rsp?.Data;
  854. var count = rsp.Count;
  855. if (telList != null)
  856. {
  857. var telGroup = _wexTelGroupRepository.Queryable().ToList();
  858. var list = (from a in telList
  859. join b in telGroup on a.TelNo equals b.TelNo into output
  860. from j in output.DefaultIfEmpty()
  861. select new TelListPageDto
  862. {
  863. Id = (j == null ? "" : j.Id),
  864. TelNo = a.TelNo,
  865. GroupName = (j == null ? "" : j.GroupName),
  866. GroupId = (j == null ? 0 : j.GroupId)
  867. }).ToList();
  868. return new PagedDto<TelListPageDto>(count, list);
  869. }
  870. return new PagedDto<TelListPageDto>(0, null);
  871. }
  872. /// <summary>
  873. /// 新增或修改分机关联分机组
  874. /// </summary>
  875. /// <param name="dto"></param>
  876. /// <returns></returns>
  877. [HttpPost("add-update-telgroup")]
  878. public async Task AddOrUpdateTelGroup([FromBody] AddOrUpdateTelGroupDto dto)
  879. {
  880. if (string.IsNullOrEmpty(dto.Id))
  881. {
  882. //新增
  883. var telGroup = new WexTelGroup();
  884. telGroup.TelNo = dto.TelNo;
  885. telGroup.GroupId = dto.GroupId;
  886. telGroup.GroupName = dto.GroupName;
  887. telGroup.ZuoGroupName = dto.ZuoGroupName;
  888. await _wexTelGroupRepository.AddAsync(telGroup, HttpContext.RequestAborted);
  889. }
  890. else
  891. {
  892. //修改
  893. var telGroup = await _wexTelGroupRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
  894. if (telGroup is null)
  895. {
  896. throw UserFriendlyException.SameMessage("未找到对应数据,无法修改");
  897. }
  898. if (dto.GroupId == 0)
  899. {
  900. await _wexTelGroupRepository.RemoveAsync(dto.Id, false, HttpContext.RequestAborted);
  901. }
  902. else
  903. {
  904. telGroup.GroupId = dto.GroupId;
  905. telGroup.GroupName = dto.GroupName;
  906. telGroup.ZuoGroupName = dto.ZuoGroupName;
  907. await _wexTelGroupRepository.UpdateAsync(telGroup);
  908. }
  909. }
  910. }
  911. /// <summary>
  912. /// 分机组列表
  913. /// </summary>
  914. /// <returns></returns>
  915. [HttpGet("telgroup-list")]
  916. public async Task<List<WexTelGroupDto>> TelGroupList()
  917. {
  918. var rsp = await _wexClient.QueryGroupAsync(new QueryGroupRequest() { }, HttpContext.RequestAborted);
  919. var groupList = rsp.Data;
  920. var list = _mapper.Map<List<WexTelGroupDto>>(groupList);
  921. return list;
  922. }
  923. /// <summary>
  924. /// 根据分机号查询分机关联坐席组
  925. /// </summary>
  926. /// <param name="telno"></param>
  927. /// <returns></returns>
  928. [HttpGet("telgroup/{telno}")]
  929. public async Task<WexTelGroup> TelGroup(string telno)
  930. {
  931. var telGroup = await _wexTelGroupRepository.GetAsync(x => x.TelNo == telno, HttpContext.RequestAborted);
  932. if (telGroup is null)
  933. {
  934. return new WexTelGroup() { TelNo = telno, };
  935. }
  936. return telGroup;
  937. }
  938. #endregion
  939. }
  940. }