qinchaoyue 8 місяців тому
батько
коміт
9ff7372dbf

+ 85 - 0
src/Hotline.Api/Controllers/Bi/BiCallController.cs

@@ -16,6 +16,7 @@ using Hotline.Users;
 using MapsterMapper;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
+using NPOI.SS.Formula.Functions;
 using SqlSugar;
 using System.Data;
 using XF.Domain.Exceptions;
@@ -403,6 +404,50 @@ public class BiCallController : BaseController
 
     }
 
+    /// <summary>
+    /// 坐席话务统计分析
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("seats/export")]
+    public async Task<FileStreamResult> ExportSeatss([FromBody] ExportExcelDto<ReportPagedRequest> dto)
+    {
+        if (!dto.QueryDto.StartTime.HasValue || !dto.QueryDto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
+        dto.QueryDto.EndTime = dto.QueryDto.EndTime.Value.AddDays(1).AddSeconds(-1);
+
+        var list =  await _callReportApplication.QuerySeatCallAsync(dto.QueryDto);
+        if (list != null && list.Count > 0)
+        {
+            list.Add(new BiSeatCallsDto()
+            {
+                Name = "合计",
+                InTotal = list.Sum(p => p.InTotal),
+                InAnswered = list.Sum(p => p.InAnswered),
+	        InHangupImmediate = list.Sum(p => p.InHangupImmediate),
+                InHanguped = list.Sum(m => m.InHanguped),
+                OutDurationAvg = list.Sum(m => m.OutDurationAvg),
+                InAvailableAnswer = list.Sum(m => m.InAvailableAnswer),
+                InHangupImmediateWhenAnswered = list.Sum(m => m.InHangupImmediateWhenAnswered),
+                OutTotal = list.Sum(m => m.OutTotal),
+                OutAnswered = list.Sum(m => m.OutAnswered),
+                LoginDuration = list.Sum(m => m.LoginDuration),
+                RestDuration = list.Sum(m => m.RestDuration),
+                InDurationAvg = list.Sum(m => m.InDurationAvg)
+            });
+        }
+
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+        var dtos = list
+            .Select(stu => _mapper.Map(stu, typeof(BiSeatCallsDto), dynamicClass))
+            .Cast<object>()
+            .ToList();
+
+        var stream = ExcelHelper.CreateStream(dtos);
+
+        return ExcelStreamResult(stream, "坐席话务统计分析");
+    }
+
     /// <summary>
     /// 坐席话务统计分析
     /// </summary>
@@ -542,6 +587,46 @@ public class BiCallController : BaseController
         return list;
     }
 
+    /// <summary>
+    /// 小时统计--导出
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("hourcall/export")]
+    [AllowAnonymous]
+    public async Task<FileStreamResult> ExportQueryHourCall([FromBody] ExportExcelDto<BiQueryHourCallDto> dto)
+    {
+        //获取配置
+        int noConnectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NoConnectByeTimes)?.SettingValue[0]);
+        int effectiveTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.EffectiveTimes)?.SettingValue[0]);
+        int connectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConnectByeTimes)?.SettingValue[0]);
+        var list = await _trCallRecordRepositoryEx.GetCallHourList(dto.QueryDto.StartTime, dto.QueryDto.EndTime, noConnectByeTimes, effectiveTimes, connectByeTimes, dto.QueryDto.Source);
+
+        if (list != null && list.Count > 0)
+        {
+            list.Add(new TrCallHourDto()
+            {
+                HourTo = "合计",
+                EffectiveCount = list.Sum(p => p.EffectiveCount),
+                ConnectByeCount = list.Sum(p => p.ConnectByeCount),
+                NoConnectByeCount = list.Sum(p => p.NoConnectByeCount),
+                QueueByeCount = list.Sum(m => m.QueueByeCount),
+                IvrByeCount = list.Sum(m => m.IvrByeCount)
+            });
+        }
+
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+        var dtos = list
+            .Select(stu => _mapper.Map(stu, typeof(TrCallHourDto), dynamicClass))
+            .Cast<object>()
+            .ToList();
+
+        var stream = ExcelHelper.CreateStream(dtos);
+
+        return ExcelStreamResult(stream, "通话时段分析");
+    }
+
+
     /// <summary>
     /// 通话时段统计明细
     /// </summary>

+ 60 - 88
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -20,8 +20,12 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
 using Hotline.Tools;
+using JiebaNet.Segmenter.Common;
 using MapsterMapper;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.IdentityModel.Tokens;
+using MongoDB.Bson;
+using Nacos.V2.Utils;
 using SqlSugar;
 using System.Data;
 using XF.Domain.Authentications;
@@ -446,6 +450,25 @@ namespace Hotline.Api.Controllers.Bi
             return new { List = res, Total = total };
         }
 
