Browse Source

Merge branch 'master' of http://git.12345lm.cn/Fengwo/hotline

Dun.Jason 1 year ago
parent
commit
dd22e3416c
41 changed files with 2318 additions and 724 deletions
  1. 20 1
      src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs
  2. 434 0
      src/Hotline.Api/Controllers/Bigscreen/EnforcementScreenController.cs
  3. 435 8
      src/Hotline.Api/Controllers/EnforcementOrderController.cs
  4. 12 2
      src/Hotline.Api/Controllers/HomeController.cs
  5. 134 48
      src/Hotline.Api/Controllers/OrderController.cs
  6. 13 1
      src/Hotline.Api/Controllers/SchedulingController.cs
  7. 1 1
      src/Hotline.Api/Controllers/SettingController.cs
  8. 1 1
      src/Hotline.Api/Controllers/UserController.cs
  9. 0 34
      src/Hotline.Api/Controllers/WorkflowController.cs
  10. 2 0
      src/Hotline.Api/Realtimes/RealtimeGroupNames.cs
  11. 4 0
      src/Hotline.Api/Realtimes/RealtimeMethods.cs
  12. 11 0
      src/Hotline.Api/Realtimes/RealtimeService.cs
  13. 79 0
      src/Hotline.Application/Bigscreen/EnforcementDataScreenRefreshService.cs
  14. 2 32
      src/Hotline.Application/FlowEngine/IWorkflowApplication.cs
  15. 145 316
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  16. 2 4
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  17. 0 4
      src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs
  18. 1 16
      src/Hotline.Application/Handlers/FlowEngine/WorkflowRecallHandler.cs
  19. 5 6
      src/Hotline.Application/JudicialManagement/EnforcementApplication.cs
  20. 1 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  21. 11 6
      src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs
  22. 7 5
      src/Hotline.Share/Dtos/FlowEngine/NextStepsDto.cs
  23. 5 0
      src/Hotline.Share/Dtos/JudicialManagement/ClueVerificationDto.cs
  24. 318 0
      src/Hotline.Share/Dtos/JudicialManagement/EnforcementBigscreenDto.cs
  25. 13 0
      src/Hotline.Share/Dtos/JudicialManagement/EnforcementOrderListDto.cs
  26. 27 0
      src/Hotline.Share/Dtos/JudicialManagement/EventClassificationOrderCountDto.cs
  27. 1 1
      src/Hotline.Share/Dtos/JudicialManagement/QueryEnforcementOrderDto.cs
  28. 92 0
      src/Hotline.Share/Dtos/JudicialManagement/QueryEventClassificationStatisticsDto.cs
  29. 102 7
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  30. 6 1
      src/Hotline.Share/Enums/FlowEngine/ECountersignPosition.cs
  31. 5 3
      src/Hotline.Share/Enums/FlowEngine/EFlowDirection.cs
  32. 9 5
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  33. 7 33
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  34. 198 144
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  35. 31 22
      src/Hotline/FlowEngine/Workflows/WorkflowStep.cs
  36. 114 0
      src/Hotline/FlowEngine/Workflows/WorkflowStepHandler.cs
  37. 5 0
      src/Hotline/JudicialManagement/EnforcementOrders.cs
  38. 39 22
      src/Hotline/Orders/OrderDomainService.cs
  39. 4 0
      src/Hotline/Realtimes/IRealtimeService.cs
  40. 18 0
      src/Hotline/Schedulings/SchedulingUser.cs
  41. 4 1
      src/XF.Domain/Constants/SettingConstants.cs

+ 20 - 1
src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs

@@ -163,6 +163,25 @@ namespace Hotline.Api.Controllers.Bigscreen
             return list;
         }
 
+        /// <summary>
+        /// 获取区域信息
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("get_system_area")]
+        [AllowAnonymous]
+        public async Task<object> GetSystemAreaAsync()
+        {
+            return await _systemAreaRepository.Queryable()
+                .Where(p => p.Id == "511500" || p.ParentId == "511500")
+                .Select(p => new
+                {
+                    p.AreaName,
+                    p.Id
+                })
+                .OrderBy(p => p.Id)
+                .ToListAsync();
+        }
+
         /// <summary>
         /// 预警热点
         /// </summary>
@@ -175,7 +194,7 @@ namespace Hotline.Api.Controllers.Bigscreen
         public async Task<List<EarlyWarningHotsPotsStatisticsDto>> EarlyWarningHotsPotsStatistics(DateTime StartDate, DateTime EndDate,string AreaCode)
         {
             EndDate = EndDate.AddDays(1).AddSeconds(-1);
-            if (AreaCode.Length==6 && AreaCode.IndexOf("0") == 5)
+            if (AreaCode.Length==6 && AreaCode.IndexOf("00") == 4)
             {
                 AreaCode = AreaCode.Remove(4);
             }

+ 434 - 0
src/Hotline.Api/Controllers/Bigscreen/EnforcementScreenController.cs

@@ -0,0 +1,434 @@
+using Hotline.JudicialManagement;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.JudicialManagement;
+using Hotline.Share.Enums.Order;
+using MapsterMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.Api.Controllers.Bigscreen
+{
+    /// <summary>
+    /// 司法行政监督管理大屏  
+    /// </summary>
+    public class EnforcementScreenController : BaseController
+    {
+        private readonly IMapper _mapper;
+        private readonly IRepository<EnforcementOrders> _enforcementOrdersRepository;
+        private readonly IRepository<SystemArea> _systemAreaRepository;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="mapper"></param>
+        /// <param name="enforcementOrdersRepository"></param>
+        /// <param name="systemAreaRepository"></param>
+        public EnforcementScreenController(IMapper mapper,
+            IRepository<EnforcementOrders> enforcementOrdersRepository,
+            IRepository<SystemArea> systemAreaRepository)
+        {
+            _mapper = mapper;
+            _enforcementOrdersRepository = enforcementOrdersRepository;
+            _systemAreaRepository = systemAreaRepository;
+        }
+
+        /// <summary>
+        /// 左上工单数据模块
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-module-statistics")]
+        public async Task<OrderModuleDto> GetOrderModuleStatisticsAsync(DateTime StartDate, DateTime EndDate)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+            var dto = new OrderModuleDto();
+
+            var data = await _enforcementOrdersRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                .Where((x, o) => o.Id != null)
+                .Where((x, o) => o.StartTime >= StartDate && o.StartTime <= EndDate)
+                .Select((x, o) => new
+                {
+                    x.Id,
+                    x.IsEnforcementOrder,
+                    x.IsTheClueTrue,
+                    x.IsPassTheBuckOrder
+                })
+                .MergeTable()
+                .Select(d => new OrderModuleDto
+                {
+                    OrderCountNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.Id != null, 1, 0)),
+                    EnforcementOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsEnforcementOrder, 1, 0)),
+                    TheClueIsTrueNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == true, 1, 0)),
+                    TheClueIsNotTrueNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == false, 1, 0)),
+                    PassTheBuckOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsPassTheBuckOrder, 1, 0))
+                })
+                .ToListAsync();
+            if (data != null && data.Count > 0)
+            {
+                dto.OrderCountNum = data[0].OrderCountNum;
+                dto.EnforcementOrderNum = data[0].EnforcementOrderNum;
+                dto.TheClueIsTrueNum = data[0].TheClueIsTrueNum;
+                dto.TheClueIsNotTrueNum = data[0].TheClueIsNotTrueNum;
+                dto.PassTheBuckOrderNum = data[0].PassTheBuckOrderNum;
+            }
+
+            dto.SatisfiedCount = await _enforcementOrdersRepository.Queryable()
+                .LeftJoin<OrderVisit>((x, o) => x.Id == o.OrderId)
+                .LeftJoin<OrderVisitDetail>((x, o, p) => o.Id == p.VisitId)
+               .Where((x, o, p) => o.VisitTime >= StartDate && o.VisitTime <= EndDate && p.VisitTarget == EVisitTarget.Org)
+               .Where((x, o, p) => o.VisitState == EVisitState.Visited && !string.IsNullOrEmpty(p.VisitOrgCode))
+               .Where((x, o, p) => SqlFunc.JsonField(o.NowEvaluate, "Key") != "1" && SqlFunc.JsonField(o.NowEvaluate, "Key") != "2")
+               .CountAsync();
+
+            return dto;
+        }
+
+        /// <summary>
+        /// 左中受理类型
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-accept-type-statistics")]
+        public async Task<List<OrderAcceptTypeStatisticsDto>> GetOrderAcceptTypeStatisticsAsync(DateTime StartDate, DateTime EndDate)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var list = await _enforcementOrdersRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                .Where((x, o) => o.Id != null && o.Status > EOrderStatus.Handling && !string.IsNullOrEmpty(o.AcceptType))
+                .Where((x, o) => o.StartTime >= StartDate && o.StartTime <= EndDate)
+                .Select((x, o) => new
+                {
+                    x.Id,
+                    o.AcceptType,
+                    o.Status,
+                    o.ExpiredStatus
+                })
+                .MergeTable()
+                 .GroupBy(x => x.AcceptType)
+                .Select(x => new OrderAcceptTypeStatisticsDto
+                {
+                    AcceptType = x.AcceptType,
+                    SumCount = SqlFunc.AggregateCount(x.Id),
+                    HandlingCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Handling && x.Status < EOrderStatus.Filed, 1, 0)),
+                    FiledCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed, 1, 0)),
+                    OverTimeCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.ExpiredStatus == EExpiredStatus.Expired, 1, 0))
+                }).ToListAsync();
+
+            return list;
+        }
+
+        /// <summary>
+        /// 左下高频事项统计
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <param name="AreaCode"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-event-type-statistics")]
+        public async Task<List<OrderEventTypeStatisticsDto>> GetOrderEventTypeStatisticsAsync(DateTime StartDate, DateTime EndDate, string AreaCode)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+            if (AreaCode.Length == 6 && AreaCode.IndexOf("00") == 4)
+                AreaCode = AreaCode.Remove(4);
+
+            var list = await _enforcementOrdersRepository.Queryable()
+               .LeftJoin<Order>((x, o) => x.Id == o.Id)
+               .LeftJoin<JudicialComplaintsEventType>((x, o, j) => x.EventTypeOneLevelId == j.Id)
+               .Where((x, o, j) => o.Id != null && x.EventTypeOneLevelId != null && j.Id != null)
+               .Where((x, o, j) => o.StartTime >= StartDate && o.StartTime <= EndDate && o.AreaCode.StartsWith(AreaCode))
+               .Select((x, o, j) => new
+               {
+                   x.Id,
+                   EventTypeId = x.EventTypeOneLevelId,
+                   j.EventTypeName
+               })
+               .MergeTable()
+                .GroupBy(p => new { p.EventTypeId, p.EventTypeName })
+                .Select(p => new OrderEventTypeStatisticsDto
+                {
+                    EventTypeId = p.EventTypeId,
+                    EventTypeName = p.EventTypeName,
+                    SumCount = SqlFunc.AggregateCount(p.Id)
+                })
+                .OrderByDescending(p => p.SumCount)
+                .Take(5)
+                .ToListAsync();
+
+            return list;
+        }
+
+        /// <summary>
+        /// 中上区域地图数据
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-area-query")]
+        public async Task<List<OrderAreaQueryDto>> GetOrderAreaQueryAsync(DateTime StartDate, DateTime EndDate)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var areaList = await _systemAreaRepository.Queryable()
+                .Where(x => !x.Id.EndsWith("00"))
+                .GroupBy(x => new
+                {
+                    Id = x.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                })
+                .Select(x => new
+                {
+                    Id = x.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                })
+                .MergeTable()
+                .LeftJoin<SystemArea>((it, o) => it.Id == o.Id)
+                .Select((it, o) => new
+                {
+                    it.Id,
+                    Name = o.AreaName
+                })
+                .ToListAsync();
+
+            var list = new List<OrderAreaQueryDto>();
+
+            foreach (var item in areaList)
+            {
+                #region 单个获取
+
+                var dto = new OrderAreaQueryDto
+                {
+                    AreaCode = item.Id,
+                    AreaName = item.Name
+                };
+
+                var data = await _enforcementOrdersRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                .Where((x, o) => o.Id != null)
+                .Where((x, o) => o.StartTime >= StartDate && o.StartTime <= EndDate && o.AreaCode.StartsWith(item.Id))
+                .Select((x, o) => new
+                {
+                    x.Id,
+                    x.IsEnforcementOrder,
+                    x.IsTheClueTrue,
+                    x.IsPassTheBuckOrder
+                })
+                .MergeTable()
+                .Select(d => new OrderModuleDto
+                {
+                    OrderCountNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.Id != null, 1, 0)),
+                    EnforcementOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsEnforcementOrder, 1, 0)),
+                    TheClueIsTrueNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == true, 1, 0)),
+                    TheClueIsNotTrueNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == false, 1, 0)),
+                    PassTheBuckOrderNum = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsPassTheBuckOrder, 1, 0))
+                })
+                .ToListAsync();
+                if (data != null && data.Count > 0)
+                {
+                    dto.OrderCountNum = data[0].OrderCountNum;
+                    dto.EnforcementOrderNum = data[0].EnforcementOrderNum;
+                    dto.TheClueIsTrueNum = data[0].TheClueIsTrueNum;
+                    dto.TheClueIsNotTrueNum = data[0].TheClueIsNotTrueNum;
+                    dto.PassTheBuckOrderNum = data[0].PassTheBuckOrderNum;
+                }
+
+                list.Add(dto);
+
+                #endregion
+            }
+            return list;
+        }
+
+        /// <summary>
+        /// 中下行政执法工单概览
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-overview")]
+        public async Task<List<OrderOverviewDto>> GetOrderOverviewAsync()
+        {
+            var list = await _enforcementOrdersRepository.Queryable()
+                  .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                  .LeftJoin<SystemArea>((x, o, p) => o.AreaCode == p.Id)
+                  .Where((x, o, p) => o.Id != null)
+                  .Where((x, o) => o.Status > EOrderStatus.WaitForAccept && o.StartTime.Value.Date == DateTime.Now.Date)
+                  .Select((x, o, p) => new OrderOverviewDto
+                  {
+                      Id = o.Id,
+                      WorkflowId = o.WorkflowId,
+                      SourceChannel = o.SourceChannel,
+                      SourceChannelCode = o.SourceChannelCode,
+                      Title = o.Title,
+                      AcceptType = o.AcceptType,
+                      AcceptTypeCode = o.AcceptTypeCode,
+                      HotspotId = o.HotspotId,
+                      HotspotName = o.HotspotName,
+                      ActualHandleOrgName = o.ActualHandleOrgName,
+                      ActualHandleOrgCode = o.ActualHandleOrgCode,
+                      Status = o.Status,
+                      ActualHandleTime = o.ActualHandleTime,
+                      ExpiredTime = o.ExpiredTime,
+                      NearlyExpiredTime = o.NearlyExpiredTime,
+                      StartTime = o.StartTime,
+                      AreaName = p.AreaName
+                  })
+                  .MergeTable()
+                  .OrderByDescending(d => d.StartTime)
+                  .Take(50)
+                  .ToListAsync();
+
+            return _mapper.Map<List<OrderOverviewDto>>(list);
+        }
+
+        /// <summary>
+        /// 右上部门满意度排行榜
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-visitorg-statistics")]
+        public async Task<List<OrderVisitOrgStatisticsDto>> GetOrderVisitOrgStatisticsAsync(DateTime StartDate, DateTime EndDate)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var list = await _enforcementOrdersRepository.Queryable()
+                 .LeftJoin<OrderVisit>((x, o) => x.Id == o.OrderId)
+                 .LeftJoin<OrderVisitDetail>((x, o, p) => o.Id == p.VisitId)
+                .Where((x, o, p) => o.VisitTime >= StartDate && o.VisitTime <= EndDate && p.VisitTarget == EVisitTarget.Org &&
+                o.VisitState == EVisitState.Visited && !string.IsNullOrEmpty(p.VisitOrgCode) && p.VisitOrgCode.Length >= 6)
+                .Select((x, o, p) => new
+                {
+                    p.VisitOrgCode,
+                    p.VisitOrgName,
+                    p.OrgProcessingResults
+                })
+                .MergeTable()
+                .GroupBy(x => new
+                {
+                    VisitOrgCode = x.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                    x.VisitOrgName
+                })
+                 .Select(x => new OrderVisitOrgStatisticsDto()
+                 {
+                     VisitOrgCode = x.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                     VisitOrgName = x.VisitOrgName,
+                     SatisfiedCount = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(x.OrgProcessingResults, "Key") != "1" && SqlFunc.JsonField(x.OrgProcessingResults, "Key") != "2", 1, 0)),
+                     VisitCount = SqlFunc.AggregateCount(x.VisitOrgName)
+                 }).MergeTable().OrderByDescending(x => x.SatisfiedCount).Take(10).ToListAsync();
+
+            return list;
+        }
+
+        /// <summary>
+        /// 右中工单月份趋势图
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-monthly-trend")]
+        public async Task<List<OrderMonthlyTrendDto>> GetOrderMonthlyTrendStatisticsAsync(int Year)
+        {
+            if (Year == 0)
+                Year = DateTime.Now.Year;
+
+            int monthCount = 12;
+            DateTime StartDate = DateTime.Parse(Year + "-01-01 00:00:00");
+            DateTime EndDate = StartDate.AddYears(1).AddSeconds(-1);
+            if (Year == DateTime.Now.Year)
+            {
+                monthCount = DateTime.Now.Month;
+                EndDate = DateTime.Now;
+            }
+
+            var queryData = await _enforcementOrdersRepository.Queryable()
+                       .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                       .Where((x, o) => o.Id != null)
+                       .Where((x, o) => o.Status > EOrderStatus.WaitForAccept && o.StartTime >= StartDate && o.StartTime <= EndDate)
+                       .Select((x, o) => new
+                       {
+                           o.Id,
+                           Month = o.StartTime.Value.Month
+                       })
+                       .MergeTable()
+                       .GroupBy(x => x.Month)
+                       .Select(x => new OrderMonthlyTrendDto
+                       {
+                           Month = x.Month,
+                           SumCount = SqlFunc.AggregateCount(x.Month)
+                       })
+                       .ToListAsync();
+
+            var orderMonthlyTrends = new List<OrderMonthlyTrendDto>();
+            for (int i = 1; i <= monthCount; i++)
+            {
+                var data = queryData.Find(p => p.Month == i);
+                var SumCount = 0;
+                if (data != null)
+                    SumCount = data.SumCount;
+                orderMonthlyTrends.Add(new OrderMonthlyTrendDto { Month = i, SumCount = SumCount });
+            }
+
+            return orderMonthlyTrends;
+        }
+
+        /// <summary>
+        /// 右下占比分析
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <param name="IsSource"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-proportion-statistics")]
+        public async Task<List<OrderProportionStatisticsDto>> GetOrderProportionStatisticsAsync(DateTime StartDate, DateTime EndDate, bool IsSource)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var SumCount = await _enforcementOrdersRepository.Queryable()
+               .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                .Where((x, o) => o.Id != null)
+               .Where((x, o) => o.StartTime >= StartDate && o.StartTime <= EndDate && o.Status > EOrderStatus.WaitForAccept)
+               .CountAsync();
+
+            if (IsSource)
+            {
+                var list = await _enforcementOrdersRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                .Where((x, o) => o.Id != null)
+                .Where((x, o) => o.StartTime >= StartDate && o.StartTime <= EndDate && o.Status > EOrderStatus.WaitForAccept)
+                .GroupBy((x, o) => new { o.SourceChannelCode, o.SourceChannel })
+                .Select((x, o) => new OrderProportionStatisticsDto()
+                {
+                    Name = o.SourceChannel,
+                    SumCount = SumCount,
+                    HasCount = SqlFunc.AggregateCount(o.SourceChannel)
+                })
+                .ToListAsync();
+                return list;
+            }
+            else
+            {
+                var list = await _enforcementOrdersRepository.Queryable()
+                .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                .Where((x, o) => o.Id != null)
+                .Where((x, o) => o.StartTime >= StartDate && o.StartTime <= EndDate && o.Status > EOrderStatus.WaitForAccept)
+                .GroupBy((x, o) => new { o.AcceptTypeCode, o.AcceptType })
+                .Select((x, o) => new OrderProportionStatisticsDto()
+                {
+                    Name = o.AcceptType,
+                    SumCount = SumCount,
+                    HasCount = SqlFunc.AggregateCount(o.AcceptTypeCode),
+                })
+                .ToListAsync();
+                return list;
+            }
+        }
+
+    }
+}

+ 435 - 8
src/Hotline.Api/Controllers/EnforcementOrderController.cs

@@ -8,11 +8,11 @@ using Hotline.Settings;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.JudicialManagement;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
 using MapsterMapper;
 using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
-using System.Diagnostics.Eventing.Reader;
 using XF.Domain.Authentications;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
