Browse Source

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

Dun.Jason 6 months ago
parent
commit
9b88876873
60 changed files with 2458 additions and 349 deletions
  1. 90 19
      src/Hotline.Api/Controllers/Bi/BiCallController.cs
  2. 304 54
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  3. 136 97
      src/Hotline.Api/Controllers/OrderController.cs
  4. 31 20
      src/Hotline.Api/Controllers/OrderRevocationController.cs
  5. 312 0
      src/Hotline.Api/Controllers/OrderTerminateController.cs
  6. 15 11
      src/Hotline.Api/Controllers/TestController.cs
  7. 76 32
      src/Hotline.Api/Controllers/UserController.cs
  8. 1 1
      src/Hotline.Api/StartupExtensions.cs
  9. 1 1
      src/Hotline.Api/config/appsettings.Development.json
  10. 20 2
      src/Hotline.Application.Contracts/Validators/CallCenter/StartEndTimeDtoValidator.cs
  11. 44 0
      src/Hotline.Application.Tests/Application/OrderVisitApplicationTest.cs
  12. 52 2
      src/Hotline.Application.Tests/Application/ZiGongCallReportApplicationTest.cs
  13. 3 1
      src/Hotline.Application.Tests/DefaultHttpContextAccessor.cs
  14. 1 0
      src/Hotline.Application.Tests/Domain/YiBinExpireTimeTest.cs
  15. 4 1
      src/Hotline.Application.Tests/Startup.cs
  16. 72 7
      src/Hotline.Application/ExportExcel/ExportApplication.cs
  17. 19 3
      src/Hotline.Application/ExportExcel/IExportApplication.cs
  18. 6 6
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  19. 15 4
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  20. 20 4
      src/Hotline.Application/Handlers/FlowEngine/WorkflowPreviousHandler.cs
  21. 18 5
      src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs
  22. 6 0
      src/Hotline.Application/Orders/IOrderApplication.cs
  23. 13 0
      src/Hotline.Application/Orders/IOrderVisitApplication.cs
  24. 39 16
      src/Hotline.Application/Orders/OrderApplication.cs
  25. 42 0
      src/Hotline.Application/Orders/OrderVisitApplication.cs
  26. 79 8
      src/Hotline.Application/StatisticalReport/CallReport/CallReportApplicationBase.cs
  27. 9 0
      src/Hotline.Application/StatisticalReport/ICallReportApplication.cs
  28. 23 16
      src/Hotline.Application/Subscribers/DatasharingSubscriber.cs
  29. 31 1
      src/Hotline.Application/Systems/BaseDataApplication.cs
  30. 16 0
      src/Hotline.Application/Users/IUserApplication.cs
  31. 49 0
      src/Hotline.Application/Users/UserApplication.cs
  32. 6 0
      src/Hotline.Repository.SqlSugar/File/FileRepository.cs
  33. 11 3
      src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs
  34. 118 1
      src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs
  35. 135 0
      src/Hotline.Share/Dtos/CallCenter/CenterReportStatisticsDto.cs
  36. 64 0
      src/Hotline.Share/Dtos/CallCenter/QueryCallsDetailDto.cs
  37. 12 2
      src/Hotline.Share/Dtos/File/FileDto.cs
  38. 6 4
      src/Hotline.Share/Dtos/Order/OrderBiDto.cs
  39. 11 2
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  40. 13 1
      src/Hotline.Share/Dtos/Order/OrderStartFlowDto.cs
  41. 222 0
      src/Hotline.Share/Dtos/Order/OrderTerminateDto.cs
  42. 41 1
      src/Hotline.Share/Dtos/Order/OrderVisitDto.cs
  43. 5 0
      src/Hotline.Share/Dtos/Order/OrderWaitedDto.cs
  44. 6 0
      src/Hotline.Share/Dtos/Order/SendBackDto.cs
  45. 10 0
      src/Hotline.Share/Dtos/Users/UserDto.cs
  46. 1 1
      src/Hotline.Share/Dtos/Users/UserPagedDto.cs
  47. 18 0
      src/Hotline.Share/Enums/Order/EAttitudeType.cs
  48. 9 2
      src/Hotline.Share/Requests/PagedKeywordRequest.cs
  49. 15 0
      src/Hotline.Share/Tools/TupleExtensions.cs
  50. 1 0
      src/Hotline/Caching/Interfaces/ISystemSettingCacheManager.cs
  51. 2 0
      src/Hotline/Caching/Services/SystemSettingCacheManager.cs
  52. 12 0
      src/Hotline/File/File.cs
  53. 13 1
      src/Hotline/FlowEngine/WorkflowModules/WorkflowModuleConsts.cs
  54. 6 1
      src/Hotline/Orders/IOrderRepository.cs
  55. 7 1
      src/Hotline/Orders/Order.cs
  56. 67 0
      src/Hotline/Orders/OrderTerminate.cs
  57. 5 0
      src/Hotline/Orders/OrderVisit.cs
  58. 73 14
      src/Hotline/Permissions/EPermission.cs
  59. 12 0
      src/Hotline/Settings/SettingConstants.cs
  60. 10 4
      src/Hotline/Users/User.cs

+ 90 - 19
src/Hotline.Api/Controllers/Bi/BiCallController.cs

@@ -1,4 +1,5 @@
 using Hotline.Application.ExportExcel;
+using Hotline.Share.Tools;
 using Hotline.Application.StatisticalReport;
 using Hotline.Application.Systems;
 using Hotline.Caching.Interfaces;
@@ -16,6 +17,7 @@ using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
 using System.Data;
+using System.Linq.Dynamic.Core;
 using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers.Bi;
@@ -81,22 +83,94 @@ public class BiCallController : BaseController
     [AllowAnonymous]
     public async Task<FileStreamResult> ExportQueryCallsAsync([FromBody] ExportExcelDto<BiQueryCallsDto> dto)
         => ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
                 dto,
                 await _callReportApplication.QueryCallsAsync(dto.QueryDto, HttpContext.RequestAborted),
                 list =>
                 {
-                    list.Add(new BiCallDto()
+                    return new BiCallDto()
                     {
                         HourRange = "合计",
                         Hour = 13,
                         Total = list.Sum(p => p.Total),
                         Answered = list.Sum(p => p.Answered),
                         Hanguped = list.Sum(p => p.Hanguped)
-                    });
+                    };
                 }),
             "话务统计分析");
 
+    /// <summary>
+    /// 话务日期统计
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("query_calls_statistics")]
+    public async Task<TotalList<QueryCallsDetailStatistics>> QueryCallsStatisticsAsync([FromQuery] StartEndTimeDto dto)
+    {
+        var items = await _callReportApplication.QueryCallsDetailStatisticsAsync(dto, HttpContext.RequestAborted);
+
+        var total = new QueryCallsDetailStatistics
+        {
+            Date = "合计",
+            InTotal = items.Sum(m => m.InTotal),
+            NotAcceptedHang = items.Sum(m => m.NotAcceptedHang),
+            InConnectionQuantity = items.Sum(m => m.InConnectionQuantity),
+            InNotAnswered = items.Sum(m => m.InNotAnswered),
+            IvrByeCount = items.Sum(m => m.IvrByeCount),
+            OutConnectionQuantity = items.Sum(m => m.OutConnectionQuantity),
+            OutNotAnswered = items.Sum(m => m.OutNotAnswered)
+        };
+
+        return new TotalList<QueryCallsDetailStatistics>(items, total);
+    }
+
+    /// <summary>
+    /// 话务日期统计-导出
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("query_calls_statistics_export")]
+    public async Task<FileStreamResult> QueryCallsStatisticsExportAsync([FromBody] ExportExcelDto<StartEndTimeDto> dto)
+    {
+        var items = (await _callReportApplication.QueryCallsDetailStatisticsAsync(dto.QueryDto, HttpContext.RequestAborted));
+        return _exportApplication.GetExcelFile(dto, items, "话务日期统计", "Date");
+    }
+
+    /// <summary>
+    /// 话务日期统计详情
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("query_calls_statistics_detail")]
+    public async Task<PagedDto<QueryCallsStatisticsDetailOutDto>> QueryCallsStatisticsDetailAsync([FromQuery] QueryCallsStatisticsDetailInDto dto)
+        => (await _callReportApplication.QueryCallsStatisticsDetailAsync(dto, HttpContext.RequestAborted))
+            .ToPaged();
+
+    /// <summary>
+    /// 话务日期统计详情--导出
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("query_calls_statistics_detail/export")]
+    public async Task<FileStreamResult> QueryCallsStatisticsDetailAsync([FromBody] ExportExcelDto<QueryCallsStatisticsDetailInDto> dto)
+    {
+        var items = (await _callReportApplication.QueryCallsStatisticsDetailAsync(dto.QueryDto, HttpContext.RequestAborted)).Item2;
+        return _exportApplication.GetExcelFile(dto, items, "话务日期统计详情", "OrderNo");
+    }
+
+    /// <summary>
+    /// 话务日期统计详情页面基础数据
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("query_calls_statistics_detail/base-data")]
+    public async Task<Dictionary<string, dynamic>> QueryCallsStatisticsDetailBaseData()
+    {
+        return _baseDataApplication
+            .EndBy()
+            .Build();
+    }
+
     /// <summary>
     /// 话务日期明细
     /// </summary>
@@ -138,12 +212,12 @@ public class BiCallController : BaseController
     public async Task<FileStreamResult> QueryCallsDetailExportAsync([FromBody] ExportExcelDto<BiQueryCallsDto> dto)
     {
         return ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
                 dto,
                 await _callReportApplication.QueryCallsDetailAsync(dto.QueryDto),
                 items =>
                 {
-                    var total = new QueryCallsDetailDto
+                    return new QueryCallsDetailDto
                     {
                         Date = "合计",
                         Hour = "",
@@ -160,9 +234,7 @@ public class BiCallController : BaseController
                         OutTotal = items.Sum(p => p.OutTotal),
                         OutConnectionQuantity = items.Sum(p => p.OutConnectionQuantity)
                     };
-                    items.Add(total);
-                }
-                )
+                })
             , "话务日期明细数据");
 
     }
@@ -200,7 +272,7 @@ public class BiCallController : BaseController
 
 
         return ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
             dto,
             (await _callReportApplication.QueryCallsDetailInTotalAsync(dto.QueryDto, dto.IsExportAll)).Item2
             ),
@@ -247,12 +319,12 @@ public class BiCallController : BaseController
     [HttpPost("query_calls_hour_detail_list_export")]
     public async Task<FileStreamResult> QueryCallsHourDetailListExportAsync([FromBody] ExportExcelDto<BiQueryCallsDto> dto)
         => ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
                 dto,
                 await _callReportApplication.QueryCallsHourDetailAsync(dto.QueryDto, HttpContext.RequestAborted),
                 items =>
                 {
-                    var total = new QueryCallsDetailDto
+                    return new QueryCallsDetailDto
                     {
                         Date = "",
                         Hour = "合计",
@@ -269,7 +341,6 @@ public class BiCallController : BaseController
                         OutTotal = items.Sum(p => p.OutTotal),
                         OutConnectionQuantity = items.Sum(p => p.OutConnectionQuantity)
                     };
-                    items.Add(total);
                 })
             , "话务日期明细-时间段");
 
@@ -283,12 +354,12 @@ public class BiCallController : BaseController
     public async Task<FileStreamResult> ExportSeatss([FromBody] ExportExcelDto<ReportRequiredPagedRequest> dto)
     {
         return ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
                 dto,
                 await _callReportApplication.QuerySeatCallAsync(dto.QueryDto, HttpContext.RequestAborted),
                 list =>
                 {
-                    list.Add(new BiSeatCallsDto()
+                    return new BiSeatCallsDto()
                     {
                         Name = "合计",
                         InTotal = list.Sum(p => p.InTotal),
@@ -303,7 +374,7 @@ public class BiCallController : BaseController
                         LoginDuration = list.Sum(m => m.LoginDuration),
                         RestDuration = list.Sum(m => m.RestDuration),
                         InDurationAvg = list.Sum(m => m.InDurationAvg)
-                    });
+                    };
                 })
             , "坐席话务统计分析");
     }
@@ -378,12 +449,12 @@ public class BiCallController : BaseController
     [AllowAnonymous]
     public async Task<FileStreamResult> ExportQueryHourCall([FromBody] ExportExcelDto<BiQueryHourCallDto> dto)
         => ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
                 dto,
                 await _callReportApplication.GetCallHourListAsync(dto.QueryDto, HttpContext.RequestAborted),
                 list =>
                 {
-                    list.Add(new TrCallHourDto()
+                    return new TrCallHourDto()
                     {
                         HourTo = "合计",
                         EffectiveCount = list.Sum(p => p.EffectiveCount),
@@ -391,7 +462,7 @@ public class BiCallController : BaseController
                         NoConnectByeCount = list.Sum(p => p.NoConnectByeCount),
                         QueueByeCount = list.Sum(m => m.QueueByeCount),
                         IvrByeCount = list.Sum(m => m.IvrByeCount)
-                    });
+                    };
                 })
             , "通话时段分析");
 
@@ -435,7 +506,7 @@ public class BiCallController : BaseController
     [HttpPost("gateway-query/export")]
     public async Task<FileStreamResult> ExportQueryGatetWay(ExportExcelDto<BiQueryGateWayDto> dto)
         => ExcelStreamResult(
-            await _exportApplication.FittingAsync(
+            _exportApplication.GetExcelFile(
                 dto,
                 await _callReportApplication.GetCallHotLineListAsync(dto.QueryDto, HttpContext.RequestAborted)
                 )

+ 304 - 54
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -1,6 +1,8 @@
-using Hotline.Application.FlowEngine;
+using Hotline.Application.ExportExcel;
+using Hotline.Application.FlowEngine;
 using Hotline.Application.Orders;
 using Hotline.Application.StatisticalReport;
+using Hotline.Application.Systems;
 using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.FlowEngine.WorkflowModules;
@@ -24,11 +26,14 @@ using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
 using Hotline.Share.Tools;
 using Hotline.Tools;
+using Mapster;
 using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
+using NPOI.SS.Formula.Functions;
 using SqlSugar;
+using System.ComponentModel;
 using System.Data;
 using XF.Domain.Authentications;
 using XF.Domain.Exceptions;
@@ -39,6 +44,8 @@ namespace Hotline.Api.Controllers.Bi
 {
     public class BiOrderController : BaseController
     {
+        private readonly IExportApplication _exportApplication;
+        private readonly BaseDataApplication _baseDataApplication;
         private readonly IOrderRepository _orderRepository;
         private readonly IRepository<Hotspot> _hotspotTypeRepository;
         private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
@@ -60,6 +67,7 @@ namespace Hotline.Api.Controllers.Bi
         private readonly IRepository<OrderScreen> _orderScreenRepository;
         private readonly IOrderSecondaryHandlingApplication _orderSecondaryHandlingApplication;
         private readonly IOrderApplication _orderApplication;
+        private readonly IOrderVisitApplication _orderVisitApplication;
         //private readonly ITimeLimitDomainService _timeLimitDomainService;
         private readonly IOrderReportApplication _orderReportApplication;
         private readonly IRepository<SystemArea> _systemAreaRepository;
@@ -69,6 +77,8 @@ namespace Hotline.Api.Controllers.Bi
         private readonly ICalcExpireTime _expireTime;
         private readonly IWorkflowApplication _workflowApplication;
         private readonly ISystemOrganizeRepository _organizeRepository;
+        private readonly IRepository<CallNative> _callNativeRepository;
+
         public BiOrderController(
             IOrderRepository orderRepository,
             IRepository<Hotspot> hotspotTypeRepository,
@@ -99,7 +109,11 @@ namespace Hotline.Api.Controllers.Bi
             IRepository<WorkflowStep> workflowStepRepository,
             IWorkflowApplication workflowApplication,
             ICalcExpireTime expireTime,
-            ISystemOrganizeRepository organizeRepository)
+            ISystemOrganizeRepository organizeRepository,
+            BaseDataApplication baseDataApplication,
+            IExportApplication exportApplication,
+            IOrderVisitApplication orderVisitApplication,
+            IRepository<CallNative> callNativeRepository)
         {
             _orderRepository = orderRepository;
             _hotspotTypeRepository = hotspotTypeRepository;
@@ -131,6 +145,10 @@ namespace Hotline.Api.Controllers.Bi
             _workflowApplication = workflowApplication;
             _expireTime = expireTime;
             _organizeRepository = organizeRepository;
+            _baseDataApplication = baseDataApplication;
+            _exportApplication = exportApplication;
+            _orderVisitApplication = orderVisitApplication;
+            _callNativeRepository = callNativeRepository;
         }
 
         /// <summary>
@@ -264,7 +282,6 @@ namespace Hotline.Api.Controllers.Bi
         /// </summary>
         /// <returns></returns>
         [HttpGet("visit/source")]
-        [AllowAnonymous]
         public async Task<IList<OrderVisitSourceChannelDto>> QueryOrderVisitSourceChannelAsync([FromQuery] QueryOrderVisitSourceChannelDto dto)
             => await _orderApplication.QueryOrderVisitSourceChannelAsync(dto);
 
@@ -273,29 +290,29 @@ namespace Hotline.Api.Controllers.Bi
         /// </summary>
         /// <returns></returns>
         [HttpPost("visit/source/export")]
-        [AllowAnonymous]
         public async Task<FileStreamResult> QueryOrderVisitSourceChannelExportAsync([FromBody] ExportExcelDto<QueryOrderVisitSourceChannelDto> dto)
         {
             var list = await _orderApplication.QueryOrderVisitSourceChannelAsync(dto.QueryDto);
-            if (list != null && list.Count > 0)
-            {
-                list.Add(new OrderVisitSourceChannelDto()
-                {
-                    SourceChannel = "合计",
-                    Count = list.Sum(p => p.Count)
-                });
-            }
-
-            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
-
-            var dtos = list
-                .Select(stu => _mapper.Map(stu, typeof(OrderVisitSourceChannelDto), dynamicClass))
-                .Cast<object>()
-                .ToList();
+            return _exportApplication.GetExcelFile(dto, list, "回访来源统计", "SourceChannel");
+        }
 
-            var stream = ExcelHelper.CreateStream(dtos);
+        /// <summary>
+        /// 回访量统计
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("visit/quantity")]
+        public async Task<IList<OrderVisitQuantityOutDto>> QueryOrderVisitQuantityAsync([FromQuery] QueryOrderVisitQuantity dto)
+            => await _orderVisitApplication.QueryOrderVisitQuantityAsync(dto);
 
-            return ExcelStreamResult(stream, "回访来源统计");
+        /// <summary>
+        /// 回访量统计--导出
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("visit/quantity/export")]
+        public async Task<FileStreamResult> QueryOrderVisitQuantityExportAsync([FromBody] ExportExcelDto<QueryOrderVisitQuantity> dto)
+        {
+            var items = await _orderVisitApplication.QueryOrderVisitQuantityAsync(dto.QueryDto);
+            return _exportApplication.GetExcelFile(dto, items, "回访量统计");
         }
 
         /// <summary>
@@ -1332,7 +1349,7 @@ namespace Hotline.Api.Controllers.Bi
         }
 
         /// <summary>
-        /// 中心报表统计
+        /// 中心报表统计--宜宾
         /// </summary>
         /// <param name="StartTime"></param>
         /// <param name="EndTime"></param>
@@ -1370,12 +1387,11 @@ namespace Hotline.Api.Controllers.Bi
                 {
                     EffectiveCount = SqlFunc.AggregateSum(SqlFunc.IIF(true, 1, 0)),
                     InvalidCount = 0,
-                    CompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status >= 300, 1, 0)),
-                    InProgressCount = SqlFunc.AggregateSum(SqlFunc.IIF((int)x.Status < 300, 1, 0))
+                    CompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed, 1, 0)),
+                    InProgressCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status < EOrderStatus.Filed, 1, 0))
                 })
-                .ToListAsync();
-            if (orderData != null && orderData.Count > 0)
-                centerReportStatisticsDto.CenterReportOrder = orderData[0];
+                .FirstAsync();
+            centerReportStatisticsDto.CenterReportOrder = orderData;
             #endregion
 
             #region 信件来源
@@ -1527,22 +1543,237 @@ namespace Hotline.Api.Controllers.Bi
                     OrgName = s.Name
                 }).ToListAsync();
 
