BiCallController.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. using Hotline.Caching.Interfaces;
  2. using Hotline.Caching.Services;
  3. using Hotline.CallCenter.Calls;
  4. using Hotline.CallCenter.Tels;
  5. using Hotline.Orders;
  6. using Hotline.Repository.SqlSugar.Extensions;
  7. using Hotline.Settings;
  8. using Hotline.Share.Dtos;
  9. using Hotline.Share.Dtos.CallCenter;
  10. using Hotline.Share.Dtos.Order;
  11. using Hotline.Share.Enums.CallCenter;
  12. using Hotline.Share.Enums.User;
  13. using Hotline.Share.Requests;
  14. using Hotline.Tools;
  15. using Hotline.Users;
  16. using MapsterMapper;
  17. using Microsoft.AspNetCore.Authorization;
  18. using Microsoft.AspNetCore.Mvc;
  19. using SqlSugar;
  20. using System.Data;
  21. using XF.Domain.Constants;
  22. using XF.Domain.Exceptions;
  23. using XF.Domain.Repository;
  24. namespace Hotline.Api.Controllers.Bi;
  25. /// <summary>
  26. /// 话务报表
  27. /// </summary>
  28. public class BiCallController : BaseController
  29. {
  30. private readonly IRepository<TrCallRecord> _trCallRecordRepository;
  31. private readonly IRepository<User> _userRepository;
  32. private readonly IRepository<TelRest> _telRestRepository;
  33. private readonly IMapper _mapper;
  34. private readonly ITrCallRecordRepository _trCallRecordRepositoryEx;
  35. private readonly ISystemSettingCacheManager _systemSettingCacheManager;
  36. private readonly IRepository<Work> _workRepository;
  37. private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
  38. public BiCallController(
  39. IRepository<TrCallRecord> trCallRecordRepository,
  40. IRepository<User> userRepository,
  41. IRepository<TelRest> telRestRepository,
  42. IMapper mapper,
  43. ITrCallRecordRepository trCallRecordRepositoryEx,
  44. ISystemSettingCacheManager systemSettingCacheManager,
  45. ISystemDicDataCacheManager sysDicDataCacheManager,
  46. IRepository<Work> workRepository)
  47. {
  48. _trCallRecordRepository = trCallRecordRepository;
  49. _userRepository = userRepository;
  50. _telRestRepository = telRestRepository;
  51. _mapper = mapper;
  52. _trCallRecordRepositoryEx = trCallRecordRepositoryEx;
  53. _systemSettingCacheManager = systemSettingCacheManager;
  54. _workRepository = workRepository;
  55. _sysDicDataCacheManager = sysDicDataCacheManager;
  56. }
  57. /// <summary>
  58. /// 话务统计分析
  59. /// </summary>
  60. /// <param name="dto"></param>
  61. /// <returns></returns>
  62. [HttpGet("calls")]
  63. [AllowAnonymous]
  64. public async Task<List<BiCallDto>> QueryCallsAsync([FromQuery] BiQueryCallsDto dto)
  65. {
  66. if (!dto.StartTime.HasValue || !dto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
  67. #region 注释
  68. //var items = await _trCallRecordRepository.Queryable()
  69. // .Where(d => d.CreatedTime >= dto.StartTime && d.CreatedTime <= dto.EndTime)
  70. // .Where(d => d.CallDirection == ECallDirection.In)
  71. // .WhereIF(!string.IsNullOrEmpty(dto.Line), d => d.Gateway == dto.Line)
  72. // .Select(d => new
  73. // {
  74. // d.CreatedTime.Year,
  75. // d.CreatedTime.Month,
  76. // d.CreatedTime.Day,
  77. // d.CreatedTime.Hour,
  78. // d.AnsweredTime,
  79. // d.EndBy
  80. // })
  81. // .MergeTable()
  82. // .GroupBy(d => new
  83. // {
  84. // d.Year,
  85. // d.Month,
  86. // d.Day,
  87. // d.Hour
  88. // })
  89. // .Select(d => new BiCallDto
  90. // {
  91. // Hour = d.Hour,
  92. // Total = SqlFunc.AggregateCount(d.Hour),
  93. // Answered = SqlFunc.AggregateSum(SqlFunc.IIF(d.AnsweredTime != null, 1, 0)),
  94. // Hanguped = SqlFunc.AggregateSum(SqlFunc.IIF(d.AnsweredTime == null && d.EndBy != null && d.EndBy.Value == EEndBy.To, 1, 0))
  95. // })
  96. // .OrderBy(d => d.Hour)
  97. // .ToListAsync(HttpContext.RequestAborted);
  98. //if (items.Count < 24)
  99. //{
  100. // for (var i = 0; i < 24; i++)
  101. // {
  102. // var item = items.FirstOrDefault(d => d.Hour == i);
  103. // if (item is null)
  104. // items.Add(new BiCallDto { Hour = i });
  105. // }
  106. // items = items.OrderBy(d => d.Hour).ToList();
  107. //}
  108. #endregion
  109. dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
  110. return await _trCallRecordRepositoryEx.GetQueryCalls(dto.StartTime.Value, dto.EndTime.Value);
  111. }
  112. /// <summary>
  113. /// 话务统计分析---导出
  114. /// </summary>
  115. /// <param name="dto"></param>
  116. /// <returns></returns>
  117. [HttpPost("calls_export")]
  118. [AllowAnonymous]
  119. public async Task<FileStreamResult> ExportQueryCallsAsync([FromBody] ExportExcelDto<BiQueryCallsDto> dto)
  120. {
  121. if (!dto.QueryDto.StartTime.HasValue || !dto.QueryDto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
  122. dto.QueryDto.EndTime = dto.QueryDto.EndTime.Value.AddDays(1).AddSeconds(-1);
  123. var list = await _trCallRecordRepositoryEx.GetQueryCalls(dto.QueryDto.StartTime.Value, dto.QueryDto.EndTime.Value);
  124. if (list != null && list.Count > 0)
  125. {
  126. list.Add(new BiCallDto()
  127. {
  128. HourRange = "合计",
  129. Hour = 13,
  130. Total = list.Sum(p => p.Total),
  131. Answered = list.Sum(p => p.Answered),
  132. Hanguped = list.Sum(p => p.Hanguped)
  133. });
  134. }
  135. dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
  136. var dtos = list
  137. .Select(stu => _mapper.Map(stu, typeof(BiCallDto), dynamicClass))
  138. .Cast<object>()
  139. .ToList();
  140. var stream = ExcelHelper.CreateStream(dtos);
  141. return ExcelStreamResult(stream, "话务统计分析");
  142. }
  143. /// <summary>
  144. /// 坐席话务统计分析
  145. /// </summary>
  146. /// <param name="dto"></param>
  147. /// <returns></returns>
  148. [HttpGet("seats")]
  149. [AllowAnonymous]
  150. public async Task<IReadOnlyList<BiSeatCallsDto>> QuerySeatCallsAsync([FromQuery] ReportPagedRequest dto)
  151. {
  152. if (!dto.StartTime.HasValue || !dto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
  153. dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
  154. //获取配置
  155. int noConnectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NoConnectByeTimes)?.SettingValue[0]);
  156. int effectiveTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.EffectiveTimes)?.SettingValue[0]);
  157. int connectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConnectByeTimes)?.SettingValue[0]);
  158. var list = await _userRepository.Queryable()
  159. .LeftJoin<TrCallRecord>((u, c) => u.Id == c.UserId)
  160. .Where(u => !u.IsDeleted && u.UserType == EUserType.Seat)
  161. .WhereIF(dto.StartTime.HasValue, (u, c) => c.CreatedTime >= dto.StartTime.Value)
  162. .WhereIF(dto.EndTime.HasValue, (u, c) => c.CreatedTime <= dto.EndTime.Value)
  163. .GroupBy((u, c) => new { c.UserName, c.UserId })
  164. .Select((u, c) => new BiSeatCallsDto
  165. {
  166. Name = c.UserName,
  167. UserId = c.UserId,
  168. InTotal = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In, 1, 0)),
  169. OutTotal = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.Out, 1, 0)),
  170. InAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null, 1, 0)),
  171. OutAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.Out && c.AnsweredTime != null, 1, 0)),
  172. InHangupImmediate = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime == null && c.RingTimes < noConnectByeTimes, 1, 0)),
  173. InHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime == null, 1, 0)),
  174. InDurationAvg = SqlFunc.Ceil(SqlFunc.AggregateAvg(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null, c.Duration, 0))),
  175. OutDurationAvg = SqlFunc.Ceil(SqlFunc.AggregateAvg(SqlFunc.IIF(c.CallDirection == ECallDirection.Out && c.AnsweredTime != null, c.Duration, 0))),
  176. InAvailableAnswer = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null && c.Duration >= effectiveTimes, 1, 0)),
  177. InHangupImmediateWhenAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null && c.Duration < connectByeTimes, 1, 0)),
  178. })
  179. .MergeTable()
  180. .ToListAsync(HttpContext.RequestAborted);
  181. list.ForEach(d =>
  182. {
  183. d.LoginDuration = _workRepository.Queryable().Where(q => q.UserId == d.UserId && q.CreationTime >= dto.StartTime && q.CreationTime <= dto.EndTime).Sum(q => q.WorkingDuration);
  184. if (d.LoginDuration != null)
  185. {
  186. d.LoginDuration = Math.Round(d.LoginDuration.Value, digits: 2);
  187. }
  188. d.RestDuration = _telRestRepository.Queryable().Where(q => q.UserId == d.UserId && q.CreationTime >= dto.StartTime && q.CreationTime <= dto.EndTime).Sum(q => q.RestDuration);
  189. d.RestDuration = Math.Round(d.RestDuration, digits: 2);
  190. });
  191. return list;
  192. }
  193. /// <summary>
  194. /// 小休统计
  195. /// </summary>
  196. /// <param name="dto"></param>
  197. /// <returns></returns>
  198. [HttpGet("rests")]
  199. [AllowAnonymous]
  200. public async Task<IReadOnlyList<BiSeatRestDto>> QuerySeatRest([FromQuery] QuerySeatRestRequest dto)
  201. {
  202. if (!dto.StartTime.HasValue || !dto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
  203. dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
  204. return await _telRestRepository.Queryable()
  205. .WhereIF(!string.IsNullOrEmpty(dto.UserName), x => x.UserName.Contains(dto.UserName))
  206. .WhereIF(!string.IsNullOrEmpty(dto.StaffNo), x => x.StaffNo.Contains(dto.StaffNo))
  207. .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime.Value)
  208. .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime.Value)
  209. .GroupBy(x => new { x.UserId, x.StaffNo, x.UserName })
  210. .Select(x => new BiSeatRestDto
  211. {
  212. UserId = x.UserId,
  213. StaffNo = x.StaffNo,
  214. UserName = x.UserName,
  215. RestCount = SqlFunc.AggregateCount(x.Id),
  216. RestDuration = SqlFunc.AggregateSum(x.RestDuration / 60) / SqlFunc.AggregateCount(x.Id)
  217. })
  218. .OrderByIF(dto.SortRule is 0, a => a.RestDuration, OrderByType.Asc)
  219. .OrderByIF(dto.SortRule is 1, a => a.RestDuration, OrderByType.Desc)
  220. .MergeTable()
  221. .ToListAsync(HttpContext.RequestAborted);
  222. }
  223. /// <summary>
  224. /// 坐席转接统计
  225. /// </summary>
  226. /// <param name="dto"></param>
  227. /// <returns></returns>
  228. [HttpGet("seatswitch")]
  229. [AllowAnonymous]
  230. public async Task<PagedDto<BiSeatSwitchDto>> QuerySeatSwitch([FromQuery] QuerySeatSwitchRequest dto)
  231. {
  232. if (!dto.StartTime.HasValue || !dto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
  233. dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
  234. var (total, items) = await _trCallRecordRepository.Queryable()
  235. .Where(x => !string.IsNullOrEmpty(x.AgentTransferNumber))
  236. .WhereIF(!string.IsNullOrEmpty(dto.UserName), x => x.UserName.Contains(dto.UserName))
  237. .WhereIF(!string.IsNullOrEmpty(dto.CDPN), x => x.CDPN.Contains(dto.CDPN))
  238. .WhereIF(dto.StartTime.HasValue, x => x.CreatedTime >= dto.StartTime.Value)
  239. .WhereIF(dto.EndTime.HasValue, x => x.CreatedTime <= dto.EndTime.Value)
  240. .Select(x => new BiSeatSwitchDto
  241. {
  242. UserId = x.UserId,
  243. CPN = x.CPN,
  244. CDPN = x.CDPN,
  245. CreatedTime = x.CreatedTime,
  246. TelNo = x.AgentTransferNumber,
  247. UserName = x.UserName,
  248. })
  249. .OrderByDescending(x => x.CreatedTime)
  250. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  251. return new PagedDto<BiSeatSwitchDto>(total, _mapper.Map<IReadOnlyList<BiSeatSwitchDto>>(items));
  252. }
  253. /// <summary>
  254. /// 小时统计
  255. /// </summary>
  256. /// <returns></returns>
  257. [HttpGet("hourcall")]
  258. [AllowAnonymous]
  259. public async Task<List<TrCallHourDto>> QueryHourCall([FromQuery] DateTime beginDate, DateTime? endDate, string source)
  260. {
  261. //获取配置
  262. int noConnectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NoConnectByeTimes)?.SettingValue[0]);
  263. int effectiveTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.EffectiveTimes)?.SettingValue[0]);
  264. int connectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConnectByeTimes)?.SettingValue[0]);
  265. var list = await _trCallRecordRepositoryEx.GetCallHourList(beginDate, endDate, noConnectByeTimes, effectiveTimes, connectByeTimes, source);
  266. return list;
  267. }
  268. /// <summary>
  269. /// 通话时段统计明细
  270. /// </summary>
  271. /// <returns></returns>
  272. [HttpGet("hourcall_list")]
  273. public async Task<object> QueryCallList([FromQuery] DateTime beginDate, DateTime? endDate, string type, string source, TimeSpan? startHourTo, int pageIndex, int pageSize)
  274. {
  275. //获取配置
  276. int noConnectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NoConnectByeTimes)?.SettingValue[0]);
  277. int effectiveTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.EffectiveTimes)?.SettingValue[0]);
  278. int connectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConnectByeTimes)?.SettingValue[0]);
  279. var list = await _trCallRecordRepositoryEx.GetCallList(beginDate, endDate, noConnectByeTimes, effectiveTimes, connectByeTimes, type, source, startHourTo, pageIndex, pageSize);
  280. return list;
  281. }
  282. /// <summary>
  283. /// 通话时段统计明细获取基本信息
  284. /// </summary>
  285. /// <param name="id"></param>
  286. /// <returns></returns>
  287. [HttpGet("hourcall_list_base")]
  288. public async Task<object> ReTransactBaseData()
  289. {
  290. var rsp = new
  291. {
  292. CallForwardingSource = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.CallForwardingSource),
  293. CallForwardingType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.CallForwardingType),
  294. };
  295. return rsp;
  296. }
  297. /// <summary>
  298. /// 热线号码统计
  299. /// </summary>
  300. /// <param name="StartDate"></param>
  301. /// <param name="EndDate"></param>
  302. /// <returns></returns>
  303. [AllowAnonymous]
  304. [HttpGet("gateway-query")]
  305. public async Task<List<CallHotLineDto>> QueryGateWay(DateTime beginDate, DateTime endDate, string gateway)
  306. {
  307. //获取配置
  308. int noConnectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NoConnectByeTimes)?.SettingValue[0]);
  309. int effectiveTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.EffectiveTimes)?.SettingValue[0]);
  310. int connectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConnectByeTimes)?.SettingValue[0]);
  311. int ringTims = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.RingTims)?.SettingValue[0]);
  312. var list = await _trCallRecordRepositoryEx.GetCallHotLineList(beginDate, endDate, gateway, noConnectByeTimes, effectiveTimes, connectByeTimes, ringTims);
  313. return list;
  314. }
  315. }