Dun.Jason 1 ano atrás
pai
commit
2668cffc96

+ 8 - 1
Hotline.sln

@@ -47,7 +47,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Api.Sdk", "src\Hotl
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApplication1", "test\WebApplication1\WebApplication1.csproj", "{D291230A-0CFD-4991-BC1A-B67C58F3857A}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hotline.Ai.Jths", "src\Hotline.Ai.Jths\Hotline.Ai.Jths.csproj", "{1634234A-379C-44DC-BFEC-7BBDEF50B47B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Ai.Jths", "src\Hotline.Ai.Jths\Hotline.Ai.Jths.csproj", "{1634234A-379C-44DC-BFEC-7BBDEF50B47B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hotline.YbEnterprise.Sdk", "src\Hotline.YbEnterprise.Sdk\Hotline.YbEnterprise.Sdk.csproj", "{C3F289D5-C50B-46DB-852C-9543EF9B0355}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -123,6 +125,10 @@ Global
 		{1634234A-379C-44DC-BFEC-7BBDEF50B47B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1634234A-379C-44DC-BFEC-7BBDEF50B47B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1634234A-379C-44DC-BFEC-7BBDEF50B47B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C3F289D5-C50B-46DB-852C-9543EF9B0355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C3F289D5-C50B-46DB-852C-9543EF9B0355}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C3F289D5-C50B-46DB-852C-9543EF9B0355}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C3F289D5-C50B-46DB-852C-9543EF9B0355}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -149,6 +155,7 @@ Global
 		{EEF30056-A626-43B2-9762-632935C1AF31} = {25C73963-4D5E-4654-804A-D2E2D360134B}
 		{D291230A-0CFD-4991-BC1A-B67C58F3857A} = {08D63205-1445-430F-A4AB-EF1744E3AC11}
 		{1634234A-379C-44DC-BFEC-7BBDEF50B47B} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{C3F289D5-C50B-46DB-852C-9543EF9B0355} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

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

@@ -1,8 +1,91 @@
-namespace Hotline.Api.Controllers.Bi;
+using Hotline.CallCenter.Calls;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Enums.CallCenter;
+using Hotline.Share.Requests;
+using Hotline.Users;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.Api.Controllers.Bi;
 
 /// <summary>
 /// 话务报表
 /// </summary>
 public class BiCallController : BaseController
 {
-}
+    private readonly IRepository<TrCallRecord> _trCallRecordRepository;
+    private readonly IRepository<User> _userRepository;
+
+    public BiCallController(
+        IRepository<TrCallRecord> trCallRecordRepository,
+        IRepository<User> userRepository)
+    {
+        _trCallRecordRepository = trCallRecordRepository;
+        _userRepository = userRepository;
+    }
+
+    [HttpGet("calls")]
+    [AllowAnonymous]
+    public async Task<IReadOnlyList<BiCallDto>> QueryCallsAsync([FromQuery] BiQueryCallsDto dto)
+    {
+        dto.StartTime ??= DateTime.Today;
+        dto.EndTime ??= DateTime.Today.AddDays(1).AddSeconds(-1);
+
+        var items = await _trCallRecordRepository.Queryable()
+            .Where(d => d.CreatedTime >= dto.StartTime && d.CreatedTime <= dto.EndTime)
+            .Where(d => d.CallDirection == ECallDirection.In)
+            .WhereIF(!string.IsNullOrEmpty(dto.Line), d => d.Gateway == dto.Line)
+            .Select(d => new
+            {
+                d.CreatedTime.Year,
+                d.CreatedTime.Month,
+                d.CreatedTime.Day,
+                d.CreatedTime.Hour,
+                d.AnsweredTime,
+                d.EndBy
+            })
+            .MergeTable()
+            .GroupBy(d => new
+            {
+                d.Year,
+                d.Month,
+                d.Day,
+                d.Hour
+            })
+            .Select(d => new BiCallDto
+            {
+                Hour = d.Hour,
+                Total = SqlFunc.AggregateCount(d.Hour),
+                Answered = SqlFunc.AggregateSum(SqlFunc.IIF(d.AnsweredTime != null, 1, 0)),
+                Hanguped = SqlFunc.AggregateSum(SqlFunc.IIF(d.AnsweredTime == null && d.EndBy != null && d.EndBy.Value == EEndBy.To, 1, 0))
+            })
+            .OrderBy(d => d.Hour)
+            .ToListAsync(HttpContext.RequestAborted);
+        
+        if (items.Count < 24)
+        {
+            for (var i = 0; i < 24; i++)
+            {
+                var item = items.FirstOrDefault(d => d.Hour == i);
+                if (item is null)
+                    items.Add(new BiCallDto { Hour = i });
+            }
+
+            items = items.OrderBy(d => d.Hour).ToList();
+        }
+
+        return items;
+    }
+
+    [HttpGet("seats")]
+    [AllowAnonymous]
+    public async Task<IReadOnlyList<BiSeatCallsDto>> QuerySeatCallsAsync([FromQuery] ReportPagedRequest dto)
+    {
+        //_userRepository.Queryable()
+        //    .LeftJoin<TrCallRecord>()
+
+        throw new NotImplementedException();
+    }
+}