@@ -22,7 +22,6 @@ namespace Hotline.Api.Controllers
 {
     public class EnforcementOrderController : BaseController
     {
-        private readonly IRepository<EnforcementOrderHander> _enforcementOrderHanderRepository;
         private readonly IRepository<EnforcementOrders> _enforcementOrdersRepository;
         private readonly IRepository<JudicialComplaintsEventType> _judicialComplaintsEventTypeRepository;
         private readonly IRepository<EnforcementHotspot> _enforcementHotspotRepository;
@@ -32,8 +31,18 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<SystemOrganize> _systemOrganizeRepository;
         private readonly ISessionContext _sessionContext;
 
-        public EnforcementOrderController(IRepository<EnforcementOrderHander> enforcementOrderHanderRepository,
-         IRepository<EnforcementOrders> enforcementOrdersRepository,
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="enforcementOrdersRepository"></param>
+        /// <param name="judicialComplaintsEventTypeRepository"></param>
+        /// <param name="enforcementHotspotRepository"></param>
+        /// <param name="mapper"></param>
+        /// <param name="workflowApplication"></param>
+        /// <param name="sysDicDataCacheManager"></param>
+        /// <param name="systemOrganizeRepository"></param>
+        /// <param name="sessionContext"></param>
+        public EnforcementOrderController(IRepository<EnforcementOrders> enforcementOrdersRepository,
          IRepository<JudicialComplaintsEventType> judicialComplaintsEventTypeRepository,
          IRepository<EnforcementHotspot> enforcementHotspotRepository,
          IMapper mapper,
@@ -42,7 +51,6 @@ namespace Hotline.Api.Controllers
          IRepository<SystemOrganize> systemOrganizeRepository,
          ISessionContext sessionContext)
         {
-            _enforcementOrderHanderRepository = enforcementOrderHanderRepository;
             _enforcementOrdersRepository = enforcementOrdersRepository;
             _judicialComplaintsEventTypeRepository = judicialComplaintsEventTypeRepository;
             _enforcementHotspotRepository = enforcementHotspotRepository;
@@ -67,6 +75,7 @@ namespace Hotline.Api.Controllers
            .WhereIF(dto.IsEnforcementOrder.HasValue, d => d.IsEnforcementOrder == dto.IsEnforcementOrder)//是否行政执法类
            .WhereIF(dto.IsPassTheBuckOrder.HasValue, d => d.IsPassTheBuckOrder == dto.IsPassTheBuckOrder)//是否推诿
            .WhereIF(dto.IsTheClueTrue.HasValue, d => d.IsTheClueTrue == dto.IsTheClueTrue)//线索是否属实
+           .WhereIF(!string.IsNullOrEmpty(dto.EventTypeId), d => dto.EventTypeId == d.EventTypeId)//事项分类
            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Order.Title.Contains(dto.Title!)) //标题
            .WhereIF(!string.IsNullOrEmpty(dto.ProvinceNo), d => d.Order.ProvinceNo.Contains(dto.ProvinceNo)) //省本地编号
            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Order.No.Contains(dto.No)) //工单编码
@@ -177,6 +186,11 @@ namespace Hotline.Api.Controllers
                 else
                     order.EventTypeName = null;
 
+                if (!string.IsNullOrEmpty(dto.EventTypeSpliceName))
+                    order.EventTypeSpliceName = dto.EventTypeSpliceName;
+                else
+                    order.EventTypeSpliceName = null;
+
                 if (!string.IsNullOrEmpty(dto.EventTypeId))
                 {
                     order.EventTypeOneLevelId = dto.EventTypeId.Substring(0, 2);
@@ -185,7 +199,7 @@ namespace Hotline.Api.Controllers
                 else
                 {
                     order.EventTypeOneLevelId = null;
-                    order.EventTypeOneLevelId = null;
+                    order.EventTypeId = null;
                 }
                 await _enforcementOrdersRepository.UpdateAsync(order, HttpContext.RequestAborted);
             }
@@ -201,6 +215,84 @@ namespace Hotline.Api.Controllers
             return await _judicialComplaintsEventTypeRepository.Queryable().ToTreeAsync(it => it.Children, it => it.ParentId, null, it => it.Id);
         }
 
+        /// <summary>
+        /// 事项分类统计
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <param name="Id"></param>
+        /// <param name="AreaCode"></param>
+        /// <returns></returns>
+        [HttpGet("event_classification_statistics")]
+        public async Task<object> GetEventClassificationStatisticsAsync(DateTime StartDate, DateTime EndDate, string Id, string AreaCode)
+        {
+
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var items = await _judicialComplaintsEventTypeRepository.Queryable()
+             .LeftJoin<EnforcementOrders>((x, o) => o.EventTypeSpliceName != null && (x.EventTypeName == o.EventTypeSpliceName || o.EventTypeSpliceName.Contains(x.EventTypeName)))
+            .LeftJoin<Order>((x, o, p) => p.Id == o.Id)
+            .Where((x, o, p) => p.StartTime >= StartDate && p.StartTime <= EndDate)
+            .WhereIF(!string.IsNullOrEmpty(AreaCode), (x, o, p) => p.AreaCode == AreaCode)
+             .Where((x, o, p) => x.ParentId == Id)
+             .GroupBy((x, o, p) => new { x.Id, x.EventTypeName })
+             .Select((x, o, p) => new
+             {
+                 Id = x.Id,
+                 Name = x.EventTypeName,
+                 Num = SqlFunc.AggregateSum(SqlFunc.IIF(p.Id != null, 1, 0)),
+                 Sublevel = SqlFunc.AggregateSum(SqlFunc.IIF(x.EventTypeName != o.EventTypeName, 1, 0)) > 0,
+             })
+             .MergeTable()
+             .ToListAsync();
+
+            var data = await _enforcementOrdersRepository.Queryable()
+                .Includes(x => x.Order)
+                .Where(d => d.Order.Id != null)
+                .Where(d => d.Order.StartTime >= StartDate && d.Order.StartTime <= EndDate)
+                .GroupBy(d => d.Id)
+                 .Select(d => new EventClassificationOrderCountDto
+                 {
+                     TheClueIsTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == true, 1, 0)),
+                     TheClueIsNotTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == false, 1, 0)),
+                     EnforcementOrder = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsEnforcementOrder, 1, 0)),
+                     PassTheBuckOrder = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsPassTheBuckOrder, 1, 0))
+                 })
+                   .ToListAsync();
+
+            var orderCount = new
+            {
+                TheClueIsTrue = data.Sum(x => x.TheClueIsTrue),
+                TheClueIsNotTrue = data.Sum(x => x.TheClueIsNotTrue),
+                EnforcementOrder = data.Sum(x => x.EnforcementOrder),
+                PassTheBuckOrder = data.Sum(x => x.PassTheBuckOrder),
+            };
+
+            return new { List = items,  OrderCount = orderCount };
+        }
+
+        /// <summary>
+        /// 事项分类统计--明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("event_classification_statistics_order_list")]
+        public async Task<PagedDto<EnforcementOrderListDto>> GetEventClassificationStatisticsOrderListAsync([FromQuery] QueryEventClassificationStatisticsDto dto)
+        {
+            dto.EndDate = dto.EndDate.AddDays(1).AddSeconds(-1);
+
+            var (total, items) = await _enforcementOrdersRepository.Queryable()
+            .Includes(x => x.Order)
+            .Where(d => d.Order.Id != null)
+            .Where(d => d.Order.StartTime >= dto.StartDate && d.Order.StartTime <= dto.EndDate && d.EventTypeId != null)
+            .WhereIF(!string.IsNullOrEmpty(dto.EventTypeId), d => d.EventTypeId.StartsWith(dto.EventTypeId))
+            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.Order.AreaCode == dto.AreaCode)
+              .OrderByDescending(d => d.CreationTime)
+           .ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<EnforcementOrderListDto>(total, _mapper.Map<List<EnforcementOrderListDto>>(items));
+        }
+
         /// <summary>
         /// 执法部门办件统计
         /// </summary>
@@ -215,7 +307,7 @@ namespace Hotline.Api.Controllers
             var data = await _enforcementOrdersRepository.Queryable()
                    .Includes(x => x.Order)
                    .Where(d => d.Order.Id != null)
-                   .Where(d => d.Order.CreationTime >= StartDate && d.Order.CreationTime <= EndDate)
+                   .Where(d => d.Order.StartTime >= StartDate && d.Order.StartTime <= EndDate)
                     .GroupBy(d => new
                     {
                         OrgCode = d.Order.OrgLevelOneCode,
@@ -223,9 +315,10 @@ namespace Hotline.Api.Controllers
                     })
                     .Select(d => new
                     {
+                        OrgCode = d.Order.OrgLevelOneCode,
                         OrgName = d.Order.OrgLevelOneName,
                         CountNum = SqlFunc.AggregateCount(d.Order.OrgLevelOneCode),
-                        TheClueIsTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value==true, 1, 0)),
+                        TheClueIsTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == true, 1, 0)),
                         TheClueIsNotTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == false, 1, 0)),
                         EnforcementOrder = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsEnforcementOrder, 1, 0))
                     })
@@ -233,5 +326,339 @@ namespace Hotline.Api.Controllers
 
             return data;
         }
+
+        /// <summary>
+        /// 执法部门办件统计---子级
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <param name="OrgCode"></param>
+        /// <returns></returns>
+        [HttpGet("enforcement_departmental_processing_statistics_child")]
+        public async Task<object> GetDepartmentalProcessingStatisticsChildAsync(DateTime StartDate, DateTime EndDate, string OrgCode)
+        {
+
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+            if (string.IsNullOrEmpty(OrgCode))
+                throw UserFriendlyException.SameMessage("部门code不能为空!");
+
+            var data = await _enforcementOrdersRepository.Queryable()
+                   .Includes(x => x.Order)
+                   .Where(d => d.Order.Id != null)
+                   .Where(d => d.Order.StartTime >= StartDate && d.Order.StartTime <= EndDate)
+                   .WhereIF(OrgCode == "001", d => d.Order.ActualHandleOrgCode == OrgCode)
+                   .WhereIF(OrgCode != "001", d => d.Order.ActualHandleOrgCode.StartsWith(OrgCode))
+                    .GroupBy(d => new
+                    {
+                        OrgCode = d.Order.ActualHandleOrgCode,
+                        OrgName = d.Order.ActualHandleOrgName
+                    })
+                    .Select(d => new
+                    {
+                        OrgCode = d.Order.ActualHandleOrgCode,
+                        OrgName = d.Order.ActualHandleOrgName,
+                        CountNum = SqlFunc.AggregateCount(d.Order.ActualHandleOrgCode),
+                        TheClueIsTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == true, 1, 0)),
+                        TheClueIsNotTrue = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsTheClueTrue.HasValue && d.IsTheClueTrue.Value == false, 1, 0)),
+                        EnforcementOrder = SqlFunc.AggregateSum(SqlFunc.IIF(d.IsEnforcementOrder, 1, 0)),
+
+                    })
+                   .ToListAsync();
+
+            return data;
+        }
+
+        /// <summary>
+        /// 执法部门办件统计---明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("enforcement_departmental_processing_statistics_order_list")]
+        public async Task<PagedDto<EnforcementOrderListDto>> GetDepartmentalProcessingStatisticsOrderListAsync([FromQuery] QueryDepartmentalProcessingStatisticsDto dto)
+        {
+            dto.EndDate = dto.EndDate.AddDays(1).AddSeconds(-1);
+
+            var (total, items) = await _enforcementOrdersRepository.Queryable()
+            .Includes(x => x.Order)
+            .Where(d => d.Order.Id != null)
+            .Where(d => d.Order.StartTime >= dto.StartDate && d.Order.StartTime <= dto.EndDate)
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && dto.OrgCode == "001", d => d.Order.ActualHandleOrgCode == dto.OrgCode)
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && dto.OrgCode != "001", d => d.Order.ActualHandleOrgCode.StartsWith(dto.OrgCode))
+            .WhereIF(dto.TheClueIsTrue == true, d => d.IsTheClueTrue == true)
+            .WhereIF(dto.TheClueIsNotTrue == true, d => d.IsTheClueTrue == false)
+            .WhereIF(dto.EnforcementOrder == true, d => d.IsEnforcementOrder == true)
+              .OrderByDescending(d => d.CreationTime)
+           .ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<EnforcementOrderListDto>(total, _mapper.Map<List<EnforcementOrderListDto>>(items));
+        }
+
+        /// <summary>
+        /// 区域分类统计
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("regional_classification_statistics")]
+        public async Task<object> GetRegionalClassificationStatisticsAsync(DateTime StartDate, DateTime EndDate)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var list = await _enforcementOrdersRepository.Queryable()
+               .Includes(x => x.Order)
+               .Where(x => x.Order.Id != null)
+               .Where(x => x.Order.StartTime >= StartDate && x.Order.StartTime <= EndDate)
+               .LeftJoin<SystemArea>((x, o) => x.Order.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) == o.Id)
+               .Where((x, o) => x.Order.Status >= EOrderStatus.Filed)
+               .GroupBy((x, o) => new
+               {
+                   AreaCode = x.Order.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                   o.AreaName,
+               })
+               .Select((x, o) => new
+               {
+                   AreaCode = x.Order.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                   AreaName = o.AreaName,
+                   OrderCountNum = SqlFunc.AggregateCount(x.Order.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6"))),
+                   TheClueIsTrue = SqlFunc.AggregateSum(SqlFunc.IIF(x.IsTheClueTrue.HasValue && x.IsTheClueTrue.Value == true, 1, 0)),
+                   TheClueIsNotTrue = SqlFunc.AggregateSum(SqlFunc.IIF(x.IsTheClueTrue.HasValue && x.IsTheClueTrue.Value == false, 1, 0)),
+                   EnforcementOrder = SqlFunc.AggregateSum(SqlFunc.IIF(x.IsEnforcementOrder, 1, 0))
+               }).MergeTable()
+                .Where(x => x.AreaCode != "519800" && x.AreaCode != "519900")
+                .OrderByDescending(it => it.OrderCountNum)
+                .ToListAsync();
+            return list;
+        }
+
+        /// <summary>
+        /// 区域分类统计---明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("regional_classification_statistics_order_list")]
+        public async Task<PagedDto<EnforcementOrderListDto>> GetRegionalClassificationStatisticsOrderListAsync([FromQuery] QueryRegionalClassificationStatisticsDto dto)
+        {
+            dto.EndDate = dto.EndDate.AddDays(1).AddSeconds(-1);
+
+            var (total, items) = await _enforcementOrdersRepository.Queryable()
+            .Includes(x => x.Order)
+            .Where(d => d.Order.Id != null)
+            .Where(d => d.Order.StartTime >= dto.StartDate && d.Order.StartTime <= dto.EndDate)
+            .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.Order.AreaCode == dto.AreaCode)
+            .WhereIF(dto.TheClueIsTrue == true, d => d.IsTheClueTrue == true)
+            .WhereIF(dto.TheClueIsNotTrue == true, d => d.IsTheClueTrue == false)
+            .WhereIF(dto.EnforcementOrder == true, d => d.IsEnforcementOrder == true)
+              .OrderByDescending(d => d.CreationTime)
+           .ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<EnforcementOrderListDto>(total, _mapper.Map<List<EnforcementOrderListDto>>(items));
+        }
+
+        /// <summary>
+        /// 部门满意度统计
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <param name="TypeId">1:办件结果 2:办件态度</param>
+        /// <returns></returns>
+        [HttpGet("enforcement_visit_org_satisfaction_statistics")]
+        public async Task<object> GetVisitAndOrgSatisfactionStatisticsAsync(DateTime StartDate, DateTime EndDate, int TypeId)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var list = await _enforcementOrdersRepository.Queryable()
+                 .LeftJoin<OrderVisit>((x, o) => x.Id == o.OrderId)
+                 .LeftJoin<OrderVisitDetail>((x, o, p) => o.Id == p.VisitId)
+                .Where((x, o, p) => o.VisitTime >= StartDate && o.VisitTime <= EndDate && p.VisitTarget == EVisitTarget.Org && o.VisitState == EVisitState.Visited && !string.IsNullOrEmpty(p.VisitOrgCode))
+                .GroupBy((x, o, p) => new
+                {
+                    VisitOrgCode = p.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6"))
+                })
+                .Select((x, o, p) => new VisitAndOrgSatisfactionStatisticsDto()
+                {
+                    OrgCode = p.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                    TotalSumCount = SqlFunc.AggregateCount(p.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6"))),
+                    VerySatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "5", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "5", 1, 0))),//非常满意数
+                    SatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "4", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "4", 1, 0))), //满意数
+                    RegardedAsSatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "-1", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "-1", 1, 0))),//视为满意
+                    DefaultSatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "0", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "0", 1, 0))),//默认满意
+                    NoSatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "2", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "2", 1, 0))),//不满意
+                    NoEvaluateCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "7", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "7", 1, 0))),//未做评价
+                    NoPutThroughCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "6", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "6", 1, 0))),//未接通
+                })
+                .MergeTable()
+                .LeftJoin<SystemOrganize>((it, o) => it.OrgCode == o.Id)
+                .Select((it, o) => new VisitAndOrgSatisfactionStatisticsDto()
+                {
+                    OrgName = o.Name,
+                    OrgCode = it.OrgCode,
+                    OrgType = o.OrgType,
+                    TotalSumCount = it.TotalSumCount,
+                    VerySatisfiedCount = it.VerySatisfiedCount,//非常满意数
+                    SatisfiedCount = it.SatisfiedCount, //满意数
+                    RegardedAsSatisfiedCount = it.RegardedAsSatisfiedCount,//视为满意
+                    DefaultSatisfiedCount = it.DefaultSatisfiedCount,//默认满意
+                    NoSatisfiedCount = it.NoSatisfiedCount,//不满意
+                    NoEvaluateCount = it.NoEvaluateCount,//未做评价
+                    NoPutThroughCount = it.NoPutThroughCount,//未接通
+                })
+                .ToListAsync();
+
+
+            var sumModel = new VisitAndOrgSatisfactionStatisticsDto()
+            {
+                OrgName = "总计",
+                TotalSumCount = list.Sum(x => x.TotalSumCount),
+                VerySatisfiedCount = list.Sum(x => x.VerySatisfiedCount),
+                SatisfiedCount = list.Sum(x => x.SatisfiedCount),
+                RegardedAsSatisfiedCount = list.Sum(x => x.RegardedAsSatisfiedCount),
+                DefaultSatisfiedCount = list.Sum(x => x.DefaultSatisfiedCount),
+                NoSatisfiedCount = list.Sum(x => x.NoSatisfiedCount),
+                NoEvaluateCount = list.Sum(x => x.NoEvaluateCount),
+                NoPutThroughCount = list.Sum(x => x.NoPutThroughCount),
+            };
+
+
+            return new { DataList = list, SumModel = sumModel };
+        }
+
+        /// <summary>
+        /// 部门满意度统计----子级
+        /// </summary>
+        /// <param name="StartDate"></param>
+        /// <param name="EndDate"></param>
+        /// <param name="TypeId">1:办件结果 2:办件态度</param>
+        /// <param name="OrgCode"></param>
+        /// <returns></returns>
+        [HttpGet("enforcement_visit_org_satisfaction_statistics_child")]
+        public async Task<object> GetVisitAndOrgSatisfactionStatisticsChildAsync(DateTime StartDate, DateTime EndDate, int TypeId, string OrgCode)
+        {
+            EndDate = EndDate.AddDays(1).AddSeconds(-1);
+
+            var list = await _enforcementOrdersRepository.Queryable()
+                 .LeftJoin<OrderVisit>((x, o) => x.Id == o.OrderId)
+                 .LeftJoin<OrderVisitDetail>((x, o, p) => o.Id == p.VisitId)
+                 .Where((x, o, p) => o.VisitTime >= StartDate && o.VisitTime <= EndDate && p.VisitTarget == EVisitTarget.Org)
+                 .Where((x, o, p) => o.VisitState == EVisitState.Visited && !string.IsNullOrEmpty(p.VisitOrgCode))
+                 .WhereIF(!string.IsNullOrEmpty(OrgCode) && OrgCode == "001", (x, o, p) => p.VisitOrgCode == OrgCode)
+                 .WhereIF(!string.IsNullOrEmpty(OrgCode) && OrgCode != "001", (x, o, p) => p.VisitOrgCode.StartsWith(OrgCode))
+                .GroupBy((x, o, p) => new
+                {
+                    VisitOrgCode = p.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("9"))
+                })
+                .Select((x, o, p) => new VisitAndOrgSatisfactionStatisticsDto()
+                {
+                    OrgCode = p.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("9")),
+                    TotalSumCount = SqlFunc.AggregateCount(p.VisitOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("9"))),
+                    VerySatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "5", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "5", 1, 0))),//非常满意数
+                    SatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "4", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "4", 1, 0))), //满意数
+                    RegardedAsSatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "-1", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "-1", 1, 0))),//视为满意
+                    DefaultSatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "0", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "0", 1, 0))),//默认满意
+                    NoSatisfiedCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "2", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "2", 1, 0))),//不满意
+                    NoEvaluateCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "7", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "7", 1, 0))),//未做评价
+                    NoPutThroughCount = SqlFunc.IIF(TypeId == 1, SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgProcessingResults, "Key") == "6", 1, 0)), SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == "6", 1, 0))),//未接通
+                })
+                .MergeTable()
+                .LeftJoin<SystemOrganize>((it, o) => it.OrgCode == o.Id)
+                .Select((it, o) => new VisitAndOrgSatisfactionStatisticsDto()
+                {
+                    OrgName = o.Name,
+                    OrgCode = it.OrgCode,
+                    OrgType = o.OrgType,
+                    TotalSumCount = it.TotalSumCount,
+                    VerySatisfiedCount = it.VerySatisfiedCount,//非常满意数
+                    SatisfiedCount = it.SatisfiedCount, //满意数
+                    RegardedAsSatisfiedCount = it.RegardedAsSatisfiedCount,//视为满意
+                    DefaultSatisfiedCount = it.DefaultSatisfiedCount,//默认满意
+                    NoSatisfiedCount = it.NoSatisfiedCount,//不满意
+                    NoEvaluateCount = it.NoEvaluateCount,//未做评价
+                    NoPutThroughCount = it.NoPutThroughCount,//未接通
+                })
+                .ToListAsync();
+
+
+            var sumModel = new VisitAndOrgSatisfactionStatisticsDto()
+            {
+                OrgName = "总计",
+                TotalSumCount = list.Sum(x => x.TotalSumCount),
+                VerySatisfiedCount = list.Sum(x => x.VerySatisfiedCount),
+                SatisfiedCount = list.Sum(x => x.SatisfiedCount),
+                RegardedAsSatisfiedCount = list.Sum(x => x.RegardedAsSatisfiedCount),
+                DefaultSatisfiedCount = list.Sum(x => x.DefaultSatisfiedCount),
+                NoSatisfiedCount = list.Sum(x => x.NoSatisfiedCount),
+                NoEvaluateCount = list.Sum(x => x.NoEvaluateCount),
+                NoPutThroughCount = list.Sum(x => x.NoPutThroughCount),
+            };
+
+
+            return new { DataList = list, SumModel = sumModel };
+        }
+
+        /// <summary>
+        /// 部门满意度统计---明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("enforcement_visit_org_satisfaction_statistics_order_list")]
+        public async Task<PagedDto<EnforcementOrgSatisfactionOrderListDto>> GetVisitAndOrgSatisfactionStatisticsOrderListAsync([FromQuery] QueryOrgSatisfactionStatisticsDto dto)
+        {
+            dto.EndDate = dto.EndDate.AddDays(1).AddSeconds(-1);
+
+            var (total, items) = await _enforcementOrdersRepository.Queryable()
+                .Includes(x => x.Order)
+                .Where(x => x.Order.Id != null)
+                .LeftJoin<OrderVisit>((x, o) => x.Id == o.OrderId)
+                .LeftJoin<OrderVisitDetail>((x, o, p) => o.Id == p.VisitId)
+                .Where((x, o, p) => o.VisitTime >= dto.StartDate && o.VisitTime <= dto.EndDate && p.VisitTarget == EVisitTarget.Org)
+                .Where((x, o, p) => o.VisitState == EVisitState.Visited && !string.IsNullOrEmpty(p.VisitOrgCode))
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && dto.OrgCode == "001", (x, o, p) => p.VisitOrgCode == dto.OrgCode)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && dto.OrgCode != "001", (x, o, p) => p.VisitOrgCode.StartsWith(dto.OrgCode))
+                 .WhereIF(dto.TypeId == 1, (x, o, p) => SqlFunc.JsonField(p.OrgProcessingResults, "Key") == dto.DateValue)
+                .WhereIF(dto.TypeId == 2, (x, o, p) => SqlFunc.JsonField(p.OrgHandledAttitude, "Key") == dto.DateValue)
+                .Select((x, o, p) => new EnforcementOrgSatisfactionOrderListDto
+                {
+                    Id = x.Id,
+                    WorkflowId = x.Order.WorkflowId,
+                    VisitId = o.Id,
+                    Status = x.Order.Status,
+                    No = x.Order.No,
+                    SourceChannel = x.Order.SourceChannel,
+                    SourceChannelCode = x.Order.SourceChannelCode,
+                    FromPhone = x.Order.FromPhone,
+                    TransferPhone = x.Order.TransferPhone,
+                    FromName = x.Order.FromName,
+                    Contact = x.Order.Contact,
+                    AcceptType = x.Order.AcceptType,
+                    AcceptTypeCode = x.Order.AcceptTypeCode,
+                    EmergencyLevel = x.Order.EmergencyLevel,
+                    Title = x.Order.Title,
+                    HotspotId = x.Order.HotspotId,
+                    HotspotName = x.Order.HotspotName,
+                    HotspotSpliceName = x.Order.HotspotSpliceName,
+                    CreationTime = x.Order.CreationTime,
+                    IsProvince = x.Order.IsProvince,
+                    ProvinceNo = x.Order.ProvinceNo,
+                    ActualHandleStepName = x.Order.ActualHandleStepName,
+                    ActualHandleTime = x.Order.ActualHandleTime,
+                    ExpiredTime = x.Order.ExpiredTime,
+                    NearlyExpiredTime = x.Order.NearlyExpiredTime,
+                    FiledTime = x.Order.FiledTime,
+                    OrgLevelOneCode = x.Order.OrgLevelOneCode,
+                    OrgLevelOneName = x.Order.OrgLevelOneName,
+                    ActualHandlerName = x.Order.ActualHandlerName,
+                    StartTime = x.Order.StartTime,
+                    ActualHandleOrgName = x.Order.ActualHandleOrgName,
+                    ActualHandleOrgCode = x.Order.ActualHandleOrgCode,
+                    IsEnforcementOrder = x.IsEnforcementOrder,
+                    IsPassTheBuckOrder = x.IsPassTheBuckOrder,
+                    IsTheClueTrue = x.IsTheClueTrue,
+                    EventTypeName = x.EventTypeName,
+                    EventTypeId = x.EventTypeId,
+                    EventTypeSpliceName = x.EventTypeSpliceName,
+                })
+               .MergeTable()
+               .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
+            return new PagedDto<EnforcementOrgSatisfactionOrderListDto>(total, _mapper.Map<IReadOnlyList<EnforcementOrgSatisfactionOrderListDto>>(items));
+
+        }
     }
 }

+ 12 - 2
src/Hotline.Api/Controllers/HomeController.cs

@@ -62,16 +62,26 @@ public class HomeController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpGet("myinfo")]
-    public async Task<User?> GetMyInfo()
+    public async Task<Object> GetMyInfo()
     {
         if (!string.IsNullOrEmpty(_sessionContext.UserId))
         {
-            return await _userRepository.Queryable()
+	        var monitor = false;
+	        var setting = _systemSettingCacheManager.GetSetting(SettingConstants.SeatsMonitor);
+	        var settingStr = setting?.SettingValue;
+	        var roles = _sessionContext.Roles;
+	        foreach (var item in settingStr)
+	        {
+		        if (roles != null && roles.Contains(item)) monitor = true;
+	        }
+
+			var user = await _userRepository.Queryable()
                 .Includes(x => x.Organization)
                 .Includes(x => x.Account)
                 .Includes(x => x.Roles)
                 .FirstAsync(x => x.Id == _sessionContext.UserId);
             //return await _userRepository.GetAsync(_sessionContext.UserId);
+            return new { User = user, Monitor = monitor };
         }
         throw UserFriendlyException.SameMessage("无效登录信息");
     }

