CallController.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. using Hotline.Application.CallCenter;
  2. using Hotline.Share.Tools;
  3. using Hotline.Caching.Interfaces;
  4. using Hotline.CallCenter.BlackLists;
  5. using Hotline.CallCenter.Calls;
  6. using Hotline.CallCenter.Configs;
  7. using Hotline.CallCenter.Tels;
  8. using Hotline.EventBus;
  9. using Hotline.Quality.Notifications;
  10. using Hotline.Settings;
  11. using Hotline.Share.Dtos;
  12. using Hotline.Share.Dtos.CallCenter;
  13. using Hotline.Share.Dtos.Quality;
  14. using Hotline.Share.Dtos.TrCallCenter;
  15. using Hotline.Share.Enums.CallCenter;
  16. using Hotline.Repository.SqlSugar.Extensions;
  17. using Microsoft.AspNetCore.Mvc;
  18. using Microsoft.Extensions.Options;
  19. using System.Threading;
  20. using XF.Domain.Exceptions;
  21. using XF.Utility.EnumExtensions;
  22. using XF.Domain.Repository;
  23. using MapsterMapper;
  24. using SqlSugar;
  25. using Hotline.Api.Filter;
  26. using Microsoft.AspNetCore.Authorization;
  27. using Hotline.Share.Dtos.Order;
  28. using Hotline.Tools;
  29. using XF.Domain.Authentications;
  30. using Hotline.Caching.Services;
  31. namespace Hotline.Api.Controllers
  32. {
  33. public class CallController : BaseController
  34. {
  35. private readonly ICallApplication _callApplication;
  36. private readonly Publisher _publisher;
  37. private readonly IMapper _mapper;
  38. private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
  39. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  40. private readonly IRepository<TelOperationXthx> _telOperationXthxRepository;
  41. private readonly IUserCacheManager _userCacheManager;
  42. private readonly ISessionContext _sessionContext;
  43. private readonly IRepository<TelActionRecord> _telActionRecordRepository;
  44. public CallController(
  45. ICallApplication callApplication,
  46. Publisher publisher,
  47. IMapper mapper,
  48. IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
  49. ISystemSettingCacheManager systemSettingCacheManager,
  50. IRepository<TelOperationXthx> telOperationXthxRepository,
  51. IUserCacheManager userCacheManager,
  52. ISessionContext sessionContext,
  53. IRepository<TelActionRecord> telActionRecordRepository)
  54. {
  55. _callApplication = callApplication;
  56. _publisher = publisher;
  57. _mapper = mapper;
  58. _callcenterOptions = callcenterOptions;
  59. _systemSettingCacheManager = systemSettingCacheManager;
  60. _telOperationXthxRepository = telOperationXthxRepository;
  61. _userCacheManager = userCacheManager;
  62. _sessionContext = sessionContext;
  63. _telActionRecordRepository = telActionRecordRepository;
  64. }
  65. /// <summary>
  66. /// 查询分机组和分机
  67. /// </summary>
  68. [HttpGet("querygrouptel")]
  69. public Task<IReadOnlyList<TelGroupDto>> QueryGroupTel()
  70. => _callApplication.QueryGroupTel(HttpContext.RequestAborted);
  71. /// <summary>
  72. /// 查询分机
  73. /// </summary>
  74. [HttpGet("tels")]
  75. public async Task<IList<TelDto>> QueryTels()
  76. => (await _callApplication.QueryTelsAsync(new QueryTelsInDto(null) { PageSize = 99999 }, HttpContext.RequestAborted)).Item2;
  77. /// <summary>
  78. /// 查询分机
  79. /// </summary>
  80. [HttpGet("tels-paged")]
  81. public async Task<PagedDto<TelDto>> QueryTelsAsync([FromQuery] QueryTelsInDto dto)
  82. => (await _callApplication.QueryTelsAsync(dto, HttpContext.RequestAborted)).ToPaged();
  83. /// <summary>
  84. /// 查询分机组
  85. /// </summary>
  86. [HttpGet("groups")]
  87. public Task<IReadOnlyList<TelGroupDto>> QueryTelGroups()
  88. => _callApplication.QueryTelGroupsAsync(HttpContext.RequestAborted);
  89. #region 黑名单
  90. //Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken);
  91. //Task RemoveBlackListAsync(string id, CancellationToken cancellationToken);
  92. //Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken);
  93. #endregion
  94. /// <summary>
  95. /// 签入
  96. /// </summary>
  97. [HttpPost("signin")]
  98. [LogFilterAlpha("分机签入")]
  99. public Task<TrOnDutyResponseDto> SignIn([FromBody] SignInDto dto)
  100. => _callApplication.SignInAsync(dto, HttpContext.RequestAborted);
  101. /// <summary>
  102. /// 签出
  103. /// </summary>
  104. [HttpPost("signout")]
  105. [LogFilterAlpha("分机签出")]
  106. public Task SignOut()
  107. => _callApplication.SingOutAsync(HttpContext.RequestAborted);
  108. /// <summary>
  109. /// 签出
  110. /// </summary>
  111. [HttpPost("signout/{telNo}")]
  112. [LogFilterAlpha("分机签出")]
  113. public Task SignOut(string telNo)
  114. => _callApplication.SingOutAsync(telNo, HttpContext.RequestAborted);
  115. /// <summary>
  116. /// 分机动作开始
  117. /// </summary>
  118. /// <returns></returns>
  119. [HttpGet("call-action-record-begin")]
  120. public async Task CallActionRecordBegin(int actionType)
  121. {
  122. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  123. if (work is null)
  124. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  125. if (actionType == 0)//小休
  126. {
  127. var isResting = await _telActionRecordRepository.AnyAsync(x => x.TelNo == work.TelNo && x.ActionType == EActionType.TelRest && !x.EndTime.HasValue, HttpContext.RequestAborted);
  128. if (!isResting)
  129. {
  130. var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, EActionType.TelRest);
  131. await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted);
  132. work.OldExtensionStatus = work.ExtensionStatus;
  133. work.ExtensionStatus = EExtensionStatus.TelRest;
  134. _userCacheManager.SetWorkByUser(work);
  135. }
  136. return;
  137. }
  138. //判断如果已经存在未结束的该动作就不新增
  139. var ishas = await _telActionRecordRepository.AnyAsync(x => x.TelNo == work.TelNo && x.ActionType == (EActionType)actionType && !x.EndTime.HasValue, HttpContext.RequestAborted);
  140. if (!ishas)
  141. {
  142. var telAction = new TelActionRecord(work.UserId, work.UserName, work.TelNo, work.QueueId, (EActionType)actionType);
  143. await _telActionRecordRepository.AddAsync(telAction, HttpContext.RequestAborted);
  144. }
  145. //动作写入成功,修改话机动作状态
  146. work.OldExtensionStatus = work.ExtensionStatus;
  147. switch (actionType)
  148. {
  149. case 1://整理
  150. work.ExtensionStatus = EExtensionStatus.Arrange;
  151. break;
  152. case 2://保持
  153. work.ExtensionStatus = EExtensionStatus.Hold;
  154. break;
  155. default:
  156. break;
  157. }
  158. _userCacheManager.SetWorkByUser(work);
  159. }
  160. /// <summary>
  161. /// 分机动作结束
  162. /// </summary>
  163. /// <returns></returns>
  164. [HttpGet("call-action-record-end")]
  165. public async Task CallActionRecordEnd(int actionType)
  166. {
  167. var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
  168. if (work is null)
  169. throw UserFriendlyException.SameMessage("分机未签入,不能操作");
  170. if (actionType == 0)//小休
  171. {
  172. var telRest = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == EActionType.TelRest && !x.EndTime.HasValue, HttpContext.RequestAborted);
  173. if (telRest is null)
  174. throw UserFriendlyException.SameMessage("未查询到分机休息信息");
  175. else
  176. {
  177. telRest.EndAction();
  178. await _telActionRecordRepository.UpdateAsync(telRest);
  179. work.OldExtensionStatus = work.ExtensionStatus;
  180. work.ExtensionStatus = EExtensionStatus.Ready;
  181. _userCacheManager.SetWorkByUser(work);
  182. }
  183. return;
  184. }
  185. var telAction = await _telActionRecordRepository.GetAsync(x => x.TelNo == work.TelNo && x.ActionType == (EActionType)actionType && !x.EndTime.HasValue, HttpContext.RequestAborted);
  186. if (telAction != null)
  187. {
  188. telAction.EndAction();
  189. await _telActionRecordRepository.UpdateAsync(telAction);
  190. }
  191. //动作结束成功,修改话机动作状态
  192. work.OldExtensionStatus = work.ExtensionStatus;
  193. switch (actionType)
  194. {
  195. case 1://整理
  196. work.ExtensionStatus = EExtensionStatus.Arrange;
  197. break;
  198. case 2://保持
  199. work.ExtensionStatus = EExtensionStatus.Hold;
  200. break;
  201. default:
  202. break;
  203. }
  204. _userCacheManager.SetWorkByUser(work);
  205. }
  206. /// <summary>
  207. /// 查询当前用户的分机状态
  208. /// </summary>
  209. /// <returns></returns>
  210. [HttpGet("tel-state")]
  211. public Task<TrOnDutyResponseDto> GetTelState()
  212. => _callApplication.GetTelStateAsync(HttpContext.RequestAborted);
  213. /// <summary>
  214. /// 查询通话记录(固定数据量)
  215. /// </summary>
  216. /// <param name="dto"></param>
  217. /// <returns></returns>
  218. [HttpGet("calls-fixed")]
  219. public async Task<List<CallNativeDto>> QueryCallsFixed([FromQuery] QueryCallsFixedDto dto)
  220. => await _callApplication.QueryCallsFixedAsync(dto, HttpContext.RequestAborted).ToPageListWithoutTotalAsync(dto, HttpContext.RequestAborted);
  221. /// <summary>
  222. /// 查询通话记录(固定数据量)
  223. /// </summary>
  224. /// <param name="dto"></param>
  225. /// <returns></returns>
  226. [HttpGet("calls-fixed/count")]
  227. public async Task<int> QueryCallsFixedCount([FromQuery] QueryCallsFixedDto dto)
  228. => await _callApplication.QueryCallsFixedAsync(dto, HttpContext.RequestAborted).CountAsync(HttpContext.RequestAborted);
  229. /// <summary>
  230. /// 导出通话记录
  231. /// </summary>
  232. /// <param name="dto"></param>
  233. /// <returns></returns>
  234. [HttpPost("calls-fixed/export")]
  235. [LogFilterAlpha("导出日志")]
  236. public async Task<FileStreamResult> ExportOrders([FromBody] ExportExcelDto<QueryCallsFixedDto> dto)
  237. {
  238. var query = _callApplication.QueryCallsFixedAsync(dto.QueryDto, HttpContext.RequestAborted);
  239. List<CallNativeDto> callls;
  240. if (dto.IsExportAll)
  241. {
  242. callls = await query.ToListAsync(HttpContext.RequestAborted);
  243. }
  244. else
  245. {
  246. var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
  247. callls = items;
  248. }
  249. dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass<CallNativeDto>(dto.ColumnInfos);
  250. var dtos = callls
  251. .Select(stu => _mapper.Map(stu, typeof(CallNativeDto), dynamicClass))
  252. .Cast<object>()
  253. .ToList();
  254. var stream = ExcelHelper.CreateStream(dtos);
  255. return ExcelStreamResult(stream, "通话记录");
  256. }
  257. /// <summary>
  258. /// 查询通话记录
  259. /// </summary>
  260. /// <param name="callId"></param>
  261. /// <returns></returns>
  262. [HttpGet("{callId}")]
  263. public Task<List<CallNative>> GetCall(string callId)
  264. {
  265. //为兼容天润通话记录返回集合
  266. if (string.IsNullOrEmpty(callId)) return default;
  267. return _callApplication.GetCallListAsync(callId, HttpContext.RequestAborted);
  268. }
  269. /// <summary>
  270. /// 通话记录基础数据
  271. /// </summary>
  272. /// <returns></returns>
  273. [HttpGet("base-data")]
  274. public object BaseData()
  275. {
  276. return new
  277. {
  278. Direction = EnumExts.GetDescriptions<ECallDirection>(),
  279. EndBy = EnumExts.GetDescriptions<EEndBy>(),
  280. };
  281. }
  282. /// <summary>
  283. /// 查询坐席操作记录(固定数据量)
  284. /// </summary>
  285. /// <param name="dto"></param>
  286. /// <returns></returns>
  287. [HttpGet("tel-operations-fixed")]
  288. public async Task<IReadOnlyList<TelOperation>> QueryTelOperationsAsync([FromQuery] QueryTelOperationsFixedDto dto) =>
  289. await _callApplication.QueryTelOperationsAsync(dto).ToPageListWithoutTotalAsync(dto, HttpContext.RequestAborted);
  290. /// <summary>
  291. /// 查询坐席操作记录(固定数据量)
  292. /// </summary>
  293. /// <param name="dto"></param>
  294. /// <returns></returns>
  295. [HttpGet("tel-operations-fixed/count")]
  296. public async Task<int> QueryTelOperationsCountAsync([FromQuery] QueryTelOperationsFixedDto dto) =>
  297. await _callApplication.QueryTelOperationsAsync(dto).CountAsync(HttpContext.RequestAborted);
  298. /// <summary>
  299. /// 查询坐席操作记录基础数据
  300. /// </summary>
  301. [HttpGet("base-data-tel-operation")]
  302. public object BaseDataTelOperation()
  303. {
  304. return new
  305. {
  306. Operations = _callApplication.GetTelOperationOptions()
  307. };
  308. }
  309. /// <summary>
  310. /// 通话转写
  311. /// </summary>
  312. /// <param name="dto"></param>
  313. /// <returns></returns>
  314. [HttpPost("calls/transliteration")]
  315. public async Task CallTransliteration([FromBody] CallTransliteration dto)
  316. {
  317. foreach (var id in dto.Ids)
  318. {
  319. var call = await _callApplication.GetTianrunCallAsync(id, HttpContext.RequestAborted);
  320. if (call is null)
  321. throw UserFriendlyException.SameMessage("通话信息错误");
  322. if (call.TransliterationState == ECallTransliterationState.Underway)
  323. throw UserFriendlyException.SameMessage("正在转写中,请勿重复点击,请稍作等待");
  324. if (call.TransliterationState == ECallTransliterationState.Succeed)
  325. throw UserFriendlyException.SameMessage("转写成功,不能重新转写");
  326. call.InitTransliterationId();
  327. await _callApplication.EditTransliterationStateAsync(call.Id, ECallTransliterationState.Underway, call.TransliterationId, HttpContext.RequestAborted);
  328. var audioFile = call.RecordingAbsolutePath;
  329. var fromNo = call.CPN;
  330. var callStartTime = call.CreatedTime;
  331. var Id = call.TransliterationId;
  332. var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ViteRecordPrefix);
  333. var handler = new AiQualityHandler()
  334. {
  335. Id = Id,
  336. Source = "AiAnswered",
  337. AudioFile = audioFile,
  338. FromNo = fromNo,
  339. CallStartTime = callStartTime,
  340. ViteRecordPrefix = setting?.SettingValue[0],
  341. };
  342. await _publisher.PublishAsync(new AiOrderQualityNotify(handler), PublishStrategy.ParallelNoWait, HttpContext.RequestAborted);
  343. }
  344. }
  345. #region 坐席动作类型统计
  346. /// <summary>
  347. /// 坐席动作类型统计
  348. /// </summary>
  349. /// <param name="dto"></param>
  350. /// <returns></returns>
  351. [HttpGet("telaction/list")]
  352. public async Task<PagedDto<TelActionListXthx>> TelActionList([FromQuery] TelActionXthxDto dto)
  353. {
  354. var (total, items) = await _telOperationXthxRepository.Queryable()
  355. .WhereIF(string.IsNullOrEmpty(dto.TelNo) == false, x => x.TelNo.Contains(dto.TelNo))
  356. .WhereIF(dto.OperationStatus != null, x => x.OperationStatus == dto.OperationStatus)
  357. .WhereIF(string.IsNullOrEmpty(dto.UserName) == false, x => x.UserName.Contains(dto.UserName))
  358. .WhereIF(dto.StartTime.HasValue, x => x.StartTime >= dto.StartTime)
  359. .WhereIF(dto.EndTime.HasValue, x => x.StartTime <= dto.EndTime)
  360. .OrderByDescending(x => x.StartTime)
  361. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  362. return new PagedDto<TelActionListXthx>(total, _mapper.Map<IReadOnlyList<TelActionListXthx>>(items));
  363. }
  364. #endregion
  365. }
  366. }