+ 3 - 3
src/Hotline.Api/Controllers/Bi/BiKnowledgeController.cs

@@ -5,6 +5,7 @@ using Hotline.Share.Enums.KnowledgeBase;
 using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
 using Hotline.Settings;
+using Hotline.Repository.SqlSugar.Extensions;
 
 namespace Hotline.Api.Controllers.Bi
 {
@@ -24,7 +25,6 @@ namespace Hotline.Api.Controllers.Bi
 		[HttpGet("data_list")]
 		public async Task<PagedDto<KnowledgeBiDataListVo>> DataList([FromQuery] KnowledgeBiDataListDto dto)
 		{
-			var total = 0;
 			var query = _knowledgeRepository.Queryable(false,true,false)
 				.LeftJoin<SystemOrganize>((x,o)=>x.SourceOrganizeId == o.Id)
 				.WhereIF(dto.CreationTimeStart.HasValue, (x,o) => x.CreationTime >= dto.CreationTimeStart)
@@ -56,8 +56,8 @@ namespace Hotline.Api.Controllers.Bi
 					query = dto.SortRule == 0 ? query.OrderBy(x => x.PassNum) : query.OrderByDescending(x => x.PassNum);
 					break;
 			}
-            var  items = query.ToPageList(dto.PageIndex, dto.PageSize,ref total);
-			return new PagedDto<KnowledgeBiDataListVo>(total, items);
+            var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
+            return new PagedDto<KnowledgeBiDataListVo>(total, items);
 		}
 	}
 }

+ 99 - 51
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -1,4 +1,5 @@
 using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Settings;
 using Hotline.Share.Dtos.Knowledge;
 using Hotline.Share.Dtos;
@@ -7,59 +8,106 @@ using Hotline.Share.Enums.KnowledgeBase;
 using Hotline.Share.Enums.Order;
 using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
