Jelajahi Sumber

Merge branch 'release' of http://110.188.24.182:10023/Fengwo/hotline into release

xf 8 bulan lalu
induk
melakukan
629d9b7659
27 mengubah file dengan 861 tambahan dan 137 penghapusan
  1. 85 0
      src/Hotline.Api/Controllers/Bi/BiCallController.cs
  2. 60 85
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  3. 1 1
      src/Hotline.Api/Controllers/CommonPController.cs
  4. 99 16
      src/Hotline.Api/Controllers/OrderController.cs
  5. 2 2
      src/Hotline.Api/config/appsettings.Development.json
  6. 10 1
      src/Hotline.Application/Orders/IOrderAnalysisApplication.cs
  7. 9 0
      src/Hotline.Application/Orders/IOrderApplication.cs
  8. 153 2
      src/Hotline.Application/Orders/OrderAnalysisApplication.cs
  9. 83 7
      src/Hotline.Application/Orders/OrderApplication.cs
  10. 3 3
      src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs
  11. 64 2
      src/Hotline.Application/StatisticalReport/CallReportApplication.cs
  12. 7 0
      src/Hotline.Application/StatisticalReport/ICallReportApplication.cs
  13. 18 1
      src/Hotline.Application/StatisticalReport/IOrderReportApplication.cs
  14. 12 1
      src/Hotline.Application/StatisticalReport/OrderReportApplication.cs
  15. 2 2
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  16. 14 0
      src/Hotline.Share/Dtos/Bi/BiOrderDto.cs
  17. 10 1
      src/Hotline.Share/Dtos/CallCenter/BiCallDto.cs
  18. 7 0
      src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs
  19. 35 3
      src/Hotline.Share/Dtos/CallCenter/BiSeatCallsDto.cs
  20. 6 5
      src/Hotline.Share/Dtos/CallCenter/TrCallDto.cs
  21. 79 2
      src/Hotline.Share/Dtos/Order/OrderAnalysisDto.cs
  22. 1 1
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  23. 30 0
      src/Hotline.Share/Dtos/Order/SendOrderReportOutDto.cs
  24. 7 0
      src/Hotline.Share/Enums/Order/EOrderStatus.cs
  25. 50 0
      src/Hotline.Share/Tools/DoubleExtensions.cs
  26. 12 0
      src/Hotline/Orders/OrderAnalysis.cs
  27. 2 2
      src/Hotline/Orders/OrderSendBackAudit.cs

+ 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 - 85
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;
@@ -450,6 +454,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>
         /// 部门不满意统计
         /// 已加验证部门
@@ -463,38 +486,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 };
         }
 
@@ -2254,70 +2247,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 _workflowStepRepository.Queryable()
-                .InnerJoin<SchedulingUser>((x , su) => x.HandlerId == su.UserId)
-                .Where((x, su) => x.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send )
-                .Where((x, su) => x.CreationTime >= dto.StartTime.Value)
-                .Where((x, su) => x.CreationTime <= dto.EndTime.Value)
-                .WhereIF(!string.IsNullOrEmpty(dto.UserName), (x, su) => su.UserName == dto.UserName)
-                .GroupBy((x, su) => new { su.UserId, su.UserName })
-                .Select((x, su) => new BiOrderSendVo
-                {
-                    UserId = su.UserId,
-                    UserName = su.UserName,
-                    SendOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status == EWorkflowStepStatus.Handled, 1, 0)),
-                    NoSendOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status != EWorkflowStepStatus.Handled, 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)
-                .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.SendOrderNum > 0 &&(t1.SendOrderNum - t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault()) > 0 ?
-							   (Math.Round((double.Parse(t1.SendOrderNum.ToString()) - double.Parse(t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault().ToString())) / double.Parse(t1.SendOrderNum.ToString()),2) * 100).ToString() + "%" : "0.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>

+ 1 - 1
src/Hotline.Api/Controllers/CommonPController.cs

@@ -159,7 +159,7 @@ namespace Hotline.Api.Controllers
             //工单
             var order = await _orderRepository.Queryable(hasHandled: false)
 	            .Includes(d => d.OrderSpecials)
-	            .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept)
+	            .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept && d.Status !=  EOrderStatus.HandOverToUnAccept)
 				.Where(d => d.Source < ESource.MLSQ || d.Source > ESource.WZSC)
 	            .Where(d => d.Status != EOrderStatus.BackToProvince && d.Status < EOrderStatus.Filed)
 	            .Where(d => d.OrderSpecials.Any() == false || d.OrderSpecials.Any(s => s.State > 0))

+ 99 - 16
src/Hotline.Api/Controllers/OrderController.cs

@@ -122,8 +122,9 @@ public class OrderController : BaseController
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
     private readonly IOrderSendBackAuditApplication _orderSendBackAuditApplication;
     private readonly Publisher _publisher;