+        /// <summary>
+        /// 部门不满意统计--导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("visit-nosatisfied/export")]
+        public async Task<FileStreamResult> ExportQueryVisitNoSatisfied([FromBody] QueryVisitNoSatisfiedDto dto)
+        {
+            if (!dto.StartTime.HasValue || !dto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
+            if (dto.AddColumnName is null || dto.AddColumnName.Count == 0) throw UserFriendlyException.SameMessage("导出字段不能为空");
+            if (dto.AddColumnName.FirstOrDefault() != "部门名称") throw UserFriendlyException.SameMessage("导出字段第一个必须是'部门名称'");
+            dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
+
+            var (dissatisfiedReason, list) = await _orderReportApplication.QueryVisitNoSatisfiedAsync(dto, _sessionContext.OrgIsCenter);
+            var dataTable = await _orderReportApplication.ExportQueryVisitNoSatisfiedAsync(dissatisfiedReason, list, dto.AddColumnName);
+
+            return ExcelStreamResult(ExcelHelper.CreateStream(dataTable), "回访不满意原因统计");
+        }
+
         /// <summary>
         /// 部门不满意统计
         /// 已加验证部门
@@ -459,38 +482,8 @@ namespace Hotline.Api.Controllers.Bi
 
             dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
 
-            var IsCenter = _sessionContext.OrgIsCenter;
-
-
-            var dissatisfiedReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
-            List<dynamic>? list = new List<dynamic>();
-            //DataTable dt = new DataTable();
-            foreach (var item in dissatisfiedReason)
-            {
-                var table = _orderVisitDetailRepository.Queryable()
-                .Includes(x => x.OrderVisit)
-                .Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Org)
-                .Where(x => x.OrgNoSatisfiedReason != null)
-                .Where(x => x.OrderVisit.VisitState == EVisitState.Visited)
-                .Where(x => !string.IsNullOrEmpty(x.VisitOrgName))
-                .WhereIF(!string.IsNullOrEmpty(dto.OrgName), x => x.VisitOrgName.Contains(dto.OrgName))
-                .WhereIF(dto.StartTime.HasValue, x => x.OrderVisit.VisitTime >= dto.StartTime.Value)
-                .WhereIF(dto.EndTime.HasValue, x => x.OrderVisit.VisitTime <= dto.EndTime.Value)
-                .WhereIF(IsCenter == false, x => x.VisitOrgCode.StartsWith(_sessionContext.RequiredOrgId))
-                .GroupBy(x => new { x.VisitOrgName, x.VisitOrgCode })
-                .Select(x => new BiVisitNoSatisfiedDto
-                {
-                    Count = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(x.OrgNoSatisfiedReason, "Key", item.DicDataValue), 1, 0)),
-                    Key = item.DicDataValue,
-                    OrgName = x.VisitOrgName,
-                    OrgCode = x.VisitOrgCode
-                })
-                .OrderByDescending(x => x.Count)
-                //.ToPivotTable(x => x.Key, x => x.OrgName, x => x.Sum(x => x.Count));
-                .ToPivotList(x => x.Key, x => new { x.OrgCode, x.OrgName }, x => x.Sum(x => x.Count));
-
-                list.AddRange(table);
-            }
+            var (dissatisfiedReason, list) = await _orderReportApplication
+                .QueryVisitNoSatisfiedAsync(_mapper.Map<QueryVisitNoSatisfiedDto>(dto), _sessionContext.OrgIsCenter);
             return new { DicReason = dissatisfiedReason, Data = list };
         }
 
@@ -2250,73 +2243,52 @@ namespace Hotline.Api.Controllers.Bi
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpGet("send_order_report")]
-        public async Task<object> SendOrderReport([FromQuery] QuerySendOrderRequest dto)
+        public async Task<List<SendOrderReportOutDto>> SendOrderReport([FromQuery] QuerySendOrderRequest dto)
         {
             if (!dto.StartTime.HasValue || !dto.EndTime.HasValue)
                 throw UserFriendlyException.SameMessage("请选择时间!");
             dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
+            return await _orderApplication.SendOrderReportAsync(dto);
+        }
 