+ 134 - 48
src/Hotline.Api/Controllers/OrderController.cs

@@ -1443,8 +1443,23 @@ public class OrderController : BaseController
             .LeftJoin<OrderScreen>((x, s) => x.Id == s.VisitDetailId && (int)s.Status < 2 && s.IsDeleted == false)
             .Where((x, s) => s.Id == null)
             .WhereIF(dto.IsHomePage.HasValue && dto.IsHomePage == true, x => x.OrderVisit.VisitTime < dto.CreationTimeEnd && x.OrderVisit.VisitTime > dto.CreationTimeStart)
-            .Where((x, s) => x.OrderVisit.VisitState != EVisitState.None && x.OrderVisit.IsCanHandle);
-        if (_sessionContext.OrgId != null && !_sessionContext.OrgIsCenter)
+            .WhereIF(!string.IsNullOrEmpty(dto.No),x=>x.OrderVisit.Order!.No!.Contains(dto.No!))
+            .WhereIF(dto.IsProvince.HasValue,x=> x.OrderVisit.Order!.IsProvince  == dto.IsProvince)
+			.WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order!.Title!.Contains(dto.Title!))
+            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.OrderVisit.Order!.AcceptType!.Contains(dto.AcceptType!))
+            .WhereIF(dto.CounterSignType.HasValue ,x=> x.OrderVisit.Order!.CounterSignType == dto.CounterSignType)
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.OrderVisit.Order!.OrgLevelOneName!.Contains(dto.OrgLevelOneName!))
+            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleStepName), x => x.OrderVisit.Order!.ActualHandleStepName!.Contains(dto.ActualHandleStepName!))
+            .WhereIF(dto.ActualHandleTime.HasValue && dto.EndActualHandleTime.HasValue, x => x.OrderVisit.Order!.ActualHandleTime >= dto.ActualHandleTime && x.OrderVisit.Order!.ActualHandleTime <= dto.EndActualHandleTime)
+            .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.JsonListObjectAny(x.OrgProcessingResults, "Value", dto.OrgProcessingResults))
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgHandledAttitude), x => SqlFunc.JsonListObjectAny(x.OrgHandledAttitude, "Value", dto.OrgHandledAttitude))
+            .WhereIF(!string.IsNullOrEmpty(dto.OrgNoSatisfiedReason), x => SqlFunc.JsonListObjectAny(x.OrgNoSatisfiedReason, "Value", dto.OrgNoSatisfiedReason))
+			.Where((x, s) => x.OrderVisit.VisitState != EVisitState.None && x.OrderVisit.IsCanHandle);
+		if (_sessionContext.OrgId != null && !_sessionContext.OrgIsCenter)
         {
             query.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
                     (x, s) => x.OrderVisit.Order.Title.Contains(dto.Keyword!) ||
@@ -1663,7 +1678,8 @@ public class OrderController : BaseController
         {
             ScreenStatus = EnumExts.GetDescriptions<EScreenStatus>(),
             ScreenType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.ScreenType),
-        };
+            CounterSignType = EnumExts.GetDescriptions<ECounterSignType>(),
+		};
         return rsp;
     }
 
@@ -2166,7 +2182,7 @@ public class OrderController : BaseController
             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, x => x.IsProvince == true)
             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, x => x.IsProvince == false)
             .WhereIF(!string.IsNullOrEmpty(dto.SensitiveWord),x=> SqlFunc.JsonArrayAny(x.Sensitive, dto.SensitiveWord))
-            .WhereIF(dto.IsSensitiveWord.HasValue  && dto.IsSensitiveWord  == true, x => x.Sensitive != null)
+            .WhereIF(dto.IsSensitiveWord.HasValue  && dto.IsSensitiveWord  == true, x => x.Sensitive != null && SqlFunc.JsonArrayLength(x.Sensitive) > 0)
 			.OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto, HttpContext.RequestAborted);
 
@@ -2506,8 +2522,9 @@ public class OrderController : BaseController
 
         try
         {
-            // 平均派单
-            if (dto.Workflow.BusinessType == EBusinessType.Send)
+			// 平均派单
+			var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+			if (dto.Workflow.BusinessType == EBusinessType.Send && averageSendOrder)
             {
                 dto.Workflow.NextHandlers = await _orderDomainService.AverageOrder(HttpContext.RequestAborted);
             }
@@ -2611,14 +2628,14 @@ public class OrderController : BaseController
     /// 查询工单办理下一步可选节点(带推荐部门)
     /// </summary>
     [HttpGet("nextsteps/{orderId}")]
-    public async Task<NextStepsDto<RecommendStepOption>> GetNextStepsWithRecommend(string orderId)
+    public async Task<NextStepsWithOpinionDto<RecommendStepOption>> GetNextStepsWithRecommend(string orderId)
     {
         var order = await _orderDomainService.GetOrderAsync(orderId, cancellationToken: HttpContext.RequestAborted);
         if (string.IsNullOrEmpty(order.WorkflowId))
             throw UserFriendlyException.SameMessage("该工单未开启流程");
         var dto = await _workflowApplication.GetNextStepsAsync(order.WorkflowId, HttpContext.RequestAborted);
         dto.ExpiredTime = order.ExpiredTime;
-        var rsp = _mapper.Map<NextStepsDto<RecommendStepOption>>(dto);
+        var rsp = _mapper.Map<NextStepsWithOpinionDto<RecommendStepOption>>(dto);
         foreach (var step in rsp.Steps)
         {
             if (dto.CurrentStepBusinessType is not EBusinessType.Send ||
@@ -2780,7 +2797,7 @@ public class OrderController : BaseController
         var isHandled = dto.IsHandled.HasValue && dto.IsHandled.Value;
         var (total, items) = await _orderRepository
             .Queryable(viewFilter: true, handlerFilter: !isHandled)
-            //.Includes(d => d.OrderSpecials)
+            .Includes(d => d.OrderSpecials)
             .WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.No.Contains(dto.Keyword) || d.Title.Contains(dto.Keyword))
             .WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
@@ -2789,11 +2806,9 @@ public class OrderController : BaseController
             .WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == false, d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now)//即将超期 未办
             .Where(d => d.Source < ESource.MLSQ || d.Source > ESource.WZSC)
             .Where(d => d.Status != EOrderStatus.BackToProvince)
-            //.Where(d=>d.OrderSpecials.Any(s=>s.State != 0 || s.Id ==  null ||  s.IsDeleted == null ))
-            //.LeftJoin(_orderSpecialRepository.Queryable(includeDeleted: true), (d, s) => d.Id == s.OrderId)
-            //.Where((d, s) => s.State != 0 || s.Id == null)
+            .Where(d => d.OrderSpecials.Any() == false ||  d.OrderSpecials.Any(s => s.State == 0) == false)
             .OrderByDescending(d => d.StartTime)
-            .ToPagedListAsync(dto, HttpContext.RequestAborted);
+			.ToPagedListAsync(dto, HttpContext.RequestAborted);
 
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
@@ -2830,14 +2845,69 @@ public class OrderController : BaseController
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
 
-    #endregion
-
-    #region 业务工单退回
-
     /// <summary>
-    /// 工单退回退回(返回前一节点)
+    /// 首页查询
     /// </summary>
-    [HttpPost("order_previous")]
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("waited/home")]
+
+    public async Task<Object> QueryWaitedHome([FromQuery] QueryOrderWaitedDto dto) {
+
+		var isHandled = dto.IsHandled.HasValue && dto.IsHandled.Value;
+		var (total, items) = await _orderRepository
+			.Queryable(viewFilter: true, handlerFilter: !isHandled)
+			.Includes(d => d.OrderSpecials)
+			.WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
+			.WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.No.Contains(dto.Keyword) || d.Title.Contains(dto.Keyword))
+			.WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
+			.WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => !d.CounterSignType.HasValue)
+			.WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == true, d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) || (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //超期 未办
+			.WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == false, d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now)//即将超期 未办
+			.Where(d => d.Source < ESource.MLSQ || d.Source > ESource.WZSC)
+			.Where(d => d.Status != EOrderStatus.BackToProvince)
+			.Where(d => d.OrderSpecials.Any() == false || d.OrderSpecials.Any(s => s.State == 0) == false)
+			.OrderByDescending(d => d.StartTime)
+			.ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+		var page1=  new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
+
+		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)
+			.ToArray();
+
+		var (total2, items2) = await _orderRepository.Queryable(viewFilter: false)
+			.WhereIF(dto.IsProvince.HasValue, d => d.IsProvince == dto.IsProvince)
+			.WhereIF(dto.IsHandled.HasValue, d => handleStatuses.Contains(d.Status))
+			.WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.No.Contains(dto.Keyword!) || d.Title.Contains(dto.Keyword!))
+			.Where(d => string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)
+			.WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == true, d => d.CounterSignType.HasValue)
+			.WhereIF(dto.IsCounterSign.HasValue && dto.IsCounterSign == false, d => !d.CounterSignType.HasValue)
+			.WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == true, d => (d.ExpiredTime < DateTime.Now && d.Status < EOrderStatus.Filed) || (d.ExpiredTime < d.ActualHandleTime && d.Status >= EOrderStatus.Filed)) //超期 未办
+			.WhereIF(dto.ExpiredOrAlmostOverdue.HasValue && dto.ExpiredOrAlmostOverdue == false, d => d.NearlyExpiredTime < DateTime.Now && d.ExpiredTime > DateTime.Now)//即将超期 未办
+			.Where(x => x.Source < ESource.MLSQ || x.Source > ESource.WZSC)
+			.Where(x => x.Status != EOrderStatus.BackToProvince)
+			.OrderBy(d => d.Status)
+			.OrderByIF(dto.IsHandled == true, d => d.StartTime, OrderByType.Asc)
+			.OrderByIF(dto.IsHandled == false, d => d.CreationTime, OrderByType.Desc)
+			.ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+		var page2 = new PagedDto<OrderDto>(total2, _mapper.Map<IReadOnlyList<OrderDto>>(items2));
+
+        return new { Waited = page1, Sign = page2 };
+	}
+
+	#endregion
+
+	#region 业务工单退回
+
+	/// <summary>
+	/// 工单退回退回(返回前一节点)
+	/// </summary>
+	[HttpPost("order_previous")]
     public async Task Previous([FromBody] OrderPreviousDto dto)
     {
         var oneSendBack = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.OneOrgSendBack)?.SettingValue[0]);
@@ -3201,21 +3271,19 @@ public class OrderController : BaseController
                 NextHandlers = dto.NextHandlers,
                 Opinion = dto.Cause
             };
-            //if (dto.AlterTime)
-            //    recall.External = new External { TimeLimit = dto.TimeLimit, TimeLimitUnit = ETimeType.WorkDay };
-            //if (dto.Files.Any()) recall.Files = dto.Files;
-
-            //todo 特提重办,按审批通过时间依据中心派至部门的规则计算期满时间,更新order
-            //期满时间
-            //if (recall is 特提)
-            //{
-            //    var expiredTimeConfig = _timeLimitDomainService.CalcExpiredTime(DateTime.Now, EFlowDirection.CenterToOrg, order.AcceptTypeCode);
-            //    _mapper.Map(expiredTimeConfig, order);
-            //    await _orderRepository.UpdateAsync(order, HttpContext.RequestAborted);
-
-            //}
-
-            await _workflowApplication.RecallAsync(recall, order.ExpiredTime, HttpContext.RequestAborted);
+			// 计算期满时间
+			//if (dto.AlterTime)
+			//{
+			var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now, order.AcceptTypeCode);
+			//var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now,
+			//	ETimeType.WorkDay,
+			//	dto.TimeLimit.Value, order.AcceptTypeCode);
+			await _orderRepository.Updateable().SetColumns(o => new Order() { ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime })
+				.Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
+			var orderDto = _mapper.Map<OrderDto>(order);
+			await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto, cancellationToken: HttpContext.RequestAborted);
+			//}
+			await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, HttpContext.RequestAborted);
             var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == dto.OrderId);
             if (publish != null)
             {
@@ -3312,11 +3380,18 @@ public class OrderController : BaseController
 				NextHandlers = dto.NextHandlers,
 				Opinion = dto.Cause
 			};
+            // 计算期满时间
             if (dto.AlterTime)
-                recall.External = new External { TimeLimit = dto.TimeLimit, TimeLimitUnit = ETimeType.WorkDay };
-
+            {
+	            var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now,
+		             ETimeType.WorkDay,
+					 dto.TimeLimit.Value, order.AcceptTypeCode);
+				await _orderRepository.Updateable().SetColumns(o => new Order() { ExpiredTime = expiredTime.EndTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime })
+                    .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
+				var orderDto = _mapper.Map<OrderDto>(order);
+	            await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto,cancellationToken: HttpContext.RequestAborted);
+            }
             await _workflowApplication.RecallAsync(recall, order.ExpiredTime, HttpContext.RequestAborted);
-
 			var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == dto.OrderId);
 			if (publish != null)
 			{
@@ -3367,7 +3442,8 @@ public class OrderController : BaseController
             special.ReplyFileJson =
                 await _fileRepository.AddFileAsync(dto.Files, special.Id, "", HttpContext.RequestAborted);
         await _orderSpecialRepository.UpdateAsync(special, HttpContext.RequestAborted);
-        if (special.State == 1)
+        var order = await _orderRepository.GetAsync(x => x.Id == special.OrderId);
+		if (special.State == 1)
         {
             var recall = new RecallDto
             {
@@ -3377,13 +3453,22 @@ public class OrderController : BaseController
                 NextHandlers = dto.NextHandlers,
                 Opinion = dto.Opinion
             };
-            //if (dto.AlterTime)
-            //    recall.External = new External { TimeLimit = dto.TimeLimit, TimeLimitUnit = dto.TimeLimitUnit };
-            //if (dto.Files.Any()) recall.Files = dto.Files;
-
-            //todo 特提重办,按审批通过时间依据中心派至部门的规则计算期满时间,更新order
-
-            await _workflowApplication.RecallAsync(recall, special.Order.ExpiredTime, HttpContext.RequestAborted);
+			//if (dto.AlterTime)
+			//    recall.External = new External { TimeLimit = dto.TimeLimit, TimeLimitUnit = dto.TimeLimitUnit };
+			//if (dto.Files.Any()) recall.Files = dto.Files;
+			// 计算期满时间
+			//if (dto.AlterTime)
+			//{
+			var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now, order.AcceptTypeCode);
+			await _orderRepository.Updateable().SetColumns(o => new Order() { ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime })
+				.Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
+			var orderDto = _mapper.Map<OrderDto>(order);
+			await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto, cancellationToken: HttpContext.RequestAborted);
+			//}
+
+			//todo 特提重办,按审批通过时间依据中心派至部门的规则计算期满时间,更新order
+
+			await _workflowApplication.RecallAsync(recall, expiredTime.ExpiredTime, HttpContext.RequestAborted);
             var publish = await _orderPublishRepository.GetAsync(x => x.OrderId == special.OrderId);
             if (publish != null)
             {
@@ -3406,7 +3491,6 @@ public class OrderController : BaseController
                 await _orderVisitRepository.UpdateAsync(visit, HttpContext.RequestAborted);
             }
 
-            var order = await _orderRepository.GetAsync(x => x.Id == special.OrderId);
             if (order != null && ("001171".Equals(special.OrgId) ||
                                   "001178".Equals(special.OrgId) ||
                                   "001180".Equals(special.OrgId)))
@@ -3615,8 +3699,10 @@ public class OrderController : BaseController
         if (order == null ) throw UserFriendlyException.SameMessage("无效工单信息!");
         if (order.CounterSignType == ECounterSignType.Center)
         {
-	        //中心会签调取方法
-        }
+            //中心会签调取方法
+            var org = await _workflowDomainService.GetLevelOneOrgsAsync(order.WorkflowId, HttpContext.RequestAborted);
+            orgs.AddRange(org);
+		}
         else
         {
 	        if (!string.IsNullOrEmpty(order.OrgLevelOneCode) && order.OrgLevelOneCode != "001")

+ 13 - 1
src/Hotline.Api/Controllers/SchedulingController.cs

@@ -23,18 +23,21 @@ namespace Hotline.Api.Controllers
 		private readonly IRepository<Scheduling> _schedulingRepository;
 		private readonly IRepository<SchedulingShift> _schedulingShiftRepository;
 		private readonly IRepository<SchedulingUser> _schedulingUserRepository;
+		private readonly IRepository<User> _userRepository;
 		private readonly IMapper _mapper;
 
 		public SchedulingController(
 			IRepository<Scheduling> schedulingRepository,
 			IRepository<SchedulingShift> schedulingShiftRepository,
 			IRepository<SchedulingUser> schedulingUserRepository,
+			IRepository<User> userRepository,
 			IMapper mapper
 			) {
 			_schedulingRepository = schedulingRepository;
 			_schedulingShiftRepository = schedulingShiftRepository;
 			_schedulingUserRepository = schedulingUserRepository;
 			_mapper= mapper;
+			_userRepository = userRepository;
 		}
 		#region 排班人员
 
@@ -51,8 +54,11 @@ namespace Hotline.Api.Controllers
 			{
 				if (string.IsNullOrEmpty(dto.UserId))
 					throw UserFriendlyException.SameMessage("请带上用户信息");
-
+				
 				var model = _mapper.Map<SchedulingUser>(dto);
+				var sysUser = await _userRepository.Queryable().Includes(u => u.Organization).Where(x => x.Id == dto.UserId).FirstAsync(HttpContext.RequestAborted);
+				model.OrgId = sysUser.Organization.Id;
+				model.OrgIdName = sysUser.Organization.Name;
 				user.Add(model);
 			}
 			await _schedulingUserRepository.AddRangeAsync(user, HttpContext.RequestAborted);
@@ -84,6 +90,9 @@ namespace Hotline.Api.Controllers
 			if (user is null)
 				throw UserFriendlyException.SameMessage("无效排班人员");
 			_mapper.Map(dto, user);
+			var sysUser = await _userRepository.Queryable().Includes(u => u.Organization).Where(x => x.Id == dto.UserId).FirstAsync(HttpContext.RequestAborted);
+			user.OrgId = sysUser.Organization.Id;
+			user.OrgIdName = sysUser.Organization.Name;
 			await _schedulingUserRepository.UpdateAsync(user, HttpContext.RequestAborted);
 		}
 
@@ -102,6 +111,9 @@ namespace Hotline.Api.Controllers
 				if (user is null)
 					throw UserFriendlyException.SameMessage("无效排班人员");
 				_mapper.Map(dto, user);
+				var sysUser = await _userRepository.Queryable().Includes(u => u.Organization).Where(x => x.Id == item.UserId).FirstAsync(HttpContext.RequestAborted);
+				user.OrgId = sysUser.Organization.Id;
+				user.OrgIdName = sysUser.Organization.Name;
 				users.Add(user);
 			}
 			await _schedulingUserRepository.UpdateRangeAsync(users, HttpContext.RequestAborted);

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

@@ -164,7 +164,7 @@ namespace Hotline.Api.Controllers
         /// <param name="year"></param>
         /// <param name="month"></param>
         /// <returns></returns>
-        [Permission(EPermission.DaySetingsList)]
+        //[Permission(EPermission.DaySetingsList)]
         [HttpGet("getdaysettings-month/{year}/{month}")]
         public async Task<List<DaySetting>> GetDaySettingsByMonth(int year,int month)
         {

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

@@ -163,7 +163,7 @@ public class UserController : BaseController
              .WhereIF(!string.IsNullOrEmpty(dto.Role), d => d.Roles.Any(x => x.Id == dto.Role))
              .OrderBy(d => d.Account.Status)
              .OrderBy(d => d.Organization.OrgType)
-             .OrderBy(d => d.Organization.Id)
+             //.OrderBy(d => d.Organization.Id)
              .OrderByDescending(d => d.CreationTime)
              .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 

+ 0 - 34
src/Hotline.Api/Controllers/WorkflowController.cs

@@ -371,40 +371,6 @@ public class WorkflowController : BaseController
         await _workflowApplication.RecallAsync(dto, null, HttpContext.RequestAborted);
     }
 
-    /// <summary>
-    /// 获取跳转可选节点
-    /// </summary>
-    /// <param name="workflowId"></param>
-    /// <returns></returns>
-    [HttpGet("{workflowId}/jump")]
-    public async Task<NextStepsDto> GetJumpSteps(string workflowId)
-    {
-        return await _workflowApplication.GetJumpStepsAsync(workflowId, HttpContext.RequestAborted);
-    }
-
-    ///// <summary>
-    ///// 跳转至任意节点(暂无需求设计)
-    ///// </summary>
-    ///// <param name="dto"></param>
-    ///// <returns></returns>
-    //[HttpPost("jump")]
-    //[Obsolete]
-    //public async Task Jump([FromBody] RecallDto dto)
-    //{
-    //    await _workflowApplication.JumpAsync(dto, HttpContext.RequestAborted);
-    //}
-
-    /// <summary>
-    /// 获取重办可选节点
-    /// </summary>
-    /// <param name="workflowId"></param>
-    /// <returns></returns>
-    [HttpGet("{workflowId}/redo")]
-    public async Task<NextStepsDto> GetRedoSteps(string workflowId)
-    {
-        return await _workflowApplication.GetRedoStepsAsync(workflowId, HttpContext.RequestAborted);
-    }
-
     /// <summary>
     /// 终止流程
     /// </summary>

+ 2 - 0
src/Hotline.Api/Realtimes/RealtimeGroupNames.cs

@@ -6,5 +6,7 @@
         public const string BigScreenDataShow = "BigScreen-DataShow";
         public const string BigScreenSeatState = "BigScreen-SeatState";
         public const string BigDataScreen = "BigData-Screen";
+        public const string EnforcementBigDataScreen = "BigData-Enforcement";
+
     }
 }

+ 4 - 0
src/Hotline.Api/Realtimes/RealtimeMethods.cs

@@ -71,6 +71,10 @@
         public static string OrderHandlingDetail = "OrderHandlingDetail";
 
         #endregion
+
+        #region 司法大屏
+        public static string EnforcementOrderHandlingDetail = "EnforcementOrderHandlingDetail"; 
+        #endregion
     }
 }
     

+ 11 - 0
src/Hotline.Api/Realtimes/RealtimeService.cs

@@ -221,6 +221,17 @@ public class RealtimeService : IRealtimeService, IScopeDependency
 
     #endregion
 
+    /// <summary>
+    /// 司法大屏--工单明细
+    /// </summary>
+    /// <param name="obj"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public Task EnforcementOrderHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.EnforcementBigDataScreen, RealtimeMethods.EnforcementOrderHandlingDetail, obj, cancellationToken);
+
+    
+
     #region private
 
     private async Task SendCallCenterMsgAsync(string userId, string msg, object? value, CancellationToken cancellationToken)

+ 79 - 0
src/Hotline.Application/Bigscreen/EnforcementDataScreenRefreshService.cs