+    private readonly IOrderAnalysisApplication _orderAnalysisApplication;
 
-    public OrderController(
+	public OrderController(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowApplication workflowApplication,
@@ -179,8 +180,9 @@ public class OrderController : BaseController
         IOptionsSnapshot<AppConfiguration> appOptions,
         IRepository<OrderModifyingRecords> orderModifyingRecordsRepository,
         IOrderSendBackAuditApplication orderSendBackAuditApplication,
-        Publisher publisher
-        )
+        Publisher publisher,
+        IOrderAnalysisApplication orderAnalysisApplication
+		)
     {
         _orderDomainService = orderDomainService;
         _orderRepository = orderRepository;
@@ -238,7 +240,9 @@ public class OrderController : BaseController
         _appOptions = appOptions;
         _orderSendBackAuditApplication = orderSendBackAuditApplication;
         _publisher = publisher;
-    }
+        _orderAnalysisApplication = orderAnalysisApplication;
+
+	}
     #endregion 
 
     #region 工单发布
@@ -3492,7 +3496,7 @@ public class OrderController : BaseController
         var (total, items) = await _orderRepository
             .Queryable(hasHandled: isHandled, isAdmin: isAdmin)
             .Includes(d => d.OrderSpecials)
-            .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept)
+            .Where(d => d.Status != EOrderStatus.WaitForAccept && d.Status != EOrderStatus.BackToUnAccept && d.Status != EOrderStatus.SpecialToUnAccept && d.Status != EOrderStatus.HandOverToUnAccept)
             .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword))
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
@@ -3578,8 +3582,8 @@ public class OrderController : BaseController
         EOrderStatus[] handleStatuses = EnumExts.GetFields<EOrderStatus>().Select(d => (EOrderStatus)d.Key).ToArray();
         handleStatuses = handleStatuses.WhereIF(dto.IsHandled.HasValue,
                 d => dto.IsHandled!.Value
-                    ? d is not EOrderStatus.WaitForAccept and not EOrderStatus.BackToUnAccept and not EOrderStatus.SpecialToUnAccept
-                    : d is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept)
+                    ? d is not EOrderStatus.WaitForAccept and not EOrderStatus.BackToUnAccept and not EOrderStatus.SpecialToUnAccept and not EOrderStatus.HandOverToUnAccept
+                    : d is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept or  EOrderStatus.HandOverToUnAccept)
             .ToArray();
         if (dto.EndTime.HasValue)
             dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
@@ -3788,8 +3792,8 @@ public class OrderController : BaseController
         EOrderStatus[] handleStatuses = EnumExts.GetFields<EOrderStatus>().Select(d => (EOrderStatus)d.Key).ToArray();
         handleStatuses = handleStatuses.WhereIF(dto.IsHandled.HasValue,
                 d => dto.IsHandled!.Value
-                    ? d is not EOrderStatus.WaitForAccept and not EOrderStatus.BackToUnAccept and not EOrderStatus.SpecialToUnAccept
-                    : d is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept)
+                    ? d is not EOrderStatus.WaitForAccept and not EOrderStatus.BackToUnAccept and not EOrderStatus.SpecialToUnAccept and  not EOrderStatus.HandOverToUnAccept
+                    : d is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept or EOrderStatus.HandOverToUnAccept)
             .ToArray();
 
         var (total2, items2) = await _orderRepository.Queryable()
@@ -4132,7 +4136,7 @@ public class OrderController : BaseController
                 continue;
             }
 
-            if (order.Status >= EOrderStatus.SpecialToUnAccept)
+            if (order.Status >= EOrderStatus.HandOverToUnAccept)
             {
                 errorCount++;
                 continue;
@@ -4183,7 +4187,7 @@ public class OrderController : BaseController
         var order = await _orderRepository.GetAsync(dto.OrderId, HttpContext.RequestAborted);
         if (order is null)
             throw UserFriendlyException.SameMessage("无效工单");
-        if (order.Status >= EOrderStatus.SpecialToUnAccept)
+        if (order.Status >= EOrderStatus.HandOverToUnAccept)
             throw UserFriendlyException.SameMessage("工单状态无效,请确认当前工单状态");
         if (order.Source <= ESource.HotlineImport)
             throw UserFriendlyException.SameMessage("工单来源无效,请确认当前工单来源");
@@ -4829,7 +4833,7 @@ public class OrderController : BaseController
             .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart)
             .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.ExpiredTime <= dto.ExpiredTimeEnd)
             .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)//工单状态