-            var items = await _workflowTraceRepository.Queryable()
-                .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
-                //.LeftJoin<WorkflowStepHandler>((x, w, wsh) => x.StepId == wsh.WorkflowStepId && wsh.IsActualHandler == true)
-                .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
-                .Where((x, w, su) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled)
-                .Where((x, w, su) => x.CreationTime >= dto.StartTime.Value)
-                .Where((x, w, su) => x.CreationTime <= dto.EndTime.Value)
-                .WhereIF(!string.IsNullOrEmpty(dto.UserName), (x, w, su) => su.UserName == dto.UserName)
-                .GroupBy((x, w, su) => new { su.UserId, su.UserName })
-                //.Having((x, w, wsh, su) => SqlFunc.AggregateCount(x.WorkflowId) == 1)
-                .Select((x, w, su) => new BiOrderSendVo
-                {
-                    UserId = su.UserId,
-                    UserName = su.UserName,
-                    SendOrderNum = SqlFunc.AggregateDistinctCount(w.ExternalId),
-                    NoSendOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.HandlerId == null || x.HandlerId == "", 1, 0)),
-                }).ToListAsync();
+        /// <summary>
+        /// 派单量统计-导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("send_order_report/export")]
+        public async Task<FileStreamResult> ExportSendOrderReport([FromBody] ExportExcelDto<QuerySendOrderRequest> dto)
+        {
+            if (!dto.QueryDto.StartTime.HasValue || !dto.QueryDto.EndTime.HasValue)
+                throw UserFriendlyException.SameMessage("请选择时间!");
+            dto.QueryDto.EndTime = dto.QueryDto.EndTime.Value.AddDays(1).AddSeconds(-1);
+            var list = await _orderApplication.SendOrderReportAsync(dto.QueryDto);
 
-            var items2 = await _workflowTraceRepository.Queryable()
-                .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
-                //.LeftJoin<WorkflowStepHandler>((x, w, wfsh) => x.StepId == wfsh.WorkflowStepId && wfsh.IsActualHandler == true)
-                .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
-                .Where((x, w, su) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled)
-                .Where((x, w, su) => x.CreationTime >= dto.StartTime.Value)
-                .Where((x, w, su) => x.CreationTime <= dto.EndTime.Value)
-                .GroupBy((x, w, su) => x.WorkflowId)
-                .Having((x, w, su) => SqlFunc.AggregateCount(x.WorkflowId) > 1)
-                .Select((x, w, su) => new { Id = x.WorkflowId, CreationTime = SqlFunc.AggregateMin(x.CreationTime) })
-                .MergeTable()
-                .LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
-                .LeftJoin<Workflow>((a, wt, wf) => wt.WorkflowId == wf.Id)
-                //.LeftJoin<WorkflowStepHandler>((a, wt, wf, wsh) => wt.StepId == wsh.WorkflowStepId && wsh.CreationTime == a.CreationTime)
-                .InnerJoin<SchedulingUser>((a, wt, wf, su) => wt.HandlerId == su.UserId)
-                .WhereIF(!string.IsNullOrEmpty(dto.UserName), ((a, wt, wf, su) => su.UserName == dto.UserName))
-                .GroupBy((a, wt, wf, su) => new { su.UserId, su.UserName })
-                .Select((a, wt, wf, su) => new BiOrderSendVo
+            if (list != null && list.Count > 0)
+            {
+                list.Add(new SendOrderReportOutDto()
                 {
-                    UserId = su.UserId,
-                    UserName = su.UserName,
-                    SendOrderNum = 0,
-                    NoSendOrderNum = 0,
-                    ReSendOrderNum = SqlFunc.AggregateDistinctCount(wf.ExternalId),
-                }).ToListAsync();
+                    UserName = "合计",
+                    SendOrderNum = list.Sum(p => p.SendOrderNum),
+                    NoSendOrderNum = list.Sum(p => p.NoSendOrderNum),
+                    ReSendOrderNum = list.Sum(p => p.ReSendOrderNum),
+                });
+            }
 
-            var res = (from t1 in items
-                       join t2 in items2 on t1.UserId equals t2.UserId into t1_t2
-                       from item in t1_t2.DefaultIfEmpty()
-                       select new
-                       {
-                           UserId = t1.UserId,
-                           UserName = t1.UserName,
-                           SendOrderNum = t1.SendOrderNum,
-                           NoSendOrderNum = t1.NoSendOrderNum,
-                           ReSendOrderNum = t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault(),
-                           ChainRate = t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault() > 0 ?
-                                     ((double.Parse(t1.SendOrderNum.ToString()) - double.Parse(t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault().ToString())) / double.Parse(t1.SendOrderNum.ToString()) * 100).ToString("F2") + "%" : "100.00%",
-                       }).ToList();
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+            var dtos = list
+                .Select(stu => _mapper.Map(stu, typeof(SendOrderReportOutDto), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "派单量统计");
 
-            return res;
         }
 
+
         /// <summary>
         /// 派单量统计明细
         /// </summary>

+ 9 - 0
src/Hotline.Application/Orders/IOrderApplication.cs

@@ -16,6 +16,7 @@ using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Requests;
+using MediatR;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Entities;
@@ -24,6 +25,14 @@ namespace Hotline.Application.Orders
 {
     public interface IOrderApplication
     {
+
+        /// <summary>
+        /// 派单量统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<List<SendOrderReportOutDto>> SendOrderReportAsync(QuerySendOrderRequest dto);
+
         /// <summary>
         /// 更新工单办理期满时间
         /// 1.更新工单 2.更新流程

+ 74 - 0
src/Hotline.Application/Orders/OrderApplication.cs

@@ -47,6 +47,7 @@ using Hotline.Share.Mq;
 using JiebaNet.Segmenter;
 using Microsoft.AspNetCore.Http;
 using WordInfo = PanGu.WordInfo;
+using Hotline.Schedulings;
 
 namespace Hotline.Application.Orders;
 
@@ -72,6 +73,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IRepository<SystemArea> _systemAreaRepository;
     private readonly IRepository<Hotspot> _hotspotRepository;
     private readonly IRepository<WorkflowStep> _workflowStepRepository;
+    private readonly IRepository<WorkflowTrace> _workflowTraceRepository;
     private readonly IRepository<SystemDicData> _systemDicDataRepository;
 
 
@@ -98,6 +100,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<WorkflowStep> workflowStepRepository,
         IRepository<SystemDicData> systemDicDataRepository
 		)
+        IRepository<WorkflowStep> workflowStepRepository
+,
+        IRepository<WorkflowTrace> workflowTraceRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -122,6 +127,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _systemDicDataRepository = systemDicDataRepository;
 
 	}