@@ -0,0 +1,79 @@
+using Hotline.Caching.Interfaces;
+using Hotline.JudicialManagement;
+using Hotline.Orders;
+using Hotline.Realtimes;
+using Hotline.Settings;
+using Hotline.Share.Dtos.JudicialManagement;
+using Hotline.Share.Enums.Order;
+using MapsterMapper;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using XF.Domain.Constants;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Bigscreen
+{
+    internal class EnforcementDataScreenRefreshService : BackgroundService
+    {
+        private readonly IServiceScopeFactory _serviceScopeFactory;
+
+        public EnforcementDataScreenRefreshService(IServiceScopeFactory serviceScopeFactory)
+        {
+            _serviceScopeFactory = serviceScopeFactory;
+        }
+
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            using var scope = _serviceScopeFactory.CreateScope();
+            var realtimeService = scope.ServiceProvider.GetRequiredService<IRealtimeService>();
+            var _enforcementOrdersRepository = scope.ServiceProvider.GetRequiredService<IRepository<EnforcementOrders>>();
+            var _mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
+            var systemSettingCacheManager = scope.ServiceProvider.GetRequiredService<ISystemSettingCacheManager>();
+
+            int times = int.Parse(systemSettingCacheManager.GetSetting(SettingConstants.EnforcementDataOrderChangedTimes)?.SettingValue[0]);
+            while (!stoppingToken.IsCancellationRequested)
+            {
+                try
+                {
+
+                    var list = await _enforcementOrdersRepository.Queryable()
+                  .LeftJoin<Order>((x, o) => x.Id == o.Id)
+                  .LeftJoin<SystemArea>((x, o, p) => o.AreaCode == p.Id)
+                  .Where((x, o, p) => o.Id != null)
+                  .Where((x, o) => o.Status > EOrderStatus.WaitForAccept && o.StartTime.Value.Date == DateTime.Now.Date)
+                  .Select((x, o, p) => new OrderOverviewDto
+                  {
+                      Id = o.Id,
+                      WorkflowId = o.WorkflowId,
+                      SourceChannel = o.SourceChannel,
+                      SourceChannelCode = o.SourceChannelCode,
+                      Title = o.Title,
+                      AcceptType = o.AcceptType,
+                      AcceptTypeCode = o.AcceptTypeCode,
+                      HotspotId = o.HotspotId,
+                      HotspotName = o.HotspotName,
+                      ActualHandleOrgName = o.ActualHandleOrgName,
+                      ActualHandleOrgCode = o.ActualHandleOrgCode,
+                      Status = o.Status,
+                      ActualHandleTime = o.ActualHandleTime,
+                      ExpiredTime = o.ExpiredTime,
+                      NearlyExpiredTime = o.NearlyExpiredTime,
+                      StartTime = o.StartTime,
+                      AreaName = p.AreaName
+                  })
+                  .MergeTable()
+                  .OrderByDescending(d => d.StartTime)
+                  .Take(50)
+                  .ToListAsync();
+
+                    var orderlist = _mapper.Map<List<OrderOverviewDto>>(list);
+
+                    await realtimeService.EnforcementOrderHandlingDetailAsync(orderlist, stoppingToken);
+                }
+                catch { }
+
+                await Task.Delay(times);
+            }
+        }
+    }
+}

+ 2 - 32
src/Hotline.Application/FlowEngine/IWorkflowApplication.cs

@@ -41,33 +41,13 @@ namespace Hotline.Application.FlowEngine
         /// </summary>
         Task RecallAsync(RecallDto dto, DateTime? expiredTime, CancellationToken cancellationToken);
 
-        ///// <summary>
-        ///// 跳转至任意节点
-        ///// </summary>
-        //Task JumpAsync(RecallDto dto, CancellationToken cancellationToken);
-
         /// <summary>
         /// 跳转至结束节点(无视流程模板配置直接跳至结束节点)
         /// </summary>
         Task JumpToEndAsync(string workflowId, string opinion, List<FileDto> file,
             EReviewResult? reviewResult = EReviewResult.Unknown,
             CancellationToken cancellationToken = default);
-
-        ///// <summary>
-        ///// 重办
-        ///// </summary>
-        //Task RedoAsync(RecallDto dto, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// 查询流程开始节点的下一节点配置
-        /// </summary>
-        Task<DefinedStepDto> GetStartOptionsAsync(string moduleCode, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// 查询节点配置可选参数
-        /// </summary>
-        Task<IReadOnlyList<Kv>> GetNextStepOptionsAsync(StepDefineBasic stepDefine, CancellationToken cancellationToken);
-
+        
         ////////
 
         /// <summary>
@@ -78,23 +58,13 @@ namespace Hotline.Application.FlowEngine
         /// <summary>
         /// 查询办理流程的下一步待选节点
         /// </summary>
-        Task<NextStepsDto<NextStepOption>> GetNextStepsAsync(string workflowId, CancellationToken cancellationToken);
+        Task<NextStepsWithOpinionDto<NextStepOption>> GetNextStepsAsync(string workflowId, CancellationToken cancellationToken);
 
         /// <summary>
         /// 查询撤回可选节点
         /// </summary>
         Task<NextStepsDto<RecallStepOption>> GetRecallStepsAsync(string workflowId, CancellationToken cancellationToken);
 
-        /// <summary>
-        /// 查询跳转可选节点
-        /// </summary>
-        Task<NextStepsDto> GetJumpStepsAsync(string workflowId, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// 查询重办可选节点
-        /// </summary>
-        Task<NextStepsDto> GetRedoStepsAsync(string workflowId, CancellationToken cancellationToken);
-
         /// <summary>
         /// 否决
         /// </summary>

+ 145 - 316
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -28,6 +28,7 @@ using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
 using Hotline.Share.Dtos.File;
 using Microsoft.Extensions.Logging;
+using System.Text;
 
 namespace Hotline.Application.FlowEngine;
 
@@ -98,7 +99,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         DateTime? expiredTime, CancellationToken cancellationToken = default)
     {
         var validator = new StartWorkflowDtoValidator();
-        var validResult = validator.Validate(dto);
+        var validResult = await validator.ValidateAsync(dto, cancellationToken);
         if (!validResult.IsValid)
             throw new UserFriendlyException(
                 $"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
@@ -144,12 +145,19 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
             externalId, cancellationToken);
 
+        var startStepHandles = new List<WorkflowStepHandler>{WorkflowStepHandler.Create(workflow.Id, workflow.ExternalId,
+            EFlowAssignType.User, _sessionContext.RequiredUserId, _sessionContext.UserName,
+            _sessionContext.RequiredOrgId, _sessionContext.OrgName)};
+
         var startStep = _workflowDomainService.CreateStartStep(workflow, startStepDefine, dto,
-            new List<Kv> { new(_sessionContext.RequiredUserId, _sessionContext.UserName) }, expiredTime);
+            new List<Kv> { new(_sessionContext.RequiredUserId, _sessionContext.UserName) },
+            startStepHandles, expiredTime);
 
         var flowAssignInfo =
             await GetNextStepFlowAssignInfoAsync(workflow, startStep, dto, firstStepDefine, isNextDynamic, cancellationToken);
 
+        var firstStepHandlers = await GetNextStepHandlersAsync(workflow, firstStepDefine, dto, cancellationToken);
+
         var counterSignType = _workflowDomainService.GetCounterSignType(startStep.BusinessType);
 
         //办理开始节点
@@ -164,7 +172,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             startStep.FileJson =
                 await _fileRepository.AddFileAsync(dto.Files, workflow.ExternalId, startStep.Id, cancellationToken);
 
-        await _workflowStepRepository.AddAsync(startStep, cancellationToken);
+        //await _workflowStepRepository.AddAsync(startStep, cancellationToken);
+        await _workflowStepRepository.AddNav(startStep)
+            .Include(d => d.StepHandlers)
+            .ExecuteCommandAsync();
         workflow.Steps.Add(startStep);
 
         //starttrace
@@ -184,7 +195,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             _sessionContext.OrgName);
 
         await _workflowDomainService.StartAsync(workflow, startStep, dto, firstStepDefine, isNextDynamic,
-            flowAssignInfo, counterSignType, expiredTime, cancellationToken);
+            flowAssignInfo, counterSignType, expiredTime, firstStepHandlers, cancellationToken);
 
         return workflow.Id;
     }
@@ -252,8 +263,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         var flowAssignInfo =
             await GetNextStepFlowAssignInfoAsync(workflow, currentStep, dto, nextStepDefine, isNextDynamic, cancellationToken);
 
+        var nextStepHandlers = await GetNextStepHandlersAsync(workflow, nextStepDefine, dto, cancellationToken);
+
         await _workflowDomainService.NextAsync(workflow, currentStep, dto, nextStepDefine, isNextDynamic,
-            flowAssignInfo, expiredTime, cancellationToken);
+            flowAssignInfo, expiredTime, nextStepHandlers, cancellationToken);
 
         return workflow;
     }
@@ -299,28 +312,12 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw UserFriendlyException.SameMessage("结束节点不支持撤回");
         //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
         var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.IsStartCountersign,
-            dto.NextHandlers,
-            cancellationToken);
-
-        await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, expiredTime, cancellationToken);
-    }
-
-    ///// <summary>
-    ///// 跳转至任意节点
-    ///// </summary>
-    //public async Task JumpAsync(RecallDto dto, CancellationToken cancellationToken)
-    //{
-    //    var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
-    //            cancellationToken: cancellationToken);
+            dto.NextHandlers, cancellationToken);
 
-    //    await _orderDomainService.ReadyToRecallAsync(workflow.ExternalId, cancellationToken);
+        var stepHandlers = await GetNextStepHandlersAsync(workflow, targetStepDefine, dto, cancellationToken);
 
-    //    var targetStepDefine = _workflowDomainService.GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode);
-    //    //var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
-    //    var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.IsStartCountersign,
-    //        dto.NextHandlers, cancellationToken);
-    //    await _workflowDomainService.JumpAsync(workflow, dto, targetStepDefine, flowAssignInfo, cancellationToken);
-    //}
+        await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, flowAssignInfo, stepHandlers, expiredTime, cancellationToken);
+    }
 
     /// <summary>
     /// 跳转至结束节点(无视流程模板配置直接跳至结束节点)
@@ -339,139 +336,11 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException("未找到实际办理节点");
 
         await _workflowDomainService.EndAsync(workflow, new BasicWorkflowDto
-            {
-                Opinion = opinion,
-                Files = files
-            }, endStepDefine, currentStep,
-            reviewResult, cancellationToken);
-    }
-
-    ///// <summary>
-    ///// 重办
-    ///// </summary>
-    //public async Task RedoAsync(RecallDto dto, CancellationToken cancellationToken)
-    //{
-    //    var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withDefine: true, withSteps: true,
-    //        cancellationToken: cancellationToken);
-    //    var targetStepDefine = _workflowDomainService.GetStepDefine(workflow.WorkflowDefinition, dto.NextStepCode);
-    //    var flowAssignInfo = await GetNextStepFlowAssignInfoByDefineAsync(targetStepDefine, dto.IsStartCountersign,
-    //        dto.NextHandlers, cancellationToken);
-    //    await _workflowDomainService.RedoAsync(workflow, dto, targetStepDefine, flowAssignInfo, cancellationToken);
-    //}
-
-    public async Task<DefinedStepDto> GetStartOptionsAsync(string moduleCode, CancellationToken cancellationToken)
-    {
-        var wfModule = await GetWorkflowModuleAsync(moduleCode, cancellationToken);
-        var definition = wfModule.Definition;
-        if (definition == null)
-            throw new UserFriendlyException("无效模板编码");
-        if (definition.Status is not EDefinitionStatus.Enable)
-            throw new UserFriendlyException("该模板不可用");
-
-        var startStep = definition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
-        if (startStep == null)
-            throw new UserFriendlyException("未正确配置发起人节点");
-        var firstStepDefines = definition.FindStepDefines(startStep.NextSteps.Select(d => d.Code));
-        if (!firstStepDefines.Any())
-            throw new UserFriendlyException("未正确配置首个办理节点");
-
-        return new DefinedStepDto
         {
-            DefinitionId = definition.Id,
-            Steps = firstStepDefines
-            // Components = firstStepDefine.Components
-        };
-    }
-
-    /// <summary>
-    /// 根据节点配置查询待选参数
-    /// </summary>
-    /// <param name="stepDefine"></param>
-    /// <param name="cancellationToken"></param>
-    /// <returns></returns>
-    /// <exception cref="ArgumentOutOfRangeException"></exception>
-    public async Task<IReadOnlyList<Kv>> GetNextStepOptionsAsync(StepDefineBasic stepDefine,
-        CancellationToken cancellationToken)
-    {
-        //if (stepDefine.StepType == EStepType.End)
-        //    throw new UserFriendlyException("结束节点无待选项");
-
-        //var handlers = new List<Kv>();
-        //switch (stepDefine.HandlerType)
-        //{
-        //    // case EHandlerType.AssignedUser:
-        //    //     var users = await _userRepository.Queryable()
-        //    //         .Where(d => stepDefine.HandlerTypeItems.Select(x => x.Id).Contains(d.Id))
-        //    //         .ToListAsync(cancellationToken);
-        //    //     handlers = users.Select(d => new Kv(d.Id, d.Name)).ToList();
-        //    //     break;
-        //    // case EHandlerType.AssignedOrg:
-        //    //     var orgs = await _organizeRepository.QueryAsync(d =>
-        //    //         d.IsEnable &&
-        //    //         stepDefine.HandlerTypeItems.Select(d => d.Id).Contains(d.Id));
-        //    //     handlers = orgs.Select(d => new Kv(d.Id, d.OrgName))
-        //    //         .ToList();
-        //    //     break;
-        //    case EHandlerType.AssignedUser:
-        //    case EHandlerType.AssignedOrg:
-        //        handlers = stepDefine.HandlerTypeItems;
-        //        break;
-        //    case EHandlerType.Role:
-        //        //当前操作人所属部门的下级部门并且属于配置包含角色
-        //        var roles = await _roleRepository.Queryable()
-        //            .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(),
-        //                x => x.User)
-        //            .Where(d => stepDefine.HandlerTypeItems.Select(x => x.Key).Contains(d.Name))
-        //            .ToListAsync(cancellationToken);
-        //        var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
-        //        handlers = users1.Select(d => new Kv(d.Id, d.Name)).ToList();
-        //        break;
-        //    case EHandlerType.OrgLevel:
-        //        //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
-        //        var levels = stepDefine.HandlerTypeItems.Select(d => d.Key).Select(d => int.Parse(d));
-        //        var levelOneOrg = _sessionContext.RequiredOrgId.GetHigherOrgCode();
-        //        //var orgs1 = await _organizeRepository.QueryAsync(d =>
-        //        //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg) &&
-        //        //    levels.Contains(d.OrgLevel));
-
-        //        var orgs1 = await _organizeRepository.Queryable()
-        //            .Where(d => d.IsEnable && levels.Contains(d.Level))
-        //            .WhereIF(!levelOneOrg.IsCenter(), d => d.Id.StartsWith(levelOneOrg))
-        //            .ToListAsync(cancellationToken);
-
-        //        handlers = orgs1.Select(d => new Kv(d.Id, d.Name)).ToList();
-        //        break;
-        //    case EHandlerType.OrgType:
-        //        var types = stepDefine.HandlerTypeItems.Select(d => d.Key)
-        //            .Select(d => Enum.Parse<EOrgType>(d));
-        //        var levelOneOrg1 = _sessionContext.RequiredOrgId.GetHigherOrgCode();
-        //        //var org2 = await _organizeRepository.QueryAsync(d =>
-        //        //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg1) &&
-        //        //    types.Contains(d.OrgType));
-
-        //        var orgs2 = await _organizeRepository.Queryable()
-        //            .Where(d => d.IsEnable && types.Contains(d.OrgType))
-        //            .WhereIF(!levelOneOrg1.IsCenter(), d => d.Id.StartsWith(levelOneOrg1))
-        //            .ToListAsync(cancellationToken);
-
-        //        handlers = orgs2.Select(d => new Kv(d.Id, d.Name)).ToList();
-        //        break;
-        //    default:
-        //        throw new ArgumentOutOfRangeException();
-        //}
-
-        //var dto = new NextStepOptionDto { Handlers = handlers };
-
-        //if (stepDefine.Components.Contains(SysDicTypeConsts.OrderRedoReason))
-        //{
-        //    var orderRedoReasons =
-        //        await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.OrderRedoReason,
-        //            cancellationToken);
-        //    dto.OrderRedoReasonOptions = orderRedoReasons.Select(d => new Kv(d.DicDataValue, d.DicDataName)).ToList();
-        //}
-
-        //return dto;
-        throw new NotImplementedException();
+            Opinion = opinion,
+            Files = files
+        }, endStepDefine, currentStep,
+            reviewResult, cancellationToken);
     }
 
     /// <summary>
@@ -487,22 +356,11 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException("该模板不可用");
 
         var startStepDefine = definition.FindStartStepDefine();
-
-        //var dto = new NextStepsDto
-        //{
-        //    DefinitionId = definition.Id,
-        //    InstanceMode = startStep.InstanceMode,
-        //    DynamicPolicy = startStep.InstancePolicy
-        //};
-
         if (startStepDefine.InstanceMode is EInstanceMode.Dynamic &&
             !_workflowDomainService.DynamicShouldTerminal(startStepDefine, _sessionContext.OrgLevel))
         {
-            //var nextStepOption = CreateDynamicStep(startStep.InstancePolicy);
-            //dto.Steps = new List<NextStepOption> { nextStepOption };
-            //return dto;
             var nextStepOption = await GetDynamicStepAsync(startStepDefine.InstancePolicy.Value,
-                startStepDefine.StepType, startStepDefine.BusinessType, cancellationToken);
+                   startStepDefine.StepType, startStepDefine.BusinessType, cancellationToken);
             return new NextStepsDto<NextStepOption>
             {
                 Steps = new List<NextStepOption> { nextStepOption }
@@ -513,33 +371,26 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         if (!firstStepDefines.Any())
             throw new UserFriendlyException("未正确配置首个办理节点");
 
-        //var steps = firstStepDefines
-        //        .Select(d => new NextStepOption { Key = d.Code, Value = d.Name })
-        //        .ToList();
         return new NextStepsDto<NextStepOption>
         {
             Steps = await GetConfigStepsAsync(definition.FlowType, startStepDefine.StepType, startStepDefine.BusinessType,
-                firstStepDefines,
-                cancellationToken)
+                firstStepDefines, cancellationToken)
         };
-        //dto.Steps = steps;
-        //return dto;
     }
 
     /// <summary>
     /// 查询办理流程的下一步待选节点
     /// </summary>
-    public async Task<NextStepsDto<NextStepOption>> GetNextStepsAsync(string workflowId, CancellationToken cancellationToken)
+    public async Task<NextStepsWithOpinionDto<NextStepOption>> GetNextStepsAsync(string workflowId, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withDefine: true, withSteps: true,
             cancellationToken: cancellationToken);
-        var currentStep =
-            _workflowDomainService.FindCurrentStepWaitForHandle(workflow, _sessionContext.RequiredUserId,
-                _sessionContext.RequiredOrgId);
+        var currentStep = _workflowDomainService.FindCurrentStepWaitForHandle(workflow,
+            _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
         if (currentStep.StepType is EStepType.End)
             throw new UserFriendlyException("结束节点无需办理");
 
-        var dto = new NextStepsDto<NextStepOption>
+        var dto = new NextStepsWithOpinionDto<NextStepOption>
         {
             CanReject = workflow.IsReviewType() && currentStep.CanReject,
             //ExpiredTime = workflow.ExpiredTime,
@@ -556,8 +407,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         {
             //动态生成下一步
             var nextStepOption = await GetDynamicStepAsync(currentStep.InstancePolicy.Value, currentStep.StepType,
-                currentStep.BusinessType,
-                cancellationToken);
+                currentStep.BusinessType, cancellationToken);
             dto.Steps = new List<NextStepOption> { nextStepOption };
             return dto;
         }
@@ -566,6 +416,17 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         {
             if (currentStep.IsCountersignEndStep)
             {
+                // 宜宾需求:会签汇总节点展示会签办理节点办理意见
+                var countersignHandleSteps = workflow.Steps.Where(d =>
+                        d.CountersignId == currentStep.CountersignId &&
+                        d.CountersignPosition == ECountersignPosition.Inner).ToList();
+                var sb = new StringBuilder();
+                foreach (var countersignHandleStep in countersignHandleSteps)
+                {
+                    sb.AppendLine($"{countersignHandleStep.GetActualHandler()?.GetHandler().Value} : {countersignHandleStep.Opinion}");
+                }
+                dto.Opinion = sb.ToString();
+
                 //当前待办节点为会签汇总节点时:检查是否为顶级会签汇总节点,t:按配置往下走,f:继续往上汇总,不需要重复往下指派
                 if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
                 {
@@ -576,13 +437,11 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                         throw new UserFriendlyException(
                             $"未查询到会签开始节点,workflowId: {workflow.Id}, startStepId: {currentStep.CountersignStartStepId}",
                             "未查询到会签开始节点,数据异常");
-                    var countersignEndOption = GetCsEndStepByPrev(workflow.Steps, startCountersignStep);
+                    var countersignEndOption = GetCsEndStepByTargetPrev(workflow.Steps, startCountersignStep);
 
                     //按会签策略
-                    var nextStepOption =
-                        await GetDynamicStepAsync(currentStep.CountersignPolicy.Value, currentStep.StepType,
-                            currentStep.BusinessType,
-                            cancellationToken);
+                    var nextStepOption = await GetDynamicStepAsync(currentStep.CountersignPolicy.Value,
+                        EStepType.Normal, currentStep.BusinessType, cancellationToken);
 
                     dto.Steps = new List<NextStepOption> { nextStepOption, countersignEndOption };
                     return dto;
@@ -591,11 +450,11 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             else
             {
                 //汇总节点
-                var countersignEndOption = GetCsEndStepByPrev(workflow.Steps, currentStep);
+                var countersignEndOption = GetCsEndStepByTargetPrev(workflow.Steps, currentStep);
                 //按会签策略
                 var nextStepOption =
-                    await GetDynamicStepAsync(currentStep.CountersignPolicy.Value, currentStep.StepType, currentStep.BusinessType,
-                        cancellationToken);
+                    await GetDynamicStepAsync(currentStep.CountersignPolicy.Value,
+                        EStepType.Normal, currentStep.BusinessType, cancellationToken);
                 dto.Steps = new List<NextStepOption> { nextStepOption, countersignEndOption };
                 return dto;
             }
@@ -607,6 +466,15 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
         dto.Steps = await GetConfigStepsAsync(workflow, currentStep, nextDefines, cancellationToken);
 
+        // 宜宾需求:汇总节点展示前一级节点办理意见
+        if (currentStep.StepType is EStepType.Summary &&
+            !currentStep.IsCountersignEndStep &&
+            string.IsNullOrEmpty(dto.Opinion))
+        {
+            var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == currentStep.PrevStepId);
+            dto.Opinion = $"{prevStep?.GetActualHandler()?.GetHandler().Value} : {prevStep?.Opinion}";
+        }
+
         return dto;
     }
 
@@ -623,13 +491,13 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             throw new UserFriendlyException("无效当前节点编号");
         var quer = workflow.Steps.Where(d =>
             d.StepType != EStepType.End &&
-            d.IsOrigin );
+            d.IsOrigin);
         if (!isEnd)
         {
             quer = quer.Where(d => d.Id != currentStep.Id);
         }
         var originSteps = quer.ToList();
-		var stepCodes = originSteps.Select(d => d.Code).ToList();
+        var stepCodes = originSteps.Select(d => d.Code).ToList();
         var stepDefines = workflow.WorkflowDefinition.FindStepDefines(stepCodes);
 
         var dto = new NextStepsDto<RecallStepOption>
@@ -664,18 +532,23 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
             stepOption.InputRealHandler = false;
 
-            //已归档工单,撤回至中心看作otc,撤回至部门看作cto
-            stepOption.FlowDirection = isWorkflowFiled
-                ? stepDefine.BusinessType is EBusinessType.Center or EBusinessType.Send
-                    ? EFlowDirection.OrgToCenter
-                    : stepDefine.BusinessType is EBusinessType.Department
-                        ? EFlowDirection.CenterToOrg
-                        : null
-                : CheckFlowDirection(currentStep.BusinessType, stepDefine.BusinessType);
+            ////已归档工单,撤回至中心看作otc,撤回至部门看作cto
+            //stepOption.FlowDirection = isWorkflowFiled
+            //    ? stepDefine.BusinessType is EBusinessType.Center or EBusinessType.Send
+            //        ? EFlowDirection.OrgToCenter
+            //        : stepDefine.BusinessType is EBusinessType.Department
+            //            ? EFlowDirection.CenterToOrg
+            //            : null
+            //    : CheckFlowDirection(currentStep.BusinessType, stepDefine.BusinessType);
+
+            //需求已调整为特提必重算期满时间
 
+            //需求:撤回选择办理对象时,默认选中该节点原办理对象
             if (originStep.Handlers.Any())
                 stepOption.Handler =
                     originStep.Handlers.FirstOrDefault(d => d.Key == originStep.HandlerId || d.Key == originStep.HandlerOrgId);
+            //if (originStep.StepHandlers.Any())
+            //    stepOption.Handler = originStep.GetActualHandler()?.GetHandler() ?? new();
 
             steps.Add(stepOption);
         }