-            .WhereIF(dto.Status != null && dto.Status == EOrderStatus.BackToUnAccept, d => d.Status <= EOrderStatus.SpecialToUnAccept);
+            .WhereIF(dto.Status != null && dto.Status == EOrderStatus.BackToUnAccept, d => d.Status <= EOrderStatus.HandOverToUnAccept);
         if (!_sessionContext.OrgIsCenter && _sessionContext.OrgId.Length >= 6)
         {
             var oneCode = _sessionContext.OrgId.Substring(0, 6);
@@ -6364,7 +6368,7 @@ public class OrderController : BaseController
         if (string.IsNullOrEmpty(dto.StepId))
         {
             await _orderRepository.Updateable()
-                .SetColumns(o => new Orders.Order() { SignerId = dto.Handler.UserId, SignerName = dto.Handler.Username, Status = EOrderStatus.HandOver })
+                .SetColumns(o => new Orders.Order() { SignerId = dto.Handler.UserId, SignerName = dto.Handler.Username, Status = EOrderStatus.HandOverToUnAccept })
                 .Where(o => o.Id == dto.OrderId).ExecuteCommandAsync(HttpContext.RequestAborted);
         }
         else
@@ -6378,11 +6382,90 @@ public class OrderController : BaseController
             {
                 new(dto.Handler.UserId,dto.Handler.Username,dto.Handler.OrgId,dto.Handler.OrgName,step.RoleId,step.RoleName, new List<WorkflowStep>{step})
             }, HttpContext.RequestAborted);
-            await _orderRepository.Updateable()
-                .SetColumns(o => new Orders.Order() { Status = EOrderStatus.HandOver })
+            var status = EOrderStatus.HandOver;
+            if (step.BusinessType == EBusinessType.Seat && step.StepType == EStepType.Start)
+                status = EOrderStatus.HandOverToUnAccept;
+			await _orderRepository.Updateable()
+                .SetColumns(o => new Orders.Order() { Status = status })
                 .Where(o => o.Id == dto.OrderId).ExecuteCommandAsync(HttpContext.RequestAborted);
         }
     }
 
-    #endregion
+	#endregion
+
+	#region 分析报告
+	/// <summary>
+	/// 新增分析报告
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <returns></returns>
+	[HttpPost("analysis")]
+	[LogFilter("新增分析报告")]
+	public async Task OrderAnalysisAdd([FromBody] AddOrderAnalysisDto dto)
+	{
+		await _orderAnalysisApplication.AddAsync(dto, HttpContext.RequestAborted);
+	}
+
+	/// <summary>
+	/// 修改分析报告
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <returns></returns>
+	[HttpPut("analysis")]
+	[LogFilter("修改分析报告")]
+	public async Task OrderAnalysisUpdate([FromBody] UpdateOrderAnalysisDto dto)
+	{
+		await _orderAnalysisApplication.UpdateAsync(dto, HttpContext.RequestAborted);
+	}
+
+	/// <summary>
+	/// 删除分析报告
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <returns></returns>
+	[HttpDelete("analysis")]
+	[LogFilter("删除分析报告")]
+	public async Task OrderAnalysisDelete([FromBody] DeleteOrderAnalysisDto dto)
+	{
+		await _orderAnalysisApplication.DeleteAsync(dto, HttpContext.RequestAborted);
+	}
+
+	/// <summary>
+	/// 分析报告列表
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <returns></returns>
+	[HttpGet("analysis_list")]
+	[LogFilter("分析报告列表")]
+	public async Task<PagedDto<OrderAnalysisDto>> OrderAnalysisListQuery([FromQuery] OrderAnalysisListDto dto)
+	{
+		var (total, items) = await _orderAnalysisApplication.ListQuery(dto, HttpContext.RequestAborted).ToPagedListAsync(dto, HttpContext.RequestAborted);
+		return new PagedDto<OrderAnalysisDto>(total, items);
+	}
+
+	/// <summary>
+	/// 分析报告明细
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <returns></returns>
+	[HttpGet("analysis_detail")]
+	[LogFilter("分析报告明细")]
+	public async Task<PagedDto<OrderDto>> OrderAnalysisDetailQuery([FromQuery] OrderAnalysisDetailDto dto)
+	{
+		var(total, items) = await _orderAnalysisApplication.DetailQuery(dto, HttpContext.RequestAborted).ToPagedListAsync(dto, HttpContext.RequestAborted);
+		return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
+	}
+
+	/// <summary>
+	/// 生成分析报告
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <returns></returns>
+	[HttpGet("analysis_report")]
+	[LogFilter("生成分析报告")]
+	public async Task<object> OrderAnalysisReportQuery([FromQuery] OrderAnalysisDetailDto dto)
+	{
+		 return await _orderAnalysisApplication.ReportQuery(dto, HttpContext.RequestAborted);
+	}
+	#endregion
 }

+ 2 - 2
src/Hotline.Api/config/appsettings.Development.json