+        _workflowTraceRepository = workflowTraceRepository;
+    }
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -1691,5 +1698,72 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return _mapper.Map<AddOrderResponse>(order);
     }
 
+    /// <summary>
+    /// 派单量统计
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<List<SendOrderReportOutDto>> SendOrderReportAsync(QuerySendOrderRequest dto)
+    {
+            var items = await _workflowTraceRepository.Queryable()
+                .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+                //.LeftJoin<WorkflowStepHandler>((x, w, wsh) => x.StepId == wsh.WorkflowStepId && wsh.IsActualHandler == true)
+                .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
+                .Where((x, w, su) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled)
+                .Where((x, w, su) => x.CreationTime >= dto.StartTime.Value)
+                .Where((x, w, su) => x.CreationTime <= dto.EndTime.Value)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserName), (x, w, su) => su.UserName == dto.UserName)
+                .GroupBy((x, w, su) => new { su.UserId, su.UserName })
+                //.Having((x, w, wsh, su) => SqlFunc.AggregateCount(x.WorkflowId) == 1)
+                .Select((x, w, su) => new BiOrderSendVo
+                {
+                    UserId = su.UserId,
+                    UserName = su.UserName,
+                    SendOrderNum = SqlFunc.AggregateDistinctCount(w.ExternalId),
+                    NoSendOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.HandlerId == null || x.HandlerId == "", 1, 0)),
+                }).ToListAsync();
+
+            var items2 = await _workflowTraceRepository.Queryable()
+                .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+                //.LeftJoin<WorkflowStepHandler>((x, w, wfsh) => x.StepId == wfsh.WorkflowStepId && wfsh.IsActualHandler == true)
+                .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
+                .Where((x, w, su) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled)
+                .Where((x, w, su) => x.CreationTime >= dto.StartTime.Value)
+                .Where((x, w, su) => x.CreationTime <= dto.EndTime.Value)
+                .GroupBy((x, w, su) => x.WorkflowId)
+                .Having((x, w, su) => SqlFunc.AggregateCount(x.WorkflowId) > 1)
+                .Select((x, w, su) => new { Id = x.WorkflowId, CreationTime = SqlFunc.AggregateMin(x.CreationTime) })
+                .MergeTable()
+                .LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
+                .LeftJoin<Workflow>((a, wt, wf) => wt.WorkflowId == wf.Id)
+                //.LeftJoin<WorkflowStepHandler>((a, wt, wf, wsh) => wt.StepId == wsh.WorkflowStepId && wsh.CreationTime == a.CreationTime)
+                .InnerJoin<SchedulingUser>((a, wt, wf, su) => wt.HandlerId == su.UserId)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserName), ((a, wt, wf, su) => su.UserName == dto.UserName))
+                .GroupBy((a, wt, wf, su) => new { su.UserId, su.UserName })
+                .Select((a, wt, wf, su) => new BiOrderSendVo
+                {
+                    UserId = su.UserId,
+                    UserName = su.UserName,
+                    SendOrderNum = 0,
+                    NoSendOrderNum = 0,
+                    ReSendOrderNum = SqlFunc.AggregateDistinctCount(wf.ExternalId),
+                }).ToListAsync();
+
+            var res = (from t1 in items
+                       join t2 in items2 on t1.UserId equals t2.UserId into t1_t2
+                       from item in t1_t2.DefaultIfEmpty()
+                       select new SendOrderReportOutDto
+                       {
+                           UserId = t1.UserId,
+                           UserName = t1.UserName,
+                           SendOrderNum = t1.SendOrderNum,
+                           NoSendOrderNum = t1.NoSendOrderNum,
+                           ReSendOrderNum = t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault(),
+                           ChainRate = t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault() > 0 ?
+                                     ((double.Parse(t1.SendOrderNum.ToString()) - double.Parse(t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault().ToString())) / double.Parse(t1.SendOrderNum.ToString()) * 100).ToString("F2") + "%" : "100.00%",
+                       }).ToList();
+        return res;
+    }
+
     #endregion
 }

+ 64 - 2
src/Hotline.Application/StatisticalReport/CallReportApplication.cs

@@ -1,8 +1,13 @@
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Tels;
 using Hotline.Settings;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Enums.CallCenter;