@@ -683,34 +556,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         return steps;
     }
 
-    /// <summary>
-    /// 查询跳转可选节点
-    /// </summary>
-    public async Task<NextStepsDto> GetJumpStepsAsync(string workflowId, CancellationToken cancellationToken)
-    {
-        var workflow =
-            await _workflowDomainService.GetWorkflowAsync(workflowId, true, cancellationToken: cancellationToken);
-        return new NextStepsDto
-        {
-            //Steps = await GetConfigStepsAsync(null, workflow.WorkflowDefinition.Steps, cancellationToken)
-        };
-    }
-
-    /// <summary>
-    /// 查询重办可选节点
-    /// </summary>
-    public async Task<NextStepsDto> GetRedoStepsAsync(string workflowId, CancellationToken cancellationToken)
-    {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, true, true,
-            cancellationToken: cancellationToken);
-        var steps = workflow.Steps.Where(d => d.StepType is EStepType.Normal && d.IsOrigin && d.IsOrg());
-        var stepDefines = workflow.WorkflowDefinition.FindStepDefines(steps.Select(d => d.Code));
-        return new NextStepsDto
-        {
-            //Steps = await GetConfigStepsAsync(null, stepDefines, cancellationToken)
-        };
-    }
-
     /// <summary>
     /// 否决
     /// </summary>
@@ -724,7 +569,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         await _workflowDomainService.RejectAsync(workflow, basicDto, cancellationToken);
     }
 
-    //开启流程调用
+    //开启流程调用
     private async Task<List<NextStepOption>> GetConfigStepsAsync(
         EFlowType flowType,
         EStepType currentStepType,
@@ -911,13 +756,25 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         {
             case EBusinessType.Center:
             case EBusinessType.Send:
-                return directionStepBusinessType is EBusinessType.Department
-                    ? EFlowDirection.CenterToOrg
-                    : null;
+                return directionStepBusinessType switch
+                {
+                    EBusinessType.Center => EFlowDirection.CenterToCenter,
+                    EBusinessType.Send => EFlowDirection.CenterToCenter,
+                    EBusinessType.Department => EFlowDirection.CenterToOrg,
+                    EBusinessType.File => EFlowDirection.CenterToFile,
+                    _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType),
+                        directionStepBusinessType, null)
+                };
             case EBusinessType.Department:
-                return directionStepBusinessType is EBusinessType.Center or EBusinessType.Send
-                    ? EFlowDirection.OrgToCenter
-                    : null;
+                return directionStepBusinessType switch
+                {
+                    EBusinessType.Center => EFlowDirection.OrgToCenter,
+                    EBusinessType.Send => EFlowDirection.OrgToCenter,
+                    EBusinessType.Department => EFlowDirection.OrgToOrg,
+                    EBusinessType.File => EFlowDirection.OrgToFile,
+                    _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType),
+                        directionStepBusinessType, null)
+                };
             case EBusinessType.File:
                 return null;
             default:
@@ -925,7 +782,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         }
     }
 
-    private NextStepOption GetCsEndStepByPrev(List<WorkflowStep> steps, WorkflowStep step)
+    private NextStepOption GetCsEndStepByTargetPrev(List<WorkflowStep> steps, WorkflowStep step)
     {
         var prevStep = steps.FirstOrDefault(d => d.Id == step.PrevStepId);
         if (prevStep is null)
@@ -935,13 +792,14 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             : $"{prevStep.HandlerOrgId.CalcOrgLevel().ToChinese()}级部门会签汇总";
         var handlers = prevStep.Handlers
             .Where(d => d.Key == prevStep.HandlerId || d.Key == prevStep.HandlerOrgId).ToList();
+        //var handler = prevStep.GetActualHandler()?.GetHandler();
 
         return new NextStepOption
         {
             Key = prevStep.Code,
             Value = text,
             BackToCountersignEnd = true,
-            StepType = prevStep.StepType,
+            StepType = EStepType.Summary,
             BusinessType = prevStep.BusinessType,
             HandlerType = prevStep.HandlerType,
             Items = handlers //new List<Kv> { new(prevStep.HandlerId, prevStep.HandlerName) },
@@ -983,12 +841,15 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         };
     }
 
-    private async Task<NextStepOption> GetDynamicStepAsync(EDynamicPolicy policy, EStepType stepType, EBusinessType businessType,
-        CancellationToken cancellationToken)
+    private async Task<NextStepOption> GetDynamicStepAsync(
+        EDynamicPolicy policy, EStepType stepType,
+        EBusinessType currentBusinessType, CancellationToken cancellationToken)
     {
         int orgLevel;
         List<Kv> items;
         string upperOrgId;
+        EBusinessType businessType;
+        EFlowDirection? flowDirection = null;
         switch (policy)
         {
             case EDynamicPolicy.OrgUpCenterTop:
@@ -997,6 +858,10 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
                 if (orgLevel == 0)
                 {
+                    businessType = EBusinessType.Send;
+                    if (currentBusinessType == EBusinessType.Department)
+                        flowDirection = EFlowDirection.OrgToCenter;
+
                     items = await _organizeRepository.Queryable()
                         .Where(d => d.IsCenter)
                         .Select(d => new Kv { Key = d.Id, Value = d.Name })
@@ -1004,6 +869,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 }
                 else
                 {
+                    businessType = EBusinessType.Department;
+
                     //上级部门Id
                     upperOrgId = _sessionContext.RequiredOrgId.GetHigherOrgId(orgLevel);
                     items = await _organizeRepository.Queryable()
@@ -1014,6 +881,11 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
                 break;
             case EDynamicPolicy.OrgUp:
+                businessType = _sessionContext.OrgIsCenter
+                    ? EBusinessType.Send
+                    : _sessionContext.RequiredOrgId.CalcOrgLevel() == 1
+                        ? EBusinessType.Send
+                        : EBusinessType.Department;
                 orgLevel = _sessionContext.OrgLevel - 1;
                 if (orgLevel <= 0) orgLevel = 1;
                 //上级部门Id
@@ -1024,6 +896,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     .ToListAsync(cancellationToken);
                 break;
             case EDynamicPolicy.OrgDownCenterTop:
+                businessType = EBusinessType.Department;
                 if (_sessionContext.OrgIsCenter)
                 {
                     orgLevel = 1;
@@ -1036,13 +909,15 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 {
                     orgLevel = _sessionContext.OrgLevel + 1;
                     items = await _organizeRepository.Queryable()
-                        .Where(d => !d.IsCenter && d.Level == orgLevel && d.Id.StartsWith(_sessionContext.RequiredOrgId))
+                        .Where(d => !d.IsCenter && d.Level == orgLevel &&
+                                    d.Id.StartsWith(_sessionContext.RequiredOrgId))
                         .Select(d => new Kv { Key = d.Id, Value = d.Name })
                         .ToListAsync(cancellationToken);
                 }
 
                 break;
             case EDynamicPolicy.OrgDown:
+                businessType = EBusinessType.Department;
                 orgLevel = _sessionContext.OrgLevel + 1;
                 items = await _organizeRepository.Queryable()
                     .Where(d => d.Level == orgLevel && d.Id.StartsWith(_sessionContext.RequiredOrgId))
@@ -1057,7 +932,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         {
             Key = orgLevel.ToString(),
             Value = orgLevel == 0 ? "热线中心" : $"{orgLevel.ToChinese()}级部门",
-            FlowDirection = null,
+            FlowDirection = flowDirection,
             StepType = stepType,
             BusinessType = businessType,
             HandlerType = EHandlerType.OrgLevel, //目前所有动态策略均属于部门等级
@@ -1065,80 +940,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         };
     }
 
-    //private async Task<IReadOnlyList<NextStepOption>> GetConfigStepsAsync(List<StepDefine> stepDefines,
-    //    CancellationToken cancellationToken)
-    //{
-    //    foreach (var stepDefine in stepDefines)
-    //    {
-    //        if (stepDefine.StepType == EStepType.End)
-    //            throw new UserFriendlyException("结束节点无待选项");
-
-    //        var handlers = new List<Kv>();
-    //        switch (stepDefine.HandlerType)
-    //        {
-    //            case EHandlerType.AssignedUser:
-    //            case EHandlerType.AssignedOrg:
-    //                handlers = stepDefine.HandlerTypeItems;
-    //                break;
-    //            case EHandlerType.Role:
-    //                //当前操作人所属部门的下级部门并且属于配置包含角色
-    //                var roles = await _roleRepository.Queryable()
-    //                    .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(),
-    //                        x => x.User)
-    //                    .Where(d => stepDefine.HandlerTypeItems.Select(x => x.Key).Contains(d.Name))
-    //                    .ToListAsync(cancellationToken);
-    //                var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
-    //                handlers = users1.Select(d => new Kv(d.Id, d.Name)).ToList();
-    //                break;
-    //            case EHandlerType.OrgLevel:
-    //                //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
-    //                var levels = stepDefine.HandlerTypeItems.Select(d => d.Key).Select(d => int.Parse(d));
-    //                var levelOneOrg = _sessionContext.RequiredOrgId.GetHigherOrgCode();
-    //                //var orgs1 = await _organizeRepository.QueryAsync(d =>
-    //                //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg) &&
-    //                //    levels.Contains(d.OrgLevel));
-
-    //                var orgs1 = await _organizeRepository.Queryable()
-    //                    .Where(d => d.IsEnable && levels.Contains(d.Level))
-    //                    .WhereIF(!levelOneOrg.IsCenter(), d => d.Id.StartsWith(levelOneOrg))
-    //                    .ToListAsync(cancellationToken);
-
-    //                handlers = orgs1.Select(d => new Kv(d.Id, d.Name)).ToList();
-    //                break;
-    //            case EHandlerType.OrgType:
-    //                var types = stepDefine.HandlerTypeItems.Select(d => d.Key)
-    //                    .Select(d => Enum.Parse<EOrgType>(d));
-    //                var levelOneOrg1 = _sessionContext.RequiredOrgId.GetHigherOrgCode();
-    //                //var org2 = await _organizeRepository.QueryAsync(d =>
-    //                //    d.IsEnable && d.OrgCode.StartsWith(levelOneOrg1) &&
-    //                //    types.Contains(d.OrgType));
-
-    //                var orgs2 = await _organizeRepository.Queryable()
-    //                    .Where(d => d.IsEnable && types.Contains(d.OrgType))
-    //                    .WhereIF(!levelOneOrg1.IsCenter(), d => d.Id.StartsWith(levelOneOrg1))
-    //                    .ToListAsync(cancellationToken);
-
-    //                handlers = orgs2.Select(d => new Kv(d.Id, d.Name)).ToList();
-    //                break;
-    //            default:
-    //                throw new ArgumentOutOfRangeException();
-    //        }
-
-    //        var dto = new NextStepOptionDto { Handlers = handlers };
-
-    //        if (stepDefine.Components.Contains(SysDicTypeConsts.OrderRedoReason))
-    //        {
-    //            var orderRedoReasons =
-    //                await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.OrderRedoReason,
-    //                    cancellationToken);
-    //            dto.OrderRedoReasonOptions = orderRedoReasons.Select(d => new Kv(d.DicDataValue, d.DicDataName)).ToList();
-    //        }
-
-    //        return dto;
-    //    }
-    //    throw new NotImplementedException();
-    //}
-
     /// <summary>
     /// 查询下一节点办理对象类型(user or org)及实际办理对象
     /// </summary>
@@ -1203,6 +1004,34 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             cancellationToken);
     }
 
+    private async ValueTask<List<WorkflowStepHandler>> GetNextStepHandlersAsync(Workflow workflow,
+        StepDefine nextStepDefine, BasicWorkflowDto dto, CancellationToken cancellationToken)
+    {
+        var assignType = FlowAssignInfo.GetAssignType(dto.HandlerType);
+        //var assignType = AssignInfo.GetAssignType(nextStepDefine.HandlerType, dto.NextHandlers.Any());
+        switch (assignType)
+        {
+            case EFlowAssignType.Org:
+                return dto.NextHandlers.Select(d => WorkflowStepHandler.Create(workflow.Id, workflow.ExternalId,
+                    assignType, orgId: d.Key, orgName: d.Value)).ToList();
+            case EFlowAssignType.User:
+                var userIds = dto.NextHandlers.Select(d => d.Key).ToList();
+                var users = await _userRepository.Queryable()
+                    .Includes(d => d.Organization)
+                    .Where(d => userIds.Contains(d.Id))
+                    .ToListAsync(cancellationToken);
+                return users.Select(d => WorkflowStepHandler.Create(workflow.Id, workflow.ExternalId,
+                        assignType, d.Id, d.Name, d.OrgId, d.Organization.Name))
+                    .ToList();
+            //case EFlowAssignType.Role:
+            //    handlers = dto.NextHandlers.Select(d => WorkflowStepHandler.Create(workflow.Id, workflow.ExternalId,
+            //        assignType, roleId: d.Key, roleName: d.Value)).ToList();
+            //    break;
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
+    }
+
     /// <summary>
     /// 按流程模板配置创建下一步办理对象
     /// </summary>

+ 2 - 4
src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs

@@ -9,7 +9,6 @@ using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.KnowledgeBase;
 using Hotline.Orders;
-using Hotline.Repository.SqlSugar.Knowledge;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
@@ -19,10 +18,7 @@ using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Quality;
 using MapsterMapper;
 using MediatR;
-using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
-using XF.Domain.Authentications;
-using XF.Domain.Entities;
 using XF.Domain.Repository;
 
 namespace Hotline.Application.Handlers.FlowEngine;
@@ -193,6 +189,8 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
                             visitDetail.OrgProcessingResults = screenSatisfy;
                             visitDetail.OrgHandledAttitude = screenSatisfy;
                             await _orderVisitedDetailRepository.UpdateAsync(visitDetail, cancellationToken);
+                            // 修改主表当前评价结果
+                            await _orderVisitRepository.Updateable().SetColumns(v=> new OrderVisit() { NowEvaluate = screenSatisfy }).Where(v=>v.Id == visitDetail.VisitId).ExecuteCommandAsync(cancellationToken);
                             //获取回访信息
                             var visit = await _orderVisitRepository.Queryable().Includes(x => x.Order)
                                 .Includes(x => x.OrderVisitDetails)

+ 0 - 4
src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs

@@ -11,17 +11,13 @@ using Hotline.Settings;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
-using Hotline.Share.Dtos.Settings;
-using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
-using Hotline.Share.Enums.Quality;
 using Hotline.Share.Mq;
 using Hotline.Users;
 using MapsterMapper;
 using MediatR;
 using Microsoft.Extensions.Logging;
-using Newtonsoft.Json;
 using XF.Domain.Authentications;
 using XF.Domain.Entities;
 using XF.Domain.Repository;

+ 1 - 16
src/Hotline.Application/Handlers/FlowEngine/WorkflowRecallHandler.cs

@@ -60,23 +60,8 @@ public class WorkflowRecallHandler : INotificationHandler<RecallNotify>
                     order.Status = EOrderStatus.SpecialToUnAccept;
                     order.BackToUnsign();
                 }
-
                 await _orderRepository.UpdateAsync(order, false, cancellationToken);
-
-                //if (data.External != null && data.External.TimeLimit.HasValue && data.External.TimeLimitUnit.HasValue)
-                //{
-                //    var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now,
-                //        data.External.TimeLimitUnit.Value,
-                //        data.External.TimeLimit.Value, order.AcceptTypeCode);
-
-                //    await _workflowDomainService.UpdateExpiredTimeAsync(workflow, expiredTime.EndTime,
-                //        expiredTime.RuleStr, data.External.TimeLimit, data.External.TimeLimitUnit, expiredTime.NearlyExpiredTime, cancellationToken);
-
-                //    var dto = _mapper.Map<OrderDto>(order);
-                //    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, dto, cancellationToken: cancellationToken);
-                //}
-
-                break;
+				break;
             case WorkflowModuleConsts.KnowledgeAdd:
             case WorkflowModuleConsts.KnowledgeUpdate:
             case WorkflowModuleConsts.KnowledgeDelete:

+ 5 - 6
src/Hotline.Application/JudicialManagement/EnforcementApplication.cs

@@ -12,7 +12,6 @@ namespace Hotline.Application.JudicialManagement
     {
         private readonly IRepository<EnforcementOrderHander> _enforcementOrderHanderRepository;
         private readonly IRepository<EnforcementOrders> _enforcementOrdersRepository;
-        private readonly IRepository<JudicialComplaintsEventType> _judicialComplaintsEventTypeRepository;
         private readonly IRepository<EnforcementHotspot> _enforcementHotspotRepository;
         private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
@@ -23,17 +22,17 @@ namespace Hotline.Application.JudicialManagement
         /// </summary>
         /// <param name="enforcementOrderHanderRepository"></param>
         /// <param name="enforcementOrdersRepository"></param>
-        /// <param name="judicialComplaintsEventTypeRepository"></param>
+        /// <param name="enforcementHotspotRepository"></param>
+        /// <param name="sysDicDataCacheManager"></param>
+        /// <param name="systemSettingCacheManager"></param>
         public EnforcementApplication(IRepository<EnforcementOrderHander> enforcementOrderHanderRepository,
            IRepository<EnforcementOrders> enforcementOrdersRepository,
-          IRepository<JudicialComplaintsEventType> judicialComplaintsEventTypeRepository,
           IRepository<EnforcementHotspot> enforcementHotspotRepository,
         ISystemDicDataCacheManager sysDicDataCacheManager,
        ISystemSettingCacheManager systemSettingCacheManager)
         {
             _enforcementOrderHanderRepository = enforcementOrderHanderRepository;
             _enforcementOrdersRepository = enforcementOrdersRepository;
-            _judicialComplaintsEventTypeRepository = judicialComplaintsEventTypeRepository;
             _enforcementHotspotRepository = enforcementHotspotRepository;
             _sysDicDataCacheManager = sysDicDataCacheManager;
             _systemSettingCacheManager = systemSettingCacheManager;
@@ -82,7 +81,7 @@ namespace Hotline.Application.JudicialManagement
                         Id = order.Id,
                         OrderNo = order.No,
                         IsEnforcementOrder = order.IsEnforcementOrder.HasValue ? order.IsEnforcementOrder.Value : false,
-                        IsTheClueTrue = false,
+                        IsTheClueTrue = null,
                         IsPassTheBuckOrder = false
                     };
 
@@ -161,7 +160,7 @@ namespace Hotline.Application.JudicialManagement
                             Id = order.Id,
                             OrderNo = order.No,
                             IsEnforcementOrder = order.IsEnforcementOrder.HasValue ? order.IsEnforcementOrder.Value : false,
-                            IsTheClueTrue = false,
+                            IsTheClueTrue = null,
                             IsPassTheBuckOrder = true
                         };
                         await _enforcementOrdersRepository.AddAsync(orderData, cancellationToken);

+ 1 - 0
src/Hotline.Application/Mappers/MapperConfigs.cs

@@ -71,6 +71,7 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.IsTheClueTrue, x => x.IsTheClueTrue)
                 .Map(d => d.EventTypeName, x => x.EventTypeName)
                 .Map(d => d.EventTypeId, x => x.EventTypeId)
+                .Map(d => d.EventTypeSpliceName, x => x.EventTypeSpliceName)
                  ;
 
 

+ 11 - 6
src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs

@@ -35,8 +35,9 @@ public class WorkflowMapperConfigs : IRegister
             .Ignore(d => d.Opinion)
             .Ignore(d => d.FileJson)
             //.Ignore(d => d.StepExpiredTime)
-            .Ignore(d=>d.Workflow)
-            .Ignore(d=>d.WorkflowTrace)
+            .Ignore(d => d.Workflow)
+            .Ignore(d => d.WorkflowTrace)
+            .IgnoreIf((d, s) => s.StepHandlers == null || !s.StepHandlers.Any(), d => d.StepHandlers)
             ;
 
         config.ForType<WorkflowStep, WorkflowTrace>()
@@ -77,12 +78,16 @@ public class WorkflowMapperConfigs : IRegister
             .IgnoreNonMapped(true);
 
         config.ForType<Workflow, WorkflowStep>()
-            .Map(d=>d.WorkflowId, s=>s.Id)
-            .Map(d=>d.ModuleId,s=>s.ModuleId)
-            .Map(d=>d.ModuleCode,s=>s.ModuleCode)
-            .Map(d=>d.ModuleName,s=>s.ModuleName)
+            .Map(d => d.WorkflowId, s => s.Id)
+            .Map(d => d.ModuleId, s => s.ModuleId)
+            .Map(d => d.ModuleCode, s => s.ModuleCode)
+            .Map(d => d.ModuleName, s => s.ModuleName)
             .IgnoreNonMapped(true)
             ;
 
+        config.ForType<BasicWorkflowDto, WorkflowStep>()
+            .Ignore(d => d.BusinessType)
+            .Ignore(d => d.HandlerType)
+            ;
     }
 }

+ 7 - 5
src/Hotline.Share/Dtos/FlowEngine/NextStepsDto.cs

@@ -36,10 +36,12 @@ public class NextStepsDto<TSteps> : NextStepsDto
     public List<TSteps> Steps { get; set; }
 }
 
-public class GetNextStepItemsDto
+public class NextStepsWithOpinionDto<TSteps> : NextStepsDto<TSteps>
 {
-    public string DefinitionId { get; set; }
-    public EInstanceMode InstanceMode { get; set; }
-    public EDynamicPolicy DynamicPolicy { get; set; }
-    public NextStepOption Step { get; set; }
+    /// <summary>
+    /// 宜宾需求:汇总节点办理前展示前一节点办理意见
+    /// </summary>
+    public string? Opinion { get; set; }
 }
+
+

+ 5 - 0
src/Hotline.Share/Dtos/JudicialManagement/ClueVerificationDto.cs

@@ -24,5 +24,10 @@
         /// 诉事项类型名称
         /// </summary>
         public string? EventTypeName { get; set; }
+
+        /// <summary>
+        /// 诉事项类型名称
+        /// </summary>
+        public string? EventTypeSpliceName { get; set; }
     }
 }

+ 318 - 0
src/Hotline.Share/Dtos/JudicialManagement/EnforcementBigscreenDto.cs