@@ -59,7 +59,7 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
+    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
   },
   "Cache": {
     "Host": "110.188.24.182",
@@ -100,7 +100,7 @@
     }
   },
   "DatabaseConfiguration": {
-    "ApplyDbMigrations": false,
+    "ApplyDbMigrations": true,
     "ApplySeed": false
   },
   "MqConfiguration": {

+ 10 - 1
src/Hotline.Application/Orders/IOrderAnalysisApplication.cs

@@ -1,12 +1,21 @@
-using System;
+using Hotline.Share.Dtos.Order;
+using SqlSugar;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Orders;
 
 namespace Hotline.Application.Orders
 {
 	public interface IOrderAnalysisApplication
 	{
+		Task AddAsync(AddOrderAnalysisDto dto, CancellationToken cancellationToken);
+		Task UpdateAsync(UpdateOrderAnalysisDto dto, CancellationToken cancellationToken);
+		Task DeleteAsync(DeleteOrderAnalysisDto dto, CancellationToken cancellationToken);
+		ISugarQueryable<OrderAnalysisDto> ListQuery(OrderAnalysisListDto dto, CancellationToken cancellationToken);
+		ISugarQueryable<Order> DetailQuery(OrderAnalysisDetailDto dto, CancellationToken cancellationToken);
+		Task<object> ReportQuery(OrderAnalysisDetailDto dto, CancellationToken cancellationToken);
 	}
 }

+ 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.更新流程

+ 153 - 2
src/Hotline.Application/Orders/OrderAnalysisApplication.cs

@@ -1,14 +1,165 @@
-using System;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Order;
+using MapsterMapper;
+using NPOI.SS.Formula.Functions;
+using SqlSugar;
+using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Net;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Share.Enums.Order;
 using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using XF.Utility.SequentialId;
+using static Hotline.AppDefaults;
 
 namespace Hotline.Application.Orders
 {
 	public class OrderAnalysisApplication : IOrderAnalysisApplication, IScopeDependency
 	{
-		public OrderAnalysisApplication() { }
+		private readonly IRepository<OrderAnalysis> _orderAnalysisRepository;
+		private readonly IMapper _mapper;
+		private readonly IOrderRepository _orderRepository;
+		private readonly IRepository<SystemArea> _systemAreaRepository;
+
+		public OrderAnalysisApplication(
+			IMapper mapper,
+			IOrderRepository orderRepository,
+			IRepository<SystemArea> systemAreaRepository,
+			IRepository<OrderAnalysis> orderAnalysisRepository
+			) 
+		{
+			_mapper = mapper;
+			_orderRepository = orderRepository;
+			_orderAnalysisRepository = orderAnalysisRepository;
+			_systemAreaRepository = systemAreaRepository;
+		}
+
+		/// <summary>
+		/// 新增
+		/// </summary>
+		/// <returns></returns>
+		public async Task AddAsync(AddOrderAnalysisDto dto, CancellationToken cancellationToken) {
+			var analysisAny =  await _orderAnalysisRepository.Queryable().AnyAsync(x => x.AnalysisName == dto.AnalysisName, cancellationToken);
+			if (analysisAny)
+				throw UserFriendlyException.SameMessage("当前报告名称已经存在,请修改报告名称!");
+
+			var analysisId = SequentialStringGenerator.Create();
+			var generatedTime = DateTime.Now;
+			var analysiss = new List<OrderAnalysis>();
+			foreach (var item in dto.AnalysisList)
+			{
+				var analysis = _mapper.Map<OrderAnalysis>(item);
+				analysis.AnalysisId = analysisId;
+				analysis.AnalysisName =  dto.AnalysisName;
+				analysis.Remark = dto.Remark;
+				analysis.GeneratedTime = generatedTime;
+				analysiss.Add(analysis);
+			}
+			await _orderAnalysisRepository.AddRangeAsync(analysiss, cancellationToken);
+		}
+		
+		/// <summary>
+		/// 修改
+		/// </summary>
+		/// <returns></returns>
+		public async Task UpdateAsync(UpdateOrderAnalysisDto dto, CancellationToken cancellationToken) 
+		{
+			var analysisIdAny = await _orderAnalysisRepository.Queryable().AnyAsync(x =>  x.AnalysisId == dto.AnalysisId, cancellationToken);
+			if (!analysisIdAny)
+				throw UserFriendlyException.SameMessage("当前报告不存在,请查询报告是否已变更!");
+			var analysisNameAny = await _orderAnalysisRepository.Queryable().AnyAsync(x => x.AnalysisName == dto.AnalysisName && x.AnalysisId != dto.AnalysisId, cancellationToken);
+			if (analysisNameAny)
+				throw UserFriendlyException.SameMessage("当前报告名称已经存在,请修改报告名称!");
+			await _orderAnalysisRepository.Updateable().SetColumns(x => new OrderAnalysis { AnalysisName = dto.AnalysisName, Remark = dto.Remark })
+				.Where(x => x.AnalysisId == dto.AnalysisId).ExecuteCommandAsync(cancellationToken);
+		}
+
+		/// <summary>
+		/// 删除
+		/// </summary>
+		/// <returns></returns>
+		public async Task DeleteAsync(DeleteOrderAnalysisDto dto, CancellationToken cancellationToken) {
+
+			await _orderAnalysisRepository.Removeable().In(x => x.AnalysisId, dto.AnalysisIds).ExecuteCommandAsync(cancellationToken);
+		}
+
+
+		/// <summary>
+		/// 查询列表
+		/// </summary>
+		/// <returns></returns>
+		public ISugarQueryable<OrderAnalysisDto> ListQuery(OrderAnalysisListDto dto, CancellationToken cancellationToken) {
+			if (dto.GeneratedEndTime.HasValue)
+				dto.GeneratedEndTime = dto.GeneratedEndTime.Value.AddDays(1).AddSeconds(-1);
+			var query = _orderAnalysisRepository.Queryable()
+				.WhereIF(!string.IsNullOrEmpty(dto.AnalysisName), x => x.AnalysisName == dto.AnalysisName)
+				.WhereIF(dto.GeneratedStartTime.HasValue && dto.GeneratedEndTime.HasValue, x => x.GeneratedTime >= dto.GeneratedStartTime && x.GeneratedTime <= dto.GeneratedEndTime)
+				.GroupBy(x => new { x.AnalysisId, x.AnalysisName, x.Remark, x.GeneratedTime })
+				.Select(x => new OrderAnalysisDto { AnalysisId = x.AnalysisId, AnalysisName = x.AnalysisName, Remark = x.Remark, GeneratedTime = x.GeneratedTime })
+				.OrderByDescending(x=> x.GeneratedTime);
+			return query;
+		}
+
+		/// <summary>
+		///  查看预警事件
+		/// </summary>
+		/// <returns></returns>
+		public ISugarQueryable<Order> DetailQuery(OrderAnalysisDetailDto dto, CancellationToken cancellationToken) {
+			var query = _orderAnalysisRepository.Queryable()
+				.LeftJoin<Order>((a, o) => a.AcceptTypeCode == o.AcceptTypeCode && a.HotspotId == o.HotspotId && o.AreaCode.StartsWith(a.AreaCode))
+				.Where((a, o) => a.AnalysisId == dto.AnalysisId)
+				.Select((a, o) => o);
+			return query;
+		}
+
+		/// <summary>
+		/// 生成报告
+		/// </summary>
+		/// <returns></returns>
+		public async Task<object> ReportQuery(OrderAnalysisDetailDto dto, CancellationToken cancellationToken) {
+
+			var analysis = await _orderAnalysisRepository.Queryable()
+				.Where(x => x.AnalysisId == dto.AnalysisId)
+				.GroupBy(x => new { x.AnalysisId, x.AnalysisName, x.GeneratedTime, x.Remark })
+				.Select(x => new { x.AnalysisId, x.AnalysisName, x.GeneratedTime, x.Remark }).FirstAsync(cancellationToken);
+
+			var orders = await _orderAnalysisRepository.Queryable()
+				.LeftJoin<Order>((a, o) => a.AcceptTypeCode == o.AcceptTypeCode && a.HotspotId == o.HotspotId && o.AreaCode.StartsWith(a.AreaCode))
+				.Where((a, o) => a.AnalysisId == dto.AnalysisId)
+				.Select((a, o) => o).ToListAsync(cancellationToken) ;
+			var introductionData = orders.GroupBy(x=>x.SourceChannel)
+				.Select(x=>new { SourceChannel = x.Key, Num  = x.Count(), Rate = Math.Round((x.Count() /(double)orders.Count) * 100, 2) })
+				.OrderByDescending(x=>x.Num);
+			var introduction = new { total = orders.Count, data = introductionData };
+			var orderAreas = orders.GroupBy(x=>x.AreaCode.Substring(0,6))
+				.Select(x=> new { AreaCode =x.Key, Num = x.Count(), Rate = Math.Round((x.Count() / (double)orders.Count) * 100, 2) })
+				.OrderByDescending(x => x.Num);
+			var areaList = await _systemAreaRepository.Queryable().Where(x=>x.Id.Length == 6).ToListAsync(cancellationToken);
+			var areaData = (from t1 in orderAreas
+							join t2 in areaList on t1.AreaCode equals t2.Id into t1_t2
+				from item in t1_t2.DefaultIfEmpty()
+				select new
+				{
+					AreaCode = t1.AreaCode,
+					Num = t1.Num,
+					Rate = t1.Rate,
+					AreaName = t1_t2.Select(x => x.AreaName).FirstOrDefault(),
+				}).OrderByDescending(x=>x.Num).ToList();
+			var areaNames = areaData.Select(x=>x.AreaName).ToList();
+
+			var area = new { AreaNames = areaNames, data = areaData };
+
+			var handle = new { HandleFiled = orders.Count(x => x.Status >= EOrderStatus.Filed), Handle = orders.Count(x => x.Status < Share.Enums.Order.EOrderStatus.Filed) };
+
+			var first = _mapper.Map<List<OrderDto>>(orders.OrderByDescending(x=>x.CreationTime).Take(2).ToList());
+			return new { Analysis = analysis, Introduction = introduction, Area = area, Handle = handle, First = first };
+		}
+
+
 	}
 }

+ 83 - 7
src/Hotline.Application/Orders/OrderApplication.cs

@@ -47,6 +47,8 @@ using Hotline.Share.Mq;
 using JiebaNet.Segmenter;
 using Microsoft.AspNetCore.Http;
 using WordInfo = PanGu.WordInfo;
+using Hotline.Schedulings;
+using XF.Domain.Entities;
 
 namespace Hotline.Application.Orders;
 
@@ -72,6 +74,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;
 
 
@@ -96,8 +99,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<SystemArea> systemAreaRepository,
         IRepository<Hotspot> hotspotRepository,
         IRepository<WorkflowStep> workflowStepRepository,
-        IRepository<SystemDicData> systemDicDataRepository
-		)
+        IRepository<SystemDicData> systemDicDataRepository,
+        IRepository<WorkflowTrace> workflowTraceRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -120,8 +123,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _hotspotRepository = hotspotRepository;
         _workflowStepRepository = workflowStepRepository;
         _systemDicDataRepository = systemDicDataRepository;