+            centerReportStatisticsDto.OrgStatisticsCityAll = new OrgStatisticsAll
+            {
+                OrgStatistics = listOrgStatisticsCityAll
+            };
+
+            //区县部门
+            var listOrgStatisticsAreaAll = await _orderRepository.Queryable()
+                .Where(o => o.CreationTime >= StartTime && o.CreationTime <= EndTime)
+                .Select(o => new
+                {
+                    OrgCode = o.ActualHandleOrgCode == null || o.ActualHandleOrgCode == "" ? "001" : o.ActualHandleOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                })
+                .MergeTable()
+                .LeftJoin<SystemOrganize>((o, s) => o.OrgCode == s.Id)
+                 .Where((o, s) => s.OrgType == EOrgType.County)
+                .GroupBy((o, s) => new
+                {
+                    o.OrgCode,
+                    s.Name
+                })
+                .Select((o, s) => new OrgStatistics
+                {
+                    CountNum = SqlFunc.AggregateCount(o.OrgCode),
+
+                    OrgName = s.Name
+                }).ToListAsync();
+
+            centerReportStatisticsDto.OrgStatisticsAreaAll = new OrgStatisticsAll
+            {
+                OrgStatistics = listOrgStatisticsAreaAll
+            };
+            #endregion
+
+            return centerReportStatisticsDto;
+        }
+
+        /// <summary>
+        /// 中心报表统计--自贡
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <returns></returns>
+        [HttpGet("center_report_forms_statistics_zg")]
+        public async Task<CenterReportStatisticsDto> ZgCenterReportFormsStatistics(DateTime StartTime, DateTime EndTime)
+        {
+            CenterReportStatisticsDto centerReportStatisticsDto = new();
+
+            //信件总量
+            int sourceChannelCount = await _orderRepository.Queryable().Where(p => p.CreationTime >= StartTime && p.CreationTime <= EndTime).CountAsync();
+
+            #region 1、通话记录
+            //通话记录
+            var callData = await _callNativeRepository.Queryable()
+               .Where(p => p.CreationTime >= StartTime && p.CreationTime <= EndTime)
+               // .Where(p => p.Gateway != "82826886" && SqlFunc.Length(p.Gateway) != 4)
+               .Select(p => new CenterReportCallInfoDto
+               {
+                   AllCallCount = SqlFunc.AggregateSum(1),//话务总量
+                   InTotal = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In, 1, 0)),//呼入总量
+                   InConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.AnsweredTime != null, 1, 0)),//呼入接通量
+                   InHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.AnsweredTime == null, 1, 0)),//呼入未接通
+                   QueueByeCount = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.WaitDuration > 0 && p.RingDuration == 0 && p.AnsweredTime == null, 1, 0)), //队列挂断
+                   IvrByeCount = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.BeginIvrTime.HasValue && !p.BeginQueueTime.HasValue && !p.BeginRingTime.HasValue && p.AnsweredTime == null, 1, 0)), //IVR挂断
+                   OutTotal = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.Out, 1, 0)),//呼出总量
+                   OutConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.Out && p.AnsweredTime != null, 1, 0)),//呼出接通量
+                   OutHanguped = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.Out && p.AnsweredTime == null, 1, 0)),//呼出未接通
+
+               })
+               .FirstAsync();
+            centerReportStatisticsDto.CenterReportCallInfoDto = callData;
+
+            #endregion
+
+            #region 2、信件回访量
+            //信件回访量
+            var centerReportVisitd = await _orderVisitRepository.Queryable()
+              .Where(x => x.VisitTime >= StartTime && x.VisitTime <= EndTime)
+              .Select(x => new CenterReportVisitdDto
+              {
+                  Visitd = SqlFunc.AggregateSum(SqlFunc.IIF(x.VisitState == EVisitState.Visited, 1, 0)),//已回访
+                  CallVisitd = SqlFunc.AggregateSum(SqlFunc.IIF(x.VisitState == EVisitState.Visited && x.VisitType == EVisitType.CallVisit, 1, 0)),// 已回访--电话
+                  SmsVisitd = SqlFunc.AggregateSum(SqlFunc.IIF(x.VisitState == EVisitState.Visited && x.VisitType == EVisitType.SmsVisit, 1, 0)),//已回访--短信
+                  OtherVisitd = SqlFunc.AggregateSum(SqlFunc.IIF(x.VisitState == EVisitState.Visited && x.VisitType != EVisitType.CallVisit && x.VisitType != EVisitType.SmsVisit, 1, 0)),//其他回访
+                  WaitVisitd = SqlFunc.AggregateSum(SqlFunc.IIF(x.VisitState != EVisitState.None && x.VisitState != EVisitState.Visited, 1, 0)),//待回访
+              }).FirstAsync();
+
+            //部门
+            var listOrg = await _orderVisitDetailRepository.Queryable()
+                .LeftJoin<OrderVisit>((it, o) => it.VisitId == o.Id)
+                .Where((it, o) => it.VisitTarget == EVisitTarget.Org && o.VisitTime >= StartTime && o.VisitTime <= EndTime && o.VisitState == EVisitState.Visited)
+                 .Select((it, o) => new Satisfaction
+                 {
+                     Dissatisfied = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") || SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2"), 1, 0)),
+                     Satisfied = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "1") || SqlFunc.JsonListObjectAny(it.OrgProcessingResults, "key", "2"), 0, 1)),
+                 })
+                .FirstAsync();
+            //计算部门满意度
+            if (listOrg != null)
+            {
+                var satisfiedCount = listOrg.Satisfied + listOrg.Dissatisfied;
+                if (satisfiedCount > 0 && listOrg.Satisfied > 0)
+                    centerReportVisitd.OrgRate = Math.Round((listOrg.Satisfied / (double)satisfiedCount) * 100, 2);
+            }
+
+            //坐席
+            var listSet = await _orderVisitDetailRepository.Queryable()
+                .LeftJoin<OrderVisit>((it, o) => it.VisitId == o.Id)
+                .Where((it, o) => it.VisitTarget == EVisitTarget.Seat && o.VisitTime >= StartTime && o.VisitTime <= EndTime && o.VisitState == EVisitState.Visited)
+                .Select((it, o) => new Satisfaction
+                {
+                    Dissatisfied = SqlFunc.AggregateSum(SqlFunc.IIF(it.SeatEvaluate == ESeatEvaluate.NoSatisfied, 1, 0)),
+                    Satisfied = SqlFunc.AggregateSum(SqlFunc.IIF(it.SeatEvaluate != ESeatEvaluate.NoSatisfied, 1, 0)),
+                })
+                .FirstAsync();
+            //计算坐席满意度
+            if (listSet != null)
+            {
+                var satisfiedCount = listSet.Satisfied + listSet.Dissatisfied;
+                if (satisfiedCount > 0 && listSet.Satisfied > 0)
+                    centerReportVisitd.OrgRate = Math.Round((listSet.Satisfied / (double)satisfiedCount) * 100, 2);
+            }
+
+            centerReportStatisticsDto.CenterReportVisitd = centerReportVisitd;
+            #endregion
+
+            #region 3、工单
+            //工单
+            var orderData = await _orderRepository.Queryable()
+                .Where(p => p.CreationTime >= StartTime && p.CreationTime <= EndTime)
+                .Select(x => new CenterReportOrderDto
+                {
+                    EffectiveCount = SqlFunc.AggregateSum(SqlFunc.IIF(true, 1, 0)),//有效
+                    InvalidCount = 0,//无效
+                    CompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed, 1, 0)),//已办结
+                    OnTimeCompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed && x.ExpiredTime < x.ActualHandleTime, 1, 0)),//按时办结
+                    CenterCompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed && x.ProcessType == EProcessType.Zhiban, 1, 0)),//中心办结
+                    OrgCompletedCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status >= EOrderStatus.Filed && x.ProcessType == EProcessType.Jiaoban, 1, 0)),//部门办结
+                    InProgressCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status < EOrderStatus.Filed, 1, 0)),//在办
+                    CenterInProgressCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status < EOrderStatus.Filed && x.ProcessType == EProcessType.Zhiban, 1, 0)),//中心在办
+                    OrgInProgressCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.Status < EOrderStatus.Filed && x.ProcessType == EProcessType.Jiaoban, 1, 0))//部门在办
+                })
+                .FirstAsync();
+            centerReportStatisticsDto.CenterReportOrder = orderData;
+            #endregion
+
+            #region 4、信件来源
+
+            //信件来源
+            var sourceChannelData = _orderRepository.Queryable()
+                .Where(p => p.CreationTime >= StartTime && p.CreationTime <= EndTime)
+                .Select(it => new
+                {
+                    SourceChannelCode = SqlFunc.IIF(SqlFunc.IsNullOrEmpty(it.SourceChannelCode), "QT", it.SourceChannelCode)
+                })
+                .MergeTable()//将查询出来的结果合并成一个新表
+                 .GroupBy(it => new { it.SourceChannelCode })//对新表进行分组
+                 .Select(it => new CenterReportOrderSourceChannelDto
+                 {
+                     Code = it.SourceChannelCode,
+                     CountNum = SqlFunc.AggregateCount(it.SourceChannelCode)
+                 });
+
+            var sourceData = await _systemDicDataRepository.Queryable()
+               .LeftJoin(sourceChannelData, (s, p) => s.DicDataValue == p.Code)
+              .Where((s, p) => s.DicTypeCode == "SourceChannel" && s.IsShow == true)
+              .Select((s, p) => new CenterReportOrderSourceChannelDto
+              {
+                  Code = s.DicDataValue,
+                  Name = s.DicDataName,
+                  CountNum = p.CountNum
+              })
+              
+              .ToListAsync();
+
+            centerReportStatisticsDto.CenterReportOrderSourceChannels = sourceData;
+            #endregion
+
+            #region 5、信件分类
+            //信件来源 AcceptType
+
+            var acceptTypeData = _orderRepository.Queryable(false, false, false)
+                .Where(p => p.CreationTime >= StartTime && p.CreationTime <= EndTime)
+                .Select(it => new
+                {
+                    AcceptTypeCode = SqlFunc.IIF(SqlFunc.IsNullOrEmpty(it.AcceptTypeCode), "40", it.AcceptTypeCode)
+                })
+                .MergeTable()//将查询出来的结果合并成一个新表
+                 .GroupBy(it => new { it.AcceptTypeCode })//对新表进行分组
+                 .Select(it => new CenterReportOrderSourceChannelDto
+                 {
+                     Code = it.AcceptTypeCode,
+                     CountNum = SqlFunc.AggregateCount(it.AcceptTypeCode)
+                 });
+
+            var acceptType = await _systemDicDataRepository.Queryable()
+              .LeftJoin(acceptTypeData, (s, p) => s.DicDataValue == p.Code)
+             .Where((s, p) => s.DicTypeCode == "AcceptType" && s.IsShow == true)
+             .Select((s, p) => new CenterReportOrderSourceChannelDto
+             {
+                 AllCountNum = sourceChannelCount,
+                 Code = s.DicDataValue,
+                 Name = s.DicDataName,
+                 CountNum = p.CountNum
+             })
+             .ToListAsync();
 
+            centerReportStatisticsDto.CenterReportOrderAcceptTypes = acceptType;
+            #endregion
 
-            //var listOrgStatisticsCityAll = await _orderRepository.Queryable()
-            //  .LeftJoin<SystemOrganize>((it, o) => it.OrgLevelOneCode == o.Id)
-            //  .Where((it, o) => (o.OrgType == EOrgType.City || o.OrgType == EOrgType.Province) && it.CreationTime >= StartTime && it.CreationTime <= EndTime)
-            // .GroupBy((it, o) => new
-            // {
-            //     it.OrgLevelOneCode,
-            //     o.Name
-            // })
-            //  .Select((it, o) => new OrgStatistics
-            //  {
-            //      CountNum = SqlFunc.AggregateCount(it.OrgLevelOneCode),
+            #region 信件分布情况
+            //市直部门
 
-            //      OrgName = it.OrgLevelOneCode == "001" ? "市民热线服务中心" : o.Name
-            //  }).ToListAsync();
+            var listOrgStatisticsCityAll = await _orderRepository.Queryable()
+                .Where(o => o.CreationTime >= StartTime && o.CreationTime <= EndTime)
+                .Select(o => new
+                {
+                    OrgCode = o.ActualHandleOrgCode == null || o.ActualHandleOrgCode == "" ? "001" : o.ActualHandleOrgCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")),
+                })
+                .MergeTable()
+                .LeftJoin<SystemOrganize>((o, s) => o.OrgCode == s.Id)
+                 .Where((o, s) => s.OrgType == EOrgType.City || s.OrgType == EOrgType.Province)
+                .GroupBy((o, s) => new
+                {
+                    o.OrgCode,
+                    s.Name
+                })
+                .Select((o, s) => new OrgStatistics
+                {
+                    CountNum = SqlFunc.AggregateCount(o.OrgCode),
+                    OrgName = s.Name
+                }).ToListAsync();
 
             centerReportStatisticsDto.OrgStatisticsCityAll = new OrgStatisticsAll
             {
@@ -1570,19 +1801,6 @@ namespace Hotline.Api.Controllers.Bi
 
                     OrgName = s.Name
                 }).ToListAsync();
-            //var listOrgStatisticsAreaAll = await _orderRepository.Queryable()
-            // .LeftJoin<SystemOrganize>((it, o) => it.OrgLevelOneCode == o.Id)
-            // .Where((it, o) => o.OrgType == EOrgType.County && it.CreationTime >= StartTime && it.CreationTime <= EndTime)
-            //.GroupBy((it, o) => new
-            //{
-            //    it.OrgLevelOneCode,
-            //    o.Name
-            //})
-            // .Select((it, o) => new OrgStatistics
-            // {
-            //     CountNum = SqlFunc.AggregateCount(it.OrgLevelOneCode),
-            //     OrgName = it.OrgLevelOneCode == "001" ? "市民热线服务中心" : o.Name
-            // }).ToListAsync();
 
             centerReportStatisticsDto.OrgStatisticsAreaAll = new OrgStatisticsAll
             {
@@ -2213,7 +2431,7 @@ namespace Hotline.Api.Controllers.Bi
             .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.Contact == dto.PhoneNo!) //联系电话
             //.WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), d => d.PushTypeCode == dto.PushTypeCode) //推送分类
             .WhereIF(!string.IsNullOrEmpty(dto.PushTypeCode), d => d.OrderPushTypes.Any(opt => opt.PushTypeCode == dto.PushTypeCode)) //推送分类
-			.WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart) //超期时间开始
+            .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.ExpiredTime >= dto.ExpiredTimeStart) //超期时间开始
             .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.ExpiredTime <= dto.ExpiredTimeEnd) //超期时间结束
             //.WhereIF(dto.Statuses.Any(), d => dto.Statuses.Contains(d.Status))  //工单状态
             .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)//工单状态
@@ -3134,6 +3352,11 @@ namespace Hotline.Api.Controllers.Bi
         [HttpGet("org-visitdetail-list-basedata")]
         public async Task<object> OrgVisitDetailListBaseData()
         {
+            return _baseDataApplication
+                .VisitSatisfaction()
+                .OrgsOptions(_sessionContext)
+                .AttitudeType()
+                .Build();
             var VisitSatisfaction = _systemDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction);
 
             if (_sessionContext.OrgIsCenter)
@@ -4198,5 +4421,32 @@ namespace Hotline.Api.Controllers.Bi
             var stream = ExcelHelper.CreateStream(dtos);
             return ExcelStreamResult(stream, "扭转信件统计");
         }
+
+        /// <summary>
+        /// 部门延期统计明细-单独菜单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("order_delay_detail")]
+        public async Task<PagedDto<OrderDelayDto>> QueryOrderDelayDataDetailAsync([FromQuery] QueryOrderDelayDataDetailRequest dto)
+        {
+            var query = _orderDelayRepository.Queryable()
+                 .Includes(x => x.Order)
+                 .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
+                 .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
+                .WhereIF(dto.TypeId != null && dto.TypeId == 1, x => x.Order.IdentityType == EIdentityType.Citizen)
+                .WhereIF(dto.TypeId != null && dto.TypeId == 2, x => x.Order.IdentityType == EIdentityType.Enterprise)
+                 .WhereIF(dto.Type is 1, x => x.DelayState == EDelayState.Pass)
+                 .WhereIF(dto.Type is 2, x => x.DelayState == EDelayState.NoPass)
+                 .WhereIF(dto.Type is 3, x => x.DelayState == EDelayState.Examining)
+                 .WhereIF(dto.Type is 4, x => x.DelayState < EDelayState.Withdraw)
+             .MergeTable();
+
+            if (_sessionContext.OrgIsCenter == false)
+                query = query.Where(x => x.ApplyOrgCode.StartsWith(_sessionContext.OrgId));
+
+            var (total, items) = await query.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+            return new PagedDto<OrderDelayDto>(total, _mapper.Map<IReadOnlyList<OrderDelayDto>>(items));
+        }
     }
 }

+ 136 - 97
src/Hotline.Api/Controllers/OrderController.cs

@@ -64,6 +64,7 @@ using XF.Domain.Entities;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
+using Newtonsoft.Json;
 
 namespace Hotline.Api.Controllers;
 
@@ -135,8 +136,8 @@ public class OrderController : BaseController
     private readonly IOrderAnalysisApplication _orderAnalysisApplication;
     private readonly ICalcExpireTime _expireTime;
     private readonly IRepository<OrderPushType> _orderPushTypeRepository;
-    private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
     private readonly IRepository<OrderRevoke> _orderRevokeRepository;
+    private readonly IOrderTerminateRepository _orderTerminateRepository;
 
     public OrderController(
         IOrderDomainService orderDomainService,
@@ -197,11 +198,12 @@ public class OrderController : BaseController
         Publisher publisher,
         IOrderAnalysisApplication orderAnalysisApplication,
         ICalcExpireTime expireTime,
-        IOptions<CityBaseConfiguration> cityBaseConfiguration,
         IRepository<OrderPushType> orderPushTypeRepository,
         ICallNativeRepository callNativeRepository,
         ICallNativeApplication callNativeApplication,
-        IRepository<OrderRevoke> orderRevokeRepository)
+        IRepository<OrderRevoke> orderRevokeRepository,
+        BaseDataApplication baseDataApplication,
+        IOrderTerminateRepository orderTerminateRepository)
     {
         _orderDomainService = orderDomainService;
         _orderRepository = orderRepository;
@@ -264,6 +266,8 @@ public class OrderController : BaseController
         _orderPushTypeRepository = orderPushTypeRepository;
         _callNativeRepository = callNativeRepository;
         _callNativeApplication = callNativeApplication;
+        _baseDataApplication = baseDataApplication;
+        _orderTerminateRepository = orderTerminateRepository;
         _orderRevokeRepository = orderRevokeRepository;
     }
     #endregion 
@@ -284,18 +288,18 @@ public class OrderController : BaseController
             .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.StartsWith(dto.Keyword!))
             .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.No == dto.No)
             .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Title == dto.Title)
-			.WhereIF(dto.PubState == EPubState.Pub, d => d.Status >= EOrderStatus.Published)
+            .WhereIF(dto.PubState == EPubState.Pub, d => d.Status >= EOrderStatus.Published)
             .WhereIF(dto.PubState == EPubState.NoPub, d => d.Status < EOrderStatus.Published)
             .WhereIF(!string.IsNullOrEmpty(dto.Channel), d => d.SourceChannelCode == dto.Channel)
-            .WhereIF(!string.IsNullOrEmpty(dto.OrderTag), d => d.OrderTagCode  == dto.OrderTag!) //工单标签
-			.WhereIF(!string.IsNullOrEmpty(dto.CenterToOrgHandlerName), d => d.CenterToOrgHandlerName == dto.CenterToOrgHandlerName! ) //派单人
-			.WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
-			.WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
+            .WhereIF(!string.IsNullOrEmpty(dto.OrderTag), d => d.OrderTagCode == dto.OrderTag!) //工单标签
+            .WhereIF(!string.IsNullOrEmpty(dto.CenterToOrgHandlerName), d => d.CenterToOrgHandlerName == dto.CenterToOrgHandlerName!) //派单人
+            .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
+            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
             .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.AcceptTypeCode == dto.AcceptType)//受理类型
             .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.HotspotSpliceName != null && d.HotspotSpliceName.Contains(dto.Hotspot))
-			.WhereIF(!string.IsNullOrEmpty(dto.FromPhone), d => d.FromPhone == dto.FromPhone) //来电号码
-            //.WhereIF(!string.IsNullOrEmpty(dto.PubMan),
-            //    d => d.AcceptorName.Contains(dto.PubMan!) || d.AcceptorStaffNo.Contains(dto.PubMan!))
+            .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), d => d.FromPhone == dto.FromPhone) //来电号码
+                                                                                              //.WhereIF(!string.IsNullOrEmpty(dto.PubMan),
+                                                                                              //    d => d.AcceptorName.Contains(dto.PubMan!) || d.AcceptorStaffNo.Contains(dto.PubMan!))
             .WhereIF(dto.PubRange == EPublicState.Pub, d => d.OrderPublish.PublishState)
             .WhereIF(dto.PubRange == EPublicState.NoPub, d => !d.OrderPublish.PublishState)
             .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, d => d.Source == ESource.ProvinceStraight)
@@ -372,12 +376,23 @@ public class OrderController : BaseController
                     orderVisit.PublishTime = DateTime.Now;
                     orderVisit.IsCanHandle = true;
                     orderVisit.EmployeeId = _sessionContext.RequiredUserId;
+
+                    if (_appOptions.Value.IsZiGong)
+                    {
+                        orderVisit.EmployeeId = string.Empty;
+                    }
                     if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null } && !order.IsProvince)
                     {
                         orderVisit.VisitState = EVisitState.Visited;
                         orderVisit.VisitTime = DateTime.Now;
                         orderVisit.VisitType = EVisitType.OtherVisit;
                         orderVisit.NowEvaluate = new Kv() { Key = "4", Value = "满意" };
+                        if (_appOptions.Value.IsZiGong)
+                        {
+                            // 根据禅道 自贡需求 Id_361, 第一条, 3小条需求;
+                            // 直办件归档后自动回访量需统计在“胡玲”的默认回访量中;
+                            orderVisit.EmployeeId = _systemSettingCacheManager.DefaultVisitEmployeeId;
+                        }
                     }
 
                     if (order.CounterSignType != ECounterSignType.Center)
@@ -385,10 +400,6 @@ public class OrderController : BaseController
                         orderVisit.IsCanAiVisit = true;
                     }
 
-                    if (_appOptions.Value.IsZiGong)
-                    {
-                        orderVisit.EmployeeId = string.Empty;
-                    }
 
                     string visitId = await _orderVisitRepository.AddAsync(orderVisit);
 
@@ -522,6 +533,10 @@ public class OrderController : BaseController
         {
             orderVisit.EmployeeId = _sessionContext.RequiredUserId;
         }
+        if (_appOptions.Value.IsZiGong)
+        {
+            orderVisit.EmployeeId = string.Empty;
+        }
 
         if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null } && !order.IsProvince)
         {
@@ -529,14 +544,15 @@ public class OrderController : BaseController
             orderVisit.VisitTime = DateTime.Now;
             orderVisit.VisitType = EVisitType.OtherVisit;
             orderVisit.NowEvaluate = new Kv() { Key = "4", Value = "满意" };
-        }
 