+using Hotline.Share.Requests;
+using Hotline.Users;
 
 namespace Hotline.Api.Controllers.Bi
 {
-	public class BiOrderController : BaseController
-	{
-		private readonly IOrderRepository _orderRepository;
-		public BiOrderController(IOrderRepository orderRepository) 
-		{
-			_orderRepository = orderRepository;
-		}
+    public class BiOrderController : BaseController
+    {
+        private readonly IOrderRepository _orderRepository;
+        public BiOrderController(IOrderRepository orderRepository)
+        {
+            _orderRepository = orderRepository;
+        }
 
-		/// <summary>
-		/// 部门数据统计
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		[HttpGet("org_data_list")]
-		public async Task<PagedDto<OrderBiOrgDataListVo>> DataList([FromQuery] OrderBiOrgDataListDto dto)
-		{
-			//TODO 会签统计待处理 目前缺少关联关系
-			var total = 0;
-			var query = _orderRepository.Queryable(false, false, false)
-				.LeftJoin<SystemOrganize>((x, o) => x.ActualHandleOrgCode == o.Id)
-				.WhereIF(dto.CreationTimeStart.HasValue, (x, o) => x.CreationTime >= dto.CreationTimeStart)
-				.WhereIF(dto.CreationTimeEnd.HasValue, (x, o) => x.CreationTime <= dto.CreationTimeEnd)
-				.WhereIF(!string.IsNullOrEmpty(dto.Keyword), (x, o) => o.Name.Contains(dto.Keyword!))
-				.GroupBy((x, o) => new { x.ActualHandleOrgCode, o.Name })
-				.Select((x, o) => new OrderBiOrgDataListVo
-				{
-					OrgName = o.Name,
-					OrgId = x.ActualHandleOrgCode,
-					HandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status > 300 && x.ExpiredTime > x.FiledTime, 1, 0)),
-					//CounterHandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.IsDeleted, 1, 0)),
-					NoHandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status < 300 && x.ExpiredTime > x.FiledTime, 1, 0)),
-					//CounterNoHandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status == EKnowledgeStatus.OnShelf, 1, 0))
-				}).MergeTable();
-			switch (dto.SortField)
-			{
-				case "handlerExtendedNum":
-					query = dto.SortRule == 0 ? query.OrderBy(x => x.HandlerExtendedNum) : query.OrderByDescending(x => x.HandlerExtendedNum);
-					break;
-				case "counterHandlerExtendedNum":
-					query = dto.SortRule == 0 ? query.OrderBy(x => x.CounterHandlerExtendedNum) : query.OrderByDescending(x => x.CounterHandlerExtendedNum);
-					break;
-				case "noHandlerExtendedNum":
-					query = dto.SortRule == 0 ? query.OrderBy(x => x.NoHandlerExtendedNum) : query.OrderByDescending(x => x.NoHandlerExtendedNum);
-					break;
-				case "counterNoHandlerExtendedNum":
-					query = dto.SortRule == 0 ? query.OrderBy(x => x.CounterNoHandlerExtendedNum) : query.OrderByDescending(x => x.CounterNoHandlerExtendedNum);
-					break;
-			}
-			var items = query.ToPageList(dto.PageIndex, dto.PageSize, ref total);
-			return new PagedDto<OrderBiOrgDataListVo>(total, items);
-		}
-	}
+        /// <summary>
+        /// 部门数据统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("org_data_list")]
+        public async Task<PagedDto<OrderBiOrgDataListVo>> OrgDataList([FromQuery] ReportPagedRequest dto)
+        {
+            //TODO 会签统计待处理 目前缺少关联关系
+            var query = _orderRepository.Queryable(false, false, false)
+                .LeftJoin<SystemOrganize>((x, o) => x.ActualHandleOrgCode == o.Id)
+                .WhereIF(dto.StartTime.HasValue, (x, o) => x.CreationTime >= dto.StartTime)
+                .WhereIF(dto.EndTime.HasValue, (x, o) => x.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.Keyword), (x, o) => o.Name.Contains(dto.Keyword!))
+                .GroupBy((x, o) => new { x.ActualHandleOrgCode, o.Name })
+                .Select((x, o) => new OrderBiOrgDataListVo
+                {
+                    OrgName = o.Name,
+                    OrgId = x.ActualHandleOrgCode,
+                    HandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status >= 300 && x.ExpiredTime > x.FiledTime, 1, 0)),
+                    //CounterHandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.IsDeleted, 1, 0)),
+                    NoHandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status < 300 && x.ExpiredTime > x.FiledTime, 1, 0)),
+                    //CounterNoHandlerExtendedNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status == EKnowledgeStatus.OnShelf, 1, 0))
+                }).MergeTable();
+            switch (dto.SortField)
+            {
+                case "handlerExtendedNum":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.HandlerExtendedNum) : query.OrderByDescending(x => x.HandlerExtendedNum);
+                    break;
+                case "counterHandlerExtendedNum":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.CounterHandlerExtendedNum) : query.OrderByDescending(x => x.CounterHandlerExtendedNum);
+                    break;
+                case "noHandlerExtendedNum":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.NoHandlerExtendedNum) : query.OrderByDescending(x => x.NoHandlerExtendedNum);
+                    break;
+                case "counterNoHandlerExtendedNum":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.CounterNoHandlerExtendedNum) : query.OrderByDescending(x => x.CounterNoHandlerExtendedNum);
+                    break;
+            }
+
+            var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
+            return new PagedDto<OrderBiOrgDataListVo>(total, items);
+        }
+
+        /// <summary>
+        /// 中心统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("centre_data_list")]
+        public async Task<PagedDto<OrderBiCentreDataListVo>> CentreDataList([FromQuery] ReportPagedRequest dto)
+        {
+            var query = _orderRepository.Queryable(false, false, false)
+                .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
+                .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
+                //.WhereIF(!string.IsNullOrEmpty(dto.Keyword), x => o.Name.Contains(dto.Keyword!))
+                .GroupBy(x => new { x.AcceptorId, x.AcceptorName })
+                .Select(x => new OrderBiCentreDataListVo
+                {
+                    UserName = x.AcceptorName,
+                    UserId = x.AcceptorId,
+                    CentreArchive = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status >= 300 && x.ProcessType == EProcessType.Zhiban, 1, 0)),
+                    CentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status >= 300 && x.ProcessType == EProcessType.Jiaoban, 1, 0)),
+                    //NoCentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status < 300 && x.ExpiredTime > x.FiledTime, 1, 0)),
+                    Invalid = SqlFunc.AggregateSum(SqlFunc.IIF(x.AcceptType == "无效", 1, 0)),
+                    Repeat = SqlFunc.AggregateSum(SqlFunc.IIF(x.DuplicateIds != null && SqlFunc.JsonArrayLength(x.DuplicateIds) > 0, 1, 0))
+                }).MergeTable();
+            switch (dto.SortField)
+            {
+                case "centreArchive":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.CentreArchive) : query.OrderByDescending(x => x.CentreArchive);
+                    break;
+                case "centreCareOf":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.CentreCareOf) : query.OrderByDescending(x => x.CentreCareOf);
+                    break;
+                case "noCentreCareOf":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.NoCentreCareOf) : query.OrderByDescending(x => x.NoCentreCareOf);
+                    break;
+                case "invalid":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.Invalid) : query.OrderByDescending(x => x.Invalid);
+                    break;
+                case "repeat":
+                    query = dto.SortRule == 0 ? query.OrderBy(x => x.Repeat) : query.OrderByDescending(x => x.Repeat);
+                    break;
+            }
+            var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
+            return new PagedDto<OrderBiCentreDataListVo>(total, items);
+        }
+    }
 }

