DefaultCallApplication.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. using Hotline.Caching.Interfaces;
  2. using Hotline.CallCenter.BlackLists;
  3. using Hotline.CallCenter.Calls;
  4. using Hotline.CallCenter.Tels;
  5. using Hotline.Orders;
  6. using Hotline.Repository.SqlSugar.Extensions;
  7. using Hotline.Share.Dtos.CallCenter;
  8. using Hotline.Share.Dtos.TrCallCenter;
  9. using Hotline.Share.Enums.CallCenter;
  10. using Hotline.Users;
  11. using MapsterMapper;
  12. using Microsoft.Extensions.Logging;
  13. using XF.Domain.Authentications;
  14. using XF.Domain.Cache;
  15. using XF.Domain.Exceptions;
  16. using XF.Domain.Repository;
  17. namespace Hotline.Application.CallCenter;
  18. public abstract class DefaultCallApplication : ICallApplication
  19. {
  20. private readonly IRepository<Tel> _telRepository;
  21. private readonly IRepository<TelGroup> _telGroupRepository;
  22. private readonly IWorkRepository _workRepository;
  23. private readonly ITelRestRepository _telRestRepository;
  24. private readonly IRepository<CallNative> _callNativeRepository;
  25. private readonly IRepository<TelOperation> _telOperationRepository;
  26. private readonly IRepository<CallidRelation> _callIdRelationRepository;
  27. private readonly ITypedCache<Work> _cacheWork;
  28. private readonly IUserCacheManager _userCacheManager;
  29. private readonly ISessionContext _sessionContext;
  30. private readonly IMapper _mapper;
  31. private readonly ILogger<DefaultCallApplication> _logger;
  32. public DefaultCallApplication(
  33. IRepository<Tel> telRepository,
  34. IRepository<TelGroup> telGroupRepository,
  35. IWorkRepository workRepository,
  36. ITelRestRepository telRestRepository,
  37. IRepository<CallNative> callNativeRepository,
  38. IRepository<TelOperation> telOperationRepository,
  39. IRepository<CallidRelation> callIdRelationRepository,
  40. ITypedCache<Work> cacheWork,
  41. IUserCacheManager userCacheManager,
  42. ISessionContext sessionContext,
  43. IMapper mapper,
  44. ILogger<DefaultCallApplication> logger)
  45. {
  46. _telRepository = telRepository;
  47. _telGroupRepository = telGroupRepository;
  48. _workRepository = workRepository;
  49. _telRestRepository = telRestRepository;
  50. _callNativeRepository = callNativeRepository;
  51. _telOperationRepository = telOperationRepository;
  52. _callIdRelationRepository = callIdRelationRepository;
  53. _cacheWork = cacheWork;
  54. _userCacheManager = userCacheManager;
  55. _sessionContext = sessionContext;
  56. _mapper = mapper;
  57. _logger = logger;
  58. }
  59. public DefaultCallApplication()
  60. {
  61. }
  62. /// <summary>
  63. /// 查询分机
  64. /// </summary>
  65. public async Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
  66. {
  67. return await _telRepository.Queryable()
  68. .Select<TelDto>()
  69. .ToListAsync(cancellationToken);
  70. }
  71. /// <summary>
  72. /// 查询分机组
  73. /// </summary>
  74. public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
  75. {
  76. return await _telGroupRepository.Queryable()
  77. .Select<TelGroupDto>()
  78. .ToListAsync(cancellationToken);
  79. }
  80. /// <summary>
  81. /// 新增黑名单
  82. /// </summary>
  83. public abstract Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken);
  84. /// <summary>
  85. /// 删除黑名单
  86. /// </summary>
  87. public abstract Task RemoveBlackListAsync(string id, CancellationToken cancellationToken);
  88. /// <summary>
  89. /// 查询黑名单
  90. /// </summary>
  91. public abstract Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken);
  92. /// <summary>
  93. /// 签入
  94. /// </summary>
  95. public async Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken)
  96. {
  97. if (string.IsNullOrEmpty(dto.TelNo))
  98. throw UserFriendlyException.SameMessage("无效分机号");
  99. var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
  100. if (work is not null)
  101. {
  102. //if (work.TelNo != dto.TelNo)
  103. //{
  104. // throw UserFriendlyException.SameMessage("当前用户已签入其他分机");
  105. //}
  106. throw UserFriendlyException.SameMessage("当前用户已签入");
  107. }
  108. var telWork = _userCacheManager.GetWorkByTelNoExp(dto.TelNo);
  109. if (telWork is not null)
  110. {
  111. throw UserFriendlyException.SameMessage("当前分机已被占用");
  112. }
  113. work = new Work(_sessionContext.RequiredUserId, _sessionContext.UserName,
  114. dto.TelNo, dto.TelNo, null, null,
  115. dto.GroupId, _sessionContext.StaffNo, null);
  116. await _workRepository.AddAsync(work, cancellationToken);
  117. return new TrOnDutyResponseDto
  118. {
  119. TelNo = dto.TelNo,
  120. QueueId = dto.GroupId,
  121. StartTime = work.StartTime,
  122. };
  123. }
  124. /// <summary>
  125. /// 签出
  126. /// </summary>
  127. public async Task SingOutAsync(CancellationToken cancellationToken)
  128. {
  129. var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
  130. if (work is null) return;
  131. var telRest =
  132. await _telRestRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, cancellationToken);
  133. if (telRest is not null)
  134. {
  135. telRest.EndRest();
  136. await _telRestRepository.UpdateAsync(telRest, cancellationToken);
  137. }
  138. work.OffDuty();
  139. await _workRepository.UpdateAsync(work, cancellationToken);
  140. await _cacheWork.RemoveAsync(work.GetKey(KeyMode.UserId), cancellationToken);
  141. await _cacheWork.RemoveAsync(work.GetKey(KeyMode.TelNo), cancellationToken);
  142. }
  143. /// <summary>
  144. /// 签出
  145. /// </summary>
  146. public async Task SingOutAsync(string telNo, CancellationToken cancellationToken)
  147. {
  148. var work = _userCacheManager.GetWorkByTelNoExp(telNo);
  149. if (work is null) return;
  150. var telRest =
  151. await _telRestRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, cancellationToken);
  152. if (telRest is not null)
  153. {
  154. telRest.EndRest();
  155. await _telRestRepository.UpdateAsync(telRest, cancellationToken);
  156. }
  157. work.OffDuty();
  158. await _workRepository.UpdateAsync(work, cancellationToken);
  159. await _cacheWork.RemoveAsync(work.GetKey(KeyMode.UserId), cancellationToken);
  160. await _cacheWork.RemoveAsync(work.GetKey(KeyMode.TelNo), cancellationToken);
  161. }
  162. /// <summary>
  163. /// 查询当前用户的分机状态
  164. /// </summary>
  165. /// <param name="cancellationToken"></param>
  166. /// <returns></returns>
  167. public async Task<TrOnDutyResponseDto> GetTelStateAsync(CancellationToken cancellationToken)
  168. {
  169. var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
  170. if (work is null) return null;
  171. return await Task.FromResult(new TrOnDutyResponseDto
  172. {
  173. TelNo = work.TelNo,
  174. QueueId = work.QueueId,
  175. StartTime = work.StartTime,
  176. StaffNo = work.StaffNo
  177. });
  178. }
  179. /// <summary>
  180. /// 定量查询通话记录
  181. /// </summary>
  182. public async Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
  183. {
  184. return await _callNativeRepository.Queryable(includeDeleted: true)
  185. .LeftJoin<Order>((d, o) => d.CallNo == o.CallId)
  186. .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), (d, o) => o.No == dto.OrderNo)
  187. .WhereIF(!string.IsNullOrEmpty(dto.FromNo), d => d.FromNo == dto.FromNo)
  188. .WhereIF(!string.IsNullOrEmpty(dto.ToNo), d => d.ToNo == dto.ToNo)
  189. .WhereIF(!string.IsNullOrEmpty(dto.UserName), d => d.UserName == dto.UserName)
  190. .WhereIF(!string.IsNullOrEmpty(dto.TelNo), d => d.TelNo == dto.TelNo)
  191. .WhereIF(dto.EndBy != null, d => d.EndBy == dto.EndBy)
  192. .WhereIF(dto.CallStartTimeStart != null, d => d.BeginIvrTime >= dto.CallStartTimeStart)
  193. .WhereIF(dto.CallStartTimeEnd != null, d => d.BeginIvrTime <= dto.CallStartTimeEnd)
  194. .WhereIF(dto.IsConnected != null, d => d.AnsweredTime != null)
  195. .WhereIF(dto.Direction != null, d => d.Direction == dto.Direction)
  196. .WhereIF(dto.WaitDurationStart != null && dto.WaitDurationStart > 0, d => d.WaitDuration >= dto.WaitDurationStart)
  197. .WhereIF(dto.WaitDurationEnd != null && dto.WaitDurationEnd > 0, d => d.WaitDuration <= dto.WaitDurationEnd)
  198. .Select((d, o) => new CallNativeDto
  199. {
  200. OrderId = o.Id,
  201. OrderNo = o.No,
  202. Title = o.Title,
  203. }, true)
  204. .ToFixedListAsync(dto, cancellationToken);
  205. }
  206. /// <summary>
  207. /// 查询分机操作记录(定量)
  208. /// </summary>
  209. public async Task<IReadOnlyList<TelOperation>> QueryTelOperationsAsync(QueryTelOperationsFixedDto dto, CancellationToken cancellationToken)
  210. {
  211. return await _telOperationRepository.Queryable()
  212. .WhereIF(!string.IsNullOrEmpty(dto.UserName), d => d.UserName == dto.UserName)
  213. .WhereIF(!string.IsNullOrEmpty(dto.StaffNo), d => d.StaffNo == dto.StaffNo)
  214. .WhereIF(!string.IsNullOrEmpty(dto.GroupId), d => d.GroupId == dto.GroupId)
  215. .WhereIF(dto.OperateState != null, d => d.OperateState == dto.OperateState)
  216. .ToFixedListAsync(dto, cancellationToken);
  217. }
  218. /// <summary>
  219. /// 依据通话记录编号获取映射后的callId
  220. /// </summary>
  221. public async Task<string> GetOrSetCallIdAsync(string callNo, CancellationToken cancellationToken)
  222. {
  223. var callOrder = await _callIdRelationRepository.GetAsync(callNo, cancellationToken);
  224. if (callOrder == null)
  225. {
  226. callOrder = new CallidRelation
  227. {
  228. Id = callNo,
  229. CallId = Ulid.NewUlid().ToString(),
  230. };
  231. try
  232. {
  233. await _callIdRelationRepository.AddAsync(callOrder, cancellationToken);
  234. }
  235. catch (Exception e)
  236. {
  237. _logger.LogError($"写入callidRelation失败:{e.Message}");
  238. }
  239. }
  240. return callOrder.CallId;
  241. }
  242. /// <summary>
  243. /// 批量获取callId
  244. /// </summary>
  245. public async Task<List<(string callNo, string callId)>> GetOrSetCallIdRangeAsync(List<string> callNos,
  246. CancellationToken cancellationToken)
  247. {
  248. var relations = await _callIdRelationRepository.Queryable()
  249. .Where(d => callNos.Contains(d.Id))
  250. .ToListAsync(cancellationToken);
  251. var rsp = new List<(string callNo, string callId)>();
  252. var newRelations = new List<CallidRelation>();
  253. foreach (var callNo in callNos)
  254. {
  255. var relation = relations.FirstOrDefault(d => d.Id == callNo);
  256. if (relation is null)
  257. {
  258. relation = new CallidRelation
  259. {
  260. Id = callNo,
  261. CallId = Ulid.NewUlid().ToString(),
  262. };
  263. newRelations.Add(relation);
  264. rsp.Add(new(relation.Id, relation.CallId));
  265. }
  266. else
  267. {
  268. rsp.Add(new(relation.Id, relation.CallId));
  269. }
  270. }
  271. await _callIdRelationRepository.AddRangeAsync(newRelations, cancellationToken);
  272. return rsp;
  273. }
  274. /// <summary>
  275. /// 查询通话记录
  276. /// </summary>
  277. public Task<CallNative?> GetCallAsync(string callId, CancellationToken cancellationToken)
  278. {
  279. if (string.IsNullOrEmpty(callId)) return null;
  280. return _callNativeRepository.GetAsync(callId, cancellationToken);
  281. }
  282. /// <summary>
  283. /// 查询通话记录
  284. /// </summary>
  285. public async Task<List<CallNative>> QueryCallsAsync(
  286. string? phone,
  287. ECallDirection? direction,
  288. DateTime? callStartTimeStart,
  289. DateTime? callStartTimeEnd,
  290. CancellationToken cancellationToken)
  291. {
  292. if (string.IsNullOrEmpty(phone))
  293. return new List<CallNative>();
  294. return await _callNativeRepository.Queryable()
  295. .WhereIF(direction.HasValue, d => d.Direction == direction)
  296. .WhereIF(callStartTimeStart != null, d => d.BeginIvrTime >= callStartTimeStart)
  297. .WhereIF(callStartTimeEnd != null, d => d.BeginIvrTime <= callStartTimeEnd)
  298. .Where(d => d.FromNo == phone || d.ToNo == phone)
  299. .OrderBy(d => d.CreationTime)
  300. .ToListAsync(cancellationToken);
  301. }
  302. #region tianrun 临时方案
  303. public Task<TrCallRecord?> GetTianrunCallAsync(string callId, CancellationToken cancellationToken)
  304. {
  305. throw new NotImplementedException();
  306. }
  307. /// <summary>
  308. /// 关联通话记录与order(添润)
  309. /// </summary>
  310. public Task RelateTianrunCallWithOrderAsync(string callId, string orderId,
  311. CancellationToken cancellationToken)
  312. {
  313. throw new NotImplementedException();
  314. }
  315. /// <summary>
  316. /// 查询通话记录
  317. /// </summary>
  318. public Task<List<TrCallRecord>> QueryTianrunCallsAsync(
  319. string? phone = null,
  320. ECallDirection? direction = null,
  321. DateTime? callStartTimeStart = null,
  322. DateTime? callStartTimeEnd = null,
  323. CancellationToken cancellationToken = default)
  324. {
  325. throw new NotImplementedException();
  326. }
  327. #endregion
  328. }