+using Hotline.Share.Enums.User;
+using Hotline.Share.Requests;
+using Hotline.Users;
+using Microsoft.AspNetCore.Http;
 using SqlSugar;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -14,14 +19,22 @@ namespace Hotline.Application.StatisticalReport
     {
         private readonly IRepository<TrCallRecord> _trCallRecordRepository;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IRepository<User> _userRepository;
+        private readonly IRepository<Work> _workRepository;
+        private readonly IRepository<TelRest> _telRestRepository;
 
         public CallReportApplication(
             IRepository<TrCallRecord> trCallRecordRepository,
-            ISystemSettingCacheManager systemSettingCacheManager)
+            ISystemSettingCacheManager systemSettingCacheManager,
+            IRepository<User> userRepository,
+            IRepository<Work> workRepository,
+            IRepository<TelRest> telRestRepository)
         {
             _trCallRecordRepository = trCallRecordRepository;
             _systemSettingCacheManager = systemSettingCacheManager;
-
+            _userRepository = userRepository;
+            _workRepository = workRepository;
+            _telRestRepository = telRestRepository;
         }
 
         /// <summary>
@@ -91,5 +104,54 @@ namespace Hotline.Application.StatisticalReport
                     .MergeTable();
         }
 
+        /// <summary>
+        /// 坐席话务统计分析
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        public async Task<IList<BiSeatCallsDto>> QuerySeatCallAsync(ReportPagedRequest dto)
+        {
+            int noConnectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NoConnectByeTimes)?.SettingValue[0]);
+            int effectiveTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.EffectiveTimes)?.SettingValue[0]);
+            int connectByeTimes = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConnectByeTimes)?.SettingValue[0]);
+
+            var list = await _userRepository.Queryable()
+                  .LeftJoin<TrCallRecord>((u, c) => u.Id == c.UserId)
+                  .Where(u => !u.IsDeleted && u.UserType == EUserType.Seat)
+                  .WhereIF(dto.StartTime.HasValue, (u, c) => c.CreatedTime >= dto.StartTime.Value)
+                  .WhereIF(dto.EndTime.HasValue, (u, c) => c.CreatedTime <= dto.EndTime.Value)
+                  .GroupBy((u, c) => new { c.UserName, c.UserId })
+                  .Select((u, c) => new BiSeatCallsDto
+                  {
+                      Name = c.UserName,
+                      UserId = c.UserId,
+                      InTotal = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In, 1, 0)),
+                      OutTotal = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.Out, 1, 0)),
+                      InAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null, 1, 0)),
+                      OutAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.Out && c.AnsweredTime != null, 1, 0)),
+                      InHangupImmediate = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime == null && c.RingTimes < noConnectByeTimes, 1, 0)),
+                      InHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime == null, 1, 0)),
+                      InDurationAvg = SqlFunc.Ceil(SqlFunc.AggregateAvg(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null, c.Duration, 0))),
+                      OutDurationAvg = SqlFunc.Ceil(SqlFunc.AggregateAvg(SqlFunc.IIF(c.CallDirection == ECallDirection.Out && c.AnsweredTime != null, c.Duration, 0))),
+                      InAvailableAnswer = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null && c.Duration >= effectiveTimes, 1, 0)),
+                      InHangupImmediateWhenAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(c.CallDirection == ECallDirection.In && c.AnsweredTime != null && c.Duration < connectByeTimes, 1, 0)),
+                  })
+                  .MergeTable()
+                  .ToListAsync();
+
+
+            list.ForEach(d =>
+            {
+                d.LoginDuration = _workRepository.Queryable().Where(q => q.UserId == d.UserId && q.CreationTime >= dto.StartTime && q.CreationTime <= dto.EndTime).Sum(q => q.WorkingDuration);
+                if (d.LoginDuration != null)
+                {
+                    d.LoginDuration = Math.Round(d.LoginDuration.Value, digits: 2);
+                }
+                d.RestDuration = _telRestRepository.Queryable().Where(q => q.UserId == d.UserId && q.CreationTime >= dto.StartTime && q.CreationTime <= dto.EndTime).Sum(q => q.RestDuration);
+                d.RestDuration = Math.Round(d.RestDuration, digits: 2);
+            });
+
+            return list;
+        }
     }
 }

+ 7 - 0
src/Hotline.Application/StatisticalReport/ICallReportApplication.cs

@@ -1,5 +1,6 @@
 using Hotline.CallCenter.Calls;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Requests;
 using SqlSugar;
 
 namespace Hotline.Application.StatisticalReport
@@ -20,5 +21,11 @@ namespace Hotline.Application.StatisticalReport
         /// <returns></returns>
         ISugarQueryable<TrCallRecord> QueryCallsDetailInTotalAsync(BiQueryCallsDto dto);
 
+        /// <summary>
+        /// 坐席话务统计分析
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<IList<BiSeatCallsDto>> QuerySeatCallAsync(ReportPagedRequest dto);
     }
 }

+ 18 - 1
src/Hotline.Application/StatisticalReport/IOrderReportApplication.cs

@@ -1,5 +1,6 @@
 using Hotline.Orders;
 using Hotline.Settings;
+using Hotline.Share.Dtos.Bi;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Requests;
 using Microsoft.AspNetCore.Mvc;