+ 10 - 8
src/Hotline.Api/Controllers/OrderController.cs

@@ -490,7 +490,7 @@ public class OrderController : BaseController
         var visitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
         var dissatisfiedReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.DissatisfiedReason);
         var visitManner = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitManner);
-        var callRecord = await _trCallRecordRepository.GetAsync(x => x.CallAccept == orderVisit.Order.CallId);
+        var callRecord = await _trCallRecordRepository.GetAsync(x => x.CallAccept == orderVisit.CallId);
         var recordingFileUrl = "";
         if (callRecord != null)
         {
@@ -1814,7 +1814,7 @@ public class OrderController : BaseController
         var dto = _mapper.Map<OrderDto>(order!);
 
         var files = await _fileRepository.Queryable()
-            .Where(x => x.Key == dto.Id && x.Classify == "理上传" && string.IsNullOrEmpty(x.FlowKey)).ToListAsync();
+            .Where(x => x.Key == dto.Id && x.Classify == "理上传" && string.IsNullOrEmpty(x.FlowKey)).ToListAsync();
         dto.Files = _mapper.Map<List<FileDto>>(files);
 
         var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == order.CallId).FirstAsync();
@@ -2571,6 +2571,8 @@ public class OrderController : BaseController
     public async Task<PagedDto<CitizenDto>> List([FromQuery] CitizenListDto dto)
     {
         var (total, items) = await _citizenRepository.Queryable()
+            .WhereIF(!string.IsNullOrEmpty(dto.PhoneNumber), x => x.PhoneNumber.Contains(dto.PhoneNumber!))
+            .WhereIF(!string.IsNullOrEmpty(dto.Label), x => x.Label != null && x.Label.Contains(dto.Label!))
             .OrderByDescending(x => x.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
         return new PagedDto<CitizenDto>(total, _mapper.Map<IReadOnlyList<CitizenDto>>(items));
@@ -2605,15 +2607,15 @@ public class OrderController : BaseController
         var oders = await _orderRepository.Queryable().Where(x => x.Contact == phone)
             .OrderBy(x => x.CreationTime, OrderByType.Desc).ToListAsync();
         var allOrderNum = oders.Count;
-        var endOrderNum = oders.Count(x =>
-            x.Status == EOrderStatus.Published || x.Status == EOrderStatus.Visited || x.Status == EOrderStatus.Filed);
+        var endOrderNum = oders.Count(x =>(int)x.Status >= 300);
         var handOrderNum =
-            oders.Count(x => x.Status == EOrderStatus.Handling || x.Status == EOrderStatus.Countersigning);
+            oders.Count(x => (int)x.Status < 300);
         var DissatisfactionNum = await _orderVisitedDetailRepository.Queryable()
             .Includes(x => x.OrderVisit)
-            .Where(x => SqlFunc.JsonField(x.OrgProcessingResults, "Value") == "不满意" ||
-                        SqlFunc.JsonField(x.OrgProcessingResults, "Value") == "非常不满意")
-            .Distinct().Select(s => new { s.OrderVisit.OrderId }).CountAsync();
+            .Includes(x=>x.OrderVisit,v=>v.Order)
+            .Where(x => x.OrderVisit.Order.Contact == phone && (SqlFunc.JsonField(x.OrgProcessingResults, "Value") == "不满意" ||
+                                                    SqlFunc.JsonField(x.OrgProcessingResults, "Value") == "非常不满意"))
+            .Distinct().Select(x => new { x.OrderVisit.OrderId }).CountAsync();
         //来电历史
         //TODO 等待通讯对接完成 还缺少 上次来电时间 
         //关注诉求

+ 1 - 1
src/Hotline.Api/Hotline.Api.csproj

@@ -13,7 +13,7 @@
     <PackageReference Include="Fw.Utility.Client" Version="1.0.0" />
     <PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
     <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.9" />
-    <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.9" />
+    <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.14" />
     <PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.1.0" />
     <PackageReference Include="Serilog.Sinks.MongoDB" Version="5.3.1" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

+ 4 - 0
src/Hotline.Api/StartupExtensions.cs

@@ -23,6 +23,7 @@ using XF.Utility.MQ;
 using Hotline.DataSharing;
 using System.Runtime.CompilerServices;
 using Hotline.Ai.Jths;
+using Hotline.Api.Sdk;
 
 namespace Hotline.Api;
 
@@ -99,6 +100,9 @@ internal static class StartupExtensions
 		//jths 
 		services.AddAiJths(configuration.GetSection("AiQuality").Get<AiQualityConfig>().Url);
 
+        //宜宾企业服务
+        services.AddYbEnterpriseSdk("", "", "", "");
+
         var aiVisitConfig = configuration.GetRequiredSection("AiVisit").Get<AiVisitConfig>();
         services.AddAiVisitService(aiVisitConfig.Url, aiVisitConfig.Appkey, aiVisitConfig.ServiceVersion, aiVisitConfig.SceneUid, aiVisitConfig.RuleUid);
 

+ 6 - 5
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -324,10 +324,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException("未找到实际办理节点");
 
         await _workflowDomainService.EndAsync(workflow, new BasicWorkflowDto
-            {
-                Opinion = opinion,
-                Files = files
-            }, endStepDefine, currentStep,
+        {
+            Opinion = opinion,
+            Files = files
+        }, endStepDefine, currentStep,
             reviewResult, cancellationToken);
     }
 
@@ -586,6 +586,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         {
             foreach (var nextStepOption in dto.Steps)
             {
+                if (nextStepOption.BusinessType is EBusinessType.Department) continue;
                 nextStepOption.InputRealHandler = true;
             }
         }
@@ -767,7 +768,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     && (stepDefine.StepType != EStepType.Summary && stepDefine.BusinessType != EBusinessType.Center))
                     users1 = users1.Where(d => d.OrgId.StartsWith(levelOneOrgId));
 