-        if (_appOptions.Value.IsZiGong)
-        {
-            orderVisit.EmployeeId = string.Empty;
+            if (_appOptions.Value.IsZiGong)
+            {
+                // 根据禅道 自贡需求 Id_361, 第一条, 3小条需求;
+                // 直办件归档后自动回访量需统计在“胡玲”的默认回访量中;
+                orderVisit.EmployeeId = _systemSettingCacheManager.DefaultVisitEmployeeId;
+            }
         }
 
-
         if (order.CounterSignType != ECounterSignType.Center)
         {
             orderVisit.IsCanAiVisit = true;
@@ -663,13 +679,13 @@ public class OrderController : BaseController
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.Order.CreationTime <= dto.CreationTimeEnd)
             .WhereIF(!string.IsNullOrEmpty(dto.FromPhone), d => d.Order.FromPhone == dto.FromPhone) //来电号码
             .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.Order.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
-			.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.Order.AcceptTypeCode == dto.AcceptType)//受理类型
+            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.Order.AcceptTypeCode == dto.AcceptType)//受理类型
             .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.Order.HotspotSpliceName != null && d.Order.HotspotSpliceName.Contains(dto.Hotspot))
-			.WhereIF(!string.IsNullOrEmpty(dto.PublishName), d => d.CreatorName.Contains(dto.PublishName!))
+            .WhereIF(!string.IsNullOrEmpty(dto.PublishName), d => d.CreatorName.Contains(dto.PublishName!))
             .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.Order.AcceptorName == dto.NameOrNo! || d.Order.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
             .WhereIF(dto.StartTime.HasValue, d => d.CreationTime >= dto.StartTime)
             .WhereIF(dto.EndTime.HasValue, d => d.CreationTime <= dto.EndTime)
-			.WhereIF(dto.Resolve.HasValue, x => x.Resolve == dto.Resolve)
+            .WhereIF(dto.Resolve.HasValue, x => x.Resolve == dto.Resolve)
             .OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
@@ -820,7 +836,7 @@ public class OrderController : BaseController
             ChannelOptions = _sysDicDataCacheManager.GetSysDicDataCache(TimeLimitBaseDataConsts.SourceChannel),
             AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
             OrderTags = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.OrderTag)
-		};
+        };
         return rsp;
     }
 
@@ -1050,7 +1066,6 @@ public class OrderController : BaseController
 
         foreach (var visit in dto.Visit)
         {
-
             try
             {
                 var details = await _orderVisitedDetailRepository
@@ -1096,6 +1111,10 @@ public class OrderController : BaseController
                 });
                 }
                 await _orderApplication.SaveOrderVisit(visitDto, HttpContext.RequestAborted);
+                await _orderVisitRepository.Updateable()
+                    .Where(m => m.Id == visit.VisitId)
+                    .SetColumns(m => m.IsBatchVisit == true)
+                    .ExecuteCommandAsync();
                 outDto.CompleteCount += 1;
             }
             catch (Exception e)
@@ -2379,9 +2398,10 @@ public class OrderController : BaseController
                 {
                 }
             }
-
+            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
             //推省上
-            if (!string.IsNullOrEmpty(model.Id) && (_cityBaseConfiguration.Value.CityProvince.OrgId.Equals(model.OrgId) || _cityBaseConfiguration.Value.CityProvinceAssign.OrgId.Equals(model.OrgId)))
+            if (!string.IsNullOrEmpty(model.Id) && (cityBase.CityProvince.OrgId.Equals(model.OrgId) || cityBase.CityProvinceAssign.OrgId.Equals(model.OrgId)))
             {
                 var orderDto = _mapper.Map<OrderDto>(order);
                 var supervise = await _orderSuperviseRepository.GetAsync(x => x.Id == model.Id);
@@ -2624,9 +2644,10 @@ public class OrderController : BaseController
                 {
                 }
             }
-
+            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
             //推省上
-            if (!string.IsNullOrEmpty(model.Id) && (_cityBaseConfiguration.Value.CityProvince.OrgId.Equals(model.OrgId) || _cityBaseConfiguration.Value.CityProvinceAssign.OrgId.Equals(model.OrgId)))
+            if (!string.IsNullOrEmpty(model.Id) && (cityBase.CityProvince.OrgId.Equals(model.OrgId) || cityBase.CityProvinceAssign.OrgId.Equals(model.OrgId)))
             {
                 var orderDto = _mapper.Map<OrderDto>(order);
                 var urge = await _orderUrgeRepository.GetAsync(x => x.Id == model.Id);
@@ -3086,8 +3107,13 @@ public class OrderController : BaseController
         {
             dto.ProvinceRevokeString = "该工单已由省平台发送撤单!请直接归档办理!";
         }
+        //终止
+        var orderTerminateList = await _orderTerminateRepository.Queryable().Where(x => x.OrderId == order.Id).ToListAsync();
+        dto.OrderTerminateStatus = orderTerminateList.Any(x => x.Status == ETerminateStatus.End) ? "同意" : orderTerminateList.Any(x => x.Status == ETerminateStatus.Refuse) ?
+            "不同意" : orderTerminateList.Any(x => x.Status == ETerminateStatus.Approval || x.Status == ETerminateStatus.SendBack) ? "审批中" : null;
 
-        return _sessionContext.OrgIsCenter ? dto : dto.DataMask();
+
+		return _sessionContext.OrgIsCenter ? dto : dto.DataMask();
     }
 
     /// <summary>
@@ -3391,7 +3417,9 @@ public class OrderController : BaseController
             throw new UserFriendlyException($"该工单已开启办理流程, No:{order.No}", "该工单已开启办理流程");
 
         ExpiredTimeWithConfig expiredTimeConfig;
-        if (dto.Workflow.NextHandlers.Any(d => d.Key == _cityBaseConfiguration.Value.CityProvince.OrgId || d.Key == _cityBaseConfiguration.Value.CityProvinceAssign.OrgId))
+        var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+        CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+        if (dto.Workflow.NextHandlers.Any(d => d.Key == cityBase.CityProvince.OrgId || d.Key == cityBase.CityProvinceAssign.OrgId))
         {
             var timeResult = await _expireTime.CalcEndTime(DateTime.Now, ETimeType.WorkDay, 45, 80, 50);
             expiredTimeConfig = new ExpiredTimeWithConfig
@@ -3780,6 +3808,7 @@ public class OrderController : BaseController
             .WhereIF(dto.StartTime.HasValue, d => d.StartTime >= dto.StartTime)
             .WhereIF(dto.EndTime.HasValue, d => d.StartTime <= dto.EndTime)
             .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent!.Value)
+            .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)
             .OrderByDescending(d => new { d.IsUrgent, d.StartTime })
             .ToPagedListAsync(dto, HttpContext.RequestAborted);
 
@@ -3837,6 +3866,7 @@ public class OrderController : BaseController
             //.Where(d => (string.IsNullOrEmpty(d.WorkflowId) && (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)))
             //.Where(d => string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId)
             .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent.Value)
+            .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)
             .Where(x => x.Source < ESource.MLSQ || x.Source > ESource.WZSC)
             .Where(x => x.Status != EOrderStatus.BackToProvince && x.Status < EOrderStatus.Filed)
             .OrderBy(d => d.Status)
@@ -4652,9 +4682,11 @@ public class OrderController : BaseController
                 }
             }
 
-            if (order != null && (_cityBaseConfiguration.Value.CityProvince.OrgId.Equals(model.OrgId) ||
-                                  _cityBaseConfiguration.Value.CityProvinceAssign.OrgId.Equals(model.OrgId) || _cityBaseConfiguration.Value.CityEnterprise.OrgId.Equals(model.OrgId) ||
-                                  _cityBaseConfiguration.Value.PublicSecurity.OrgId.Equals(model.OrgId)))
+            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+            if (order != null && (cityBase.CityProvince.OrgId.Equals(model.OrgId) ||
+                                  cityBase.CityProvinceAssign.OrgId.Equals(model.OrgId) || cityBase.CityEnterprise.OrgId.Equals(model.OrgId) ||
+                                  cityBase.PublicSecurity.OrgId.Equals(model.OrgId)))
             {
                 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFlowRecalled,
                     new PublishSpecialDto { Order = _mapper.Map<OrderDto>(order), Special = _mapper.Map<OrderSpecialDto>(model) },
@@ -4905,9 +4937,11 @@ public class OrderController : BaseController
                 }
             }
 
-            if (order != null && (_cityBaseConfiguration.Value.CityProvince.OrgId.Equals(special.OrgId) ||
-                                  _cityBaseConfiguration.Value.CityProvinceAssign.OrgId.Equals(special.OrgId) || _cityBaseConfiguration.Value.CityEnterprise.OrgId.Equals(special.OrgId) ||
-                                  _cityBaseConfiguration.Value.PublicSecurity.OrgId.Equals(special.OrgId)))
+            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+            if (order != null && (cityBase.CityProvince.OrgId.Equals(special.OrgId) ||
+                                  cityBase.CityProvinceAssign.OrgId.Equals(special.OrgId) || cityBase.CityEnterprise.OrgId.Equals(special.OrgId) ||
+                                  cityBase.PublicSecurity.OrgId.Equals(special.OrgId)))
             {
                 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFlowRecalled,
                     new PublishSpecialDto { Order = _mapper.Map<OrderDto>(order), Special = _mapper.Map<OrderSpecialDto>(special) },
@@ -5017,10 +5051,11 @@ public class OrderController : BaseController
                         await _orderVisitRepository.UpdateAsync(visit, HttpContext.RequestAborted);
                     }
                 }
-
-                if (order != null && (_cityBaseConfiguration.Value.CityProvince.OrgId.Equals(special.OrgId) ||
-                                      _cityBaseConfiguration.Value.CityProvinceAssign.OrgId.Equals(special.OrgId) || _cityBaseConfiguration.Value.CityEnterprise.OrgId.Equals(special.OrgId) ||
-                                      _cityBaseConfiguration.Value.PublicSecurity.OrgId.Equals(special.OrgId)))
+                var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+                CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+                if (order != null && (cityBase.CityProvince.OrgId.Equals(special.OrgId) ||
+                                      cityBase.CityProvinceAssign.OrgId.Equals(special.OrgId) || cityBase.CityEnterprise.OrgId.Equals(special.OrgId) ||
+                                      cityBase.PublicSecurity.OrgId.Equals(special.OrgId)))
                 {
                     await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFlowRecalled,
                         new PublishSpecialDto { Order = _mapper.Map<OrderDto>(order), Special = _mapper.Map<OrderSpecialDto>(special) },
@@ -5201,6 +5236,8 @@ public class OrderController : BaseController
         var step = await _workflowApplication.GetRecallStepsAsync(id, HttpContext.RequestAborted);
         var specialSeats = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.SpecialSeats).SettingValue[0]);
         var specialSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.SpecialSendOrder).SettingValue[0]);
+        var order = await _orderRepository.Queryable().Where(d => d.WorkflowId == id).FirstAsync(HttpContext.RequestAborted);
+        if (order == null) throw UserFriendlyException.SameMessage("无效工单信息!");
 
         var baseTypeId = string.Empty;
         if (step != null && step.Steps.Any() && _sessionContext.Roles.Contains("zuoxi") && specialSeats &&
@@ -5223,7 +5260,8 @@ public class OrderController : BaseController
             SpecialReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.SpecialReason),
             InstaShotSpecialReason = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.InstaShotSpecialReason),
             Step = step,
-            BaseTypeId = baseTypeId
+			IsTerminate = await _orderTerminateRepository.Queryable().Where(d=>d.OrderId == order.Id && d.Status == ETerminateStatus.End).AnyAsync(),
+			BaseTypeId = baseTypeId
         };
         return rsp;
     }
@@ -5249,7 +5287,8 @@ public class OrderController : BaseController
             SpecialTimeType = EnumExts.GetDescriptions<ETimeType>(),
             SpecialReason = isInstaShot ? _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.InstaShotSpecialReason) : _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.SpecialReason),
             ReTransactErrorType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.ReTransactErrorType),
-            Step = step,
+            IsTerminate = await _orderTerminateRepository.Queryable().Where(d => d.OrderId == order.Id && d.Status == ETerminateStatus.End).AnyAsync(),
+			Step = step,
             Orgs = orgs,
         };
         return rsp;
@@ -5696,7 +5735,7 @@ public class OrderController : BaseController
 
     #endregion
 
-    #region 工单观察
+    #region 工单观察、关注
 
     /// <summary>
     /// 新增工单观察
@@ -5756,8 +5795,8 @@ public class OrderController : BaseController
     public async Task<PagedDto<OrderObserveDto>> List([FromQuery] OrderObserveListDto dto)
     {
         var quer = _orderApplication.OrderObserveList(dto);
-		var (total, items) = await
-			quer.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+        var (total, items) = await
+            quer.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
         return new PagedDto<OrderObserveDto>(total, _mapper.Map<IReadOnlyList<OrderObserveDto>>(items));
     }
 
@@ -5767,70 +5806,70 @@ public class OrderController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpPost("order_observe/list/export")]
-	public async Task<FileStreamResult> ListExport([FromBody] ExportExcelDto<OrderObserveListDto> dto)
+    public async Task<FileStreamResult> ListExport([FromBody] ExportExcelDto<OrderObserveListDto> dto)
     {
-	    var query = _orderApplication.OrderObserveList(dto.QueryDto);
-	    List<OrderObserve> data;
-	    if (dto.IsExportAll)
-	    {
-		    data = await query.ToListAsync(HttpContext.RequestAborted);
-	    }
-	    else
-	    {
-		    var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
-		    data = items;
-	    }
+        var query = _orderApplication.OrderObserveList(dto.QueryDto);
+        List<OrderObserve> data;
+        if (dto.IsExportAll)
+        {
+            data = await query.ToListAsync(HttpContext.RequestAborted);
+        }
+        else
+        {
+            var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+            data = items;
+        }
 
-	    var dataDtos = _mapper.Map<ICollection<OrderObserveDto>>(data);
+        var dataDtos = _mapper.Map<ICollection<OrderObserveDto>>(data);
 
-	    dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
 
-	    var dtos = dataDtos
-		    .Select(stu => _mapper.Map(stu, typeof(OrderObserveDto), dynamicClass))
-		    .Cast<object>()
-		    .ToList();
+        var dtos = dataDtos
+            .Select(stu => _mapper.Map(stu, typeof(OrderObserveDto), dynamicClass))
+            .Cast<object>()
+            .ToList();
 
-	    var stream = ExcelHelper.CreateStream(dtos);
+        var stream = ExcelHelper.CreateStream(dtos);
 
-	    return ExcelStreamResult(stream, "工单关注列表");
+        return ExcelStreamResult(stream, "工单关注列表");
     }
 
-	/// <summary>
-	/// 获取工单观察
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[HttpGet("order_observe/{id}")]
+    /// <summary>
+    /// 获取工单观察
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("order_observe/{id}")]
     public async Task<OrderObserve> OrderObserveEntity(string id)
     {
         return await _orderObserveRepository.Queryable()
             .FirstAsync(x => x.Id == id);
     }
 
-	/// <summary>
-	/// 列表页面基础数据
-	/// </summary>
-	/// <returns></returns>
-	[HttpGet("order_observe/base-data")]
-	public async Task<object> OrderObserveBaseData()
-	{
-		var rsp = new
-		{
-			AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType)
-		};
-		return rsp;
-	}
-
-	#endregion
-
-	#region 工单终结
-
-	/// <summary>
-	/// 新增工单终结
-	/// </summary>
-	/// <param name="dtos"></param>
-	/// <returns></returns>
-	[Permission(EPermission.AddOrderFinality)]
+    /// <summary>
+    /// 列表页面基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("order_observe/base-data")]
+    public async Task<object> OrderObserveBaseData()
+    {
+        var rsp = new
+        {
+            AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType)
+        };
+        return rsp;
+    }
+
+    #endregion
+
+    #region 工单终结
+
+    /// <summary>
+    /// 新增工单终结
+    /// </summary>
+    /// <param name="dtos"></param>
+    /// <returns></returns>
+    [Permission(EPermission.AddOrderFinality)]
     [HttpPost("order_finality")]
     [LogFilter("新增工单终结")]
     public async Task Add([FromBody] OrderFinalityAddDto dto)
@@ -6524,7 +6563,7 @@ public class OrderController : BaseController
         }
         var oldInfo = _mapper.Map<OrderDto>(modifyRecordsInfo);
         oldInfo.CenterOpinion = modifyRecordsInfo.CenterToOrgOpinion;
-        return new { OldInfo=oldInfo, NewInfo = newInfo };
+        return new { OldInfo = oldInfo, NewInfo = newInfo };
     }
 
     #endregion

+ 31 - 20
src/Hotline.Api/Controllers/OrderRevocationController.cs

@@ -1,12 +1,14 @@
 using Hotline.Application.FlowEngine;
 using Hotline.Caching.Interfaces;
 using Hotline.FlowEngine.WorkflowModules;
+using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
 using Hotline.Push.Notifies;
 using Hotline.SeedData;
 using Hotline.Settings;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
 using Hotline.Users;
@@ -31,6 +33,7 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<SystemOrganize> _systemOrganizeRepository;
         private readonly IRepository<User> _userRepository;
         private readonly IMediator _mediator;
+        private readonly IWorkflowDomainService _workflowDomainService;
 
 
         public OrderRevocationController(IMapper mapper,
@@ -41,7 +44,8 @@ namespace Hotline.Api.Controllers
             ISystemSettingCacheManager systemSettingCacheManager,
             IRepository<SystemOrganize> systemOrganizeRepository,
             IRepository<User> userRepository,
-             IMediator mediator)
+             IMediator mediator,
+             IWorkflowDomainService workflowDomainService)
         {
             _mapper = mapper;
             _orderRevocationRepository = orderRevocationRepository;
@@ -52,6 +56,7 @@ namespace Hotline.Api.Controllers
             _systemOrganizeRepository = systemOrganizeRepository;
             _userRepository = userRepository;
             _mediator = mediator;
+            _workflowDomainService = workflowDomainService;
         }
 
         /// <summary>
@@ -140,27 +145,33 @@ namespace Hotline.Api.Controllers
 
                             #region 处理短信业务
                             //如果需要发短信、处理短信业务
-                            if (dto.IsSendSms)
-                            {
-                                //处理短信业务
-                                var acceptSmsRoleIds = _systemSettingCacheManager.GetSetting(SettingConstants.AcceptSmsRoleIds)?.SettingValue;
-                                //查询部门所有账号
-                                var userlist = await _userRepository.Queryable().Where(x =>
-                                    x.OrgId == order.CurrentHandleOrgId && !string.IsNullOrEmpty(x.PhoneNo) &&
-                                    x.Roles.Any(d => acceptSmsRoleIds.Contains(d.Id))).ToListAsync();
-                                //发送短信
-                                foreach (var user in userlist)
+                            if (dto.IsSendSms && !string.IsNullOrEmpty(order.WorkflowId))
+                            {  //查询当前工单的实际办理节点,如果在热线中心不处理,如果在部门需要更新期满时间
+                                var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withSteps: true, withTraces: true, cancellationToken: HttpContext.RequestAborted);
+                                var nowWorkflow = workflow.Steps.Where(p => p.Id == order.ActualHandleStepId && p.BusinessType >= EBusinessType.Department && p.BusinessType <= EBusinessType.DepartmentLeader).FirstOrDefault();
+                                //在部门才需要发送短信
+                                if (nowWorkflow != null && order.CenterToOrgTime.HasValue)
                                 {
-                                    var messageDto = new Share.Dtos.Push.MessageDto
+                                    //处理短信业务
+                                    var acceptSmsRoleIds = _systemSettingCacheManager.GetSetting(SettingConstants.AcceptSmsRoleIds)?.SettingValue;
+                                    //查询部门所有账号
+                                    var userlist = await _userRepository.Queryable().Where(x =>
+                                        x.OrgId == order.CurrentHandleOrgId && !string.IsNullOrEmpty(x.PhoneNo) &&
+                                        x.Roles.Any(d => acceptSmsRoleIds.Contains(d.Id))).ToListAsync();
+                                    //发送短信
+                                    foreach (var user in userlist)
                                     {
-                                        PushBusiness = EPushBusiness.OrderRevocationSms,
-                                        PushPlatform = EPushPlatform.Sms,
-                                        Name = user.Name,
-                                        TemplateCode = "1016",
-                                        Params = new List<string>() { order.No },
-                                        TelNumber = user.PhoneNo,
-                                    };
-                                    await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
+                                        var messageDto = new Share.Dtos.Push.MessageDto
+                                        {
+                                            PushBusiness = EPushBusiness.OrderRevocationSms,
+                                            PushPlatform = EPushPlatform.Sms,
+                                            Name = user.Name,
+                                            TemplateCode = "1016",
+                                            Params = new List<string>() { order.No },
+                                            TelNumber = user.PhoneNo,
+                                        };
+                                        await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
+                                    }
                                 }
 
                             }

+ 312 - 0
src/Hotline.Api/Controllers/OrderTerminateController.cs