-
-	}
+        _workflowTraceRepository = workflowTraceRepository;
+    }
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -284,7 +287,13 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         DateTime stTime = _timeLimitDomainService.WorkDay(DateTime.Now);
         var IsCenter = _sessionContext.OrgIsCenter;
 
-        return _orderRepository.Queryable(canView: !IsCenter).Includes(d => d.OrderDelays)
+        return _orderRepository.Queryable(canView: false).Includes(d => d.OrderDelays)
+		    .Where(d => SqlFunc.Subqueryable<WorkflowStep>()
+			        .Where(step => step.ExternalId == d.Id &&
+			                       ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
+			                        (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
+			                        (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId))))
+			        .Any())
             .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
             //.WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!))
             .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No))
@@ -1423,12 +1432,12 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 CentreArchive = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status >= EOrderStatus.Filed && it.ProcessType == EProcessType.Zhiban && it.AcceptType != "无效", 1, 0)), //中心归档件
                                                                                                                                                                             //CentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status >= EOrderStatus.Filed && (it.FileUserRole == EFileUserType.Org || it.FileUserRole == EFileUserType.Dispatch), 1, 0)), //转办信件
                 CentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF(it.AcceptType != "无效" && (it.ProcessType == EProcessType.Jiaoban || (it.ActualHandleStepName == "派单组" && it.Status < EOrderStatus.Filed) || (it.ActualHandleStepName == "班长审批" && it.Status < EOrderStatus.Filed)), 1, 0)),