-                handlers = users1.Select(d => new Kv(d.Id, d.Name)).ToList();
+                handlers = users1.Where(d => d != null && !string.IsNullOrEmpty(d.Id)).Select(d => new Kv(d.Id, d.Name)).ToList();
                 break;
             case EHandlerType.OrgLevel:
                 //当前操作人所属部门的垂直部门并且属于配置orgLevel的部门

+ 1 - 0
src/Hotline.Application/Hotline.Application.csproj

@@ -16,6 +16,7 @@
     <ProjectReference Include="..\Hotline.NewRock\Hotline.NewRock.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
     <ProjectReference Include="..\Hotline.Wex\Hotline.Wex.csproj" />
+    <ProjectReference Include="..\Hotline.YbEnterprise.Sdk\Hotline.YbEnterprise.Sdk.csproj" />
     <ProjectReference Include="..\Hotline\Hotline.csproj" />
     <ProjectReference Include="..\Tr.Sdk\Tr.Sdk.csproj" />
     <ProjectReference Include="..\XF.EasyCaching\XF.EasyCaching.csproj" />

+ 2 - 2
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarRepositoryExtensions.cs

@@ -13,7 +13,7 @@ namespace Hotline.Repository.SqlSugar.Extensions
     public static class SqlSugarRepositoryExtensions
     {
         public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
-            where TEntity : class, IEntity<string>, new()
+            where TEntity : class, new()
         {
             RefAsync<int> total = 0;
             var items = await query.ToPageListAsync(pageIndex, pageSize, total);
@@ -21,7 +21,7 @@ namespace Hotline.Repository.SqlSugar.Extensions
         }
 
         public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, PagedRequest dto, CancellationToken cancellationToken = default)