@@ -0,0 +1,312 @@
+using Hotline.Orders;
+using Hotline.Permissions;
+using Hotline.Quality;
+using Hotline.Share.Dtos.Quality;
+using Hotline.Share.Dtos;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using Hotline.Api.Filter;
+using Hotline.Caching.Services;
+using Hotline.FlowEngine.WorkflowModules;
+using Hotline.Repository.SqlSugar.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Settings;
+using SqlSugar;
+using XF.Utility.EnumExtensions;
+using MapsterMapper;
+using Hotline.File;
+using Hotline.Application.FlowEngine;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Application.Orders;
+using Hotline.Share.Requests;
+using Hotline.Tools;
+
+namespace Hotline.Api.Controllers
+{
+	/// <summary>
+	/// 终止管理
+	/// </summary>
+	public class OrderTerminateController : BaseController
+	{
+		private readonly ISessionContext _sessionContext;
+		private readonly IMapper _mapper;
+		private readonly IFileRepository _fileRepository;
+		private readonly IWorkflowApplication _workflowApplication;
+		private readonly IRepository<Workflow> _workflowRepository;
+		private readonly IWorkflowDomainService _workflowDomainService;
+		private readonly IOrderRepository _orderRepository;
+		private readonly IOrderDomainService _orderDomainService;
+		private readonly IOrderTerminateRepository _orderTerminateRepository;
+		private readonly IOrderApplication _orderApplication;
+
+		public OrderTerminateController(
+			ISessionContext sessionContext,
+			IMapper mapper,
+			IFileRepository fileRepository,
+			IWorkflowApplication workflowApplication,
+			IRepository<Workflow> workflowRepository,
+			IWorkflowDomainService workflowDomainService,
+			IOrderRepository orderRepository,
+			IOrderDomainService orderDomainService,
+			IOrderTerminateRepository orderTerminateRepository,
+			IOrderApplication orderApplication
+			) 
+		{ 
+			_sessionContext = sessionContext;
+			_mapper = mapper;
+			_fileRepository = fileRepository;
+			_workflowApplication = workflowApplication;
+			_workflowRepository = workflowRepository;
+			_workflowDomainService = workflowDomainService;
+			_orderRepository = orderRepository;
+			_orderDomainService = orderDomainService;
+			_orderTerminateRepository = orderTerminateRepository;
+			_orderApplication= orderApplication;
+		}
+
+		/// <summary>
+		/// 工单终止待申请列表
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[Permission(EPermission.CanOrderTerminate)]
+		[HttpGet("may-terminate")]
+		public async Task<PagedDto<OrderDto>> MayOrderTerminateList([FromQuery] OrderTerminateListDto dto)
+		{
+			var isAdmin = _orderDomainService.IsCheckAdmin();
+			var (total, items) =await _orderRepository.Queryable(isAdmin:isAdmin)
+				.Includes(d=>d.OrderTerminates)
+				.Where(d=> SqlFunc.Subqueryable<OrderTerminate>().Where(t=> t.OrderId  == d.Id && t.Status != ETerminateStatus.SendBackStart).NotAny())
+				.Where(d => d.Status >= EOrderStatus.Filed && d.ActualHandleOrgCode.StartsWith(_sessionContext.OrgId))
+				.WhereIF(!string.IsNullOrEmpty(dto.No),d=>d.No!.Contains(dto.No!))
+				.WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Title!.Contains(dto.Title!))
+				.WhereIF(dto.ApplyStartTime.HasValue && dto.ApplyEndTime.HasValue,
+					d => d.OrderTerminates.Any(t=>t.CreationTime >= dto.ApplyStartTime && t.CreationTime<= dto.ApplyEndTime))
+				.WhereIF(dto.StartTime.HasValue && dto.EndTime.HasValue,d=>d.StartTime >= dto.StartTime && d.StartTime <= dto.EndTime)
+				.OrderByDescending(d => d.StartTime)
+				.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+			return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
+		}
+
+
+		/// <summary>
+		/// 工单终止列表
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpGet]
+		public async Task<PagedDto<OrderTerminateEntityDto>> OrderTerminateList([FromQuery] OrderTerminateListDto dto)
+		{
+			var (total, items) = await _orderApplication.OrderTerminateList(dto)
+				.ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+			return new PagedDto<OrderTerminateEntityDto>(total, _mapper.Map<IReadOnlyList<OrderTerminateEntityDto>>(items));
+		}
+
+		/// <summary>
+		/// 工单终止列表导出
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+
+		[HttpPost("export")]
+		public async Task<FileStreamResult> OrderTerminateListExport([FromBody] ExportExcelDto<OrderTerminateListDto> dto)
+		{
+			var query = _orderApplication.OrderTerminateList(dto.QueryDto);
+			List<OrderTerminate> data;
+			if (dto.IsExportAll)
+			{
+				data = await query.ToListAsync(HttpContext.RequestAborted);
+			}
+			else
+			{
+				var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+				data = items;
+			}
+
+			var dataDtos = _mapper.Map<ICollection<OrderTerminateEntityDto>>(data);
+
+			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+			var dtos = dataDtos
+				.Select(stu => _mapper.Map(stu, typeof(OrderTerminateEntityDto), dynamicClass))
+				.Cast<object>()
+				.ToList();
+
+			var stream = ExcelHelper.CreateStream(dtos);
+
+			return ExcelStreamResult(stream, "工单终止列表");
+		}
+
+
+		/// <summary>
+		/// 开始工单终止流程
+		/// </summary>
+		//[Permission(EPermission.ApplyTerminate)]
+		[HttpPost("startflow")]
+		[LogFilter("开始工单终止流程")]
+		public async Task StartFlow([FromBody] TerminateStartFlowDto dto)
+		{
+			var screenAny = await _orderTerminateRepository.AnyAsync(x =>
+				x.OrderId == dto.Data.OrderId && x.Status == ETerminateStatus.Approval);
+			if (screenAny)
+				throw UserFriendlyException.SameMessage("该工单已提起终止申请,正在审批过程中,不能申请");
+
+			var isNoPass = await _orderTerminateRepository.AnyAsync(x => x.Status ==  ETerminateStatus.Refuse && x.OrderId == dto.Data.OrderId);
+			if (isNoPass)
+				throw UserFriendlyException.SameMessage("该工单已被拒绝过甄别申请,不能再次申请");
+
+			var model = _mapper.Map<OrderTerminate>(dto.Data);
+			model.Status = ETerminateStatus.Approval ;
+			model.InitId();
+			if (dto.Data.Files.Any())
+				model.FileJson = await _fileRepository.AddFileAsync(dto.Data.Files, model.Id, "", HttpContext.RequestAborted);
+			else
+				model.FileJson = new List<Share.Dtos.File.FileJson>();
+			await _orderTerminateRepository.AddAsync(model, HttpContext.RequestAborted);
+			try
+			{
+				var startDto = _mapper.Map<StartWorkflowDto>(dto.Workflow);
+				startDto.DefinitionModuleCode = WorkflowModuleConsts.OrderTerminate;
+				startDto.Opinion = dto.Data.Content;
+				startDto.Title = "申请终止流程";
+				await _workflowApplication.StartWorkflowAsync(startDto, _sessionContext, model.Id,	
+					cancellationToken: HttpContext.RequestAborted);
+			}
+			catch (Exception e)
+			{
+				await _orderTerminateRepository.RemoveAsync(model.Id);
+				model.Id = string.Empty;
+				throw new UserFriendlyException($"工单开启终止流程失败!, {e.Message}", "工单开启终止流程失败");
+			}
+		}
+
+
+		/// <summary>
+		/// 工单终止修改后下一步流程
+		/// </summary>
+		[HttpPost("initial_nextFlow")]
+		[LogFilter("办理工单终止流程")]
+		public async Task InitialNextFlow([FromBody] TerminateNextFlowDto dto)
+		{
+			var model = await _orderTerminateRepository.GetAsync(dto.Data.Id);
+
+			if (dto.Data.Files.Any())
+				model.FileJson = await _fileRepository.AddFileAsync(dto.Data.Files, model.Id, "", HttpContext.RequestAborted);
+			else
+				model.FileJson = new List<Share.Dtos.File.FileJson>();
+
+			model.Content = dto.Data.Content;
+			model.IsRecommit = true;
+			await _orderTerminateRepository.UpdateAsync(model, HttpContext.RequestAborted);
+			try
+			{
+				dto.NextWorkflow.WorkflowId = model.WorkflowId;
+				await _workflowApplication.NextAsync(dto.NextWorkflow, _sessionContext,
+					cancellationToken: HttpContext.RequestAborted);
+			}
+			catch (Exception e)
+			{
+				throw new UserFriendlyException($"工单终止下一步流程失败!, {e.Message}", "工单终止下一步流程失败");
+			}
+		}
+
+		/// <summary>
+		/// 查询工单终止流程开启参数
+		/// </summary>
+		/// <returns></returns>
+		[HttpGet("screen/startflow")]
+		public async Task<NextStepsDto> GetTerminateFlowStartOptionsAsync()
+		{
+			return await _workflowApplication.GetStartStepsAsync(WorkflowModuleConsts.OrderTerminate,
+				HttpContext.RequestAborted);
+		}
+
+		/// <summary>
+		/// 查询工单终止流程参数
+		/// </summary>
+		/// <returns></returns>
+		[HttpGet("workflow/{id}")]
+		public async Task<string> GetTerminateWorkFlowAsync(string id)
+		{
+			var workflow = await _workflowRepository.Queryable().FirstAsync(x => x.ExternalId == id);
+			return workflow.Id;
+		}
+
+		/// <summary>
+		///  甄别查询流程办理下一步可选节点
+		/// </summary>
+		/// <param name="workflowId"></param>
+		/// <returns></returns>
+		[HttpGet("{workflowId}/nextsteps")]
+		public async Task<NextStepsDto> OrderTerminateNextsteps(string workflowId)
+		{
+			return await _workflowApplication.GetNextStepsAsync(workflowId, HttpContext.RequestAborted);
+		}
+
+		/// <summary>
+		/// 终止详情
+		/// </summary>
+		/// <param name="id"></param>
+		/// <returns></returns>
+		[HttpGet("{id}")]
+		public async Task<OrderTerminateEntityDto> Entity(string id)
+		{
+			var model = await _orderTerminateRepository.Queryable()
+				.Includes(x => x.Order) 
+				.Includes(x => x.Workflow, d => d.Steps)
+				.FirstAsync(x => x.Id == id);
+			var rspModel = _mapper.Map<OrderTerminateEntityDto>(model);
+			rspModel.IsCanHandle = model.Workflow?.IsCanHandle(
+				_sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, _sessionContext.Roles) ?? false;
+			if (model.Status ==  ETerminateStatus.SendBackStart)
+				rspModel.IsCanHandle = false;
+			rspModel.Handle = false;
+			if (!string.IsNullOrEmpty(rspModel.WorkflowId))
+			{
+				rspModel.Handle = await _workflowDomainService.CheckCurrentIsStartStepAsync(rspModel.WorkflowId, _sessionContext.RequiredUserId,
+					_sessionContext.RequiredOrgId, HttpContext.RequestAborted);
+			}
+
+			if (rspModel.FileJson != null && rspModel.FileJson.Any())
+			{
+				var ids = rspModel.FileJson.Select(x => x.Id).ToList();
+				rspModel.Files = await _fileRepository.GetFilesAsync(ids, HttpContext.RequestAborted);
+			}
+			return rspModel;
+		}
+
+		/// <summary>
+		/// 列表页面基础数据
+		/// </summary>
+		/// <returns></returns>
+		[HttpGet("base")]
+		public async Task<object> BaseData()
+		{
+			var rsp = new
+			{
+				Status = EnumExts.GetDescriptions<ETerminateStatus>(),
+			};
+			return rsp;
+		}
+
+		/// <summary>
+		/// 终止修改理由
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpPut("update_content")]
+		[LogFilter("终止修改理由")]
+		public async Task Update([FromBody] OrderTerminateContentDto dto)
+		{
+			await _orderTerminateRepository.Updateable().SetColumns(x => new OrderTerminate { Content = dto.Content }).Where(x => x.Id == dto.Id).ExecuteCommandAsync();
+		}
+
+	}
+}

+ 15 - 11
src/Hotline.Api/Controllers/TestController.cs

@@ -41,6 +41,7 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.JudicialManagement;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Quality;
+using Hotline.Share.Mq;
 using Hotline.Users;
 using MapsterMapper;
 using MediatR;
@@ -49,7 +50,9 @@ using Microsoft.AspNetCore.Builder.Extensions;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using MiniExcelLibs;
+using Newtonsoft.Json;
 using SqlSugar;
+using StackExchange.Redis;
 using XC.RSAUtil;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
@@ -125,7 +128,6 @@ public class TestController : BaseController
     //private readonly ITypedCache<List<User>> _cache;
     //private readonly ICacheManager<User> _cache;
     private readonly ICalcExpireTime _expireTime;
-    private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
 
 
 	public TestController(
@@ -177,8 +179,7 @@ IOrderDomainService orderDomainService,
 ICallApplication callApplication,
         IOptionsSnapshot<AppConfiguration> appOptions,
         ISystemSettingCacheManager systemSettingCacheManager,
-        ICalcExpireTime expireTime,
-        IOptions<CityBaseConfiguration> cityBaseConfiguration
+        ICalcExpireTime expireTime
 		)
     {
         _logger = logger;
@@ -228,7 +229,6 @@ ICallApplication callApplication,
         _appOptions = appOptions;
         _systemSettingCacheManager = systemSettingCacheManager;
         _expireTime = expireTime;
-        _cityBaseConfiguration = cityBaseConfiguration;
 	}
 
 
@@ -692,13 +692,17 @@ ICallApplication callApplication,
     [HttpGet("t5")]
     public async Task<string> GetUserAllowAnonymous()
     {
-        //var users = await _userRepository.Queryable()
-        //    .FirstAsync(d => d.Name == "xf", HttpContext.RequestAborted);
-        //return users.Id;
-        var b = _cityBaseConfiguration.Value;
-
-		var a = await _expireTime.GetWorkDay(DateTime.Now);
-        return string.Empty;
+		//var users = await _userRepository.Queryable()
+		//    .FirstAsync(d => d.Name == "xf", HttpContext.RequestAborted);
+		//return users.Id;
+		//var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+		//CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+		//var b = cityBase;
+
+		//var a = await _expireTime.GetWorkDay(DateTime.Now);
+		//自动延期订阅
+		_capPublisher.PublishDelay(DateTime.Parse("2024-10-14 14:28:00") - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay, new PublishAutomaticDelayDto() { OrderId = "08dcebff-2fae-4c30-824f-fe2ef4d582ae" });
+		return string.Empty;
 	}
 
     [HttpGet("t6")]

+ 76 - 32
src/Hotline.Api/Controllers/UserController.cs

@@ -1,29 +1,30 @@
-using Hotline.CallCenter.Tels;
+using Hotline.Application.Users;
+using Hotline.Caching.Interfaces;
+using Hotline.CallCenter.Tels;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Permissions;
 using Hotline.Repository.SqlSugar.Extensions;
-using Hotline.Users;
-using MapsterMapper;
-using Microsoft.AspNetCore.Mvc;
-using XF.Domain.Authentications;
-using XF.Domain.Exceptions;
+using Hotline.Settings.CommonOpinions;
 using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Users;
+using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.Order;
-using Microsoft.AspNetCore.Authorization;
+using Hotline.Share.Enums.User;
+using Hotline.Share.Requests;
+using Hotline.Tools;
+using Hotline.Users;
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using SqlSugar;
+using System.Data;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
 using XF.Domain.Options;
-using XF.Utility.EnumExtensions;
-using Hotline.Caching.Interfaces;
-using Hotline.Share.Enums.CallCenter;
-using System;
-using Hotline.Settings.CommonOpinions;
-using Hotline.Share.Enums.Identity;
-using Hotline.Share.Enums.User;
 using XF.Domain.Repository;
-using Hotline.SeedData;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Api.Controllers;
 
@@ -45,6 +46,7 @@ public class UserController : BaseController
     private readonly IOptions<IdentityConfiguration> _identityConfigurationAccessor;
     private readonly ITelRestRepository _telRestRepository;
     private readonly IRepository<SystemCommonOpinion> _commonOpinionRepository;
+    private readonly IUserApplication _userApplication;
 
     public UserController(
         ISessionContext sessionContext,
@@ -59,7 +61,8 @@ public class UserController : BaseController
         IAccountDomainService accountDomainService,
         IOptions<IdentityConfiguration> identityConfigurationAccessor,
         ITelRestRepository telRestRepository,
-        IRepository<SystemCommonOpinion> commonOpinionRepository)
+        IRepository<SystemCommonOpinion> commonOpinionRepository,
+        IUserApplication userApplication)
     {
         _sessionContext = sessionContext;
         _userDomainService = userDomainService;
@@ -74,6 +77,7 @@ public class UserController : BaseController
         _identityConfigurationAccessor = identityConfigurationAccessor;
         _telRestRepository = telRestRepository;
         _commonOpinionRepository = commonOpinionRepository;
+        _userApplication = userApplication;
     }
 
 
@@ -155,26 +159,66 @@ public class UserController : BaseController
     [HttpGet("paged")]
     public async Task<PagedDto<UserDto>> QueryPaged([FromQuery] UserPagedDto dto)
     {
-        var (total, items) = await _userRepository.Queryable(includeDeleted: true)
-            .Includes(d => d.Account)
-            .Includes(d => d.Roles)
-            .Includes(d => d.Organization)
-            .Where(d => d.Account.AccountType == EAccountType.Personal && d.Id != SysAccountSeedData.Id)
-            .WhereIF(_sessionContext.OrgIsCenter == false, d => d.OrgId.StartsWith(_sessionContext.RequiredOrgId))
-            .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-                d => d.Name.Contains(dto.Keyword!) || d.PhoneNo.Contains(dto.Keyword!) ||
-                     d.Account.UserName.Contains(dto.Keyword))
-            .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => d.OrgId == dto.OrgCode)
-            .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)
-            .OrderByDescending(d => d.CreationTime)
-            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+        var query =  _userApplication.QueryPaged(dto);
+        var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+        //var (total, items) = await _userRepository.Queryable(includeDeleted: true)
+        //    .Includes(d => d.Account)
+        //    .Includes(d => d.Roles)
+        //    .Includes(d => d.Organization)
+        //    .Where(d => d.Account.AccountType == EAccountType.Personal && d.Id != SysAccountSeedData.Id)
+        //    .WhereIF(_sessionContext.OrgIsCenter == false, d => d.OrgId.StartsWith(_sessionContext.RequiredOrgId))
+        //    .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
+        //        d => d.Name.Contains(dto.Keyword!) || d.PhoneNo.Contains(dto.Keyword!) ||
+        //             d.Account.UserName.Contains(dto.Keyword))
+        //    .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => d.OrgId == dto.OrgCode)
+        //    .WhereIF(!string.IsNullOrEmpty(dto.Role), d => d.Roles.Any(x => x.Id == dto.Role))
+        //    .WhereIF(!string.IsNullOrEmpty(dto.Name), d => d.Name.Contains(dto.Name))
+        //    .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.PhoneNo.Contains(dto.PhoneNo))
+        //    .WhereIF(!string.IsNullOrEmpty(dto.OrgName), d => d.Organization.Name.Contains(dto.OrgName))
+        //    .OrderBy(d => d.Account.Status)
+        //    .OrderBy(d => d.Organization.OrgType)
+        //    //.OrderBy(d => d.Organization.Id)
+        //    .OrderByDescending(d => d.CreationTime)
+        //    .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
         return new PagedDto<UserDto>(total, _mapper.Map<IReadOnlyList<UserDto>>(items));
     }
 
+    /// <summary>
+    /// 分页查询用户---导出
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("paged/_export")]
+    public async Task<FileStreamResult> QueryPagedExport([FromBody] ExportExcelDto<UserPagedDto> dto)
+    {
+        var query =  _userApplication.QueryPaged(dto.QueryDto);
+        List<User> data;
+        if (dto.IsExportAll)
+        {
+            data = await query.ToListAsync(HttpContext.RequestAborted);
+        }
+        else
+        {
+            var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+            data = items;
+        }
+
+        var dataDtos = _mapper.Map<ICollection<UserDto>>(data);
+
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+        var dtos = dataDtos
+            .Select(stu => _mapper.Map(stu, typeof(UserDto), dynamicClass))
+            .Cast<object>()
+            .ToList();
+
+        var stream = ExcelHelper.CreateStream(dtos);
+
+        return ExcelStreamResult(stream, "部门通讯录数据");
+    }
+
     /// <summary>
     /// 更新用户
     /// </summary>

+ 1 - 1
src/Hotline.Api/StartupExtensions.cs

@@ -189,7 +189,7 @@ internal static class StartupExtensions
         services.AddScoped<IExpireTimeSupplier, HourSupplier>();
 
         //services.AddScoped<LogFilterAttribute>();
-        // ServiceLocator.Instance = services.BuildServiceProvider();
+        //ServiceLocator.Instance = services.BuildServiceProvider();
         return builder.Build();
     }
 

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