-                NoCentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF(it.AcceptType != "无效" && (it.Status <= EOrderStatus.SpecialToUnAccept), 1, 0)), //坐席待办 //中心领导?市领导? 是否在统计条件中
+                NoCentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF(it.AcceptType != "无效" && (it.Status <= EOrderStatus.HandOverToUnAccept), 1, 0)), //坐席待办 //中心领导?市领导? 是否在统计条件中
                                                                                                                                                   //CentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status >= EOrderStatus.Filed && it.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(it.AcceptType == "无效", 1, 0)),
                 Repeat = SqlFunc.AggregateSum(SqlFunc.IIF(it.DuplicateIds != null && SqlFunc.JsonArrayLength(it.DuplicateIds) > 0, 1, 0)),
-                Subtotal = SqlFunc.AggregateSum(SqlFunc.IIF((it.Status >= EOrderStatus.Filed && it.ProcessType == EProcessType.Zhiban && it.AcceptType != "无效") || (it.AcceptType != "无效" && (it.ProcessType == EProcessType.Jiaoban || (it.ActualHandleStepName == "派单组" && it.Status < EOrderStatus.Filed) || (it.ActualHandleStepName == "班长审批" && it.Status < EOrderStatus.Filed))) || (it.Status <= EOrderStatus.SpecialToUnAccept) || it.AcceptType == "无效" || (it.DuplicateIds != null && SqlFunc.JsonArrayLength(it.DuplicateIds) > 0), 1, 0))
+                Subtotal = SqlFunc.AggregateSum(SqlFunc.IIF((it.Status >= EOrderStatus.Filed && it.ProcessType == EProcessType.Zhiban && it.AcceptType != "无效") || (it.AcceptType != "无效" && (it.ProcessType == EProcessType.Jiaoban || (it.ActualHandleStepName == "派单组" && it.Status < EOrderStatus.Filed) || (it.ActualHandleStepName == "班长审批" && it.Status < EOrderStatus.Filed))) || (it.Status <= EOrderStatus.HandOverToUnAccept) || it.AcceptType == "无效" || (it.DuplicateIds != null && SqlFunc.JsonArrayLength(it.DuplicateIds) > 0), 1, 0))
             }).MergeTable();
         switch (dto.SortField)
         {
@@ -1691,5 +1700,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
 }