-            where TEntity : class, IEntity<string>, new()
+            where TEntity : class, new()
         {
             RefAsync<int> total = 0;
             var items = await query.ToPageListAsync(dto.PageIndex, dto.PageSize, total);

+ 31 - 0
src/Hotline.Share/Dtos/CallCenter/BiCallDto.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.CallCenter
+{
+    public class BiCallDto
+    {
+        /// <summary>
+        /// 小时跨度
+        /// </summary>
+        public string HourRange => Hour == 23 ? "23~24" : $"{Hour:00}~{(Hour + 1):00}";
+
+        public int Hour { get; set; }
+
+        public int Total { get; set; }
+
+        /// <summary>
+        /// 应答量
+        /// </summary>
+        public int Answered { get; set; }
+
+        /// <summary>
+        /// 挂断量
+        /// </summary>
+        public int Hanguped { get; set; }
+
+    }
+}

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

@@ -0,0 +1,11 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.CallCenter;
+
+public record BiQueryCallsDto: ReportPagedRequest
+{
+    /// <summary>
+    /// 线路
+    /// </summary>
+    public string? Line { get; set; }
+}

+ 97 - 0
src/Hotline.Share/Dtos/CallCenter/BiSeatCallsDto.cs

@@ -0,0 +1,97 @@
+namespace Hotline.Share.Dtos.CallCenter;
+
+/// <summary>
+/// 坐席话务量统计
+/// </summary>
+public class BiSeatCallsDto
+{
+    /// <summary>
+    /// 坐席姓名
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 工号
+    /// </summary>
+    public string StaffNo { get; set; }
+
+    /// <summary>
+    /// 呼入总量
+    /// </summary>
+    public int InTotal { get; set; }
+
+    /// <summary>
+    /// 呼出总量
+    /// </summary>
+    public int OutTotal { get; set; }
+
+    /// <summary>
+    /// 呼入接通量
+    /// </summary>
+    public int InAnswered { get; set; }
+
+    /// <summary>
+    /// 呼出接通量
+    /// </summary>
+    public int OutAnswered { get; set; }
+
+    /// <summary>
+    /// 呼入秒挂
+    /// </summary>
+    public int InHangupImmediate { get; set; }
+
+    /// <summary>
+    /// 呼入未接
+    /// </summary>
+    public int InHanguped { get; set; }
+
+    /// <summary>
+    /// 呼入平均时长
+    /// </summary>
+    public double InDurationAvg { get; set; }
+
+    /// <summary>
+    /// 呼出平均时长
+    /// </summary>
+    public double OutDurationAvg { get; set; }
+
+    /// <summary>
+    /// 有效接通量
+    /// </summary>
+    public int InAvailableAnswer { get; set; }
+
+    /// <summary>
+    /// 呼入接通秒挂
+    /// </summary>
+    public int InHangupImmediateWhenAnswered { get; set; }
+
+    /// <summary>
+    /// 登录时长(分钟)
+    /// </summary>
+    public int LoginDuration { get; set; }
+
+    /// <summary>
+    /// 小修+摘机时长
+    /// </summary>
+    public int RestDuration { get; set; }
+
+    /// <summary>
+    /// 呼入接通率
+    /// </summary>
+    public double InAnsweredRate => InTotal > 0 ? Math.Round(InAnswered / InTotal, digits: 4) : 0;
+
+    /// <summary>
+    /// 呼出接通率
+    /// </summary>
+    public double OutAnsweredRate => OutTotal > 0 ? Math.Round(OutAnswered / OutTotal, digits: 4) : 0;
+
+    /// <summary>
+    /// 呼入有效接通率
+    /// </summary>
+    public double AvailableAnswerRate => InTotal > 0 ? Math.Round(InAvailableAnswer / InTotal, digits: 4) : 0;
+
+    /// <summary>
+    /// 工作效率
+    /// </summary>
+    public double WorkRate => LoginDuration > 0 ? Math.Round(1 - RestDuration / LoginDuration, digits: 4) : 0;
+}

+ 16 - 21
src/Hotline.Share/Dtos/Order/OrderBiDto.cs

@@ -21,27 +21,22 @@ namespace Hotline.Share.Dtos.Order
 		public int Subtotal => HandlerExtendedNum + CounterHandlerExtendedNum + NoHandlerExtendedNum + CounterNoHandlerExtendedNum;
 	}
 