@@ -0,0 +1,318 @@
+using Hotline.Share.Enums.Order;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.JudicialManagement
+{
+    /// <summary>
+    /// 左上工单数据模块
+    /// </summary>
+    public class OrderModuleDto
+    {
+        /// <summary>
+        /// 行政执法工单
+        /// </summary>
+        public int EnforcementOrderNum { get; set; }
+
+        /// <summary>
+        /// 线索属实
+        /// </summary>
+        public int TheClueIsTrueNum { get; set; }
+
+        /// <summary>
+        /// 线索不属实
+        /// </summary>
+        public int TheClueIsNotTrueNum { get; set; }
+
+        /// <summary>
+        /// 推诿工单
+        /// </summary>
+        public int PassTheBuckOrderNum { get; set; }
+
+        /// <summary>
+        /// 满意工单
+        /// </summary>
+        public int SatisfiedCount { get; set; }
+
+        /// <summary>
+        /// 工单总量
+        /// </summary>
+        public int OrderCountNum { get; set; }
+
+
+    }
+
+    /// <summary>
+    /// 左中受理类型
+    /// </summary>
+    public class OrderAcceptTypeStatisticsDto
+    {
+        /// <summary>
+        /// 类型名称
+        /// </summary>
+        public string AcceptType { get; set; }
+
+        /// <summary>
+        /// 总量
+        /// </summary>
+        public int SumCount { get; set; }
+
+        /// <summary>
+        /// 在办
+        /// </summary>
+        public int HandlingCount { get; set; }
+
+        /// <summary>
+        /// 已办
+        /// </summary>
+        public int FiledCount { get; set; }
+
+        /// <summary>
+        /// 超期
+        /// </summary>
+        public int OverTimeCount { get; set; }
+    }
+
+    /// <summary>
+    /// 左下高频事项统计
+    /// </summary>
+    public class OrderEventTypeStatisticsDto
+    {
+        /// <summary>
+        /// id
+        /// </summary>
+        public string EventTypeId { get; set; }
+
+        /// <summary>
+        /// 名称
+        /// </summary>
+        public string EventTypeName { get; set; }
+
+        /// <summary>
+        /// 总量
+        /// </summary>
+        public int SumCount { get; set; }
+    }
+
+    /// <summary>
+    /// 中上区域数据统计
+    /// </summary>
+    public class OrderAreaQueryDto
+    {
+        /// <summary>
+        /// 区域Id
+        /// </summary>
+        public string AreaCode { get; set; }
+
+        /// <summary>
+        /// 区域名称
+        /// </summary>
+        public string AreaName { get; set; }
+
+        /// <summary>
+        /// 行政执法工单
+        /// </summary>
+        public int EnforcementOrderNum { get; set; }
+
+        /// <summary>
+        /// 线索属实
+        /// </summary>
+        public int TheClueIsTrueNum { get; set; }
+
+        /// <summary>
+        /// 线索不属实
+        /// </summary>
+        public int TheClueIsNotTrueNum { get; set; }
+
+        /// <summary>
+        /// 推诿工单
+        /// </summary>
+        public int PassTheBuckOrderNum { get; set; }
+
+        /// <summary>
+        /// 工单总量
+        /// </summary>
+        public int OrderCountNum { get; set; }
+    }
+
+    /// <summary>
+    /// 中下行政执法工单概览
+    /// </summary>
+    public class OrderOverviewDto
+    {
+        public string Id { get; set; }
+        public string WorkflowId { get; set; }
+
+        /// <summary>
+        /// 来源渠道
+        /// </summary>
+        public string? SourceChannel { get; set; }
+        public string? SourceChannelCode { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public string? AcceptType { get; set; }
+        public string? AcceptTypeCode { get; set; }
+
+        /// <summary>
+        /// 热点
+        /// </summary>
+        public string? HotspotId { get; set; }
+        public string? HotspotName { get; set; }
+
+        /// <summary>
+        /// 实际办理部门名称
+        /// </summary>
+        public string? ActualHandleOrgName { get; set; }
+
+        /// <summary>
+        /// 实际办理部门编码
+        /// </summary>
+        public string? ActualHandleOrgCode { get; set; }
+
+        /// <summary>
+        /// 过期状态
+        /// </summary>
+        public EExpiredStatus? ExpiredStatus => CalculateExpiredState();
+
+        /// <summary>
+        /// 过期状态
+        /// </summary>
+        public string ExpiredStatusText => ExpiredStatus?.GetDescription() ?? string.Empty;
+
+        /// <summary>
+        /// 工单状态
+        /// </summary>
+        public EOrderStatus Status { get; set; }
+        public string StatusText => Status.GetDescription();
+
+        /// <summary>
+        /// 实际办理时间
+        /// </summary>
+        public DateTime? ActualHandleTime { get; set; }
+
+        /// <summary>
+        /// 超期时间(期满时间)
+        /// </summary>
+        public DateTime? ExpiredTime { get; set; }
+
+        /// <summary>
+        /// 即将超期时间
+        /// </summary>
+        public DateTime? NearlyExpiredTime { get; set; }
+
+        /// <summary>
+        /// 工单开始时间(受理/接办时间=流程开启时间)
+        /// </summary>
+        public DateTime? StartTime { get; set; }
+
+        /// <summary>
+        /// 区域
+        /// </summary>
+        public string AreaName { get; set; }
+
+        public EExpiredStatus? CalculateExpiredState()
+        {
+            DateTime? dateTime = DateTime.Now;
+            if (Status >= EOrderStatus.Filed)
+            {
+                dateTime = ActualHandleTime;
+            }
+            //ExpiredStatus
+            if (ExpiredTime.HasValue)
+            {
+                if (dateTime < NearlyExpiredTime)
+                {
+                    return EExpiredStatus.Normal;
+                }
+                else if (dateTime > NearlyExpiredTime && dateTime < ExpiredTime)
+                {
+                    return EExpiredStatus.GoingToExpired;
+                }
+                else
+                {
+                    return EExpiredStatus.Expired;
+                }
+            }
+            return null;
+        }
+    }
+
+    /// <summary>
+    /// 右上满意度排行榜
+    /// </summary>
+    public class OrderVisitOrgStatisticsDto
+    {
+        /// <summary>
+        /// 部门Code
+        /// </summary>
+        public string VisitOrgCode { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        public string VisitOrgName { get; set; }
+
+        /// <summary>
+        /// 满意量
+        /// </summary>
+        public int SatisfiedCount { get; set; }
+
+        /// <summary>
+        /// 总量
+        /// </summary>
+        public int VisitCount { get; set; }
+
+        /// <summary>
+        /// 满意率
+        /// </summary>
+        public double SatisfiedRate => CalcSatisfiedRate();
+
+        public double CalcSatisfiedRate()
+        {
+            if (SatisfiedCount == 0 || VisitCount == 0)
+            {
+                return 0;
+            }
+            return Math.Round((SatisfiedCount / (double)VisitCount) * 100, 2);
+        }
+    }
+
+    /// <summary>
+    /// 工单月份趋势图
+    /// </summary>
+    public class OrderMonthlyTrendDto
+    {
+        public int Month { get; set; }
+
+        public int SumCount { get; set; }
+    }
+
+    /// <summary>
+    /// 右下占比分析
+    /// </summary>
+    public class OrderProportionStatisticsDto
+    {
+        public string Name { get; set; }
+
+        public int SumCount { get; set; }
+
+        public int HasCount { get; set; }
+
+        public double HasRate => CalcHasRate();
+
+        public double CalcHasRate()
+        {
+            if (HasCount == 0 || SumCount == 0)
+            {
+                return 0;
+            }
+            return Math.Round((HasCount / (double)SumCount) * 100, 2);
+        }
+    }
+}

+ 13 - 0
src/Hotline.Share/Dtos/JudicialManagement/EnforcementOrderListDto.cs

@@ -167,6 +167,11 @@ namespace Hotline.Share.Dtos.JudicialManagement
         /// </summary>
         public string? EventTypeName { get; set; }
 
+        /// <summary>
+        /// 诉事项类型名称
+        /// </summary>
+        public string? EventTypeSpliceName { get; set; }
+
         /// <summary>
         /// 是否显示修改按钮
         /// </summary>
@@ -198,4 +203,12 @@ namespace Hotline.Share.Dtos.JudicialManagement
             return null;
         }
     }
+
+    public  class EnforcementOrgSatisfactionOrderListDto: EnforcementOrderListDto
+    {
+        /// <summary>
+        /// 回访主表ID
+        /// </summary>
+        public string VisitId { get; set; }
+    }
 }

+ 27 - 0
src/Hotline.Share/Dtos/JudicialManagement/EventClassificationOrderCountDto.cs

@@ -0,0 +1,27 @@
+namespace Hotline.Share.Dtos.JudicialManagement
+{
+    public class EventClassificationOrderCountDto
+    {
+        /// <summary>
+        /// 线索属实
+        /// </summary>
+        public int TheClueIsTrue { get; set; }
+        /// <summary>
+        /// 线索不属实
+        /// </summary>
+        public int TheClueIsNotTrue { get; set; }
+
+        /// <summary>
+        /// 行政执法类工单
+        /// </summary>
+        public int EnforcementOrder { get; set; }
+
+        /// <summary>
+        /// 推诿工单
+        /// </summary>
+        public int PassTheBuckOrder { get; set; }
+
+
+
+    }
+}

+ 1 - 1
src/Hotline.Share/Dtos/JudicialManagement/QueryEnforcementOrderDto.cs

@@ -46,7 +46,7 @@ namespace Hotline.Share.Dtos.JudicialManagement
         /// <summary>
         /// 事项类型
         /// </summary>
-        public List<string> EventTypeId { get; set; } = new();
+        public string EventTypeId { get; set; } 
 
         /// <summary>
         /// 接办部门(√)

+ 92 - 0
src/Hotline.Share/Dtos/JudicialManagement/QueryEventClassificationStatisticsDto.cs

@@ -0,0 +1,92 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.JudicialManagement
+{
+    public record QueryEventClassificationStatisticsDto : PagedRequest
+    {
+        public DateTime StartDate { get; set; }
+        public DateTime EndDate { get; set; }
+        public string EventTypeId { get; set; }
+
+        public string AreaCode { get; set; }
+    }
+
+    public record QueryDepartmentalProcessingStatisticsDto: PagedRequest
+    {
+        public DateTime StartDate { get; set; }
+        public DateTime EndDate { get; set; }
+
+        /// <summary>
+        /// 部门code
+        /// </summary>
+        public string OrgCode { get; set; }
+
+        /// <summary>
+        /// 线索属实
+        /// </summary>
+        public bool TheClueIsTrue { get; set; }
+
+        /// <summary>
+        /// 线索不属实
+        /// </summary>
+        public bool TheClueIsNotTrue { get; set; }
+
+        /// <summary>
+        /// 行政执法工单
+        /// </summary>
+        public bool EnforcementOrder { get; set; }
+    }
+
+    /// <summary>
+    /// 
+    /// </summary>
+    public record QueryRegionalClassificationStatisticsDto : PagedRequest
+    {
+        public DateTime StartDate { get; set; }
+        public DateTime EndDate { get; set; }
+
+        /// <summary>
+        /// 区域code
+        /// </summary>
+        public string AreaCode { get; set; }
+
+        /// <summary>
+        /// 线索属实
+        /// </summary>
+        public bool TheClueIsTrue { get; set; }
+
+        /// <summary>
+        /// 线索不属实
+        /// </summary>
+        public bool TheClueIsNotTrue { get; set; }
+
+        /// <summary>
+        /// 行政执法工单
+        /// </summary>
+        public bool EnforcementOrder { get; set; }
+    }
+
+    /// <summary>
+    /// 
+    /// </summary>
+    public record QueryOrgSatisfactionStatisticsDto : PagedRequest
+    {
+        public DateTime StartDate { get; set; }
+        public DateTime EndDate { get; set; }
+
+        /// <summary>
+        /// 部门code
+        /// </summary>
+        public string OrgCode { get; set; }
+
+        /// <summary>
+        /// 1:办件结果 2:办件态度
+        /// </summary>
+        public int TypeId { get; set; }
+
+        /// <summary>
+        /// 满意度值
+        /// </summary>
+        public string DateValue { get; set; }
+    }
+}

+ 102 - 7
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -6,6 +6,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.Users;
+using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Requests;
@@ -524,16 +525,110 @@ namespace Hotline.Share.Dtos.Order
 
 	public record MayScreenListDto : PagedKeywordRequest
     {
-	    /// <summary>
-	    /// 部门编号
-	    /// </summary>
-	    public string VisitOrgCode { get; set; }
-
 	    public DateTime? CreationTimeStart { get; set; }
 	    public DateTime? CreationTimeEnd { get; set; }
-
         public bool? IsHomePage { get; set; }
-    }
+
+        /// <summary>
+        /// 工单编号
+        /// </summary>
+        public string? No { get; set; }
+
+        /// <summary>
+        /// 是否省工单
+        /// </summary>
+        public bool? IsProvince { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string? Title { get; set; }
+
+        /// <summary>
+        /// 来源渠道(电话、网站、APP等)
+        /// </summary>
+        public string? SourceChannel { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public string? AcceptType { get; set; }
+
+        /// <summary>
+        /// 会签类型
+        /// </summary>
+        public ECounterSignType? CounterSignType { get; set; }
+
+        /// <summary>
+        /// 一级部门名称
+        /// </summary>
+        public string? OrgLevelOneName { get; set; }
+
+        /// <summary>
+        /// 实际办理节点名称(会签状态此字段保存最外层会签发起节点名称)
+        /// </summary>
+        public string? ActualHandleStepName { get; set; }
+
+        /// <summary>
+        /// 实际办理时间
+        /// </summary>
+        public DateTime? ActualHandleTime { get; set; }
+
+        /// <summary>
+        /// 实际办理时间
+        /// </summary>
+        public DateTime? EndActualHandleTime { get; set; }
+
+		/// <summary>
+		/// 归档时间(暂为流程结束时间,因流程结束自动归档)
+		/// </summary>
+		public DateTime? FiledTime { get; set; }
+
+        /// <summary>
+        /// 归档时间(暂为流程结束时间,因流程结束自动归档)
+        /// </summary>
+        public DateTime? EndFiledTime { get; set; }
+
+		/// <summary>
+		/// 受理时间
+		/// </summary>
+		public DateTime? CreationTime { get; set; }
+
+		/// <summary>
+		/// 受理时间
+		/// </summary>
+		public DateTime? EndCreationTime { get; set; }
+
+		/// <summary>
+		/// 回访时间
+		/// </summary>
+		public DateTime? VisitTime { get; set; }
+
+		/// <summary>
+		/// 回访时间
+		/// </summary>
+		public DateTime? EndVisitTime { get; set; }
+
+		/// <summary>
+		/// 回访部门名称
+		/// </summary>
+		public string? VisitOrgName { get; set; }
+
+		/// <summary>
+		/// 部门办件结果
+		/// </summary>
+		public string? OrgProcessingResults { get; set; }
+
+        /// <summary>
+        /// 部门办件态度
+        /// </summary>
+        public string? OrgHandledAttitude { get; set; }
+
+        /// <summary>
+        /// 不满意原因
+        /// </summary>
+        public string? OrgNoSatisfiedReason { get; set; }
+	}
 
 	public record UrgeListDto : PagedKeywordRequest
     {

+ 6 - 1
src/Hotline.Share/Enums/FlowEngine/ECountersignPosition.cs

@@ -19,5 +19,10 @@ public enum ECountersignPosition
     /// 本身不直接处于会签流程中,但外层有会签流程嵌套(非上级节点开启会签,当前节点会签并未结束)
     /// 会签汇总节点
     /// </summary>
-    Outer = 2
+    Outer = 2,
+
+    /// <summary>
+    /// 会签汇总节点
+    /// </summary>
+    End = 3,
 }

+ 5 - 3
src/Hotline.Share/Enums/FlowEngine/EFlowDirection.cs

@@ -6,9 +6,11 @@ public enum EFlowDirection
 
     OrgToCenter = 1,
 
-    //CenterToCenter = 2,
+    CenterToCenter = 2,
 
-    //OrgToOrg = 3,
+    OrgToOrg = 3,
 
-    //CenterToFile = 4,
+    CenterToFile = 4,
+
+    OrgToFile = 5,
 }

+ 9 - 5
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -23,7 +23,7 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         Task StartAsync(Workflow workflow, WorkflowStep startStep, BasicWorkflowDto dto, StepDefine firstStepDefine,
             bool isNextDynamic, FlowAssignInfo flowAssignInfo, ECounterSignType? counterSignType, DateTime? expiredTime,
-            CancellationToken cancellationToken);
+            List<WorkflowStepHandler> stepHandlers, CancellationToken cancellationToken);
 
         /// <summary>
         /// 查询工作流
@@ -48,7 +48,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 办理(流转至下一节点)
         /// </summary>
         Task NextAsync(Workflow workflow, WorkflowStep currentStep, NextWorkflowDto dto, StepDefine nextStepDefine,
-            bool isNextDynamic, FlowAssignInfo flowAssignInfo, DateTime? expiredTime,
+            bool isNextDynamic, FlowAssignInfo flowAssignInfo, DateTime? expiredTime, List<WorkflowStepHandler> stepHandlers,
             CancellationToken cancellationToken);
 
         /// <summary>
@@ -62,7 +62,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 撤回(返回到之前任意节点)
         /// </summary>
         Task RecallAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine, FlowAssignInfo flowAssignInfo,
-            DateTime? expiredTime, CancellationToken cancellationToken);
+            List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime, CancellationToken cancellationToken);
 
         /// <summary>
         /// 撤回至开始节点
@@ -147,7 +147,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 创建开始节点
         /// </summary>
         WorkflowStep CreateStartStep(Workflow workflow, StepDefine startStepDefine, BasicWorkflowDto dto,
-            List<Kv> handlers, DateTime? expiredTime);
+            List<Kv> handlers, List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime);
 
         /// <summary>
         /// 查询未完成节点
@@ -217,8 +217,12 @@ namespace Hotline.FlowEngine.Workflows
         /// 批量改派工单至指定用户
         /// </summary>
         Task<ICollection<string>> ChangeHandlerRangeAsync(string sendPoolId,
-            IReadOnlyList<(string userId, string username, IReadOnlyList<string> stepIds)> handlers,
+            IReadOnlyList<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers,
             CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 查询工单办理中的一级部门
+        /// </summary>
+        Task<ICollection<Kv>> GetLevelOneOrgsAsync(string workflowId, CancellationToken cancellation);
     }
 }

+ 7 - 33
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -462,33 +462,6 @@ public partial class Workflow
         ClearHandlers();
     }
 
-    ///// <summary>
-    ///// 更新实际办理节点、部门数据
-    ///// </summary>
-    //private void ActualHandle(
-    //    WorkflowStep currentStep,
-    //    string? actualHandlerId,
-    //    string? actualHandlerName,
-    //    string? actualHandleOrgCode,
-    //    string? actualHandleOrgName,
-    //    string? actualHandleOrgAreaCode,
-    //    string? actualHandleOrgAreaName)
-    //{
-    //    ActualHandleStepCode = currentStep.Code;
-    //    ActualHandleStepName = currentStep.Name;
-    //    ActualHandleStepId = currentStep.Id;
-    //    ActualHandleStepCreateTime = currentStep.CreationTime;
-
-    //    ActualHandleStepAcceptTime = currentStep.AcceptTime;
-    //    ActualHandleTime = DateTime.Now;
-    //    ActualHandlerId = actualHandlerId;
-    //    ActualHandlerName = actualHandlerName;
-    //    ActualHandleOrgCode = actualHandleOrgCode;
-    //    ActualHandleOrgName = actualHandleOrgName;
-    //    ActualHandleOrgAreaCode = actualHandleOrgAreaCode;
-    //    ActualHandleOrgAreaName = actualHandleOrgAreaName;
-    //}
-
     /// <summary>
     /// 归档时为最终办理意见赋值
     /// </summary>
@@ -611,12 +584,6 @@ public partial class Workflow
         return Countersigns.All(d => d.IsCompleted());
     }
 
-    ///// <summary>
-    ///// 流程是否处于会签中
-    ///// </summary>
-    ///// <returns></returns>
-    //public bool IsInCountersign() => !string.IsNullOrEmpty(TopCountersignStepId);
-
     /// <summary>
     /// 开始会签
     /// </summary>
@@ -880,5 +847,12 @@ public partial class Workflow
             CsActualHandleOrgIds.Add(orgId);
     }
 
+    public bool IsTopCountersignEndStep(WorkflowStep step)
+    {
+        if (string.IsNullOrEmpty(TopCountersignStepId))
+            throw new UserFriendlyException("该流程当前未开启会签");
+        return step.IsCountersignEndStep && step.CountersignStartStepId == TopCountersignStepId;
+    }
+
     #endregion
 }

+ 198 - 144
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -2,7 +2,6 @@
 using Hotline.FlowEngine.Definitions;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
-using Hotline.Orders;
 using Hotline.SeedData;
 using Hotline.Settings;
 using Hotline.Share.Dtos;
@@ -98,7 +97,8 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         public async Task StartAsync(Workflow workflow, WorkflowStep startStep, BasicWorkflowDto dto,
             StepDefine firstStepDefine, bool isNextDynamic, FlowAssignInfo flowAssignInfo,
-            ECounterSignType? counterSignType, DateTime? expiredTime, CancellationToken cancellationToken)
+            ECounterSignType? counterSignType, DateTime? expiredTime, List<WorkflowStepHandler> stepHandlers,
+            CancellationToken cancellationToken)
         {
             //1. 创建first节点 (和trace)2.办理开始节点 
 
@@ -121,7 +121,7 @@ namespace Hotline.FlowEngine.Workflows
 
             //firststeps
             var firstSteps = await CreateNextStepsAsync(workflow, startStep, dto, firstStepDefine,
-                isNextDynamic, flowAssignInfo, expiredTime, cancellationToken);
+                isNextDynamic, flowAssignInfo, expiredTime, stepHandlers, cancellationToken);
             if (firstSteps.Any())
                 workflow.Steps.AddRange(firstSteps);
 
@@ -130,9 +130,9 @@ namespace Hotline.FlowEngine.Workflows
             //await HandleStepAsync(startStep, workflow, dto, flowAssignInfo.FlowAssignType, counterSignType,
             //    cancellationToken);
 
-            //赋值当前节点的下级办理节点
-            if (dto.IsStartCountersign)
-                startStep.CreateCountersignSteps(firstSteps);
+            ////赋值当前节点的下级办理节点
+            //if (dto.IsStartCountersign)
+            //    startStep.CreateCountersignSteps(firstSteps);
 
             await _workflowStepRepository.UpdateAsync(startStep, cancellationToken);
 
@@ -193,7 +193,7 @@ namespace Hotline.FlowEngine.Workflows
             if (withCountersigns)
                 query = query.Includes(d => d.Countersigns, x => x.Members);
             if (withSteps)
-                query = query.Includes(d => d.Steps);
+                query = query.Includes(d => d.Steps, x => x.StepHandlers);
             //if (withTraces)
             //    query = query.Includes(d => d.Traces);
 
@@ -313,7 +313,7 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         public async Task NextAsync(Workflow workflow, WorkflowStep currentStep, NextWorkflowDto dto,
             StepDefine nextStepDefine, bool isNextDynamic, FlowAssignInfo flowAssignInfo,
-            DateTime? expiredTime, CancellationToken cancellationToken)
+            DateTime? expiredTime, List<WorkflowStepHandler> stepHandlers, CancellationToken cancellationToken)
         {
             ValidatePermission(workflow, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
             //CheckWhetherRunnable(workflow.Status);
@@ -367,26 +367,26 @@ namespace Hotline.FlowEngine.Workflows
 
             _mapper.Map(dto, workflow);
 
-            //操作为回到会签汇总时,更新开始会签节点的会签办理状态
-            if (currentStep.IsInCountersign() && dto.BackToCountersignEnd)
-            {
-                if (currentStep.IsCountersignEndStep)
-                {
-                    if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
-                    {
-                        //汇总节点(非顶级)
-                        var csStartStep =
-                            workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
-                        if (csStartStep is null)
-                            throw new UserFriendlyException("未查询到会签开始节点");
-                        PrevStepCsHandled(workflow, csStartStep, ref updateSteps);
-                    }
-                }
-                else if (currentStep.CountersignPosition is ECountersignPosition.Inner)
-                {
-                    PrevStepCsHandled(workflow, currentStep, ref updateSteps);
-                }
-            }
+            ////操作为回到会签汇总时,更新开始会签节点的会签办理状态
+            //if (currentStep.IsInCountersign() && dto.BackToCountersignEnd)
+            //{
+            //    if (currentStep.IsCountersignEndStep)
+            //    {
+            //        if (!currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+            //        {
+            //            //汇总节点(非顶级)
+            //            var csStartStep =
+            //                workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
+            //            if (csStartStep is null)
+            //                throw new UserFriendlyException("未查询到会签开始节点");
+            //            PrevStepCsHandled(workflow, csStartStep, ref updateSteps);
+            //        }
+            //    }
+            //    else if (currentStep.CountersignPosition is ECountersignPosition.Inner)
+            //    {
+            //        PrevStepCsHandled(workflow, currentStep, ref updateSteps);
+            //    }
+            //}
 
             //会签办理节点办理时更新会签members字段
             if (currentStep.CountersignPosition is ECountersignPosition.Inner)
@@ -405,7 +405,10 @@ namespace Hotline.FlowEngine.Workflows
                     .ExecuteCommandAsync();
             }
 
-            await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken);
+            //await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken);
+            await _workflowStepRepository.UpdateNav(updateSteps)
+                .Include(d => d.StepHandlers)
+                .ExecuteCommandAsync();
 
             await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
 
@@ -446,25 +449,19 @@ namespace Hotline.FlowEngine.Workflows
                 return;
             }
 
-            ////是否从中心流转出去,重新计算expiredTime 
-            //var isCenterToOrg = CheckIfFlowFromCenterToOrg(currentStep, nextStepDefine);
-            //var isOrgToCenter = false;
-            ////if (isCenterToOrg)
-            ////    workflow.CenterToOrg(CalculateExpiredTime(workflow.WorkflowDefinition.Code));//todo 过期时间
-
             //创建下一/N个节点(会签汇总节点:会签未全部办理时不创建,最后一个会签办理节点创建会签汇总节点)
             var nextSteps = await CreateNextStepsAsync(workflow, currentStep, dto, nextStepDefine, isNextDynamic,
-                flowAssignInfo, expiredTime, cancellationToken);
+                flowAssignInfo, expiredTime, stepHandlers, cancellationToken);
 