@@ -101,6 +102,22 @@ namespace Hotline.Application.StatisticalReport
         /// <returns></returns>
         ISugarQueryable<AcceptTypeStatisticsDto> AcceptTypeStatistics(AcceptTypeStatisticsReq dto);
 
-        
+
+        /// <summary>
+        /// 部门不满意统计
+        /// 已加验证部门
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="isCenter"></param>
+        /// <returns></returns>
+        Task<(IReadOnlyList<SystemDicData> dissatisfiedReason, List<dynamic>? list)> QueryVisitNoSatisfiedAsync(QueryVisitNoSatisfiedDto dto, bool isCenter);
+
+        /// <summary>
+        /// 部门不满意统计-导出
+        /// </summary>
+        /// <param name="dissatisfiedReason"></param>
+        /// <param name="list"></param>
+        /// <returns></returns>
+        Task<DataTable> ExportQueryVisitNoSatisfiedAsync(IReadOnlyList<SystemDicData> dissatisfiedReason, List<dynamic>? list, List<string> addColumnName);
     }
 }

+ 128 - 1
src/Hotline.Application/StatisticalReport/OrderReportApplication.cs

@@ -1,15 +1,23 @@
 using Hotline.Application.Orders;
+using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
 using Hotline.Settings;
 using Hotline.Settings.TimeLimits;
+using Hotline.Share.Dtos.Bi;
+using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
+using JiebaNet.Segmenter.Common;
 using MapsterMapper;
+using MediatR;
+using Microsoft.IdentityModel.Tokens;
 using SqlSugar;
+using System.Data;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -20,6 +28,7 @@ namespace Hotline.Application.StatisticalReport
     public class OrderReportApplication : IOrderReportApplication, IScopeDependency
     {
         private readonly IOrderRepository _orderRepository;
+        private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
         private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
         private readonly IRepository<OrderDelay> _orderDelayRepository;
         private readonly IMapper _mapper;
@@ -56,7 +65,8 @@ namespace Hotline.Application.StatisticalReport
             IOrderSecondaryHandlingApplication orderSecondaryHandlingApplication,
             ITimeLimitDomainService timeLimitDomainService,
             IRepository<SystemDicData> systemDicDataRepository
-
+,
+            ISystemDicDataCacheManager sysDicDataCacheManager
             )
         {
             _orderRepository = orderRepository;
@@ -70,6 +80,7 @@ namespace Hotline.Application.StatisticalReport
             _orderSecondaryHandlingApplication = orderSecondaryHandlingApplication;
             _timeLimitDomainService = timeLimitDomainService;
             _systemDicDataRepository = systemDicDataRepository;
+            _sysDicDataCacheManager = sysDicDataCacheManager;
         }
         /// <summary>
         /// 部门办件统计表---新