@@ -33,7 +33,7 @@
   },
   "AllowedHosts": "*",
   "AppConfiguration": {
-    "AppScope": "ZiGong",
+    "AppScope": "YiBin",
     "YiBin": {
       "CallCenterType": "TianRun", //XunShi、WeiErXin、TianRun、XingTang
       //智能回访

+ 20 - 2
src/Hotline.Application.Contracts/Validators/CallCenter/StartEndTimeDtoValidator.cs

@@ -1,5 +1,6 @@
 using FluentValidation;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Requests;
 using System;
 using System.Collections.Generic;
@@ -14,8 +15,16 @@ public class StartEndTimeDtoValidator : AbstractValidator<StartEndTimeDto>
 {
     public StartEndTimeDtoValidator()
     {
-        RuleFor(d => d.StartTime).NotEmpty();
-        RuleFor(d => d.EndTime).NotEmpty();
+        RuleFor(d => d.StartTime).Must(m => m != default(DateTime)).WithMessage("请选择开始时间");
+        RuleFor(d => d.EndTime).Must(m => m != default(DateTime)).WithMessage("请选择结束时间");
+    }
+}
+
+public class QueryOrderVisitQuantityValidator : AbstractValidator<QueryOrderVisitQuantity>
+{
+    public QueryOrderVisitQuantityValidator()
+    {
+        Include(new StartEndTimeDtoValidator());
     }
 }
 
@@ -31,6 +40,15 @@ public class ReportRequiredPagedRequestValidator : AbstractValidator<ReportRequi
 public class QueryCallListDtoValidator : AbstractValidator<QueryCallListDto>
 {
     public QueryCallListDtoValidator()
+    {
+        RuleFor(d => d.StartTime).Must(m => m != default(DateTime)).WithMessage("请选择开始时间");
+        RuleFor(d => d.EndTime).Must(m => m != default(DateTime)).WithMessage("请选择结束时间"); ;
+    }
+}
+
+public class PagedStartEndTimeDtoValidator : AbstractValidator<PagedStartEndTimeDto>
+{
+    public PagedStartEndTimeDtoValidator()
     {
         RuleFor(d => d.StartTime).NotEmpty();
         RuleFor(d => d.EndTime).NotEmpty();

+ 44 - 0
src/Hotline.Application.Tests/Application/OrderVisitApplicationTest.cs

@@ -0,0 +1,44 @@
+using Hotline.Application.Orders;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Tools;
+using Shouldly;
+using SqlSugar.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Tests.Application;
+public class OrderVisitApplicationTest
+{
+    private readonly IOrderVisitApplication _orderVisitApplication;
+
+    public OrderVisitApplicationTest(IOrderVisitApplication orderVisitApplication)
+    {
+        _orderVisitApplication = orderVisitApplication;
+    }
+
+    /// <summary>
+    /// 统计测试
+    /// </summary>
+    [Theory]
+    [InlineData("2024/07/01", "2024/10/10", null)]
+    [InlineData("2024/07/01", "2024/10/10", "test")]
+    public async Task QueryOrderVisitQuantity_Test(string start, string end, string? name)
+    {
+        var inDto = new QueryOrderVisitQuantity
+        {
+            StartTime = DateTime.Parse(start),
+            EndTime = DateTime.Parse(end),
+            EmployeeName = name
+        };
+        var items = await _orderVisitApplication.QueryOrderVisitQuantityAsync(inDto);
+        items.ShouldNotBeNull();
+
+        if (name.NotNullOrEmpty())
+        {
+            items.Any().ShouldBeTrue();
+        }
+    }
+}

+ 52 - 2
src/Hotline.Application.Tests/Application/ZiGongCallReportApplicationTest.cs

@@ -1,6 +1,8 @@
 using Hotline.Application.StatisticalReport.CallReport;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Requests;
+using Hotline.Share.Tools;
 using Shouldly;
 using SqlSugar.Extensions;
 using System;
@@ -13,16 +15,19 @@ namespace Hotline.Application.Tests.Application;
 public class ZiGongCallReportApplicationTest
 {
     private readonly ZiGongCallReportApplication _ziGongCallReportApplication;
+    private CancellationToken cancellation;
 
     public ZiGongCallReportApplicationTest(ZiGongCallReportApplication ziGongCallReportApplication)
     {
         _ziGongCallReportApplication = ziGongCallReportApplication;
+        cancellation = new CancellationToken();
     }
 
     [Fact]
     public async Task QueryCallsDetailInTotal_Test()
     {
-        var inDto = new BiQueryCallsDto {
+        var inDto = new BiQueryCallsDto
+        {
             StartTime = "2024-07-29".ObjToDate(),
             EndTime = "2024-07-29 23:59:59".ObjToDate()
         };
@@ -39,7 +44,52 @@ public class ZiGongCallReportApplicationTest
             EndTime = DateTime.Now
         };
 
-        var result = await _ziGongCallReportApplication.QuerySeatCallAsync(inDto, new CancellationToken());
+        var result = await _ziGongCallReportApplication.QuerySeatCallAsync(inDto, cancellation);
+        result.ShouldNotBeNull();
+    }
+
+    [Theory]
+    [InlineData(null, null, null, null)]
+    [InlineData(null, "19136073037", null, null)]
+    [InlineData(null, null, "67387546", null)]
+    [InlineData("20240805000001", null, null, null)]
+    [InlineData(null, null, null, "From")]
+    public async Task QueryCallsStatisticsDetail_Test(string? orderNo, string? fromNo, string? toNo, string? endBy)
+    {
+        var inDto = new QueryCallsStatisticsDetailInDto
+        {
+            StartTime = "2024-07-29".ObjToDate(),
+            EndTime = "2024-07-29 23:59:59".ObjToDate(),
+            OrderNo = orderNo,
+            FromNo = fromNo,
+            ToNo = toNo,
+            EndBy = endBy?.ToEnum<EEndBy>()
+        };
+        var (total, items) = await _ziGongCallReportApplication.QueryCallsStatisticsDetailAsync(inDto, cancellation);
+        total.ShouldNotBe(0);
+        items.Any(m => m.OrderNo.NotNullOrEmpty()).ShouldBeTrue();
+        items.Any(m => m.OrderTitle.NotNullOrEmpty()).ShouldBeTrue();
+        if (fromNo != null)
+            items.Any(m => m.FromNo != fromNo).ShouldBeFalse();
+        if (toNo != null)
+            items.Any(m => m.ToNo != toNo).ShouldBeFalse();
+        if (orderNo != null)
+            items.Any(m => m.OrderNo != orderNo).ShouldBeFalse();
+        if (endBy != null)
+            items.Any(m => m.EndBy != inDto.EndBy).ShouldBeFalse();
+    }
+
+    [Theory]
+    [InlineData()]
+    public async Task QueryCallsDetailStatistics_Test()
+    {
+        var inDto = new StartEndTimeDto 
+        { 
+            StartTime = "2024-07-29".ObjToDate(),
+            EndTime = "2024-07-29 23:59:59".ObjToDate(),
+        };
+        var result = await _ziGongCallReportApplication.QueryCallsDetailStatisticsAsync(inDto, cancellation);
+        result.Any(m => m.InConnectionRate.IsNullOrEmpty()).ShouldBeFalse();
         result.ShouldNotBeNull();
     }
 }

+ 3 - 1
src/Hotline.Application.Tests/DefaultHttpContextAccessor.cs

@@ -7,11 +7,13 @@ using System.Security.Claims;
 using System.Text;
 using System.Threading.Tasks;
 using XF.Domain.Authentications;
+using XF.Domain.Dependency;
 
 namespace Hotline.Application.Tests;
-public class DefaultHttpContextAccessor : IHttpContextAccessor, ISessionContext
+public class DefaultHttpContextAccessor : ISessionContext, IScopeDependency
 {
     private HttpContext _content = new DefaultHttpContext();
+
     private HttpContext GetContext()
     { 
         var context = new DefaultHttpContext();

+ 1 - 0
src/Hotline.Application.Tests/Domain/YiBinExpireTimeTest.cs

@@ -67,6 +67,7 @@ public class YiBinExpireTimeTest
 
     [Theory]
     [InlineData("2024-09-04 14:00:00", "CenterToOrg", "08dccc8f-37b0-40d8-8112-1afb2230c5a3", "2024-09-05 14:00:00")]
+    [InlineData("2024-10-03 09:23:46", "CenterToCenter", "08dcce43-1b95-4a12-813a-b48d7f4ec3bd", "2024-10-14 9:00:00")]
     public async Task CalcExpiredTime_Test(string beginTxt, string flowTxt, string orderId, string expected)
     {
         var beginTime = DateTime.Parse(beginTxt);

+ 4 - 1
src/Hotline.Application.Tests/Startup.cs

@@ -46,6 +46,7 @@ using Hotline.Application.CallCenter.Calls;
 using Hotline.CallCenter.Configs;
 using Tr.Sdk;
 using Hotline.Application.StatisticalReport.CallReport;
+using XF.Domain.Authentications;
 
 namespace Hotline.Application.Tests;
 public class Startup
@@ -150,7 +151,9 @@ public class Startup
             services.AddScoped<YiBinCallReportApplication>();
             services.AddScoped<IMediator, MediatorMock>();
 
-            ServiceLocator.Instance = services.BuildServiceProvider();
+            services.AddScoped<ISessionContext, DefaultHttpContextAccessor>();
+
+            //ServiceLocator.Instance = services.BuildServiceProvider();
         }
 
         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

+ 72 - 7
src/Hotline.Application/ExportExcel/ExportApplication.cs

@@ -1,10 +1,15 @@
 
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Tools;
 using Hotline.Tools;
+using Mapster;
 using MapsterMapper;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
 using MiniExcelLibs;
+using System.Reflection;
 using XF.Domain.Dependency;
 
 namespace Hotline.Application.ExportExcel
@@ -12,10 +17,12 @@ namespace Hotline.Application.ExportExcel
     public class ExportApplication : IExportApplication, IScopeDependency
     {
         private readonly IMapper _mapper;
+        private readonly IHttpContextAccessor _httpContextAccessor;
 
-        public ExportApplication(IMapper mapper)
+        public ExportApplication(IMapper mapper, IHttpContextAccessor httpContextAccessor)
         {
             _mapper = mapper;
+            _httpContextAccessor = httpContextAccessor;
         }
 
         /// <summary>
@@ -25,7 +32,7 @@ namespace Hotline.Application.ExportExcel
         /// <param name="list">数据集</param>
         /// <param name="name">导出文件名(不传则生成yyyyMMddhhmmss)</param>
         /// <returns></returns>
-        public FileStreamResult ExportData<T>(List<T> list, string? name)
+        public FileStreamResult ExportData<T>(IList<T> list, string? name)
         {
             var stream = new MemoryStream();
             stream.SaveAs(list);
@@ -36,21 +43,79 @@ namespace Hotline.Application.ExportExcel
             };
         }
 
-        public async Task<Stream> FittingAsync<T, D>(ExportExcelDto<D> dto, List<T> items, Action<List<T>>? action = null)
+        public Stream GetExcelFile<T, D>(ExportExcelDto<D> dto, IList<T> items, Func<IList<T>, T>? func = null)
         {
-            if (items != null && items.Count > 0)
+            if (items != null && items.Count > 0 && func != null)
             {
-                action?.Invoke(items);
+                items.Add(func.Invoke(items));
             }
 
             dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
 
             var dtos = items
-                .Select(stu => _mapper.Map(stu, typeof(BiCallDto), dynamicClass))
+                .Select(stu => _mapper.Map(stu, typeof(T), dynamicClass))
                 .Cast<object>()
                 .ToList();
 
             return ExcelHelper.CreateStream(dtos);
         }
+
+        public FileStreamResult GetExcelFile<T, D>(ExportExcelDto<D> dto, IList<T> items, string fileName, Func<IList<T>, T>? func = null)
+        {
+            var tail = DateTime.Now.ToString("yyyyMMddhhmmss") + ".xlsx";
+            fileName = string.IsNullOrEmpty(fileName)
+                ? tail
+                : $"{fileName}_{tail}";
+            _httpContextAccessor.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
+            var stream = GetExcelFile(dto, items, func);
+            return new FileStreamResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+            {
+                FileDownloadName = fileName
+            };
+
+        }
+
+        public FileStreamResult GetExcelFile<T, D>(ExportExcelDto<D> dto, IList<T> items, string fileName, string totalName) where T : new()
+        {
+            var fieldsAll = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+            var fields = fieldsAll.Where(f => IsNumericType(f.FieldType)); // 只选择数值类型字段
+            var totalField = fieldsAll.Where(m => m.Name.Contains(totalName)).First();
+
+            var sumDict = new Dictionary<FieldInfo, dynamic>();
+
+            foreach (var field in fields)
+            {
+                sumDict[field] = 0;
+            }
+
+            foreach (var item in items)
+            {
+                foreach (var field in fields)
+                {
+                    var value = field.GetValue(item);
+                    if (value != null)
+                    {
+                        sumDict[field] += (dynamic)value; // 使用 dynamic 累加
+                    }
+                }
+            }
+
+            var resultItem = new T();
+            totalField.SetValue(resultItem, "合计");
+            foreach (var field in fields)
+            {
+                field.SetValue(resultItem, sumDict[field]);
+            }
+            items.Add(resultItem);
+            return GetExcelFile(dto, items, fileName);
+        }
+
+        private static bool IsNumericType(Type type)
+        {
+            return type == typeof(int) || type == typeof(float) || type == typeof(double) ||
+                   type == typeof(decimal) || type == typeof(long) || type == typeof(short) ||
+                   type == typeof(byte) || type == typeof(uint) || type == typeof(ulong) ||
+                   type == typeof(ushort) || type == typeof(sbyte);
+        }
     }
-}
+  }

+ 19 - 3
src/Hotline.Application/ExportExcel/IExportApplication.cs

@@ -1,4 +1,6 @@
-using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.Order;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using System;
 using System.Collections.Generic;
@@ -17,8 +19,22 @@ namespace Hotline.Application.ExportExcel
         /// <param name="list">数据集List<T></param>
         /// <param name="name">导出文件名(不传则生成yyyyMMddhhmmss)</param>
         /// <returns></returns>
-        FileStreamResult ExportData<T>(List<T> list, string? name);
+        FileStreamResult ExportData<T>(IList<T> list, string? name);
 
-        Task<Stream> FittingAsync<T, D>(ExportExcelDto<D> dto, List<T> items, Action<List<T>>? action = null);
+        Stream GetExcelFile<T, D>(ExportExcelDto<D> dto, IList<T> items, Func<IList<T>, T>? func = null);
+
+        FileStreamResult GetExcelFile<T, D>(ExportExcelDto<D> dto, IList<T> items,string fileName, Func<IList<T>, T>? func = null);
+
+        /// <summary>
+        /// 导入数据
+        /// </summary>
+        /// <typeparam name="T">导出数据的类型</typeparam>
+        /// <typeparam name="D">导出请求入参类型</typeparam>
+        /// <param name="dto">请求入参</param>
+        /// <param name="items">被导出的数据</param>
+        /// <param name="fileName">excel 文件名</param>
+        /// <param name="totalName">需要填写"统计"两字的字段名称, 字段名称必须是 T 中的字段</param>
+        /// <returns></returns>
+        FileStreamResult GetExcelFile<T, D>(ExportExcelDto<D> dto, IList<T> items, string fileName, string totalName) where T : new();
     }
 }

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

@@ -32,6 +32,7 @@ using System.Text;
 using System.Diagnostics;
 using Hotline.Configurations;
 using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
 using NPOI.SS.Formula.Functions;
 
 namespace Hotline.Application.FlowEngine;
@@ -59,7 +60,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     private readonly IFileRepository _fileRepository;
     private readonly ILogger<WorkflowApplication> _logger;
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
-    private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
 
 	public WorkflowApplication(
         IDefinitionDomainService definitionDomainService,
@@ -80,8 +80,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         IMapper mapper,
         IFileRepository fileRepository,
         ISystemSettingCacheManager systemSettingCacheManager,
-		ILogger<WorkflowApplication> logger,
-        IOptions<CityBaseConfiguration> cityBaseConfiguration)
+		ILogger<WorkflowApplication> logger)
     {
         _definitionDomainService = definitionDomainService;
         _workflowDomainService = workflowDomainService;
@@ -102,7 +101,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         _fileRepository = fileRepository;
         _logger = logger;
         _systemSettingCacheManager = systemSettingCacheManager;
-        _cityBaseConfiguration = cityBaseConfiguration;
 	}
 
     public async Task<string> StartWorkflowAsync(StartWorkflowDto dto, ISessionContext current, string externalId,
@@ -1761,11 +1759,13 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// </summary>
     public async Task UpdateProvinceHandleResultFilesAsync(string workflowId, List<FileDto> files, CancellationToken cancellationToken)
     {
-        var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withSteps: true, withTraces: true,
+	    var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+	    CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+		var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withSteps: true, withTraces: true,
             cancellationToken: cancellationToken);
         var step = workflow.Steps.FirstOrDefault(d =>
 			//d.StepHandlers.Any(d => d.OrgId == _cityBaseConfiguration.Value.CityProvince.OrgId || d.OrgId == _cityBaseConfiguration.Value.CityProvinceAssign.OrgId));
-			d.HandlerOrgId == _cityBaseConfiguration.Value.CityProvince.OrgId || d.HandlerOrgId == _cityBaseConfiguration.Value.CityProvinceAssign.OrgId);
+			d.HandlerOrgId == cityBase.CityProvince.OrgId || d.HandlerOrgId == cityBase.CityProvinceAssign.OrgId);
         if (step is not null)
         {
             step.FileJson = await _fileRepository.AddFileAsync(files, workflow.ExternalId, step.Id, cancellationToken);

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

@@ -45,8 +45,9 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
     private readonly Publisher _publisher;
     private readonly ICalcExpireTime _expireTime;
+    private readonly IRepository<OrderTerminate> _orderTerminateRepository;
 
-    public WorkflowEndHandler(
+	public WorkflowEndHandler(
         IMapper mapper,
         IKnowledgeDomainService knowledgeDomainService,
         IOrderDomainService orderDomainService,
@@ -61,9 +62,9 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
         IOptionsSnapshot<AppConfiguration> appOptions,
         ISystemSettingCacheManager systemSettingCacheManager,
         Publisher publisher,
-        ILogger<WorkflowEndHandler> logger
-,
-        ICalcExpireTime expireTime)
+        ILogger<WorkflowEndHandler> logger,
+        ICalcExpireTime expireTime,
+        IRepository<OrderTerminate> orderTerminateRepository)
     {
         _mapper = mapper;
         _knowledgeDomainService = knowledgeDomainService;
@@ -81,6 +82,7 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
         _publisher = publisher;
         _logger = logger;
         _expireTime = expireTime;
+        _orderTerminateRepository = orderTerminateRepository;
     }
 
     /// <summary>Handles a notification</summary>
@@ -331,6 +333,15 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
                         }
                     }
                     break;
+                case WorkflowModuleConsts.OrderTerminate:
+                    var orderTerminate = await _orderTerminateRepository.Queryable()
+                        .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
+                    if (orderTerminate != null)
+                    {
+                        orderTerminate.Status = isReviewPass ? ETerminateStatus.End : ETerminateStatus.Refuse;
+                        await _orderTerminateRepository.UpdateAsync(orderTerminate, cancellationToken);
+                    }
+                    break;
             }
 
         }

+ 20 - 4
src/Hotline.Application/Handlers/FlowEngine/WorkflowPreviousHandler.cs

@@ -35,8 +35,9 @@ namespace Hotline.Application.Handlers.FlowEngine
         private readonly IMediator _mediator;
         private readonly ISessionContext _sessionContext;
         private readonly IRepository<OrderScreenDetail> _orderScreenDetailRepository;
+        private readonly IRepository<OrderTerminate> _orderTerminateRepository;
 
-        public WorkflowPreviousHandler(
+		public WorkflowPreviousHandler(
             IOrderDomainService orderDomainService,
             IOrderRepository orderRepository,
             IOrderScreenRepository orderScreenRepository,
@@ -49,8 +50,9 @@ namespace Hotline.Application.Handlers.FlowEngine
             IRepository<User> userRepository,
             IMediator mediator,
             ISessionContext sessionContext,
-            IRepository<OrderScreenDetail> orderScreenDetailRepository
-            )
+            IRepository<OrderScreenDetail> orderScreenDetailRepository,
+            IRepository<OrderTerminate> orderTerminateRepository
+			)
         {
             _orderDomainService = orderDomainService;
             _orderRepository = orderRepository;
@@ -65,6 +67,7 @@ namespace Hotline.Application.Handlers.FlowEngine
             _orderDelayRepository = orderDelayRepository;
             _sessionContext = sessionContext;
             _orderScreenDetailRepository = orderScreenDetailRepository;
+            _orderTerminateRepository = orderTerminateRepository;
         }
 
         /// <summary>Handles a notification</summary>
@@ -190,7 +193,20 @@ namespace Hotline.Application.Handlers.FlowEngine
                     case WorkflowModuleConsts.KnowledgeDelete:
                     case WorkflowModuleConsts.TelRestApply:
                         break;
-                }
+                    case WorkflowModuleConsts.OrderTerminate:
+	                    var orderTerminate = await _orderTerminateRepository.Queryable()
+		                    .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
+	                    if (orderTerminate != null)
+	                    {
+		                    orderTerminate.Status = ETerminateStatus.SendBack;
+							if (notification.TargetStep.StepType is EStepType.Start)
+		                    {
+                                orderTerminate.Status = ETerminateStatus.SendBackStart;
+							}
+		                    await _orderTerminateRepository.UpdateAsync(orderTerminate, cancellationToken);
+	                    }
+	                    break;
+				}
 
             }
             catch (Exception e)

+ 18 - 5
src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs

@@ -42,8 +42,9 @@ namespace Hotline.Application.Handlers.FlowEngine
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
         private readonly IMediator _mediator;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IRepository<OrderTerminate> _orderTerminateRepository;
 
-        public WorkflowStartHandler(
+		public WorkflowStartHandler(
             IOrderDomainService orderDomainService,
             IKnowledgeDomainService knowledgeDomainService,
             IOrderRepository orderRepository,
@@ -56,8 +57,9 @@ namespace Hotline.Application.Handlers.FlowEngine
             ICallApplication callApplication,
             IOptionsSnapshot<AppConfiguration> appOptions,
             IMediator mediator,
-            ISystemSettingCacheManager systemSettingCacheManager
-        )
+            ISystemSettingCacheManager systemSettingCacheManager,
+            IRepository<OrderTerminate> orderTerminateRepository
+		)
         {
             _orderDomainService = orderDomainService;
             _knowledgeDomainService = knowledgeDomainService;
@@ -73,7 +75,8 @@ namespace Hotline.Application.Handlers.FlowEngine
             _appOptions = appOptions;
             _mediator = mediator;
             _systemSettingCacheManager = systemSettingCacheManager;
-        }
+            _orderTerminateRepository = orderTerminateRepository;
+		}
 
         /// <summary>Handles a notification</summary>
         /// <param name="notification">The notification</param>
@@ -239,7 +242,17 @@ namespace Hotline.Application.Handlers.FlowEngine
                         }
 
                         break;