-            //赋值当前节点的下级办理节点
-            if (dto.IsStartCountersign
-               //|| (currentStep.IsInCountersign() &&
-               //    !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
-               )
-            {
-                currentStep.CreateCountersignSteps(nextSteps);
-                await _workflowStepRepository.UpdateAsync(currentStep, cancellationToken);
-            }
+            ////赋值当前节点的下级办理节点
+            //if (dto.IsStartCountersign
+            //   //|| (currentStep.IsInCountersign() &&
+            //   //    !currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
+            //   )
+            //{
+            //    currentStep.CreateCountersignSteps(nextSteps);
+            //    await _workflowStepRepository.UpdateAsync(currentStep, cancellationToken);
+            //}
 
             //更新办理对象(nextSteps无元素表示当前节点为会签办理节点且当前会签没有全部办理完成)
             workflow.UpdateHandlers(_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId,
@@ -472,14 +469,6 @@ namespace Hotline.FlowEngine.Workflows
 
             //todo 计算办理工作时长
 
-            ////更新当前办理节点信息
-            //workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign,
-            //    _sessionContext.RequiredUserId, _sessionContext.UserName,
-            //    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-            //    _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-            //    currentStep, nextSteps?.FirstOrDefault());
-
-
             //指派实际办理节点
             UpdateActualStep(workflow, dto, nextStepDefine, nextSteps);
 
@@ -567,7 +556,10 @@ namespace Hotline.FlowEngine.Workflows
                 await DuplicateStepWithTraceAsync(workflow, prevStep, EWorkflowTraceStatus.Previous, cancellationToken);
 
             //remove workflow.steps
-            await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
+            //await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
+            await _workflowStepRepository.RemoveNav(removeSteps)
+                .Include(d => d.StepHandlers)
+                .ExecuteCommandAsync();
 
             if (workflow.Status is EWorkflowStatus.Completed)
                 workflow.SetStatusRunnable();
@@ -646,13 +638,14 @@ namespace Hotline.FlowEngine.Workflows
         /// 批量改变办理对象
         /// </summary>
         public async Task<ICollection<string>> ChangeHandlerRangeAsync(string sendPoolId,
-            IReadOnlyList<(string userId, string username, IReadOnlyList<string> stepIds)> handlers,
+            IReadOnlyList<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers,
             CancellationToken cancellationToken)
         {
             var stepsIds = handlers.SelectMany(d => d.stepIds).ToList();
             var steps = await _workflowStepRepository.Queryable()
                 .Includes(d => d.Workflow)
                 .Includes(d => d.WorkflowTrace)
+                .Includes(d => d.StepHandlers)
                 .Where(d => stepsIds.Contains(d.Id))
                 .ToListAsync(cancellationToken);
             foreach (var handler in handlers)
@@ -667,6 +660,11 @@ namespace Hotline.FlowEngine.Workflows
                 var thisSteps = steps.Where(d => handler.stepIds.Contains(d.Id)).ToList();
                 foreach (var thisStep in thisSteps)
                 {
+                    var stepHandler = WorkflowStepHandler.Create(thisStep.Workflow.Id, thisStep.Workflow.ExternalId,
+                        thisStep.FlowAssignType ?? EFlowAssignType.User, handler.userId, handler.username, handler.orgId, handler.orgName);
+                    thisStep.StepHandlers.Clear();
+                    thisStep.StepHandlers.Add(stepHandler);
+
                     thisStep.Handlers = thisHandlers;
 
                     //update trace
@@ -688,11 +686,26 @@ namespace Hotline.FlowEngine.Workflows
             await _workflowStepRepository.UpdateNav(steps)
                 .Include(d => d.WorkflowTrace)
                 .Include(d => d.Workflow)
+                .Include(d=>d.StepHandlers)
                 .ExecuteCommandAsync();
 
             return steps.Select(d => d.WorkflowId).ToList();
         }
 
+        /// <summary>
+        /// 查询工单办理中的一级部门
+        /// </summary>
+        public async Task<ICollection<Kv>> GetLevelOneOrgsAsync(string workflowId, CancellationToken cancellation)
+        {
+            var workflow = await GetWorkflowAsync(workflowId, withSteps: true, cancellationToken: cancellation);
+            return workflow.Steps.Where(d => d.BusinessType == EBusinessType.Department &&
+                                             d.HandlerType == EHandlerType.OrgLevel &&
+                                             d.StepHandlers.Any(d =>
+                                                 !string.IsNullOrEmpty(d.OrgId) && d.OrgId.CheckIfOrgLevelIs(1)))
+                .Select(d => new Kv(d.StepHandlers.First().OrgId, d.StepHandlers.First().OrgName))
+                .ToList();
+        }
+
         /// <summary>
         /// 查找当前会签内所有节点(含start,end)
         /// </summary>
@@ -717,7 +730,8 @@ namespace Hotline.FlowEngine.Workflows
         /// 撤回(返回到之前任意节点)
         /// </summary>
         public async Task RecallAsync(Workflow workflow, RecallDto dto, StepDefine targetStepDefine,
-            FlowAssignInfo flowAssignInfo, DateTime? expiredTime, CancellationToken cancellationToken)
+            FlowAssignInfo flowAssignInfo, List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime,
+            CancellationToken cancellationToken)
         {
             var targetStep = workflow.Steps.FirstOrDefault(d => d.Code == dto.NextStepCode && d.IsOrigin);
             if (targetStep is null)
@@ -727,7 +741,7 @@ namespace Hotline.FlowEngine.Workflows
             await RecallTraceAsync(workflow.Id, dto.Opinion, cancellationToken);
 
             var isOrgToCenter = await RecallAsync(workflow, dto, flowAssignInfo, targetStepDefine, targetStep,
-                EWorkflowTraceStatus.Recall, expiredTime, cancellationToken);
+                EWorkflowTraceStatus.Recall, stepHandlers, expiredTime, cancellationToken);
 
             workflow.ResetHandlers(flowAssignInfo.FlowAssignType, flowAssignInfo.HandlerObjects);
 
@@ -1075,7 +1089,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 创建开始节点
         /// </summary>
         public WorkflowStep CreateStartStep(Workflow workflow, StepDefine startStepDefine,
-            BasicWorkflowDto dto, List<Kv> handles, DateTime? expiredTime)
+            BasicWorkflowDto dto, List<Kv> handles, List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime)
         {
             //startstep
             var nextSteps = _mapper.Map<List<StepSimple>>(startStepDefine.NextSteps);
@@ -1090,6 +1104,7 @@ namespace Hotline.FlowEngine.Workflows
             _mapper.Map(dto, startStep);
             _mapper.Map(workflow, startStep);
             startStep.Handlers = handles;
+            startStep.StepHandlers = stepHandlers;
             startStep.NextSteps = nextSteps;
             startStep.IsMain = true;
             startStep.IsOrigin = true;
@@ -1115,8 +1130,15 @@ namespace Hotline.FlowEngine.Workflows
             WorkflowStep currentStep, EReviewResult? reviewResult = EReviewResult.Unknown,
             CancellationToken cancellationToken = default)
         {
+            var endStepHandles = new List<WorkflowStepHandler>
+            {
+                WorkflowStepHandler.Create(workflow.Id, workflow.ExternalId,
+                    EFlowAssignType.User, _sessionContext.RequiredUserId, _sessionContext.UserName,
+                    _sessionContext.RequiredOrgId, _sessionContext.OrgName)
+            };
+
             //create endStep
-            var endStep = await CreateEndStepAsync(workflow, endStepDefine, currentStep, cancellationToken);
+            var endStep = await CreateEndStepAsync(workflow, endStepDefine, currentStep, endStepHandles, cancellationToken);
             workflow.Steps.Add(endStep);
 
             //update endTrace
@@ -1222,83 +1244,86 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         private async Task<WorkflowStep> CreateStartStepAsync(Workflow workflow, StepDefine startStepDefine,
-            BasicWorkflowDto dto, List<Kv> handles, EWorkflowTraceStatus traceStatus, DateTime? expiredTime,
-            CancellationToken cancellationToken)
+            BasicWorkflowDto dto, List<Kv> handles, List<WorkflowStepHandler> stepHandlers, EWorkflowTraceStatus traceStatus,
+            DateTime? expiredTime, CancellationToken cancellationToken)
         {
-            var startStep = CreateStartStep(workflow, startStepDefine, dto, handles, expiredTime);
-            await _workflowStepRepository.AddAsync(startStep, cancellationToken);
+            var startStep = CreateStartStep(workflow, startStepDefine, dto, handles, stepHandlers, expiredTime);
+            //await _workflowStepRepository.AddAsync(startStep, cancellationToken);
+            await _workflowStepRepository.AddNav(startStep)
+                .Include(d => d.StepHandlers)
+                .ExecuteCommandAsync();
             await CreateTraceAsync(workflow, startStep, traceStatus, cancellationToken);
             return startStep;
         }
 
-        //更新目标节点前一节点的会签办理完成状态
-        private void PrevStepCsHandled(Workflow workflow, WorkflowStep targetStep, ref List<WorkflowStep> updateSteps)
-        {
-            var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == targetStep.PrevStepId);
-            if (prevStep is null)
-                throw new UserFriendlyException("未查询到目标节点的前一节点");
-
-            var csStep = prevStep.CountersignSteps.FirstOrDefault(d => d.StepId == targetStep.Id);
-            if (csStep is null)
-                throw new UserFriendlyException("未查询到当前待办节点");
-            csStep.Completed = true;
-            updateSteps.Add(prevStep);
-        }
+        ////更新目标节点前一节点的会签办理完成状态
+        //private void PrevStepCsHandled(Workflow workflow, WorkflowStep targetStep, ref List<WorkflowStep> updateSteps)
+        //{
+        //    var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == targetStep.PrevStepId);
+        //    if (prevStep is null)
+        //        throw new UserFriendlyException("未查询到目标节点的前一节点");
+
+        //    var csStep = prevStep.CountersignSteps.FirstOrDefault(d => d.StepId == targetStep.Id);
+        //    if (csStep is null)
+        //        throw new UserFriendlyException("未查询到当前待办节点");
+        //    csStep.Completed = true;
+        //    updateSteps.Add(prevStep);
+        //}
 
         /// <summary>
         /// 创建下1/N个节点
         /// </summary>
         private async Task<List<WorkflowStep>> CreateNextStepsAsync(Workflow workflow, WorkflowStep currentStep,
             BasicWorkflowDto dto, StepDefine nextStepDefine, bool isNextDynamic, FlowAssignInfo flowAssignInfo,
-            DateTime? expiredTime, CancellationToken cancellationToken)
+            DateTime? expiredTime, List<WorkflowStepHandler> stepHandlers, CancellationToken cancellationToken)
         {
             List<WorkflowStep> nextSteps = new();
             if (currentStep.IsInCountersign())
             {
                 if (currentStep.IsCountersignEndStep)
                 {
-                    //todo check if current is topend f: csStartStep.prev
-                    //todo t: check if dto.StartCs t: csconfig f: config
+                    // check if current is topend f: csStartStep.prev
+                    // t: check if dto.StartCs t: csconfig f: config
                     if (currentStep.IsTopCountersignEndStep(workflow.TopCountersignStepId))
                     {
                         if (dto.IsStartCountersign)
                         {
                             //依据会签策略创建会签下一级节点
                             nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                                flowAssignInfo.FlowAssignType, expiredTime, cancellationToken);
+                                flowAssignInfo.FlowAssignType, expiredTime, stepHandlers, cancellationToken);
                         }
                         else
                         {
                             //创建普通节点(根据配置)
                             nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                                flowAssignInfo, EWorkflowTraceStatus.Normal, expiredTime, cancellationToken);
+                                flowAssignInfo, EWorkflowTraceStatus.Normal, expiredTime, stepHandlers, cancellationToken);
                         }
                     }
                     else
                     {
-                        //todo csStartStep.prev
+                        // csStartStep.prev
                         var csStartStep =
                             workflow.Steps.FirstOrDefault(d => d.Id == currentStep.CountersignStartStepId);
                         if (csStartStep is null)
                             throw new UserFriendlyException("未查询到会签节点");
 
-                        nextSteps = await CreateCsEndStepsByPrevStepAsync(workflow, csStartStep, dto, expiredTime,
-                            cancellationToken);
+                        nextSteps = await CreateCsEndStepsByTargetPrevAsync(workflow, csStartStep, dto, stepHandlers,
+                            expiredTime, cancellationToken);
                     }
                 }
                 else
                 {
                     if (dto.BackToCountersignEnd)
                     {
-                        //todo check if cs all complete, create next
-                        nextSteps = await CreateCsEndStepsByPrevStepAsync(workflow, currentStep, dto, expiredTime,
-                            cancellationToken);
+                        // check if cs all complete, create next
+                        nextSteps = await CreateCsEndStepsByTargetPrevAsync(workflow, currentStep, dto, stepHandlers,
+                            expiredTime, cancellationToken);
                     }
                     else
                     {
                         //依据会签策略创建会签下一级节点
                         nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                            flowAssignInfo.FlowAssignType, expiredTime, cancellationToken);
+                            flowAssignInfo.FlowAssignType, expiredTime, stepHandlers, cancellationToken);
                     }
                 }
             }
@@ -1306,19 +1331,19 @@ namespace Hotline.FlowEngine.Workflows
             {
                 //依据会签策略创建会签下一级节点
                 nextSteps = await CreateCountersignStepsAsync(workflow, nextStepDefine, currentStep, dto,
-                    flowAssignInfo.FlowAssignType, expiredTime, cancellationToken);
+                    flowAssignInfo.FlowAssignType, expiredTime, stepHandlers, cancellationToken);
             }
             else if (isNextDynamic)
             {
                 //创建动态下一级节点
                 nextSteps = await CreateDynamicStepsAsync(workflow, nextStepDefine, currentStep, dto, flowAssignInfo,
-                    expiredTime, cancellationToken);
+                    expiredTime, stepHandlers, cancellationToken);
             }
             else
             {
                 //创建普通节点(根据配置)
                 nextSteps = await CreateConfigStepsAsync(workflow, nextStepDefine, currentStep, dto, flowAssignInfo,
-                    EWorkflowTraceStatus.Normal, expiredTime, cancellationToken);
+                    EWorkflowTraceStatus.Normal, expiredTime, stepHandlers, cancellationToken);
             }
 
             return nextSteps;
@@ -1331,6 +1356,7 @@ namespace Hotline.FlowEngine.Workflows
             BasicWorkflowDto dto,
             FlowAssignInfo flowAssignInfo,
             DateTime? expiredTime,
+            List<WorkflowStepHandler> stepHandlers,
             CancellationToken cancellationToken)
         {
             var handlerType = nextStepDefine.InstancePolicy switch
@@ -1344,8 +1370,9 @@ namespace Hotline.FlowEngine.Workflows
             };
 
             return await CreateStepsAsync(workflow, nextStepDefine, prevStep, dto,
-                flowAssignInfo.FlowAssignType, dto.NextHandlers, null, EWorkflowStepStatus.WaitForAccept,
-                ECountersignPosition.None, false, EWorkflowTraceStatus.Normal, handlerType, expiredTime, cancellationToken);
+                flowAssignInfo.FlowAssignType, dto.NextHandlers, stepHandlers, null, EWorkflowStepStatus.WaitForAccept,
+                ECountersignPosition.None, false, EWorkflowTraceStatus.Normal, handlerType, expiredTime,
+                cancellationToken: cancellationToken);
         }
 
         private Task<List<WorkflowStep>> CreateCountersignStepsAsync(
@@ -1355,10 +1382,11 @@ namespace Hotline.FlowEngine.Workflows
             BasicWorkflowDto dto,
             EFlowAssignType flowAssignType,
             DateTime? expiredTime,
-            CancellationToken cancellationToken
+            List<WorkflowStepHandler> stepHandlers,
+            CancellationToken cancellationToken = default
         )
         {
-            var countersignId = prevStep.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
+            var countersignId = dto.IsStartCountersign ? prevStep.StartCountersignId : prevStep.CountersignId;
 
             var handlerType = stepDefine.CountersignPolicy switch
             {
@@ -1370,27 +1398,29 @@ namespace Hotline.FlowEngine.Workflows
                 _ => throw new ArgumentOutOfRangeException()
             };
 
-            return CreateStepsAsync(workflow, stepDefine, prevStep, dto, flowAssignType, dto.NextHandlers,
+            return CreateStepsAsync(workflow, stepDefine, prevStep, dto, flowAssignType, dto.NextHandlers, stepHandlers,
                 countersignId, EWorkflowStepStatus.WaitForAccept, prevStep.GetNextStepCountersignPosition(),
-                false, EWorkflowTraceStatus.Normal, handlerType, expiredTime, cancellationToken);
+                false, EWorkflowTraceStatus.Normal, handlerType, expiredTime, cancellationToken: cancellationToken);
         }
 
         /// <summary>
         /// 根据传入节点的上一节点创建会签汇总节点(汇总传入节点的前一节点)
         /// </summary>
-        private async Task<List<WorkflowStep>> CreateCsEndStepsByPrevStepAsync(Workflow workflow, WorkflowStep step,
-            BasicWorkflowDto dto, DateTime? expiredTime, CancellationToken cancellationToken)
+        private async Task<List<WorkflowStep>> CreateCsEndStepsByTargetPrevAsync(Workflow workflow, WorkflowStep step,
+            BasicWorkflowDto dto, List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime, CancellationToken cancellationToken)
         {
-            var prevStep = workflow.Steps.FirstOrDefault(d => d.Id == step.PrevStepId);
-            if (prevStep is null)
+            var countersignStartStep = workflow.Steps.FirstOrDefault(d => d.Id == step.PrevStepId);
+            if (countersignStartStep is null)
                 throw new UserFriendlyException("未查询到当前节点上级节点");
             var nextSteps = new List<WorkflowStep>();
             //会签未全部办理则不创建汇总节点
-            if (prevStep.StartedCountersignHasAllHandled())
+
+            var csInnerSteps = workflow.Steps.Where(d => d.PrevStepId == countersignStartStep.Id).ToList();
+            if (csInnerSteps.All(d => d.Status == EWorkflowStepStatus.Handled))
             {
-                //todo 创建会签汇总节点
+                // 创建会签汇总节点
                 var countersignEndStep =
-                    await CreateCountersignEndStepAsync(prevStep, dto, expiredTime, cancellationToken);
+                    await CreateCountersignEndStepAsync(countersignStartStep, dto, stepHandlers, expiredTime, cancellationToken);
                 nextSteps = new List<WorkflowStep> { countersignEndStep };
 
                 //create trace
@@ -1403,7 +1433,8 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         private async Task<WorkflowStep> CreateCountersignEndStepAsync(WorkflowStep countersignStartStep,
-            BasicWorkflowDto dto, DateTime? expiredTime, CancellationToken cancellationToken)
+            BasicWorkflowDto dto, List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime,
+            CancellationToken cancellationToken = default)
         {
             var csEndStep = _mapper.Map<WorkflowStep>(countersignStartStep);
             csEndStep.Status = EWorkflowStepStatus.WaitForAccept;
@@ -1411,21 +1442,26 @@ namespace Hotline.FlowEngine.Workflows
             csEndStep.PrevStepCode = null;
             csEndStep.IsOrigin = false;
             csEndStep.CountersignId = countersignStartStep.StartCountersignId;
-            csEndStep.CountersignPosition = ECountersignPosition.Outer;
-            csEndStep.CountersignSteps = new();
+            csEndStep.CountersignPosition = ECountersignPosition.End;
+            //csEndStep.CountersignSteps = new();
             csEndStep.IsCountersignEndStep = true;
             csEndStep.CountersignStartStepId = countersignStartStep.Id;
             csEndStep.Name = dto.NextStepName;
             //csEndStep.TimeLimit = GetTimeLimit("");
             csEndStep.StepExpiredTime = expiredTime;
+            csEndStep.BusinessType = dto.BusinessType;
             csEndStep.Handlers = countersignStartStep.Handlers
                 .Where(d => d.Key == countersignStartStep.HandlerId || d.Key == countersignStartStep.HandlerOrgId)
                 .ToList();
+            csEndStep.StepHandlers = stepHandlers;
 
             csEndStep.Reset();
             csEndStep.ResetParameters();
 
-            await _workflowStepRepository.AddAsync(csEndStep, cancellationToken);
+            //await _workflowStepRepository.AddAsync(csEndStep, cancellationToken);
+            await _workflowStepRepository.AddNav(csEndStep)
+                .Include(d => d.StepHandlers)
+                .ExecuteCommandAsync();
             return csEndStep;
         }
 
@@ -1456,6 +1492,10 @@ namespace Hotline.FlowEngine.Workflows
                 _sessionContext.RequiredOrgId, _sessionContext.OrgName,
                 _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
                 _sessionContext.OrgIsCenter, opinion, nextStepCode);
+
+            var handler = step.FindActualHandler(_sessionContext.Roles, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId);
+            if (handler is not null)
+                handler.IsActualHandler = true;
         }
 
 
@@ -1723,7 +1763,7 @@ namespace Hotline.FlowEngine.Workflows
 
         private async Task<bool> RecallAsync(Workflow workflow, BasicWorkflowDto dto, FlowAssignInfo flowAssignInfo,
             StepDefine targetStepDefine, WorkflowStep targetStep, EWorkflowTraceStatus traceStatus,
-            DateTime? expiredTime, CancellationToken cancellationToken)
+            List<WorkflowStepHandler> stepHandlers, DateTime? expiredTime, CancellationToken cancellationToken)
         {
             var targetIsStartStep = targetStepDefine.StepType is EStepType.Start;
 
@@ -1740,7 +1780,10 @@ namespace Hotline.FlowEngine.Workflows
             var removeSteps = GetStepsBehindTargetStep(workflow.Steps, targetStep);
             if (removeSteps.Any())
             {
-                await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
+                //await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
+                await _workflowStepRepository.RemoveNav(removeSteps)
+                    .Include(d => d.StepHandlers)
+                    .ExecuteCommandAsync();
                 workflow.Steps.RemoveAll(d => removeSteps.Contains(d));
             }
 
@@ -1750,39 +1793,39 @@ namespace Hotline.FlowEngine.Workflows
                 workflow.SetStatusRunnable();
 
             var targetStepNew = targetIsStartStep
-                ? await CreateStartStepAsync(workflow, targetStepDefine, dto, dto.NextHandlers, traceStatus, expiredTime,
+                ? await CreateStartStepAsync(workflow, targetStepDefine, dto, dto.NextHandlers, stepHandlers, traceStatus, expiredTime,
                     cancellationToken)
                 : (await CreateStepsAsync(workflow, targetStepDefine, targetPrevStep, dto,
-                    flowAssignInfo.FlowAssignType, dto.NextHandlers,
+                    flowAssignInfo.FlowAssignType, dto.NextHandlers, stepHandlers,
                     null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, true, traceStatus,
-                    null, expiredTime, cancellationToken)).First();
+                    null, expiredTime, cancellationToken: cancellationToken)).First();
 
 
-            //更新当前办理节点信息
-            //workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign,
-            //    _sessionContext.RequiredUserId, _sessionContext.UserName,
-            //    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
-            //    _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName,
-            //    nextStep: targetStepNew);
+            //更新实际办理节点信息
             workflow.UpdateActualStepWhenAssign(targetStepNew, targetStep.HandlerOrgName, targetStep.HandlerOrgId);
 
             //calc workflow expired time
             var isOrgToCenter = CheckIfFlowFromOrgToCenter(workflow, targetStep);
-            //if (isOrgToCenter)
-            //    workflow.ExpiredTime = CalculateExpiredTime("");//todo calc expiredTime
-
+           
             return isOrgToCenter;
         }
 