@@ -1620,5 +1631,121 @@ namespace Hotline.Application.StatisticalReport
             return query;
         }
 
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="isCenter"></param>
+        /// <returns></returns>
+        public async Task<(IReadOnlyList<SystemDicData> dissatisfiedReason, List<dynamic>? list)> QueryVisitNoSatisfiedAsync(QueryVisitNoSatisfiedDto dto, bool isCenter)
+        {
+            //var dissatisfiedReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
+            // 保留只需要导出的列 
+            var dissatisfiedReason = _sysDicDataCacheManager
+                .GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
+
+            if (dto.AddColumnName.Any())
+            {
+                dissatisfiedReason = dissatisfiedReason
+                .Where(m => dto.AddColumnName.Contains(m.DicDataName))
+                .ToList();
+            }
+
+            List<dynamic>? list = new List<dynamic>();
+            //DataTable dt = new DataTable();
+            foreach (var item in dissatisfiedReason)
+            {
+                var table = _orderVisitDetailRepository.Queryable()
+                .Includes(x => x.OrderVisit)
+                .Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Org)
+                .Where(x => x.OrgNoSatisfiedReason != null)
+                .Where(x => x.OrderVisit.VisitState == EVisitState.Visited)
+                .Where(x => !string.IsNullOrEmpty(x.VisitOrgName))
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgName), x => x.VisitOrgName.Contains(dto.OrgName))
+                .WhereIF(dto.StartTime.HasValue, x => x.OrderVisit.VisitTime >= dto.StartTime.Value)
+                .WhereIF(dto.EndTime.HasValue, x => x.OrderVisit.VisitTime <= dto.EndTime.Value)
+                .WhereIF(isCenter == false, x => x.VisitOrgCode.StartsWith(_sessionContext.RequiredOrgId))
+                .GroupBy(x => new { x.VisitOrgName, x.VisitOrgCode })
+                .Select(x => new BiVisitNoSatisfiedDto
+                {
+                    Count = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(x.OrgNoSatisfiedReason, "Key", item.DicDataValue), 1, 0)),
+                    Key = item.DicDataValue,
+                    OrgName = x.VisitOrgName,
+                    OrgCode = x.VisitOrgCode
+                })
+                .OrderByDescending(x => x.Count)
+                //.ToPivotTable(x => x.Key, x => x.OrgName, x => x.Sum(x => x.Count));
+                .ToPivotList(x => x.Key, x => new { x.OrgCode, x.OrgName }, x => x.Sum(x => x.Count));
+
+                list.AddRange(table);
+            }
+            return (dissatisfiedReason, list);
+        }
+
+        public async Task<DataTable> ExportQueryVisitNoSatisfiedAsync(IReadOnlyList<SystemDicData> dissatisfiedReason, List<dynamic>? list, List<string> addColumnName)
+        {
+            var dataTable = new DataTable();
+            foreach (var item in addColumnName)
+            {
+                dataTable.Columns.Add(item);
+            }
+
+            Dictionary<string, DataRow> dicRow = new();
+
+            // 先拿部门名称
+            // bug 部门名称重复时有问题
+            // 循环填充 首列 数据
+            foreach (var item in list)
+            {
+                foreach (var property in (IDictionary<string, object>)item)
+                {
+                    if (property.Key == "OrgName")
+                    {
+                        var name = property.Value.ToString();
+                        if (name.IsNullOrEmpty()) continue;
+                        if (dicRow.Any(m => m.Key == name)) continue;
+                        var dr = dataTable.NewRow();
+                        dr[0] = name;
+                        dicRow.Add(name, dr);
+                    }
+                }
+            }
+
+            var drCount = dataTable.NewRow();
+            drCount[0] = "合计";
+            dicRow.Add("合计", drCount);
+
+            for (int i = 0;i < dissatisfiedReason.Count;i++)
+            { // 循环填充列数据
+
+                var total = 0;
+                for (int l = 0;l < list.Count;l++)
+                {
+                    var columnIndex = i + 1;
+                    var value = string.Empty;
+                    var orgName = string.Empty;
+                    foreach (var property in (IDictionary<string, object>)list[l])
+                    {
+                        if (property.Key.ToLower().Equals("orgname")) orgName = property.Value.ToString();
+                        if (property.Key.ToLower().Equals(columnIndex.ToString()))
+                        {
+                            value = property.Value.ToString();
+                            total += int.Parse(value!);
+                        }
+                    }
+                    if (!value.IsNullOrEmpty() && !orgName.IsNullOrEmpty())
+                    {
+                        dicRow[orgName!][columnIndex] = value;
+                    }
+                    if (l + 1 == list.Count)
+                        dicRow["合计"][columnIndex] = total;
+                }
+            }
+            foreach (var item in dicRow)
+            {
+                dataTable.Rows.Add(item.Value);
+            }
+            return dataTable;
+        }
     }
 }

+ 14 - 0
src/Hotline.Share/Dtos/Bi/BiOrderDto.cs

@@ -2,6 +2,20 @@
 
 namespace Hotline.Share.Dtos.Bi
 {
+    public record QueryVisitNoSatisfiedDto
+    {
+        public string OrgName { get; set; }
+
+        public DateTime? StartTime { get; set; }
+
+        public DateTime? EndTime { get; set; }
+
+        /// <summary>
+        /// 导出列名
+        /// </summary>
+        public List<string> AddColumnName { get; set; } = new();
+    }
+
     public record VisitAndOrgSatisfactionDetailDto : PagedRequest
     {
         public DateTime StartTime { get; set; }

+ 7 - 0
src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs

@@ -13,4 +13,11 @@ public record BiQueryCallsDto: ReportPagedRequest
     /// 查询类型 1:呼入总量明细;2:接通总量明细
     /// </summary>
     public string? TypeCode { get; set; } = "1";
+}
+
+public class BiQueryHourCallDto
+{
+    public DateTime StartTime { get; set; }
+    public DateTime? EndTime { get; set; }
+    public string Source {get; set; }
 }

+ 35 - 3
src/Hotline.Share/Dtos/CallCenter/BiSeatCallsDto.cs

@@ -1,4 +1,6 @@
-namespace Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Tools;
+
+namespace Hotline.Share.Dtos.CallCenter;
 
 /// <summary>
 /// 坐席话务量统计
@@ -76,34 +78,64 @@ public class BiSeatCallsDto
     public int InHangupImmediateWhenAnswered { get; set; }
 
     /// <summary>
-    /// 登录时长(分钟)
+    /// 登录时长(秒)
     /// </summary>
     public double? LoginDuration { get; set; }
 
     /// <summary>
-    /// 小修+摘机时长
+    /// 登录时长(显示)
+    /// </summary>
+    public string LoginDurationString => this.LoginDuration.SecondsToString();
+
+    /// <summary>
+    /// 小休+摘机时长 (秒)
     /// </summary>
     public double RestDuration { get; set; }
 
+    /// <summary>
+    /// 小休+摘机时长(显示)
+    /// </summary>
+    public string RestDurationString => this.RestDuration.SecondsToString();
+
     /// <summary>
     /// 呼入接通率
     /// </summary>
     public double InAnsweredRate => InTotal > 0 ? Math.Round(((double)InAnswered / (double)InTotal) * 100, digits: 4) : 0;
 
+    /// <summary>
+    /// 呼入接通率(显示)
+    /// </summary>
+    public string InAnsweredRateString => this.InAnsweredRate + "%";
+
     /// <summary>
     /// 呼出接通率
     /// </summary>
     public double OutAnsweredRate => OutTotal > 0 ? Math.Round(((double)OutAnswered / (double)OutTotal) * 100, digits: 4) : 0;
 
+    /// <summary>
+    /// 呼出接通率(显示)
+    /// </summary>
+    public string OutAnsweredRateString => this.OutAnsweredRate + "%";
+
     /// <summary>
     /// 呼入有效接通率
     /// </summary>
     public double AvailableAnswerRate => InTotal > 0 ? Math.Round(((double)InAvailableAnswer / (double)InTotal) * 100, digits: 4) : 0;
 
+    /// <summary>
+    /// 呼入有效接通率(显示)
+    /// </summary>
+    public string AvailableAnswerRateString => this.AvailableAnswerRate + "%";
+
     /// <summary>
     /// 工作效率
     /// </summary>
     public double WorkRate => LoginDuration > 0 ? Math.Round((1 - (double)RestDuration / (double)LoginDuration) * 100, digits: 4) : 0;
+
+    /// <summary>
+    /// 工作效率(显示)
+    /// </summary>
+    public string WorkRateString => this.WorkRate + "%";
 }
 
 