-                }
+                    case WorkflowModuleConsts.OrderTerminate:
+                        var orderTerminate = await _orderTerminateRepository.Queryable()
+	                        .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
+                        if (orderTerminate != null)
+                        {
+	                        orderTerminate.WorkflowId = workflow.Id;
+							orderTerminate.Status = ETerminateStatus.Approval;
+                            await _orderTerminateRepository.UpdateAsync(orderTerminate, cancellationToken);
+						}
+                        break;
+				}
 
             }
             catch (Exception e)

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

@@ -306,6 +306,12 @@ namespace Hotline.Application.Orders
         /// <returns></returns>
         ISugarQueryable<OrderObserve> OrderObserveList(OrderObserveListDto dto);
 
+        /// <summary>
+        /// 终止列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<OrderTerminate> OrderTerminateList(OrderTerminateListDto dto);
 
 	}
 }

+ 13 - 0
src/Hotline.Application/Orders/IOrderVisitApplication.cs

@@ -0,0 +1,13 @@
+using Hotline.Share.Dtos.Order;
+
+namespace Hotline.Application.Orders;
+
+public interface IOrderVisitApplication
+{
+    /// <summary>
+    /// 回访量统计
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<List<OrderVisitQuantityOutDto>> QueryOrderVisitQuantityAsync(QueryOrderVisitQuantity dto, CancellationToken cancellationToken = default);
+}

+ 39 - 16
src/Hotline.Application/Orders/OrderApplication.cs

@@ -64,6 +64,7 @@ using Hotline.Share.Tools;
 using Hotline.EventBus;
 using Hotline.Orders.Notifications;
 using Hotline.OrderTranspond;
+using Newtonsoft.Json;
 
 namespace Hotline.Application.Orders;
 
@@ -102,9 +103,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IRepository<OrderScreen> _orderScreenRepository;
     private readonly IRepository<OrderSendBackAudit> _orderSendBackAuditRepository;
     private readonly ICalcExpireTime _expireTime;
-    private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
     private readonly IRepository<OrderObserve> _orderObserveRepository;
-
+    private readonly IOrderTerminateRepository _orderTerminateRepository;
 
 	public OrderApplication(
         IOrderDomainService orderDomainService,
@@ -136,11 +136,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IMediator mediator,
         IRepository<OrderVisitDetail> orderVisitedDetailRepository,
         IOptionsSnapshot<AppConfiguration> appOptions,
-        IOptions<CityBaseConfiguration> cityBaseConfiguration,
         ISystemDicDataCacheManager sysDicDataCacheManager,
         Publisher publisher,
         IRepository<TranspondCityRawData> transpondCityRawDataRepository,
-        IRepository<OrderObserve> orderObserveRepository)
+        IRepository<OrderObserve> orderObserveRepository,
+        IOrderTerminateRepository orderTerminateRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -174,9 +174,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _sysDicDataCacheManager = sysDicDataCacheManager;
         _publisher = publisher;
         _transpondCityRawDataRepository = transpondCityRawDataRepository;
-        _cityBaseConfiguration = cityBaseConfiguration;
         _orderObserveRepository = orderObserveRepository;
-
+        _orderTerminateRepository = orderTerminateRepository;
 	}
 
     /// <summary>
@@ -2119,16 +2118,15 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 
 
 
+	#region private
 
-    #region private
-
-    /// <summary>
-    /// 接受外部工单(除省平台)
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <param name="cancellationToken"></param>
-    /// <returns></returns>
-    private async Task<AddOrderResponse> ReceiveOrderFromOtherPlatformAsync(AddOrderDto dto, List<FileDto> files,
+	/// <summary>
+	/// 接受外部工单(除省平台)
+	/// </summary>
+	/// <param name="dto"></param>
+	/// <param name="cancellationToken"></param>
+	/// <returns></returns>
+	private async Task<AddOrderResponse> ReceiveOrderFromOtherPlatformAsync(AddOrderDto dto, List<FileDto> files,
         ISessionContext current, CancellationToken cancellationToken)
     {
         if (string.IsNullOrEmpty(dto.ExternalId))
@@ -2206,7 +2204,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             //特提(撤回至发起)
             if (!string.IsNullOrEmpty(order.WorkflowId))
             {
-                current = SessionContextCreator.CreateSessionContext(_sessionContext, "province", _cityBaseConfiguration.Value);
+	            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+	            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+				current = SessionContextCreator.CreateSessionContext(_sessionContext, "province", cityBase);
                 await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, order.Status >= EOrderStatus.Filed, cancellationToken);
                 //current = SessionContextCreator.CreateSessionContext("province", _cityBaseConfiguration.Value);
                 //await _workflowDomainService.RecallToStartStepAsync(order.WorkflowId, "省工单重派", current, cancellationToken);
@@ -2342,5 +2342,28 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .OrderByDescending(d => d.CreationTime);
 	}
 
+    /// <summary>
+    /// 终止列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<OrderTerminate> OrderTerminateList(OrderTerminateListDto dto) 
+    {
+	    var handler = dto.AuditStatus is 2;
+		var isAdmin = _orderDomainService.IsCheckAdmin();
+		return _orderTerminateRepository.Queryable(hasHandled: handler, isAdmin: isAdmin)
+            .Includes(d => d.Order)
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Order.No!.Contains(dto.No!))
+            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Order.Title!.Contains(dto.Title!))
+            .WhereIF(dto.ApplyStartTime.HasValue && dto.ApplyEndTime.HasValue,
+                d => d.CreationTime >= dto.ApplyStartTime && d.CreationTime <= dto.ApplyEndTime)
+            //.WhereIF(dto.AuditStatus is 1 , d=>d.Status == ETerminateStatus.Approval || d.Status == ETerminateStatus.SendBack )
+            //.WhereIF(dto.AuditStatus is 2, d => d.Status == ETerminateStatus.End || dto.Status == ETerminateStatus.Refuse)
+			.WhereIF(dto.QueryType is 1, d => d.CreatorId == _sessionContext.UserId)
+            .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)
+            .WhereIF(dto.StartTime.HasValue && dto.EndTime.HasValue, d => d.Order.StartTime >= dto.StartTime && d.Order.StartTime <= dto.EndTime)
+            .OrderByDescending(d => d.CreationTime);
+
+	}
 	#endregion
 }

+ 42 - 0
src/Hotline.Application/Orders/OrderVisitApplication.cs

@@ -0,0 +1,42 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
+using SqlSugar;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Orders;
+
+/// <summary>
+/// 回访服务
+/// </summary>
+public class OrderVisitApplication : IOrderVisitApplication, IScopeDependency
+{
+    private readonly IOrderVisitRepository _orderVisitRepository;
+
+    public OrderVisitApplication(IOrderVisitRepository orderVisitRepository)
+    {
+        _orderVisitRepository = orderVisitRepository;
+    }
+
+    public async Task<List<OrderVisitQuantityOutDto>> QueryOrderVisitQuantityAsync(QueryOrderVisitQuantity dto, CancellationToken cancellationToken)
+    {
+        var query = _orderVisitRepository
+            .Queryable()
+            .WhereIF(dto.EmployeeName.NotNullOrEmpty(), m => m.Employee.Name.Contains(dto.EmployeeName))
+            .Where(m => m.CreationTime >= dto.StartTime && m.CreationTime <= dto.EndTime && m.VisitType != null)
+            .GroupBy(m => m.EmployeeId)
+            .Select(m => new OrderVisitQuantityOutDto
+            {
+                EmployeeId = m.EmployeeId,
+                EmployeeName = m.Employee.Name,
+                CallVisitCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.VisitType == EVisitType.CallVisit, 1, 0)), // 电话回访量
+                DefaultVisitCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.VisitType == EVisitType.ArtificialVisit, 1, 0)), // 默认回访量(人工回访)
+                SmsVisitCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.VisitType == EVisitType.SmsVisit, 1, 0)), // 短信回访量
+                // TotalVisitCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.VisitType != null, 1, 0)) // 总回访量
+            });
+
+
+        return await query.ToListAsync(cancellationToken);
+    }
+}

+ 79 - 8
src/Hotline.Application/StatisticalReport/CallReport/CallReportApplicationBase.cs

@@ -15,6 +15,8 @@ using Hotline.Users;
 using XF.Domain.Repository;
 using Hotline.CallCenter.Tels;
 using Hotline.Share.Dtos;
+using Hotline.Share.Tools;
+using JiebaNet.Segmenter.Common;
 
 namespace Hotline.Application.StatisticalReport.CallReport;
 
@@ -35,11 +37,6 @@ public abstract class CallReportApplicationBase : ICallReportApplication
         _telRestRepository = telRestRepository;
     }
 
-    //public virtual async Task<PagedDto<TrCallDto>> GetCallDetailListAsync(GetCallListDto dto, CancellationToken cancellationToken)
-    //{
-    //    throw new NotImplementedException();
-    //}
-
     public virtual async Task<List<CallHotLineDto>> GetCallHotLineListAsync(BiQueryGateWayDto dto, CancellationToken requestAborted)
     {
         int noConnectByeTimes = _systemSettingCacheManager.NoConnectByeTimes;
@@ -78,10 +75,9 @@ public abstract class CallReportApplicationBase : ICallReportApplication
     public virtual async Task<List<QueryCallsDetailDto>> QueryCallsDetailAsync(BiQueryCallsDto dto)
     {
         //超时接通量
-        int CallInOverConnRingTime = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.CallInOverConnRingTime)?.SettingValue[0]);
+        int CallInOverConnRingTime = _systemSettingCacheManager.CallInOverConnRingTime;
         //坐席超时挂断时间
-        int SeatChaoTime = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.SeatChaoTime)?.SettingValue[0]);
-
+        int SeatChaoTime = _systemSettingCacheManager.SeatChaoTime;
         //未接秒挂时间
         int noConnectByeTimes = _systemSettingCacheManager.NoConnectByeTimes;
         //呼入有效时间
@@ -151,6 +147,39 @@ public abstract class CallReportApplicationBase : ICallReportApplication
         return await query.ToPagedListAsync(dto.PageIndex, dto.PageSize);
     }
 
+    public virtual async Task<List<QueryCallsDetailStatistics>> QueryCallsDetailStatisticsAsync(StartEndTimeDto dto, CancellationToken cancellationToken)
+    {
+        //超时接通量
+        int CallInOverConnRingTime = _systemSettingCacheManager.CallInOverConnRingTime;
+        //坐席超时挂断时间
+        int SeatChaoTime = _systemSettingCacheManager.SeatChaoTime;
+        //未接秒挂时间
+        int noConnectByeTimes = _systemSettingCacheManager.NoConnectByeTimes;
+        //呼入有效时间
+        int effectiveTimes = _systemSettingCacheManager.EffectiveTimes;
+        //接通秒挂时间
+        int connectByeTimes = _systemSettingCacheManager.ConnectByeTimes;
+
+        var callData = await _callNativeRepository.Queryable()
+                .Where(p => p.CreationTime >= dto.StartTime && p.CreationTime <= dto.EndTime)
+                 .GroupBy(p => p.CreationTime.ToString("yyyy-MM-dd"))
+                .Select(p => new QueryCallsDetailStatistics
+                {
+                    Date = p.CreationTime.ToString("yyyy-MM-dd"),
+                    InTotal = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In, 1, 0)),//呼入总量
+                    InConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.AnsweredTime != null, 1, 0)),//呼入接通量
+                    NotAcceptedHang = SqlFunc.AggregateSum(SqlFunc.IIF(p.Duration == 0 && p.RingDuration <= noConnectByeTimes && p.RingDuration > 0 && p.Direction == ECallDirection.In, 1, 0)), //呼入队列挂断
+                    InNotAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(p.Duration == 0 && p.TelNo != "0" && p.Direction == ECallDirection.In, 1, 0)), // 挂机量
+                    IvrByeCount = SqlFunc.AggregateSum(SqlFunc.IIF(p.Direction == ECallDirection.In && p.BeginIvrTime.HasValue && !p.BeginQueueTime.HasValue && !p.BeginRingTime.HasValue && p.AnsweredTime == null, 1, 0)), //IVR挂断
+                    OutConnectionQuantity = SqlFunc.AggregateSum(SqlFunc.IIF(p.AnsweredTime != null && p.Direction == ECallDirection.Out, 1, 0)), // 呼出接通量
+                    OutNotAnswered = SqlFunc.AggregateSum(SqlFunc.IIF(p.AnsweredTime == null && p.Direction == ECallDirection.Out, 1, 0)), // 呼出未接量
+                })
+                .OrderBy(p => p.Date)
+                .ToListAsync(cancellationToken);
+
+        return callData;
+    }
+
     public virtual async Task<List<QueryCallsDetailDto>> QueryCallsHourDetailAsync(BiQueryCallsDto dto, CancellationToken cancellationToken)
     {
         //超时接通量
@@ -166,6 +195,48 @@ public abstract class CallReportApplicationBase : ICallReportApplication
         return await _callNativeRepository.QueryCallsHourDetail(dto.StartTime.Value, dto.EndTime.Value, noConnectByeTimes, effectiveTimes, connectByeTimes, CallInOverConnRingTime, SeatChaoTime, dto.Line);
     }
 
+    public virtual async Task<(int, List<QueryCallsStatisticsDetailOutDto>)> QueryCallsStatisticsDetailAsync(QueryCallsStatisticsDetailInDto dto, CancellationToken cancellationToken)
+    {
+        dto.FieldName ??= string.Empty;
+        dto.FieldName = dto.FieldName.ToLower();
+        //超时接通量
+        int CallInOverConnRingTime = _systemSettingCacheManager.CallInOverConnRingTime;
+        //坐席超时挂断时间
+        int SeatChaoTime = _systemSettingCacheManager.SeatChaoTime;
+        //未接秒挂时间
+        int noConnectByeTimes = _systemSettingCacheManager.NoConnectByeTimes;
+        //呼入有效时间
+        int effectiveTimes = _systemSettingCacheManager.EffectiveTimes;
+        //接通秒挂时间
+        int connectByeTimes = _systemSettingCacheManager.ConnectByeTimes;
+        var query = _callNativeRepository.Queryable(includeDeleted: true)
+            .LeftJoin<Order>((c, o) => c.Id == o.CallId)
+            .WhereIF(dto.OrderNo.NotNullOrEmpty(), (c, o) => o.No == dto.OrderNo)
+            .WhereIF(dto.FromNo.NotNullOrEmpty(), (c, o) => c.FromNo == dto.FromNo)
+            .WhereIF(dto.ToNo.NotNullOrEmpty(), (c, o) => c.ToNo == dto.ToNo)
+            .WhereIF(dto.TelNo.NotNullOrEmpty(), (c, o) => c.TelNo == dto.TelNo)
+            .WhereIF(dto.EndBy.IsNotNull(), (c, o) => c.EndBy == dto.EndBy)
+            .Where((c, o) => c.CreationTime >= dto.StartTime && c.CreationTime <= dto.EndTime);
+
+        if (dto.FieldName == "intotal") // 呼入总量
+            query = query.Where((c, o) => c.Direction == ECallDirection.In);
+        if (dto.FieldName == "inconnectionquantity") // 呼入接通
+            query = query.Where((c, o) => c.Direction == ECallDirection.In && c.AnsweredTime != null);
+        if (dto.FieldName == "innotanswered") // 挂机量
+            query = query.Where((c, o) => c.Direction == ECallDirection.In && c.Duration ==0 && c.TelNo != "0");
+        if (dto.FieldName == "notacceptedhang") // 呼入队列挂断
+            query = query.Where((c, o) => c.Duration == 0 && c.RingDuration <= noConnectByeTimes && c.RingDuration > 0 && c.Direction == ECallDirection.In);
+        if (dto.FieldName == "ivrbyecount") // 呼入IVR挂断
+            query = query.Where((c, o) => c.Direction == ECallDirection.In && c.BeginIvrTime.HasValue && !c.BeginQueueTime.HasValue && !c.BeginRingTime.HasValue && c.AnsweredTime == null);
+        if (dto.FieldName == "outconnectionquantity") // 呼出接通量
+            query = query.Where((c, o) => c.AnsweredTime != null && c.Direction == ECallDirection.Out);
+        if (dto.FieldName == "outnotanswered") // 呼出未接通
+            query = query.Where((c, o) => c.AnsweredTime == null && c.Direction == ECallDirection.Out);
+        return await query
+            .Select<QueryCallsStatisticsDetailOutDto>()
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, cancellationToken);
+    }
+
     /// <summary>
     /// 坐席话务统计分析
     /// </summary>

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

@@ -37,6 +37,15 @@ namespace Hotline.Application.StatisticalReport
         Task<List<TrCallHourDto>> GetCallHourListAsync(BiQueryHourCallDto dto, CancellationToken cancellationToken);
         Task<TotalData<BiSeatSwitchDto>> GetCallListAsync(QueryCallListDto dto, CancellationToken cancellationToken);
         Task<List<CallHotLineDto>> GetCallHotLineListAsync(BiQueryGateWayDto dto, CancellationToken cancellationToken);
+        Task<List<QueryCallsDetailStatistics>> QueryCallsDetailStatisticsAsync(StartEndTimeDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 话务日期统计详情
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<(int, List<QueryCallsStatisticsDetailOutDto>)> QueryCallsStatisticsDetailAsync(QueryCallsStatisticsDetailInDto dto, CancellationToken cancellationToken);
         // Task<PagedDto<TrCallDto>> GetCallDetailListAsync(GetCallListDto dto, CancellationToken cancellationToken);
     }
 }

+ 23 - 16
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -27,6 +27,7 @@ using Hotline.Share.Mq;
 using MapsterMapper;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -68,7 +69,6 @@ namespace Hotline.Application.Subscribers
         private readonly ICallApplication _callApplication;
         private readonly IRepository<OrderPublish> _orderPublishRepository;
         private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
-        private readonly IOptions<CityBaseConfiguration> _cityBaseConfiguration;
         private readonly ISessionContext _sessionContext;
 
         public DataSharingSubscriber(
@@ -100,9 +100,8 @@ namespace Hotline.Application.Subscribers
             IOptionsSnapshot<AppConfiguration> appOptions,
             ICallApplication callApplication,
             IRepository<OrderPublish> orderPublishRepository,
-            ISystemDicDataCacheManager sysDicDataCacheManager,
-            IOptions<CityBaseConfiguration> cityBaseConfiguration,
-            ISessionContext sessionContext)
+            ISessionContext sessionContext,
+            ISystemDicDataCacheManager sysDicDataCacheManager)
         {
             _orderSendBackRepository = orderSendBackRepository;
             _workflowApplication = workflowApplication;
@@ -133,7 +132,6 @@ namespace Hotline.Application.Subscribers
             _callApplication = callApplication;
             _orderPublishRepository = orderPublishRepository;
             _sysDicDataCacheManager = sysDicDataCacheManager;
-            _cityBaseConfiguration = cityBaseConfiguration;
             _sessionContext = sessionContext;
         }
 
@@ -258,9 +256,11 @@ namespace Hotline.Application.Subscribers
                 }
             };
             await _orderRevokeRepository.AddAsync(orderRevoke, cancellationToken);
+            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
 
-            //宜宾需求:特提至中心(优先派单组无派单组节点就特提至坐席),由派单员归档
-            var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
+			//宜宾需求:特提至中心(优先派单组无派单组节点就特提至坐席),由派单员归档
+			var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, cityBase);
             if (string.IsNullOrEmpty(order?.WorkflowId))
             {
                 var startDto = new StartWorkflowDto
@@ -433,7 +433,9 @@ namespace Hotline.Application.Subscribers
                             x.Status == EScreenStatus.Approval)
                 .FirstAsync(cancellationToken);
 
-            var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
+            var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+            CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+			var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, cityBase);
             await _workflowApplication.HandleToEndAsync(current,
                 orderScreen.WorkflowId, "省上推送甄别结果", null,
                 dto.ProvinceScreenResult.AuditResult
@@ -679,7 +681,10 @@ namespace Hotline.Application.Subscribers
                         orderDelay.FileJson = await _fileRepository.AddFileAsync(dto.Files, orderDelay.Id, orderDelay.WorkflowId, cancellationToken);
                     await _orderDelayRepository.UpdateAsync(orderDelay, cancellationToken);
 
-                    var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
+                    var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+                    CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
+
+					var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, cityBase);
                     await _workflowApplication.HandleToEndAsync(current, orderDelay.WorkflowId, dto.Opinion, dto.Files,
                         dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed, cancellationToken);
                 }
@@ -698,14 +703,16 @@ namespace Hotline.Application.Subscribers
             if (string.IsNullOrEmpty(order.WorkflowId))
                 throw new UserFriendlyException($"该工单未开启流程,orderId: {dto.OrderId}");
 
-            //if (dto.Files != null && dto.Files.Any())
-            //{
-            //    order.FileJson = await _fileRepository.AddFileAsync(dto.Files, order.Id, order.WorkflowId,
-            //        cancellationToken);
-            //    await _orderRepository.FileAsync(order, cancellationToken);
-            //}
+			//if (dto.Files != null && dto.Files.Any())
+			//{
+			//    order.FileJson = await _fileRepository.AddFileAsync(dto.Files, order.Id, order.WorkflowId,
+			//        cancellationToken);
+			//    await _orderRepository.FileAsync(order, cancellationToken);
+			//}
+			var setting = _systemSettingCacheManager.GetSetting(SettingConstants.CityBaseConfiguration)?.SettingValue[0];
+			CityBaseConfiguration cityBase = JsonConvert.DeserializeObject<CityBaseConfiguration>(setting);
 