-        private ICollection<WorkflowStep> GetStepsBehindTargetStep(List<WorkflowStep> steps, WorkflowStep targetStep)
+        private List<WorkflowStep> GetStepsBehindTargetStep(List<WorkflowStep> steps, WorkflowStep targetStep)
         {
             var behindSteps = new List<WorkflowStep> { targetStep };
-            var nextSteps = steps.Where(d => d.PrevStepId == targetStep.Id).ToList();
+            if (!steps.Any()) return behindSteps;
+
+            var nextSteps = targetStep.IsStartCountersign
+                ? steps.Where(d => d.CountersignId == targetStep.StartCountersignId).ToList()
+                : steps.Where(d => d.PrevStepId == targetStep.Id).ToList();
+
+            //var nextSteps = steps.Where(d => d.PrevStepId == targetStep.Id).ToList();
             if (!nextSteps.Any())
                 return behindSteps;
             foreach (var nextStep in nextSteps)
             {
-                behindSteps.AddRange(GetStepsBehindTargetStep(steps, nextStep));
+                var leftSteps = steps.Except(behindSteps).ToList();
+                behindSteps.AddRange(GetStepsBehindTargetStep(leftSteps, nextStep));
             }
 
             return behindSteps;
@@ -1804,6 +1847,7 @@ namespace Hotline.FlowEngine.Workflows
             Workflow workflow,
             StepDefine endStepDefine,
             WorkflowStep prevStep,
+            List<WorkflowStepHandler> stepHandlers,
             CancellationToken cancellationToken)
         {
             if (workflow.Steps.Any(d => d.StepType == EStepType.End))
@@ -1812,8 +1856,8 @@ namespace Hotline.FlowEngine.Workflows
             var handler = new Kv { Key = _sessionContext.RequiredUserId, Value = _sessionContext.UserName };
 
             var step = CreateStep(workflow, endStepDefine, prevStep, null, new List<Kv> { handler },
-                null, null, null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, DateTime.Now,
-                endStepDefine.Name, true);
+                stepHandlers, null, null, null, EWorkflowStepStatus.WaitForAccept,
+                ECountersignPosition.None, DateTime.Now, endStepDefine.Name, true);
 
             //step.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
             //    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
@@ -1837,6 +1881,7 @@ namespace Hotline.FlowEngine.Workflows
             FlowAssignInfo flowAssignInfo,
             EWorkflowTraceStatus traceStatus,
             DateTime? expiredTime,
+            List<WorkflowStepHandler> stepHandlers,
             CancellationToken cancellationToken)
         {
             List<Kv> handlers;
@@ -1859,8 +1904,8 @@ namespace Hotline.FlowEngine.Workflows
             }
 
             return await CreateStepsAsync(workflow, stepDefine, prevStep, dto, flowAssignInfo.FlowAssignType, handlers,
-                null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, true, traceStatus,
-                null, expiredTime, cancellationToken);
+                stepHandlers, null, EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
+                true, traceStatus, null, expiredTime, cancellationToken);
         }
 
         private async Task<List<WorkflowStep>> CreateStepsAsync(
@@ -1870,6 +1915,7 @@ namespace Hotline.FlowEngine.Workflows
             BasicWorkflowDto dto,
             EFlowAssignType? flowAssignType,
             List<Kv> handlers,
+            List<WorkflowStepHandler> stepHandlers,
             string? countersignId,
             EWorkflowStepStatus stepStatus,
             ECountersignPosition csPosition,
@@ -1880,18 +1926,18 @@ namespace Hotline.FlowEngine.Workflows
             CancellationToken cancellationToken = default
         )
         {
-            //var countersignId = prevStep.IsStartCountersign
-            //    ? prevStep.StartCountersignId
-            //    : prevStep.IsInCountersign() ? prevStep.Id : null;
-
             List<WorkflowStep> steps = new();
             if (dto.IsStartCountersign)
             {
                 foreach (var handler in handlers)
                 {
-                    var step = CreateStep(workflow, stepDefine, prevStep, flowAssignType, new List<Kv> { handler },
-                        dto.NextStepCode, dto.NextMainHandler, countersignId,
-                        stepStatus, csPosition, expiredTime, dto.NextStepName, isOrigin, handlerType);
+                    var step = CreateStep(workflow, stepDefine, prevStep, flowAssignType,
+                        new List<Kv> { handler }, new(), dto.NextStepCode,
+                        dto.NextMainHandler, countersignId, stepStatus, csPosition, expiredTime,
+                        dto.NextStepName, isOrigin, handlerType, dto.BusinessType);
+
+                    var stepHandler = stepHandlers.First(d => d.GetHandler().Key == handler.Key);
+                    step.StepHandlers = new List<WorkflowStepHandler> { stepHandler };
 
                     steps.Add(step);
                 }
@@ -1899,13 +1945,16 @@ namespace Hotline.FlowEngine.Workflows
             else
             {
                 var step = CreateStep(workflow, stepDefine, prevStep, flowAssignType, handlers,
-                    dto.NextStepCode, dto.NextMainHandler, countersignId,
-                    stepStatus, csPosition, expiredTime, dto.NextStepName, isOrigin, handlerType);
+                    stepHandlers, dto.NextStepCode, dto.NextMainHandler, countersignId, stepStatus,
+                    csPosition, expiredTime, dto.NextStepName, isOrigin, handlerType, dto.BusinessType);
 
                 steps.Add(step);
             }
 
-            await _workflowStepRepository.AddRangeAsync(steps, cancellationToken);
+            //await _workflowStepRepository.AddRangeAsync(steps, cancellationToken);
+            await _workflowStepRepository.AddNav(steps)
+                .Include(d => d.StepHandlers)
+                .ExecuteCommandAsync();
 
             //create traces todo add range traces
             foreach (var step in steps)
@@ -2011,7 +2060,10 @@ namespace Hotline.FlowEngine.Workflows
 
                 HandleStepsByTerminalCs(startCountersignStep, workflow.Steps, workflow.Traces, ref updateSteps, ref updateTraces);
                 if (updateSteps.Any())
-                    await _workflowStepRepository.RemoveRangeAsync(updateSteps, cancellationToken);
+                    //await _workflowStepRepository.RemoveRangeAsync(updateSteps, cancellationToken);
+                    await _workflowStepRepository.RemoveNav(updateSteps)
+                        .Include(d => d.StepHandlers)
+                        .ExecuteCommandAsync();
                 if (updateTraces.Any())
                     await _workflowTraceRepository.UpdateRangeAsync(updateTraces, cancellationToken);
 
@@ -2074,10 +2126,7 @@ namespace Hotline.FlowEngine.Workflows
             var isHandled = step.Status is EWorkflowStepStatus.Handled;
             var opinion = $"会签未办理完成,由 {_sessionContext.OrgName} 的 {_sessionContext.UserName} 终止办理";
             if (step.IsStartCountersign)
-            {
-                step.CountersignSteps.ForEach(d => d.Completed = true);
                 step.CountersignEnd();
-            }
 
             if (step.Status is not EWorkflowStepStatus.Handled)
             {
@@ -2158,6 +2207,7 @@ namespace Hotline.FlowEngine.Workflows
             WorkflowStep prevStep,
             EFlowAssignType? flowAssignType,
             List<Kv> handlers,
+            List<WorkflowStepHandler> stepHandlers,
             string nextStepCode,
             string? nextMainHandler,
             string? countersignId,
@@ -2166,7 +2216,8 @@ namespace Hotline.FlowEngine.Workflows
             DateTime? expiredTime,
             string stepName,
             bool isOrigin,
-            EHandlerType? handlerType = null //动态节点依据动态策略判断
+            EHandlerType? handlerType = null, //动态节点依据动态策略判断
+            EBusinessType? businessType = null
         )
         {
             if (!handlers.Any())
@@ -2178,6 +2229,7 @@ namespace Hotline.FlowEngine.Workflows
 
             step.FlowAssignType = flowAssignType;
             step.Handlers = handlers;
+            step.StepHandlers = stepHandlers;
             step.NextStepCode = step.StepType is EStepType.End ? string.Empty : nextStepCode;
             step.IsMain = isMain;
             step.PrevStepId = prevStep.Id;
@@ -2192,6 +2244,8 @@ namespace Hotline.FlowEngine.Workflows
 
             if (handlerType.HasValue)
                 step.HandlerType = handlerType.Value;
+            if (businessType.HasValue)
+                step.BusinessType = businessType.Value;
 
             return step;
         }

+ 31 - 22
src/Hotline/FlowEngine/Workflows/WorkflowStep.cs

@@ -38,7 +38,6 @@ public class WorkflowStep : StepBasicEntity
     /// </summary>
     public bool IsOrigin { get; set; }
 
-
     #region 会签
 
     /// <summary>
@@ -53,11 +52,11 @@ public class WorkflowStep : StepBasicEntity
     /// </summary>
     public ECountersignPosition CountersignPosition { get; set; }
 
-    /// <summary>
-    /// 会签直属办理节点
-    /// </summary>
-    [SugarColumn(ColumnDataType = "json", IsJson = true)]
-    public List<CountersignStep> CountersignSteps { get; set; } = new();
+    ///// <summary>
+    ///// 会签直属办理节点
+    ///// </summary>
+    //[SugarColumn(ColumnDataType = "json", IsJson = true)]
+    //public List<CountersignStep> CountersignSteps { get; set; } = new();
 
     /// <summary>
     /// 发起的会签是否汇总
@@ -92,10 +91,13 @@ public class WorkflowStep : StepBasicEntity
     [Navigate(NavigateType.ManyToOne, nameof(WorkflowId))]
     public Workflow Workflow { get; set; }
 
-    [Navigate(NavigateType.OneToOne, nameof(Id), 
+    [Navigate(NavigateType.OneToOne, nameof(Id),
         nameof(Workflows.WorkflowTrace.StepId))]
     public WorkflowTrace WorkflowTrace { get; set; }
 
+    [Navigate(NavigateType.OneToMany, nameof(WorkflowStepHandler.WorkflowStepId))]
+    public List<WorkflowStepHandler> StepHandlers { get; set; }
+
     #region Method
 
     /// <summary>
@@ -161,10 +163,10 @@ public class WorkflowStep : StepBasicEntity
         IsStartedCountersignEnd = false;
     }
 
-    public void CreateCountersignSteps(List<WorkflowStep> nextSteps) =>
-        CountersignSteps = nextSteps
-            .Select(d => new CountersignStep { StepId = d.Id })
-            .ToList();
+    //public void CreateCountersignSteps(List<WorkflowStep> nextSteps) =>
+    //    CountersignSteps = nextSteps
+    //        .Select(d => new CountersignStep { StepId = d.Id })
+    //        .ToList();
 
 
     /// <summary>
@@ -228,17 +230,17 @@ public class WorkflowStep : StepBasicEntity
         StepExpiredTime = null;
     }
 
-    /// <summary>
-    /// 发起会签节点发起的会签是否全都办理完成
-    /// </summary>
-    /// <returns></returns>
-    public bool StartedCountersignHasAllHandled()
-    {
-        //if (!HasStartedCountersign())
-        //    throw new UserFriendlyException("该节点未发起会签");
-        //outer属于特殊会签
-        return CountersignSteps.All(d => d.Completed);
-    }
+    ///// <summary>
+    ///// 发起会签节点发起的会签是否全都办理完成
+    ///// </summary>
+    ///// <returns></returns>
+    //public bool StartedCountersignHasAllHandled()
+    //{
+    //    //if (!HasStartedCountersign())
+    //    //    throw new UserFriendlyException("该节点未发起会签");
+    //    //outer属于特殊会签
+    //    return CountersignSteps.All(d => d.Completed);
+    //}
 
     /// <summary>
     /// 动态实例化节点是否应该终止
@@ -256,6 +258,13 @@ public class WorkflowStep : StepBasicEntity
         return IsCountersignEndStep && CountersignStartStepId == topCountersignStepId;
     }
 
+    public WorkflowStepHandler?
+        FindActualHandler(ICollection<string> roles, string? userId = null, string? orgId = null) =>
+        StepHandlers.FirstOrDefault(d => d.IsHandler(roles, userId, orgId));
+
+    public WorkflowStepHandler? GetActualHandler() =>
+        StepHandlers.FirstOrDefault(d => d.IsActualHandler);
+
     #endregion
 }
 

+ 114 - 0
src/Hotline/FlowEngine/Workflows/WorkflowStepHandler.cs

@@ -0,0 +1,114 @@
+using Hotline.Share.Dtos;
+using SqlSugar;
+using XF.Domain.Entities;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.FlowEngine.Workflows;
+
+public class WorkflowStepHandler : CreationEntity
+{
+    public string WorkflowId { get; set; }
+    public string ExternalId { get; set; }
+    public string WorkflowStepId { get; set; }
+
+    /// <summary>
+    /// 流程指派类型
+    /// </summary>
+    public EFlowAssignType FlowAssignType { get; set; }
+
+    public string? UserId { get; set; }
+    public string? Username { get; set; }
+    public string? OrgId { get; set; }
+    public string? OrgName { get; set; }
+    public string? RoleId { get; set; }
+    public string? RoleName { get; set; }
+
+    /// <summary>
+    /// 是否为实际办理者
+    /// </summary>
+    public bool IsActualHandler { get; set; }
+
+    [Navigate(NavigateType.ManyToOne, nameof(WorkflowStepId))]
+    public WorkflowStep WorkflowStep { get; set; }
+
+    #region method
+
+    public static WorkflowStepHandler Create(string workflowId, string externalId, EFlowAssignType flowAssignType, string? userId = null, string? username = null,
+        string? orgId = null, string? orgName = null, string? roleId = null, string? roleName = null)
+    {
+        var handler = new WorkflowStepHandler
+        {
+            WorkflowId = workflowId,
+            ExternalId = externalId,
+        };
+        handler.Assign(flowAssignType, userId, username, orgId, orgName, roleId, roleName);
+        return handler;
+    }
+
+    public void Assign(EFlowAssignType assignType, string? userId, string? username,
+        string? orgId, string? orgName, string? roleId, string? roleName)
+    {
+        FlowAssignType = assignType;
+        switch (assignType)
+        {
+            case EFlowAssignType.Org:
+                if (string.IsNullOrEmpty(orgId) || string.IsNullOrEmpty(orgName))
+                    throw new ArgumentNullException(nameof(orgId));
+                OrgId = orgId;
+                OrgName = orgName;
+                break;
+            case EFlowAssignType.User:
+                if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(username) ||
+                   string.IsNullOrEmpty(orgId) || string.IsNullOrEmpty(orgName))
+                    throw new ArgumentNullException(nameof(userId));
+                UserId = userId;
+                Username = username;
+                OrgId = orgId;
+                OrgName = orgName;
+                break;
+            //case EFlowAssignType.Role:
+            //    if (string.IsNullOrEmpty(roleId) || string.IsNullOrEmpty(roleName))
+            //        throw new ArgumentNullException(nameof(roleId));
+            //    RoleId = roleId;
+            //    RoleName = roleName;
+            //    break;
+            default:
+                throw new ArgumentOutOfRangeException(nameof(assignType), assignType, null);
+        }
+    }
+
+    public bool IsHandler(ICollection<string> roleIds, string? userId = null, string? orgId = null)
+    {
+        switch (FlowAssignType)
+        {
+            case EFlowAssignType.Org:
+                if (string.IsNullOrEmpty(OrgId))
+                    throw new UserFriendlyException($"数据异常, {nameof(OrgId)}");
+                return OrgId.Equals(orgId, StringComparison.OrdinalIgnoreCase);
+            case EFlowAssignType.User:
+                if (string.IsNullOrEmpty(UserId))
+                    throw new UserFriendlyException($"数据异常, {nameof(UserId)}");
+                return UserId.Equals(userId, StringComparison.OrdinalIgnoreCase);
+            //case EFlowAssignType.Role:
+            //    if (string.IsNullOrEmpty(RoleId))
+            //        throw new UserFriendlyException($"数据异常, {nameof(RoleId)}");
+            //    return roleIds.Any() && roleIds.Contains(RoleId, StringComparer.OrdinalIgnoreCase);
+            default:
+                throw new ArgumentOutOfRangeException();
+        }
+    }
+
+    public Kv GetHandler()
+    {
+        return FlowAssignType switch
+        {
+            EFlowAssignType.Org => new Kv(OrgId, OrgName),
+            EFlowAssignType.User => new Kv(UserId, Username),
+            //EFlowAssignType.Role => new Kv(RoleId, RoleName),
+            _ => throw new ArgumentOutOfRangeException()
+        };
+    }
+
+    #endregion
+}

+ 5 - 0
src/Hotline/JudicialManagement/EnforcementOrders.cs

@@ -36,6 +36,11 @@ namespace Hotline.JudicialManagement
         /// </summary>
         public string? EventTypeName { get; set; }
 
+        /// <summary>
+        /// 诉事项类型名称
+        /// </summary>
+        public string? EventTypeSpliceName { get; set; }
+
         /// <summary>
         /// 诉事项类型一级Id
         /// </summary>

+ 39 - 22
src/Hotline/Orders/OrderDomainService.cs

@@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Http;
 using Hotline.Settings;
 using Hotline.Share.Dtos;
 using XF.Domain.Constants;
+using System.Collections.Generic;
 
 namespace Hotline.Orders;
 
@@ -183,21 +184,36 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     /// <returns></returns>
     public async Task<List<Kv>> AverageOrder(CancellationToken cancellationToken)
     {
-        var user = await _userRepository.Queryable().Where(x => x.Id == OrderDefaults.SourceChannel.SendPoolId).FirstAsync(cancellationToken);
-        DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
-        //根据当前时间获取排班信息
+        var time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
         var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
             .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.AtWork == true)
             .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
-        if (scheduling != null)
-        {
-            user = await _userRepository.GetAsync(x => x.Id == scheduling.SchedulingUser.UserId, cancellationToken);
-            scheduling.SendOrderNum++;
-            await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
-        }
-        List<Kv> res = new List<Kv>();
-        res.Add(new Kv { Key = user.Id, Value = user.Name });
-        return res;
+        if (scheduling is null)
+            return new List<Kv> { new(OrderDefaults.SourceChannel.SendPoolId, "待派单池") };
+
+        var user = await _userRepository.GetAsync(x => x.Id == scheduling.SchedulingUser.UserId, cancellationToken);
+        if (user is null)
+            throw new UserFriendlyException("无效用户编号");
+        scheduling.SendOrderNum++;
+        await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
+        return new List<Kv> { new(user.Id, user.Name) };
+
+
+        //var user = await _userRepository.Queryable(includeDeleted: true).Where(x => x.Id == OrderDefaults.SourceChannel.SendPoolId).FirstAsync(cancellationToken);
+        //DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
+        ////根据当前时间获取排班信息
+        //var scheduling = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
+        //    .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay && x.AtWork == true)
+        //    .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
+        //if (scheduling != null)
+        //{
+        //    user = await _userRepository.GetAsync(x => x.Id == scheduling.SchedulingUser.UserId, cancellationToken);
+        //    scheduling.SendOrderNum++;
+        //    await _schedulingRepository.UpdateAsync(scheduling, cancellationToken);
+        //}
+        //List<Kv> res = new List<Kv>();
+        //res.Add(new Kv { Key = user.Id, Value = user.Name });
+        //return res;
     }
 
     /// <summary>
@@ -213,7 +229,9 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         var steps = await _workflowDomainService.GetUnhandleStepIdsFromSendPoolAsync(OrderDefaults.SourceChannel.SendPoolId, cancellationToken);
         var stepsList = steps.ToList();
 
-        var user = await _userRepository.GetAsync(userId, cancellationToken);
+        var user = await _userRepository.Queryable()
+            .Includes(d => d.Organization)
+            .FirstAsync(d => d.Id == userId, cancellationToken);
         DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
         var schedulings = await _schedulingRepository.Queryable().Includes(x => x.SchedulingUser)
             .Where(x => x.SchedulingTime == time && x.WorkingTime <= DateTime.Now.TimeOfDay && x.OffDutyTime >= DateTime.Now.TimeOfDay).CountAsync(cancellationToken);
@@ -224,11 +242,11 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             var sendNum = stepsList.Count() / schedulings;
             for (int i = 0; i < sendNum; i++)
             {
-                stepIds.Add(stepsList[i]);
-                stepsList.Remove(stepsList[i]);
+                stepIds.Add(stepsList[0]);
+                stepsList.Remove(stepsList[0]);
             }
-            List<(string userId, string username, IReadOnlyList<string> stepIds)> handlers = new();
-            ; handlers.Add(new ValueTuple<string, string, IReadOnlyList<string>>(user.Id, user.Name, stepIds));
+            List<(string, string, string, string, IReadOnlyList<string> stepIds)> handlers = new();
+            ; handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(user.Id, user.Name, user.OrgId, user.Organization.Name, stepIds));
             var workflowIds = await _workflowDomainService.ChangeHandlerRangeAsync(OrderDefaults.SourceChannel.SendPoolId, handlers, cancellationToken);
             var orders = await _orderRepository.Queryable().Includes(d => d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
                 .ToListAsync(cancellationToken);
@@ -261,7 +279,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
             var stepsList = steps.ToList();
 
             var sendNum = steps.Count() / schedulings.Count();
-            List<(string userId, string username, IReadOnlyList<string> stepIds)> handlers = new();
+            List<(string userId, string username, string orgId, string orgName, IReadOnlyList<string> stepIds)> handlers = new();
             if (sendNum > 0)
             {
 
@@ -273,7 +291,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
                         stepIds.Add(stepsList[0]);
                         stepsList.Remove(stepsList[0]);
                     }
-                    handlers.Add(new ValueTuple<string, string, IReadOnlyList<string>>(scheduling.SchedulingUser.UserId, scheduling.SchedulingUser.UserName, stepIds));
+                    handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(scheduling.SchedulingUser.UserId, scheduling.SchedulingUser.UserName, scheduling.SchedulingUser.OrgId, scheduling.SchedulingUser.OrgIdName, stepIds));
 
                 }
             }
@@ -286,13 +304,12 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
                     stepIds.Add(stepsList[0]);
                     stepsList.Remove(stepsList[0]);
                 }
-                handlers.Add(new ValueTuple<string, string, IReadOnlyList<string>>(schedulings[0].SchedulingUser.UserId, schedulings[0].SchedulingUser.UserName, stepIds));
-
+                handlers.Add(new ValueTuple<string, string, string, string, IReadOnlyList<string>>(schedulings[0].SchedulingUser.UserId, schedulings[0].SchedulingUser.UserName, schedulings[0].SchedulingUser.OrgId, schedulings[0].SchedulingUser.OrgIdName, stepIds));
             }
             if (handlers.Any())
             {
                 var workflowIds = await _workflowDomainService.ChangeHandlerRangeAsync(OrderDefaults.SourceChannel.SendPoolId, handlers, cancellationToken);
-                var orders = await _orderRepository.Queryable().Includes(d=>d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
+                var orders = await _orderRepository.Queryable().Includes(d => d.Workflow).Where(d => workflowIds.Contains(d.WorkflowId))
                     .ToListAsync(cancellationToken);
                 foreach (var order in orders)
                 {

+ 4 - 0
src/Hotline/Realtimes/IRealtimeService.cs

@@ -61,5 +61,9 @@ namespace Hotline.Realtimes
         Task OrderHandlingDetailAsync(object obj, CancellationToken cancellationToken);
         #endregion
 
+        #region 司法大屏
+
+        Task EnforcementOrderHandlingDetailAsync(object obj, CancellationToken cancellationToken); 
+        #endregion
     }
 }

+ 18 - 0
src/Hotline/Schedulings/SchedulingUser.cs

@@ -1,4 +1,5 @@
 using Hotline.Orders;
+using Hotline.Settings;
 using Hotline.Users;
 using SqlSugar;
 using System;
@@ -26,10 +27,27 @@ namespace Hotline.Schedulings
 		[SugarColumn(ColumnDescription = "用户名称")]
 		public string? UserName { get; set; }
 
+
+		/// <summary>
+		/// 用户机构ID
+		/// </summary>
+		[SugarColumn(ColumnDescription = "用户机构ID")]
+		public string? OrgId { get; set; }
+
+
+		/// <summary>
+		/// 用户机构名称
+		/// </summary>
+		[SugarColumn(ColumnDescription = "用户机构名称")]
+		public string? OrgIdName { get; set; }
+
 		/// <summary>
 		/// 
 		/// </summary>
 		[Navigate(NavigateType.OneToOne, nameof(UserId))]
 		public User User { get; set; }
+
+		[Navigate(NavigateType.OneToOne, nameof(OrgId))]
+		public SystemOrganize Organize { get; set; }
 	}
 }

+ 4 - 1
src/XF.Domain/Constants/SettingConstants.cs

@@ -207,6 +207,9 @@ namespace XF.Domain.Constants
         /// </summary>
         public const string EnforcementPassTheBuckNum = "EnforcementPassTheBuckNum";
 
-
+        /// <summary>
+        /// 行政执法大屏工单数据推送间隔
+        /// </summary>
+        public const string EnforcementDataOrderChangedTimes = "EnforcementDataOrderChangedTimes";
     }
 }