-
-	public record OrderBiOrgDataListDto: PagedKeywordRequest
+	public class OrderBiCentreDataListVo
 	{
-		/// <summary>
-		/// 开始时间
-		/// </summary>
-		public DateTime? CreationTimeStart { get; set; }
-
-		/// <summary>
-		/// 结束时间
-		/// </summary>
-		public DateTime? CreationTimeEnd { get; set; }
-
-		/// <summary>
-		/// 排序字段 
-		/// </summary>
-		public string SortField { get; set; }
-
-		/// <summary>
-		/// 排序规则  0 升序  1 降序
-		/// </summary>
-		public int SortRule { get; set; }
+		public string UserName { get; set; }
+
+		public string UserId { get; set; }
+
+		public int CentreArchive { get; set; }
+
+		public int CentreCareOf { get; set; }
+
+		public int NoCentreCareOf { get; set; }
+
+		public int Invalid { get; set; }
+
+		public int Repeat { get; set; }
+
+		public int Subtotal => CentreArchive + CentreCareOf + NoCentreCareOf + Invalid + Repeat;
 	}
 }

+ 30 - 0
src/Hotline.Share/Dtos/Users/RestDto.cs

@@ -0,0 +1,30 @@
+using Hotline.Share.Enums.CallCenter;
+
+namespace Hotline.Share.Dtos.Users;
+
+public record RestDto
+{
+    public string Id { get; set; }
+
+    public string TelId { get; set; }
+
+    public string TelNo { get; set; }
+
+    public string UserId { get; set; }
+
+    public string UserName { get; set; }
+
+    public DateTime CreationTime { get; set; }
+
+    public DateTime? StartTime { get; set; }
+
+    public DateTime? EndTime { get; set; }
+
+    public double RestDuration { get; set; }
+
+    public string Reason { get; set; }
+
+    public ETelRestApplyStatus ApplyStatus { get; set; }
+
+    public string? WorkflowId { get; set; }
+}

+ 5 - 31
src/Hotline.Share/Dtos/Users/UserDto.cs

@@ -1,40 +1,12 @@
 using Hotline.Share.Dtos.Org;
 using Hotline.Share.Dtos.Roles;
-using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.Identity;
 using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.User;
 using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.Users;
 