-            var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, _cityBaseConfiguration.Value);
+			var current = SessionContextCreator.CreateSessionContext(_sessionContext, dto.Source, cityBase);
             switch (dto.FinishType)
             {
                 case "0":

+ 31 - 1
src/Hotline.Application/Systems/BaseDataApplication.cs

@@ -1,7 +1,10 @@
 using Hotline.Caching.Interfaces;
+using Hotline.Repository.SqlSugar.System;
 using Hotline.Settings;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.Order;
+using J2N.Collections.ObjectModel;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -10,6 +13,7 @@ using System.Linq;
 using System.Reflection;
 using System.Text;
 using System.Threading.Tasks;
+using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Utility.EnumExtensions;
 
@@ -17,13 +21,17 @@ namespace Hotline.Application.Systems;
 public class BaseDataApplication : IScopeDependency
 {
     private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
+    private readonly ISystemOrganizeRepository _systemOrganizeRepository;
 
-    public BaseDataApplication(ISystemDicDataCacheManager sysDicDataCacheManager)
+    public BaseDataApplication(ISystemDicDataCacheManager sysDicDataCacheManager, ISystemOrganizeRepository systemOrganizeRepository)
     {
         _sysDicDataCacheManager = sysDicDataCacheManager;
+        _systemOrganizeRepository = systemOrganizeRepository;
     }
 
     private ConcurrentDictionary<string, dynamic> _baseData = new ConcurrentDictionary<string, dynamic>();
+
+
     //private ConcurrentDictionary<string, int> _baseType = new ConcurrentDictionary<string, int>();
     //private ConcurrentDictionary<string, int> _enumType = new ConcurrentDictionary<string, int>();
     #region 内部方法
@@ -111,4 +119,26 @@ public class BaseDataApplication : IScopeDependency
         Add(SysDicTypeConsts.CallForwardingType);
         return this;
     }
+    public BaseDataApplication EndBy()
+    {
+        Add(typeof(EEndBy));
+        return this;
+    }
+
+    public BaseDataApplication OrgsOptions(ISessionContext sessionContext)
+    {
+        IReadOnlyList<SystemOrganize> items;
+        if (sessionContext.OrgIsCenter)
+            items = _systemOrganizeRepository.GetOrgJson().GetAwaiter().GetResult();
+        else
+            items = _systemOrganizeRepository.GetOrgJsonForUser(sessionContext.RequiredOrgId).GetAwaiter().GetResult();
+        _baseData.TryAdd("OrgsOptions", items);
+        return this;
+    }
+
+    public BaseDataApplication AttitudeType()
+    {
+        Add(typeof(EAttitudeType));
+        return this;
+    }
 }

+ 16 - 0
src/Hotline.Application/Users/IUserApplication.cs

@@ -0,0 +1,16 @@
+using Hotline.Share.Dtos.Users;
+using Hotline.Users;
+using SqlSugar;
+
+namespace Hotline.Application.Users
+{
+    public interface IUserApplication
+    {
+        /// <summary>
+        /// 查询用户数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<User> QueryPaged(UserPagedDto dto);
+    }
+}

+ 49 - 0
src/Hotline.Application/Users/UserApplication.cs

@@ -0,0 +1,49 @@
+using Hotline.SeedData;
+using Hotline.Share.Dtos.Users;
+using Hotline.Share.Enums.Identity;
+using Hotline.Users;
+using SqlSugar;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Users
+{
+    public class UserApplication : IUserApplication, IScopeDependency
+    {
+        private readonly IRepository<User> _userRepository;
+        private readonly ISessionContext _sessionContext;
+
+        public UserApplication(IRepository<User> userRepository, ISessionContext sessionContext)
+        {
+            _userRepository = userRepository;
+            _sessionContext = sessionContext;
+        }
+
+        /// <summary>
+        /// 查询用户数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        public ISugarQueryable<User> QueryPaged(UserPagedDto dto)
+        {
+            return _userRepository.Queryable(includeDeleted: true)
+             .Includes(d => d.Account)
+             .Includes(d => d.Roles)
+             .Includes(d => d.Organization)
+             .Where(d => d.Account.AccountType == EAccountType.Personal && d.Id != SysAccountSeedData.Id)
+             .WhereIF(_sessionContext.OrgIsCenter == false, d => d.OrgId.StartsWith(_sessionContext.RequiredOrgId))
+             .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
+                 d => d.Name.Contains(dto.Keyword!) || d.PhoneNo.Contains(dto.Keyword!) ||
+                      d.Account.UserName.Contains(dto.Keyword))
+             .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => d.OrgId == dto.OrgCode)
+             .WhereIF(!string.IsNullOrEmpty(dto.Role), d => d.Roles.Any(x => x.Id == dto.Role))
+             .WhereIF(!string.IsNullOrEmpty(dto.Name), d => d.Name.Contains(dto.Name))
+             .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.PhoneNo.Contains(dto.PhoneNo))
+             .WhereIF(!string.IsNullOrEmpty(dto.OrgName), d => d.Organization.Name.Contains(dto.OrgName))
+             .OrderBy(d => d.Account.Status)
+             .OrderBy(d => d.Organization.OrgType)
+             .OrderByDescending(d => d.CreationTime);
+        }
+    }
+}

+ 6 - 0
src/Hotline.Repository.SqlSugar/File/FileRepository.cs

@@ -10,6 +10,7 @@ using SqlSugar;
 using System.Threading;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 
 namespace Hotline.Repository.SqlSugar.File
@@ -34,6 +35,11 @@ namespace Hotline.Repository.SqlSugar.File
 			await Db.Deleteable<Hotline.File.File>().In(x=>x.Id , deleteFilesId).ExecuteCommandAsync(cancellationToken);
 			foreach (FileDto file in files)
 			{
+				if (string.IsNullOrEmpty(file.Path) || string.IsNullOrEmpty(file.AllPath))
+					throw UserFriendlyException.SameMessage("附件信息错误,请检查后重新上传附件!");
+				var names = file.FileName.Split(".");
+				file.Name = names[0];
+				file.Type = names[1];
 				file.OrgName = _sessionContext.OrgName;
 				file.OrgId = _sessionContext.OrgId;
 				file.UserId = _sessionContext.UserId;

+ 11 - 3
src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs

@@ -1249,7 +1249,7 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .Where(x => x.OrderVisit.VisitState == EVisitState.Visited && x.VisitTarget == EVisitTarget.Org)
                 .WhereIF(IsCenter == false, x => x.VisitOrgCode.StartsWith(_sessionContext.RequiredOrgId))
                 .WhereIF(dto.OrgVisitStatisticsType.HasValue, x => x.OrderVisit.Order.ProcessType == (EProcessType)((int)dto.OrgVisitStatisticsType))
-                .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), dto.AttitudeType == EAttitudeType.ProcessingResult ? x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults : x => SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == dto.OrgProcessingResults)
                 .WhereIF(!string.IsNullOrEmpty(dto.VisitUser), x => x.OrderVisit.Employee.Name.Contains(dto.VisitUser))
                 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order.No == dto.No)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order.Title.Contains(dto.Title))
@@ -1279,7 +1279,8 @@ namespace Hotline.Repository.SqlSugar.Orders
                     VisitUser = x.OrderVisit.Employee.Name,
                     VisitType = x.OrderVisit.VisitType,
                     VisitTime = x.OrderVisit.VisitTime,
-                    OrgProcessingResults = SqlFunc.JsonField(x.OrgProcessingResults, "Value"),
+                    OrgProcessingResults = dto.AttitudeType == EAttitudeType.ProcessingResult ? SqlFunc.JsonField(x.OrgProcessingResults, "Value")
+                    : SqlFunc.JsonField(x.OrgHandledAttitude, "Value"),
                     Content = x.OrderVisit.Order.Content,
                     FileOpinion = x.OrderVisit.Order.FileOpinion,
                     FiledTime = x.OrderVisit.Order.FiledTime,
@@ -1343,7 +1344,14 @@ namespace Hotline.Repository.SqlSugar.Orders
         }
     }
 
-    public class OrderDelayRepository : BaseRepositoryWorkflow<OrderDelay>, IOrderDelayRepository, IScopeDependency
+    public class OrderTerminateRepository : BaseRepositoryWorkflow<OrderTerminate>, IOrderTerminateRepository, IScopeDependency
+    {
+	    public OrderTerminateRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+	    {
+	    }
+    }
+
+	public class OrderDelayRepository : BaseRepositoryWorkflow<OrderDelay>, IOrderDelayRepository, IScopeDependency
     {
         public OrderDelayRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
         {

+ 118 - 1
src/Hotline.Share/Dtos/CallCenter/BiQueryCallsDto.cs

@@ -1,5 +1,7 @@
-using Hotline.Share.Requests;
+using Hotline.Share.Enums.CallCenter;
+using Hotline.Share.Requests;
 using System.ComponentModel.DataAnnotations;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.CallCenter;
 
@@ -37,6 +39,15 @@ public class BiQueryGateWayDto : StartEndTimeDto
 }
 
 public class StartEndTimeDto
+{
+    [Required]
+    public DateTime StartTime { get; set; }
+
+    [Required]
+    public DateTime EndTime { get; set; }
+}
+
+public record PagedStartEndTimeDto : PagedRequest 
 {
     public DateTime StartTime { get; set; }
     public DateTime EndTime { get; set; }
@@ -72,4 +83,110 @@ public class TotalData<T>
 
     public int Total { get; set; }
     public List<T> Data { get; set; }
+}
+
+public record QueryCallsStatisticsDetailInDto : PagedStartEndTimeDto
+{
+    /// <summary>
+    /// 工单编号
+    /// </summary>
+    public string? OrderNo { get; set; }
+
+    /// <summary>
+    /// 主叫
+    /// </summary>
+    public string? FromNo { get; set; }
+
+    /// <summary>
+    /// 被叫
+    /// </summary>
+    public string? ToNo { get; set; }
+
+    /// <summary>
+    /// 分机号码
+    /// </summary>
+    public string? TelNo { get; set; }
+
+    /// <summary>
+    /// 挂断方
+    /// </summary>
+    public EEndBy? EndBy { get; set; }
+
+    /// <summary>
+    /// 上一个页面点击的字段名称
+    /// </summary>
+    public string? FieldName { get; set; }
+}
+
+public class QueryCallsStatisticsDetailOutDto
+{
+    public string Id { get; set; }
+    public string? OrderId { get; set; }
+
+    /// <summary>
+    /// 工单号
+    /// </summary>
+    public string? OrderNo { get; set; }
+
+    /// <summary>
+    /// 工单标题
+    /// </summary>
+    public string? OrderTitle { get; set; }
+
+    /// <summary>
+    /// 主叫
+    /// </summary>
+    public string FromNo { get; set; }
+
+    /// <summary>
+    /// 被叫
+    /// </summary>
+    public string ToNo { get; set; }
+
+    /// <summary>
+    /// 响应分机号
+    /// </summary>
+    public string TelNo { get; set; }
+
+    /// <summary>
+    /// 挂断方
+    /// </summary>
+    public EEndBy? EndBy { get; set; }
+
+    public string EndByTxt => EndBy.GetDescription();
+
+    /// <summary>
+    /// 话务员姓名
+    /// </summary>
+    public string UserName { get; set; }
+
+    /// <summary>
+    /// 分机组id(技能组Id)
+    /// </summary>
+    public string? GroupId { get; set; }
+
+    /// <summary>
+    /// IVR开始时间
+    /// </summary>
+    public DateTime? BeginIvrTime { get; set; }
+
+    /// <summary>
+    /// 接听时间
+    /// </summary>
+    public DateTime? AnsweredTime { get; set; }
+
+    /// <summary>
+    /// 挂机时间
+    /// </summary>
+    public DateTime EndTime { get; set; }
+
+    /// <summary>
+    /// 通话时长(秒)
+    /// </summary>
+    public int Duration { get; set; }
+
+    /// <summary>
+    /// 语音文件路径
+    /// </summary>
+    public string AudioFile { get; set; }
 }

+ 135 - 0
src/Hotline.Share/Dtos/CallCenter/CenterReportStatisticsDto.cs

@@ -8,6 +8,11 @@ namespace Hotline.Share.Dtos.CallCenter
 {
     public class CenterReportStatisticsDto
     {
+        /// <summary>
+        /// 电话
+        /// </summary>
+        public CenterReportCallInfoDto CenterReportCallInfoDto {  get; set; }
+
         /// <summary>
         /// 电话
         /// </summary>
@@ -44,6 +49,62 @@ namespace Hotline.Share.Dtos.CallCenter
         public OrgStatisticsAll OrgStatisticsAreaAll { get; set; }
     }
 
+    /// <summary>
+    /// 话务情况
+    /// </summary>
+    public class CenterReportCallInfoDto
+    {
+        /// <summary>
+        /// 话务总量
+        /// </summary>
+        public int AllCallCount { get; set; }
+
+        /// <summary>
+        /// 呼入总量
+        /// </summary>
+        public int InTotal { get; set; }
+
+        /// <summary>
+        /// 呼出总量
+        /// </summary>
+        public int OutTotal { get; set; }
+
+        /// <summary>
+        /// 呼入接通量
+        /// </summary>
+        public int InConnectionQuantity { get; set; }
+
+        /// <summary>
+        /// 呼出接通量
+        /// </summary>
+        public int OutConnectionQuantity { get; set; }
+
+        /// <summary>
+        /// 呼入未接通
+        /// </summary>
+        public int InHanguped { get; set; }
+
+        /// <summary>
+        /// 呼出未接通
+        /// </summary>
+        public int OutHanguped { get; set; }
+
+        /// <summary>
+        /// 队列挂断
+        /// </summary>
+        public int QueueByeCount { get; set; }
+
+        /// <summary>
+        /// IVR挂断
+        /// </summary>
+        public int IvrByeCount { get; set; }
+
+        /// <summary>
+        /// 呼入未接通
+        /// </summary>
+        public int CallInHanguped => InHanguped - QueueByeCount - IvrByeCount;
+    }
+
     /// <summary>
     /// 话务量
     /// </summary>
@@ -100,10 +161,53 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public int CompletedCount { get; set; }
 
+        /// <summary>
+        /// 按时办结
+        /// </summary>
+        public int OnTimeCompletedCount { get; set; }
+
+        /// <summary>
+        /// 按时办结率
+        /// </summary>
+        public double OnTimeCompletedCountClc => CalcRate();
+
+        /// <summary>
+        /// 计算办结率
+        /// </summary>
+        /// <returns></returns>
+        public double CalcRate()
+        {
+            if (OnTimeCompletedCount > 0 && CompletedCount > 0)
+                return Math.Round(((double)OnTimeCompletedCount / CompletedCount) * 100, 2);
+            return 0;
+        }
+
+        /// <summary>
+        /// 中心已办结
+        /// </summary>
+        public int CenterCompletedCount { get; set; }
+
+        /// <summary>
+        /// 部门已办结
+        /// </summary>
+        public int OrgCompletedCount { get; set; }
+
         /// <summary>
         /// 在办
         /// </summary>
         public int InProgressCount { get; set; }
+
+        /// <summary>
+        /// 中心在办
+        /// </summary>
+        public int CenterInProgressCount { get; set; }
+
+        /// <summary>
+        /// 部门在办
+        /// </summary>
+        public int OrgInProgressCount { get; set; }
+
+
     }
 
     /// <summary>
@@ -161,6 +265,21 @@ namespace Hotline.Share.Dtos.CallCenter
         /// </summary>
         public int Visitd { get; set; }
 
+        /// <summary>
+        /// 已回访--电话
+        /// </summary>
+        public int CallVisitd { get; set; }
+
+        /// <summary>
+        /// 已回访--短信
+        /// </summary>
+        public int SmsVisitd { get; set; }
+
+        /// <summary>
+        /// 其他回访
+        /// </summary>
+        public int OtherVisitd { get; set; }
+
         /// <summary>
         /// 待回访
         /// </summary>
@@ -219,8 +338,24 @@ namespace Hotline.Share.Dtos.CallCenter
 
     public class OrgStatistics
     {
+        /// <summary>
+        /// 部门编码
+        /// </summary>
+        public string OrgCode { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
         public string OrgName { get; set; }
 
+        /// <summary>
+        /// 工单数量
+        /// </summary>
         public int CountNum { get; set; }
+
+        /// <summary>
+        /// 办件次数
+        /// </summary>
+        public int HandleCountNum => CountNum;
     }
 }

+ 64 - 0
src/Hotline.Share/Dtos/CallCenter/QueryCallsDetailDto.cs

@@ -125,4 +125,68 @@
             return Math.Round((Quantity / (double)Count) * 100, 3) + "%";
         }
     }
+
+    public class QueryCallsDetailStatistics
+    {
+
+        /// <summary>
+        /// 日期
+        /// </summary>
+        public string Date { get; set; }
+
+        /// <summary>
+        /// 呼入总量 (呼入接通 + 挂机量 + 呼入队列挂断)
+        /// </summary>
+        public int InTotal { get; set; }
+
+        /// <summary>
+        /// 呼入队列挂断
+        /// </summary>
+        public int NotAcceptedHang { get; set; }
+
+        /// <summary>
+        /// 呼入接通量
+        /// </summary>
+        public int InConnectionQuantity { get; set; }
+
+        /// <summary>
+        /// 挂机量(呼入 + telno 非空 + 通话时间是0)
+        /// </summary>
+        public int InNotAnswered { get; set; }
+
+        /// <summary>
+        /// 呼入接通率
+        /// </summary>
+        public string InConnectionRate => CalcSatisfiedRate(InTotal, InConnectionQuantity + InNotAnswered);
+
+        /// <summary>
+        /// 呼入IVR挂断
+        /// </summary>
+        public int IvrByeCount { get; set; }
+
+        /// <summary>
+        /// 呼出接通量
+        /// </summary>
+        public int OutConnectionQuantity { get; set; }
+
+        /// <summary>
+        /// 呼出未接通
+        /// </summary>
+        public int OutNotAnswered { get; set; }
+
+        /// <summary>
+        /// 计算满意度
+        /// </summary>
+        /// <param name="Count">总数</param>
+        /// <param name="Quantity"></param>
+        /// <returns></returns>
+        public string CalcSatisfiedRate(int Count, int Quantity)
+        {
+            if (Count <= 0 || Quantity <= 0)
+                return 0 + "%";
+
+            return Math.Round((Quantity / (double)Count) * 100, 3) + "%";
+        }
+
+    }
 }

+ 12 - 2
src/Hotline.Share/Dtos/File/FileDto.cs

@@ -9,7 +9,12 @@ namespace Hotline.Share.Dtos.File
 {
 	public class FileDto
 	{
-        /// <summary>
+		/// <summary>
+		/// 附件全称
+		/// </summary>
+		public string FileName { get; set; }
+
+		/// <summary>
 		/// 附件名称
 		/// </summary>
 		public string? Name { get; set; }
@@ -73,7 +78,12 @@ namespace Hotline.Share.Dtos.File
 		/// </summary>
 		public string? Path { get; set; }
 
-}
+		/// <summary>
+		/// 完整附件路径
+		/// </summary>
+		public string? AllPath { get; set; }
+
+	}
 	public class UpdateFileDto: FileDto
 	{
 		public string Id { get; set; }

+ 6 - 4
src/Hotline.Share/Dtos/Order/OrderBiDto.cs

@@ -350,10 +350,12 @@ namespace Hotline.Share.Dtos.Order
 		/// </summary>
 		public DateTime? VisitTime { get; set; }
 
-		/// <summary>
-		/// 满意度
-		/// </summary>
-		public string OrgProcessingResults { get; set; }
+        /// <summary>
+        /// 满意度
+        /// 如果入参 EAttitudeType = 1 是 办件结果
+        /// 如果入参 EAttitudeType = 2 是 办事态度
+        /// </summary>
+        public string OrgProcessingResults { get; set; }
 
 		/// <summary>
 		/// 受理内容

+ 11 - 2
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -677,11 +677,11 @@ namespace Hotline.Share.Dtos.Order
             this.FromName = maskString;
             this.FromGender = EGender.Unknown;
             this.FromPhone = maskString;
-
             this.FullAddress = maskString;
             this.Address = maskString;
             this.City = maskString;
             this.Street = maskString;
+            this.ContactMask = maskString;
             return this;
         }
 
@@ -745,8 +745,17 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public bool? ProvinceSendBack { get; set; }
 
+		/// <summary>
+		/// 终止
+		/// </summary>
+		public List<OrderTerminateDto> OrderTerminates { get; set; }
 
-    }
+        /// <summary>
+        /// 终止状态
+        /// </summary>
+        public string? OrderTerminateStatus { get; set; }
+
+	}
 
     public class UpdateOrderDto : AddOrderDto
     {

+ 13 - 1
src/Hotline.Share/Dtos/Order/OrderStartFlowDto.cs

@@ -22,7 +22,19 @@ namespace Hotline.Share.Dtos.Order
 
     }
 