+ 3 - 3
src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs

@@ -229,9 +229,9 @@ namespace Hotline.Application.Orders
                 .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.OrderVisit.Order!.OrgLevelOneName!.Contains(dto.OrgLevelOneName!))
                 .WhereIF(!string.IsNullOrEmpty(dto.CurrentHandleOrgName), x => x.OrderVisit.Order!.CurrentHandleOrgName!.Contains(dto.CurrentHandleOrgName!))
                 .WhereIF(dto.CurrentHandleTime.HasValue && dto.EndCurrentHandleTime.HasValue, x => x.OrderVisit.Order!.ActualHandleTime >= dto.CurrentHandleTime && x.OrderVisit.Order!.ActualHandleTime <= dto.EndCurrentHandleTime)
-                .WhereIF(dto.FiledTime.HasValue && dto.EndFiledTime.HasValue, x => x.OrderVisit.Order!.FiledTime == dto.FiledTime && x.OrderVisit.Order!.FiledTime <= dto.EndFiledTime)
-                .WhereIF(dto.CreationTime.HasValue && dto.EndCreationTime.HasValue, x => x.OrderVisit.Order!.CreationTime == dto.CreationTime && x.OrderVisit.Order!.CreationTime <= dto.EndCreationTime)
-                .WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue, x => x.OrderVisit.VisitTime == dto.VisitTime && x.OrderVisit.VisitTime <= dto.EndVisitTime)
+                .WhereIF(dto.FiledTime.HasValue && dto.EndFiledTime.HasValue, x => x.OrderVisit.Order!.FiledTime >= dto.FiledTime && x.OrderVisit.Order!.FiledTime <= dto.EndFiledTime)
+                .WhereIF(dto.CreationTime.HasValue && dto.EndCreationTime.HasValue, x => x.OrderVisit.Order!.CreationTime >= dto.CreationTime && x.OrderVisit.Order!.CreationTime <= dto.EndCreationTime)
+                .WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue, x => x.OrderVisit.VisitTime >= dto.VisitTime && x.OrderVisit.VisitTime <= dto.EndVisitTime)
                 .WhereIF(!string.IsNullOrEmpty(dto.VisitOrgName), x => x.VisitOrgName!.Contains(dto.VisitOrgName!))
                 .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults)
                 .WhereIF(!string.IsNullOrEmpty(dto.OrgHandledAttitude), x => SqlFunc.JsonListObjectAny(x.OrgHandledAttitude, "Key", dto.OrgHandledAttitude))

+ 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);
     }
 }

File diff ditekan karena terlalu besar
+ 12 - 1
src/Hotline.Application/StatisticalReport/OrderReportApplication.cs


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

@@ -210,8 +210,8 @@ namespace Hotline.Repository.SqlSugar.Extensions
                 //获取原生SQL推荐 5.1.4.63  性能OK
                 //Log.Information(UtilMethods.GetNativeSql(sql, pars));
 
-                //Log.Information("Sql: {0}", sql);
-                //Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
+                Log.Information("Sql: {0}", sql);
+                Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
             };
             db.Aop.OnError = (exp) =>//SQL报错
             {

+ 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; }

+ 10 - 1
src/Hotline.Share/Dtos/CallCenter/BiCallDto.cs

@@ -27,5 +27,14 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public int Hanguped { get; set; }
 
-    }
+		/// <summary>
+		/// 接通率 接通数量/呼入数量%(保留小数点后两位)
+		/// </summary>
+		//public string AnsweredRate => GetAnsweredRate();
+
+		//public string GetAnsweredRate()
+  //      {
+	 //       return Math.Round((Answered / (double)Total) * 100,2) + "%";
+  //      }
+	}
 }

+ 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);
             }

+ 79 - 2
src/Hotline.Share/Dtos/Order/OrderAnalysisDto.cs