+ 6 - 5
src/Hotline.Share/Dtos/CallCenter/TrCallDto.cs

@@ -18,12 +18,13 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public string EndHourTo { get; set; }
 
-		public DateTime DateTimeTo { get; set; }
+        public DateTime DateTimeTo { get; set; }
 
         /// <summary>
         /// 总计
         /// </summary>
         public int Count => EffectiveCount + ConnectByeCount + NoConnectByeCount + QueueByeCount + IvrByeCount;
+
         /// <summary>
         /// 有效接通
         /// </summary>
@@ -60,7 +61,7 @@ namespace Hotline.Share.Dtos.CallCenter
         /// 呼入
         /// </summary>
         public int CallInCount { get; set; }
-        
+
         /// <summary>
         /// 接通
         /// </summary>
@@ -78,7 +79,7 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public double CalcCallInConnectRate()
         {
-            if (CallInCount!=0 && ConnectCount!=0)
+            if (CallInCount != 0 && ConnectCount != 0)
             {
                 return Math.Round((ConnectCount / (double)CallInCount) * 100, 2);
             }
@@ -98,7 +99,7 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public double CalcAveDuration()
         {
-            if ((EffectiveCount + ConnectByeCount)!=0 && DurationSum != 0)
+            if ((EffectiveCount + ConnectByeCount) != 0 && DurationSum != 0)
             {
                 return Math.Round((double)DurationSum / (EffectiveCount + ConnectByeCount), 1);
             }
@@ -129,7 +130,7 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public double CalcEffectiveRate()
         {
-            if (TimelyAnswerCount != 0 && ConnectCount!=0)
+            if (TimelyAnswerCount != 0 && ConnectCount != 0)
             {
                 return Math.Round((TimelyAnswerCount / (double)ConnectCount) * 100, 2);
             }

+ 30 - 0
src/Hotline.Share/Dtos/Order/SendOrderReportOutDto.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Order;
+public class SendOrderReportOutDto
+{
+    public string UserId {get; set; }
+
+    public string UserName { get; set; }
+
+    /// <summary>
+    /// 派单量
+    /// </summary>
+    public int SendOrderNum { get; set; }
+
+    /// <summary>
+    /// 待派单量
+    /// </summary>
+    public int NoSendOrderNum { get; set; }
+
+    /// <summary>
+    /// 重办量
+    /// </summary>
+    public int ReSendOrderNum { get; set; }
+
+    public string ChainRate {get;set; }
+}

+ 50 - 0
src/Hotline.Share/Tools/DoubleExtensions.cs

@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Tools;
+
+public static class DoubleExtensions
+{
+
+    /// <summary>
+    /// 秒数转换成 x天x小时x分钟x秒 的字符串
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    public static string SecondsToString(this double value)
+    { 
+        if (value <= 0) return string.Empty;
+
+        var timeSpan = TimeSpan.FromSeconds(value);
+        var days = timeSpan.Days;
+        var hours = timeSpan.Hours;
+        var minutes = timeSpan.Minutes;
+        var seconds = timeSpan.Seconds;
+
+        var sb = new StringBuilder();
+        if (days > 0)
+        {
+            sb.Append($"{days}天");
+        }
+        if (hours > 0 || days > 0)
+        {
+            sb.Append($"{hours}小时");
+        }
+        if (hours > 0 || days > 0 || minutes > 0)
+        {
+            sb.Append($"{minutes}分钟");
+        }
+        sb.Append($"{seconds}秒");
+        return sb.ToString();
+    }
+
+    public static string SecondsToString(this double? value)
+    {
+        if (value is null) return string.Empty;
+
+        return value.Value.SecondsToString();
+    }
+}