-
-public record RestDto
-{
-    public string Id { get; set; }
-
-    public string TelId { get; set; }
-
-    public string TelNo { get; set; }
-
-    public string UserId { get; set; }
-
-    public string UserName { get; set; }
-
-    public DateTime CreationTime { get; set; }
-
-    public DateTime? StartTime { get; set; }
-
-    public DateTime? EndTime { get; set; }
-
-    public double RestDuration { get; set; }
-
-    public string Reason { get; set; }
-
-    public ETelRestApplyStatus ApplyStatus { get; set; }
-
-    public string? WorkflowId { get; set; }
-}
-
 public record UserDto : AddUserDto
 {
     public string Id { get; set; }
@@ -43,6 +15,8 @@ public record UserDto : AddUserDto
 
     public DateTime CreationTime { get; set; }
 
+    public string UserTypeText => UserType.GetDescription();
+
     public string RoleNames { get; set; }
 
     public string State { get; set; }
@@ -84,9 +58,9 @@ public record AddUserDto
     public string? OrgId { get; set; }
 
     /// <summary>
-    /// 部门编码(冗余)
+    /// 用户类型
     /// </summary>
-    public string? OrgCode { get; set; }
+    public EUserType UserType { get; set; }
 
     /// <summary>
     /// 默认分机号

+ 15 - 0
src/Hotline.Share/Enums/User/EUserType.cs

@@ -0,0 +1,15 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.User;
+
+public enum EUserType
+{
+    [Description("普通")]
+    Normal = 0,
+
+    /// <summary>
+    /// 坐席(统计需要)
+    /// </summary>
+    [Description("坐席")]
+    Seat = 1,
+}

+ 24 - 0
src/Hotline.Share/Requests/PagedKeywordRequest.cs

@@ -3,4 +3,28 @@
 public record PagedKeywordRequest : PagedRequest
 {
     public string? Keyword { get; set; }
+}
+
+public record ReportPagedRequest : PagedKeywordRequest 
+{
+
+	/// <summary>
+	/// 开始时间
+	/// </summary>
+	public DateTime? StartTime { get; set; }
+
+	/// <summary>
+	/// 结束时间
+	/// </summary>
+	public DateTime? EndTime { get; set; }
+
+	/// <summary>
+	/// 排序字段 
+	/// </summary>
+	public string SortField { get; set; }
+
+	/// <summary>
+	/// 排序规则  0 升序  1 降序
+	/// </summary>
+	public int SortRule { get; set; }
 }

+ 15 - 0
src/Hotline.YbEnterprise.Sdk/Hotline.YbEnterprise.Sdk.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
+    <PackageReference Include="RestSharp" Version="110.2.0" />
+    <PackageReference Include="Fw.Utility.UnifyResponse" Version="1.0.0" />
+  </ItemGroup>
+
+</Project>

+ 18 - 0
src/Hotline.YbEnterprise.Sdk/HotlineYbEnterpriseStartupExtensions.cs

@@ -0,0 +1,18 @@
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Api.Sdk
+{
+    public static class HotlineYbEnterpriseStartupExtensions
+    {
+        public static IServiceCollection AddYbEnterpriseSdk(this IServiceCollection services, string baseUrl, string clientId, string clientSecret, string tenantId)
+        {
+            
+            return services;
+        }
+    }
+}

+ 1 - 1
src/Hotline/Orders/Order.cs

@@ -170,7 +170,7 @@ namespace Hotline.Orders
         /// <summary>
         /// 重复工单Id
         /// </summary>
-        [SugarColumn(ColumnDescription = "重复工单Id", ColumnDataType = "json", IsJson = true, IsNullable = true)]
+        [SugarColumn(ColumnDescription = "重复工单Id", ColumnDataType = "json", IsJson = true)]
         public List<string>? DuplicateIds { get; set; }
 
         /// <summary>

+ 6 - 9
src/Hotline/Users/User.cs

@@ -3,6 +3,7 @@ using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Settings;
 using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.User;
 using SqlSugar;
 using XF.Domain.Entities;
 using XF.Domain.Repository;
@@ -18,7 +19,6 @@ namespace Hotline.Users
         /// <summary>
         /// 手机号(冗余)
         /// </summary>
-        [SugarColumn(IsNullable = true)]
         public string? PhoneNo { get; set; }
 
         /// <summary>
@@ -31,27 +31,24 @@ namespace Hotline.Users
         /// <summary>
         /// 工号
         /// </summary>
-        [SugarColumn(IsNullable = true)]
         public string? StaffNo { get; set; }
 
         /// <summary>
         /// 部门Id
         /// </summary>
-        [SugarColumn(IsNullable = true)]
         public string? OrgId { get; set; }
 
-        ///// <summary>
-        ///// 部门编码(冗余)
-        ///// </summary>
-        //[SugarColumn(IsNullable = true)]
-        //public string? OrgCode { get; set; }
 
         /// <summary>
         /// 默认分机号
         /// </summary>
-        [SugarColumn(IsNullable = true)]
         public string? DefaultTelNo { get; set; }
 
+        /// <summary>
+        /// 用户类型
+        /// </summary>
+        public EUserType UserType { get; set; }
+
         /// <summary>
         /// 所属部门
         /// </summary>