-    public class ScreenNextFlowDto
+    public class TerminateStartFlowDto : StartWorkflowDto<OrderTerminateDto>
+    {
+
+    }
+
+    public class TerminateNextFlowDto
+	{
+	    public OrderTerminateDto Data { get; set; }
+
+	    public NextWorkflowDto NextWorkflow { get; set; }
+    }
+
+	public class ScreenNextFlowDto
     {
         public OrderScreenDto Data { get; set; }
 

+ 222 - 0
src/Hotline.Share/Dtos/Order/OrderTerminateDto.cs

@@ -0,0 +1,222 @@
+using Hotline.Share.Dtos.File;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Share.Requests;
+using System.ComponentModel;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Order
+{
+	public class OrderTerminateDto
+	{
+
+		/// <summary>
+		/// 终止Id
+		/// </summary>
+		public string Id { get; set; }
+
+		/// <summary>
+		/// 工单id
+		/// </summary>
+		public string OrderId { get; set; }
+
+
+		/// <summary>
+		/// 终止状态
+		/// </summary>
+		public ETerminateStatus? Status { get; set; }
+
+		/// <summary>
+		/// 工单编号
+		/// </summary>
+		public string No { get; set; }
+
+		/// <summary>
+		/// 终止理由
+		/// </summary>
+		public string? Content { get; set; }
+
+		/// <summary>
+		/// 附件
+		/// </summary>
+		public List<FileJson>? FileJson { get; set; }
+
+		/// <summary>
+		/// 附件列表
+		/// </summary>
+		public List<FileDto> Files { get; set; } = new();
+	}
+
+	public class OrderTerminateBaseDto
+	{
+		public DateTime? LastModificationTime { get; set; }
+
+		public bool IsDeleted { get; set; }
+
+		/// <summary>
+		/// 删除时间
+		/// </summary>
+		public DateTime? DeletionTime { get; set; }
+
+
+		/// <summary>
+		/// 创建时间
+		/// </summary>
+		public DateTime CreationTime { get; set; }
+
+		public string Id { get; set; }
+
+		/// <summary>
+		/// 组织Id
+		/// </summary>
+		public string? CreatorOrgId { get; set; }
+
+
+		public string? CreatorOrgName { get; set; }
+
+		/// <summary>
+		/// 创建人
+		/// </summary>
+		public string? CreatorId { get; set; }
+
+		public string? CreatorName { get; set; }
+
+		public string? WorkflowId { get; set; }
+	}
+
+	public class OrderTerminateEntityDto : OrderTerminateBaseDto 
+	{
+		/// <summary>
+		/// 工单
+		/// </summary>
+		public OrderDto Order { get; set; }
+
+		/// <summary>
+		/// 是否可办理
+		/// </summary>
+		public bool IsCanHandle { get; set; }
+
+		/// <summary>
+		/// 审批状态 
+		/// </summary>
+		public ETerminateStatus? Status { get; set; }
+
+
+		public string StatusText => Status.GetDescription();
+
+		/// <summary>
+		/// 终止理由
+		/// </summary>
+		public string? Content { get; set; }
+
+
+		/// <summary>
+		/// 办理 true  审批 false 
+		/// </summary>
+		public bool Handle { get; set; }
+
+		/// <summary>
+		/// 附件列表
+		/// </summary>
+		public List<FileDto> Files { get; set; } = new();
+
+		public List<FileJson>? FileJson { get; set; }
+	}
+
+	public record OrderTerminateListDto : PagedRequest
+	{
+		/// <summary>
+		/// 工单编号
+		/// </summary>
+		public string? No { get; set; }
+
+		/// <summary>
+		/// 工单标题
+		/// </summary>
+		public string? Title { get; set; }
+
+		/// <summary>
+		/// 审批状态 
+		/// </summary>
+		public ETerminateStatus? Status { get; set; }
+
+		/// <summary>
+		/// 申请开始时间
+		/// </summary>
+		public DateTime? ApplyStartTime { get; set; }
+
+		/// <summary>
+		/// 申请结束时间
+		/// </summary>
+		public DateTime? ApplyEndTime { get; set; }
+
+		/// <summary>
+		/// 工单受理开始时间
+		/// </summary>
+		public DateTime? StartTime { get; set; }
+
+		/// <summary>
+		/// 工单受理结束时间
+		/// </summary>
+		public DateTime? EndTime { get; set; }
+
+		/// <summary>
+		/// 查询类型  0 全部 1自己
+		/// </summary>
+		public int?  QueryType { get; set; }
+
+		/// <summary>
+		/// 审批状态  0 全部  1待审批 2 已审批
+		/// </summary>
+		public int? AuditStatus { get; set; }
+	}
+
+	public class OrderTerminateContentDto
+	{
+		/// <summary>
+		/// 终止id
+		/// </summary>
+		public string Id { get; set; }
+
+		/// <summary>
+		/// 终止理由
+		/// </summary>
+		public string? Content { get; set; }
+	}
+
+	public enum ETerminateStatus
+	{
+		/// <summary>
+		/// 审批中
+		/// </summary>
+		[Description("审批中")]
+		Approval = 1,
+
+		/// <summary>
+		/// 终止完成
+		/// </summary>
+		[Description("终止同意")]
+		End = 2,
+
+		/// <summary>
+		/// 终止拒绝
+		/// </summary>
+		[Description("终止不同意")]
+		Refuse = 3,
+
+		/// <summary>
+		/// 退回
+		/// </summary>
+		[Description("终止退回")]
+		SendBack = 4,
+
+		/// <summary>
+		/// 退回待重提
+		/// </summary>
+		[Description("退回待重提")]
+		SendBackStart = 5,
+	}
+}

+ 41 - 1
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -1,4 +1,5 @@
-using Hotline.Share.Dtos.Users;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.Users;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Requests;
 using System;
@@ -127,6 +128,13 @@ namespace Hotline.Share.Dtos.Order
         public EProcessType? ProcessType { get; set; }
     }
 
+    public class QueryOrderVisitQuantity : StartEndTimeDto
+    {
+        /// <summary>
+        /// 访问员
+        /// </summary>
+        public string? EmployeeName { get; set; }
+    }
 
     public record QueryOrderVisitSourceChannelDto
     {
@@ -622,6 +630,38 @@ namespace Hotline.Share.Dtos.Order
         SMSUnsatisfied = 41,
     }
 
+    public class OrderVisitQuantityOutDto
+    {
+        /// <summary>
+        /// 回访员
+        /// </summary>
+        public string EmployeeName { get; set; }
+
+        /// <summary>
+        /// 回访员
+        /// </summary>
+        public string EmployeeId { get; set; }
+
+        /// <summary>
+        /// 电话回访量
+        /// </summary>
+        public int CallVisitCount { get; set; }
+
+        /// <summary>
+        /// 默认回访量
+        /// </summary>
+        public int DefaultVisitCount { get; set; }
+
+        /// <summary>
+        /// 短信回访量
+        /// </summary>
+        public int SmsVisitCount { get; set; }
+
+        /// <summary>
+        /// 总回访量
+        /// </summary>
+        public int TotalVisitCount => SmsVisitCount + DefaultVisitCount + CallVisitCount;
+    }
     public class OrderVisitSourceChannelDto
     {
         public int Count { get; set; }

+ 5 - 0
src/Hotline.Share/Dtos/Order/OrderWaitedDto.cs

@@ -41,6 +41,11 @@ namespace Hotline.Share.Dtos.Order
         /// 区域
         /// </summary>
         public string? AreaCode { get; set; }
+
+        /// <summary>
+        /// 工单状态
+        /// </summary>
+        public EOrderStatus? Status { get; set; }
 	}
 
     /// <summary>

+ 6 - 0
src/Hotline.Share/Dtos/Order/SendBackDto.cs

@@ -71,6 +71,12 @@ namespace Hotline.Share.Dtos.Order
 
 		public string? AuditUser { get; set; }
 
+
+		/// <summary>
+		/// 审批时间
+		/// </summary>
+		public DateTime? AuditTime { get; set; }
+
 		/// <summary>
 		/// 申请部门ID
 		/// </summary>

+ 10 - 0
src/Hotline.Share/Dtos/Users/UserDto.cs

@@ -43,6 +43,11 @@ public record AddUserDto
     /// </summary>
     public string? PhoneNo { get; set; }
 
+    /// <summary>
+    /// 座机号码
+    /// </summary>
+    public string? LandlineNumber { get; set; }
+
     /// <summary>
     /// 展示名称(Identity.DisplayName)
     /// </summary>
@@ -99,6 +104,11 @@ public record UpdateUserDto
     /// </summary>
     public string? PhoneNo { get; set; }
 
+    /// <summary>
+    /// 座机号码
+    /// </summary>
+    public string? LandlineNumber { get; set; }
+
     /// <summary>
     /// 展示名称(Identity.DisplayName)
     /// </summary>

+ 1 - 1
src/Hotline.Share/Dtos/Users/UserPagedDto.cs

@@ -3,7 +3,7 @@ using Hotline.Share.Requests;
 
 namespace Hotline.Share.Dtos.Users
 {
-    public record UserPagedDto(string? OrgCode, string? Role) : PagedKeywordRequest;
+    public record UserPagedDto(string? OrgCode, string? Role,string? Name,string?OrgName,string? PhoneNo) : PagedKeywordRequest;
 
     public record RestPagedDto(string? KeyWords,DateTime? BeginTime,DateTime? EndTime, ETelRestApplyStatus? Status,string? Reason): PagedRequest;
 }

+ 18 - 0
src/Hotline.Share/Enums/Order/EAttitudeType.cs

@@ -0,0 +1,18 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Order;
+public enum EAttitudeType
+{
+    /// <summary>
+    /// 办件结果
+    /// </summary>
+    [Description("办件结果")]
+    ProcessingResult = 1,
+
+    /// <summary>
+    /// 办事态度
+    /// </summary>
+    [Description("办事态度")]
+    WorkAttitude = 2
+}
+

+ 9 - 2
src/Hotline.Share/Requests/PagedKeywordRequest.cs

@@ -296,7 +296,7 @@ public record QueryOrderDelayDataListRequest : ReportPagedRequest
     public string? OrgName { get; set; }
 }
 
-public record QueryOrderDelayDataDetailRequest : ReportPagedRequest
+public record QueryOrderDelayDataDetailRequest : ReportRequiredPagedRequest
 {
     public string? OrgCode { get; set; }
 
@@ -433,7 +433,8 @@ public record OrgVisitDetailListReq: PagedKeywordRequest
     public EOrgVisitStatisticsType? OrgVisitStatisticsType { get; set; }
 
     /// <summary>
-    /// 办件结果
+    /// AttitudeType = 1 是 办件结果
+    /// AttitudeType = 2 是 办事态度
     /// </summary>
     public string? OrgProcessingResults { get; set; }
 
@@ -485,6 +486,12 @@ public record OrgVisitDetailListReq: PagedKeywordRequest
     public DateTime? VisitTimeStart { get; set; }
 
     public DateTime? VisitTimeEnd { get; set; }
+
+    /// <summary>
+    /// 办件态度类型(1:办件结果; 2:办事态度)
+    /// 默认 办件结果;
+    /// </summary>
+    public EAttitudeType AttitudeType { get; set; } = EAttitudeType.ProcessingResult;
 }
 
 

+ 15 - 0
src/Hotline.Share/Tools/TupleExtensions.cs

@@ -0,0 +1,15 @@
+using Hotline.Share.Dtos;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Tools;
+public static class TupleExtensions
+{
+    public static PagedDto<T> ToPaged<T>(this (int, List<T>) tuple)
+    {
+        return new PagedDto<T>(tuple.Item1, tuple.Item2);
+    }
+}

+ 1 - 0
src/Hotline/Caching/Interfaces/ISystemSettingCacheManager.cs

@@ -13,5 +13,6 @@ namespace Hotline.Caching.Interfaces
         int SeatChaoTime { get; }
         int RingTimes { get; }
         string RecordPrefix { get; }
+        string DefaultVisitEmployeeId { get; }
     }
 }

+ 2 - 0
src/Hotline/Caching/Services/SystemSettingCacheManager.cs

@@ -51,5 +51,7 @@ namespace Hotline.Caching.Services
         public int RingTimes => int.Parse(GetSetting(SettingConstants.RingTimes)?.SettingValue[0]);
 
         public string RecordPrefix => GetSetting(SettingConstants.RecordPrefix)?.SettingValue[0].Trim().ToString();
+
+        public string DefaultVisitEmployeeId => GetSetting(SettingConstants.DefaultVisitEmployeeId)?.SettingValue[0].Trim().ToString();
     }
 }

+ 12 - 0
src/Hotline/File/File.cs

@@ -7,6 +7,12 @@ namespace Hotline.File
 	[Description("附件")]
 	public class File : FullStateEntity
 	{
+		/// <summary>
+		/// 附件全称
+		/// </summary>
+		[SugarColumn(ColumnDescription = "附件全称")]
+		public string? FileName { get; set; }
+
 		/// <summary>
 		/// 附件名称
 		/// </summary>
@@ -81,5 +87,11 @@ namespace Hotline.File
 		/// </summary>
 		[SugarColumn(ColumnDescription = "附件路径")]
 		public string? Path { get; set; }
+
+		/// <summary>
+		/// 完整附件路径
+		/// </summary>
+		[SugarColumn(ColumnDescription = "完整附件路径")]
+		public string? AllPath { get; set; }
 	}
 }

+ 13 - 1
src/Hotline/FlowEngine/WorkflowModules/WorkflowModuleConsts.cs

@@ -44,7 +44,18 @@ public class WorkflowModuleConsts
     /// </summary>
     public const string TelRestApply = "TelRestApply";
 
-    public static List<WorkflowModule> AllModules =>
+    /// <summary>
+    /// 工单终止
+    /// </summary>
+    public const string OrderTerminate = "OrderTerminate";
+
+    /// <summary>
+    /// 二次办理
+    /// </summary>
+    public const string OrderSecondaryHandling = "OrderSecondaryHandling";
+
+
+	public static List<WorkflowModule> AllModules =>
         new()
         {
             new(OrderHandle, "工单办理"),
@@ -55,5 +66,6 @@ public class WorkflowModuleConsts
             new(OrderDelay,"工单延期"),
             new(OrderPrevious,"工单退回"),
             new(OrderScreen,"工单甄别"),
+            new(OrderTerminate,"工单终止"),
         };
 }

+ 6 - 1
src/Hotline/Orders/IOrderRepository.cs

@@ -185,7 +185,12 @@ namespace Hotline.Orders
 
     }
 
-    public interface IOrderDelayRepository : IRepositoryWorkflow<OrderDelay>
+    public interface IOrderTerminateRepository : IRepositoryWorkflow<OrderTerminate>
+    {
+
+    }
+
+	public interface IOrderDelayRepository : IRepositoryWorkflow<OrderDelay>
     {
 
     }

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

@@ -1080,8 +1080,14 @@ namespace Hotline.Orders
         public List<OrderScreen> OrderScreens { get; set; }
 
         /// <summary>
-        /// 
+        /// 终止
         /// </summary>
+        [Navigate(NavigateType.OneToMany, nameof(OrderTerminate.OrderId))]
+        public List<OrderTerminate> OrderTerminates { get; set; }
+
+		/// <summary>
+		/// 
+		/// </summary>
 		[Navigate(NavigateType.OneToMany, nameof(OrderSpecial.OrderId))]
         public List<OrderSpecial> OrderSpecials { get; set; }
 

+ 67 - 0
src/Hotline/Orders/OrderTerminate.cs

@@ -0,0 +1,67 @@
+using Hotline.FlowEngine.Workflows;
+using Hotline.Share.Dtos.File;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Share.Dtos.Order;
+using XF.Domain.Repository;
+
+namespace Hotline.Orders
+{
+	[Description("工单终止")]
+	public class OrderTerminate : WorkflowEntity
+	{
+		/// <summary>
+		/// 工单id
+		/// </summary>
+		[SugarColumn(ColumnDescription = "工单id")]
+		public string OrderId { get; set; }
+
+		/// <summary>
+		/// 工单编号
+		/// </summary>
+		[SugarColumn(ColumnDescription = "工单编号")]
+		public string No { get; set; }
+
+		/// <summary>
+		/// 终止状态
+		/// </summary>
+		[SugarColumn(ColumnDescription = "终止状态")]
+		public ETerminateStatus? Status { get; set; }
+
+		/// <summary>
+		/// 重提终止
+		/// </summary>
+		[SugarColumn(ColumnDescription = "重提终止",DefaultValue ="f")]
+		public bool IsRecommit { get; set; }
+
+		/// <summary>
+		/// 终止理由
+		/// </summary>
+		[SugarColumn(ColumnDescription = "终止理由", ColumnDataType = "varchar(2000)")]
+		public string? Content { get; set; }
+
+		/// <summary>
+		/// 附件
+		/// </summary>
+		[SugarColumn(ColumnDescription = "附件", ColumnDataType = "json", IsJson = true, IsNullable = true)]
+		public List<FileJson>? FileJson { get; set; }
+
+		/// <summary>
+		/// 工单
+		/// </summary>
+		[Navigate(NavigateType.OneToOne, nameof(OrderId))]
+		public Order Order { get; set; }
+
+		/// <summary>
+		/// 流程
+		/// </summary>
+		[Navigate(NavigateType.OneToOne, nameof(WorkflowId))]
+		public Workflow? Workflow { get; set; }
+	}
+
+}

+ 5 - 0
src/Hotline/Orders/OrderVisit.cs

@@ -161,6 +161,11 @@ public class OrderVisit : CreationEntity
     /// </summary>
     public bool? IsEffectiveAiVisit { get; set; }
 
+    /// <summary>
+    /// 是否批量回访
+    /// </summary>
+    public bool? IsBatchVisit { get; set; }
+
     public void AiVisitTime()
     {
         LastVisitTime = DateTime.Now;

+ 73 - 14
src/Hotline/Permissions/EPermission.cs

@@ -855,14 +855,72 @@ namespace Hotline.Permissions
         [Display(GroupName = "二次办理", Name = "二次办理查询", Description = "二次办理查询")]
         SecondHandleQuery = 201507,
 
-        #endregion
-        #endregion
+		#endregion
+		#endregion
 
-        #region 工单修改
-        /// <summary>
-        /// 工单修改列表
-        /// </summary>
-        [Display(GroupName ="工单修改列表",Name = "工单修改列表", Description = "工单修改列表")]
+		#region 终止管理
+
+		/// <summary>
+		/// 终止管理
+		/// </summary>
+		[Display(GroupName = "业务管理", Name = "终止管理", Description = "终止管理")]
+		OrderTerminateManage = 201600,
+
+		#region 终止待办
+		/// <summary>
+		/// 终止待办
+		/// </summary>
+		[Display(GroupName = "终止待办", Name = "终止待办", Description = "终止待办")]
+		TerminateOrder = 201601,
+		/// <summary>
+		/// 终止审批
+		/// </summary>
+		[Display(GroupName = "终止待办", Name = "终止审批", Description = "终止审批")]
+		TerminateOrderAudit = 201602,
+		/// <summary>
+		/// 终止退回
+		/// </summary>
+		[Display(GroupName = "终止待办", Name = "终止退回", Description = "终止退回")]
+		TerminateOrderReturn = 201603,
+		#endregion
+
+		#region 终止待申请
+		/// <summary>
+		/// 待终止列表
+		/// </summary>
+		[Display(GroupName = "业务管理", Name = "待终止列表", Description = "待终止列表")]
+		CanOrderTerminate = 201604,
+		/// <summary>
+		/// 申请终止
+		/// </summary>
+		[Display(GroupName = "业务管理", Name = "申请终止", Description = "申请终止")]
+		ApplyTerminate = 201605,
+		#endregion
+
+		#region 终止列表
+		/// <summary>
+		/// 终止列表
+		/// </summary>
+		[Display(GroupName = "业务管理", Name = "终止列表", Description = "终止列表")]
+		OrderTerminate = 201606,
+		/// <summary>
+		/// 终止审批
+		/// </summary>
+		[Display(GroupName = "业务管理", Name = "终止审批", Description = "终止审批")]
+		OrderTerminateAudit = 201607,
+		/// <summary>
+		/// 终止退回
+		/// </summary>
+		[Display(GroupName = "业务管理", Name = "终止退回", Description = "终止退回")]
+		OrderTerminateReturn = 201608,
+		#endregion
+		#endregion
+
+		#region 工单修改
+		/// <summary>
+		/// 工单修改列表
+		/// </summary>
+		[Display(GroupName ="工单修改列表",Name = "工单修改列表", Description = "工单修改列表")]
         ModifyOrderList = 201600,
         /// <summary>
         /// 交办单导出
@@ -875,14 +933,15 @@ namespace Hotline.Permissions
         /// </summary>
         [Display(GroupName ="工单修改",Name ="工单修改",Description ="工单修改")]
         ModifyOrder = 201602,
-        #endregion
-        #endregion
+		#endregion
 
-        #region 质检管理(40,00,00)
-        /// <summary>
-        /// 质检管理
-        /// </summary>
-        [Display(GroupName = "质检管理",Name ="质检管理",Description ="质检管理")]
+		#endregion
+
+		#region 质检管理(40,00,00)
+		/// <summary>
+		/// 质检管理
+		/// </summary>
+		[Display(GroupName = "质检管理",Name ="质检管理",Description ="质检管理")]
         QualityManage = 400000,
 
         #region 质检中心

+ 12 - 0
src/Hotline/Settings/SettingConstants.cs

@@ -1,5 +1,6 @@
 using Hotline.Share.Enums.Order;
 using Hotline.Users;
+using Org.BouncyCastle.Bcpg.OpenPgp;
 
 namespace Hotline.Settings
 {
@@ -535,5 +536,16 @@ namespace Hotline.Settings
         /// 旧数据通知公告知识库附件地址	
         /// </summary>
         public const string OldFilesUrls = "OldFilesUrls";
+
+		/// <summary>
+		/// 市州基本信息配置
+		/// </summary>
+		public const string CityBaseConfiguration = "CityBaseConfiguration";
+
+        /// <summary>
+        /// 中心直办件默认回访人Id
+        /// 根据禅道 自贡需求 Id_361, 第一条, 3小条需求;
+        /// </summary>
+        public const string DefaultVisitEmployeeId = "DefaultVisitEmployeeId";
     }
 }

+ 10 - 4
src/Hotline/Users/User.cs

@@ -21,6 +21,12 @@ namespace Hotline.Users
         /// </summary>
         public string? PhoneNo { get; set; }
 
+        /// <summary>
+        /// 座机号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "座机号码", ColumnDataType = "varchar(50)")]
+        public string? LandlineNumber { get; set; }
+
         /// <summary>
         /// 展示名称(Identity.DisplayName)
         /// </summary>
@@ -63,11 +69,11 @@ namespace Hotline.Users
         /// </summary>
         public int? OldUserId { get; set; }
 
-		/// <summary>
-		/// 所属部门
-		/// </summary>
+        /// <summary>
+        /// 所属部门
+        /// </summary>
 
-		[Navigate(NavigateType.OneToOne, nameof(OrgId))]
+        [Navigate(NavigateType.OneToOne, nameof(OrgId))]
         public SystemOrganize Organization { get; set; }
 
         [Navigate(NavigateType.OneToOne, nameof(Id))]