@@ -1,4 +1,5 @@
-using System;
+using Hotline.Share.Requests;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
@@ -8,6 +9,33 @@ namespace Hotline.Share.Dtos.Order
 {
 	public class AddOrderAnalysisDto
 	{
+		/// <summary>
+		/// 报告名称
+		/// </summary>
+		public string AnalysisName { get; set; }
+
+		/// <summary>
+		/// 备注
+		/// </summary>
+		public string? Remark { get; set; }
+
+		/// <summary>
+		/// 开始时间
+		/// </summary>
+		public DateTime? StartTime { get; set; }
+
+		/// <summary>
+		/// 结束时间
+		/// </summary>
+		public DateTime? EndTime { get; set; }
+
+		/// <summary>
+		///  多选对象
+		/// </summary>
+		public List<AddOrderAnalysisListDto> AnalysisList { get; set; }
+	}
+
+	public class AddOrderAnalysisListDto {
 		/// <summary>
 		/// 受理类型
 		/// </summary>
@@ -44,7 +72,7 @@ namespace Hotline.Share.Dtos.Order
 		/// <summary>
 		/// 报告id
 		/// </summary>
-		public string AnalysisId { get; set; }
+		public List<string> AnalysisIds { get; set; }
 	}
 
 	public class UpdateOrderAnalysisDto
@@ -98,4 +126,53 @@ namespace Hotline.Share.Dtos.Order
 
 		public string? CreatorName { get; set; }
 	}
+
+	public class OrderAnalysisDto  {
+
+
+		/// <summary>
+		/// 报告id
+		/// </summary>
+		public string AnalysisId { get; set; }
+
+		/// <summary>
+		/// 报告名称
+		/// </summary>
+		public string AnalysisName { get; set; }
+
+		/// <summary>
+		/// 备注
+		/// </summary>
+		public string? Remark { get; set; }
+
+		/// <summary>
+		/// 生成时间
+		/// </summary>
+		public DateTime? GeneratedTime { get; set; }
+
+	}
+
+	public record OrderAnalysisListDto : PagedKeywordRequest {
+
+		/// <summary>
+		/// 报告名称
+		/// </summary>
+		public string AnalysisName { get; set; }
+
+		/// <summary>
+		/// 生成时间
+		/// </summary>
+		public DateTime? GeneratedStartTime { get; set; }
+
+		/// <summary>
+		/// 结束时间
+		/// </summary>
+		public DateTime? GeneratedEndTime { get; set; }
+	}
+
+	public record OrderAnalysisDetailDto : PagedKeywordRequest
+	{
+		public string AnalysisId { get; set; }
+	}
+
 }

+ 1 - 1
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -412,7 +412,7 @@ namespace Hotline.Share.Dtos.Order
         /// 是否可编辑
         /// </summary>
         public bool CanEdit => !string.IsNullOrEmpty(SignerId) &&
-                               (Status is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept);
+                               (Status is EOrderStatus.WaitForAccept or EOrderStatus.BackToUnAccept or EOrderStatus.SpecialToUnAccept or EOrderStatus.HandOverToUnAccept);
 
         /// <summary>
         /// 是否可签收

+ 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; }
+}

+ 7 - 0
src/Hotline.Share/Enums/Order/EOrderStatus.cs

@@ -24,6 +24,13 @@ public enum EOrderStatus
     [Description("特提待受理")]
     SpecialToUnAccept = 2,
 
+    /// <summary>
+    /// 已开启流程,移交开始节点
+    /// 待受理,可编辑
+    /// </summary>
+    [Description("移交待受理")]
+    HandOverToUnAccept = 3,
+
 	/// <summary>
 	/// 退回省平台(锁定,不可操作)
 	/// </summary>

+ 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();
+    }
+}

+ 12 - 0
src/Hotline/Orders/OrderAnalysis.cs

@@ -72,5 +72,17 @@ namespace Hotline.Orders
 		[SugarColumn(ColumnDescription = "生成时间")]
 		public DateTime? GeneratedTime { get; set; }
 
+		/// <summary>
+		/// 开始时间
+		/// </summary>
+		[SugarColumn(ColumnDescription = "开始时间")]
+		public DateTime? StartTime { get; set; }
+
+		/// <summary>
+		/// 结束时间
+		/// </summary>
+		[SugarColumn(ColumnDescription = "结束时间")]
+		public DateTime? EndTime { get; set; }
+
 	}
 }

+ 2 - 2
src/Hotline/Orders/OrderSendBackAudit.cs

@@ -79,13 +79,13 @@ namespace Hotline.Orders
 		/// 申请部门ID
 		/// </summary>
 		[SugarColumn(ColumnDescription = "申请部门ID")]
-		public string ApplyOrgId { get; set; }
+		public string? ApplyOrgId { get; set; }
 
 		/// <summary>
 		/// 申请部门名称
 		/// </summary>
 		[SugarColumn(ColumnDescription = "申请部门名称")]
-		public string ApplyOrgName { get; set; }
+		public string? ApplyOrgName { get; set; }
 
 		/// <summary>
 		/// 退回部门ID

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini