Jelajahi Sumber

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

Dun.Jason 5 hari lalu
induk
melakukan
78c13b77d7
38 mengubah file dengan 2189 tambahan dan 262 penghapusan
  1. 42 45
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  2. 256 0
      src/Hotline.Api/Controllers/CallNativeContrroller.cs
  3. 429 11
      src/Hotline.Api/Controllers/FwThirdController.cs
  4. 63 0
      src/Hotline.Api/Controllers/IPPbxController.cs
  5. 14 0
      src/Hotline.Api/Controllers/IdentityController.cs
  6. 144 10
      src/Hotline.Api/Controllers/OrderController.cs
  7. 8 8
      src/Hotline.Api/StartupHelper.cs
  8. 1 1
      src/Hotline.Api/config/appsettings.Development.json
  9. 5 1
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  10. 8 0
      src/Hotline.Application/Identity/IIdentityAppService.cs
  11. 16 3
      src/Hotline.Application/Identity/IdentityAppService.cs
  12. 12 3
      src/Hotline.Application/Mappers/CallMapperConfigs.cs
  13. 21 6
      src/Hotline.Application/OrderApp/IOrderApplication.cs
  14. 524 111
      src/Hotline.Application/OrderApp/OrderApplication.cs
  15. 26 2
      src/Hotline.Application/Snapshot/RedPackApplication.cs
  16. 2 0
      src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs
  17. 85 0
      src/Hotline.Share/Dtos/CallNative/BlackPhoneListDto.cs
  18. 27 0
      src/Hotline.Share/Dtos/CallNative/BlackPhoneListQuery.cs
  19. 27 9
      src/Hotline.Share/Dtos/FlowEngine/Workflow/NextWorkflowDto.cs
  20. 23 6
      src/Hotline.Share/Dtos/Order/OrderBiDto.cs
  21. 5 0
      src/Hotline.Share/Dtos/Order/SendOrderReportOutDto.cs
  22. 37 0
      src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs
  23. 43 0
      src/Hotline.Share/Dtos/WebPortal/GetOrderCodePwd.cs
  24. 8 0
      src/Hotline.Share/Dtos/WebPortal/WebFlowAcceptDto.cs
  25. 22 0
      src/Hotline.Share/Enums/CallCenter/ECallIdentity.cs
  26. 3 9
      src/Hotline.Share/Requests/PagedKeywordRequest.cs
  27. 36 0
      src/Hotline/CallCenter/BlackLists/WhiteBlackLog.cs
  28. 5 0
      src/Hotline/CallCenter/Calls/CallNative.cs
  29. 106 0
      src/Hotline/Orders/OrderDelayAutomatic.cs
  30. 9 4
      src/Hotline/Orders/OrderDomainService.cs
  31. 29 21
      src/Hotline/Push/FWMessage/PushDomainService.cs
  32. 8 0
      src/Hotline/Snapshot/Contracts/ISnapshotPointsDomainService.cs
  33. 15 10
      src/Hotline/Snapshot/Services/SnapshotPointsDomainService.cs
  34. 1 1
      src/TianQue.Sdk/Models/ApiReponse.cs
  35. 77 0
      src/XingTang.Sdk/XingtangBlackPhone.cs
  36. 10 0
      src/XingTang.Sdk/XingtangCall.cs
  37. 33 0
      src/XingTang.Sdk/XingtangWhitePhone.cs
  38. 9 1
      test/Hotline.Tests/Infrastructure/TianQueTest.cs

+ 42 - 45
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -4877,55 +4877,52 @@ namespace Hotline.Api.Controllers.Bi
             if (!dto.StartTime.HasValue || !dto.EndTime.HasValue)
                 throw UserFriendlyException.SameMessage("请选择时间!");
 
-            var (total, items) = await _workflowTraceRepository.Queryable()
-                //.LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
-                .InnerJoin<SchedulingUser>((x, su) => x.HandlerId == su.UserId)
-                .Where((x, su) => x.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send)
-                .Where((x, su) => x.CreationTime >= dto.StartTime.Value && x.CreationTime <= dto.EndTime.Value && su.UserId == dto.UserId)
-                .WhereIF(dto.TitleCode.ToUpper() == "NOSENDORDERNUM", (x, su) => x.Status != EWorkflowStepStatus.Handled)
-                .WhereIF(dto.TitleCode.ToUpper() == "SENDORDERNUM", (x, su) => x.Status == EWorkflowStepStatus.Handled)
-                .GroupBy((x, su) => x.ExternalId)
-                .Select((x, su) => new { Id = x.ExternalId })
-                .MergeTable()
-                .LeftJoin<Order>((a, b) => a.Id == b.Id)
-                .Select((a, b) => b)
-                .ToPagedListAsync(dto, HttpContext.RequestAborted);
+            var (total, items) = await _orderApplication.QuerySendOrderDetail(dto).ToPagedListAsync(dto, HttpContext.RequestAborted);
 
-            if (dto.TitleCode.ToUpper() == "RESENDORDERNUM")
-            {
-                (total, items) = await _workflowTraceRepository.Queryable()
-                       .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
-                       //.LeftJoin<WorkflowStepHandler>((x, w, wfsh) => x.StepId == wfsh.WorkflowStepId && wfsh.IsActualHandler == true)
-                       .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
-                       .Where((x, w, su) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled
-                                            && !string.IsNullOrEmpty(x.NextMainHandler) && x.NextMainHandler != OrgSeedData.CenterId)
-                       .Where((x, w, su) => x.CreationTime >= dto.StartTime.Value)
-                       .Where((x, w, su) => x.CreationTime <= dto.EndTime.Value)
-                       .GroupBy((x, w, su) => x.WorkflowId)
-                       .Having((x, w, su) => SqlFunc.AggregateCount(x.WorkflowId) > 1)
-                        .Select((x, w, su) => new { Id = x.WorkflowId, CreationTime = SqlFunc.AggregateMin(x.CreationTime) })
-                       .MergeTable()
-                       .LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
-                       .LeftJoin<Workflow>((a, wt, wf) => wt.WorkflowId == wf.Id)
-                       //.LeftJoin<WorkflowStepHandler>((a, wt, wf, wsh) => wt.StepId == wsh.WorkflowStepId && wsh.CreationTime == a.CreationTime)
-                       .InnerJoin<SchedulingUser>((a, wt, wf, su) => wt.HandlerId == su.UserId)
-                       .Where((a, wt, wf, su) => su.UserId == dto.UserId)
-                       .GroupBy((a, wt, wf, su) => wf.ExternalId)
-                       .Select((a, wt, wf, su) => new { Id = wf.ExternalId })
-                       .MergeTable()
-                       .LeftJoin<Order>((a, b) => a.Id == b.Id)
-                       .Select((a, b) => b)
-                       .ToPagedListAsync(dto, HttpContext.RequestAborted);
-            }
             return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
         }
 
-        /// <summary>
-        /// 二次办理统计
-        /// </summary>
-        /// <param name="dto"></param>
-        /// <returns></returns>
-        [HttpGet("secondary_handling_report")]
+
+		/// <summary>
+		/// 派单量统计明细
+		/// </summary>
+		/// <returns></returns>
+		[HttpPost("send_order_detail_report/_export")]
+		[LogFilterAlpha("导出日志")]
+		public async Task<FileStreamResult> QuerySendOrderDetailExport([FromBody] ExportExcelDto<QuerySendOrderDetailRequest> dto)
+		{
+			var query =  _orderApplication.QuerySendOrderDetail(dto.QueryDto);
+			List<Order> data;
+			if (dto.IsExportAll)
+			{
+				data = await query.ToListAsync(HttpContext.RequestAborted);
+			}
+			else
+			{
+				var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+				data = items;
+			}
+
+			var secondaryHandlingDtos = _mapper.Map<ICollection<OrderDto>>(data);
+
+			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass<OrderDto>(dto.ColumnInfos);
+
+			var dtos = secondaryHandlingDtos
+				.Select(stu => _mapper.Map(stu, typeof(OrderDto), dynamicClass))
+				.Cast<object>()
+				.ToList();
+
+			var stream = ExcelHelper.CreateStream(dtos);
+
+			return ExcelStreamResult(stream, "二次办理列表数据");
+		}
+
+		/// <summary>
+		/// 二次办理统计
+		/// </summary>
+		/// <param name="dto"></param>
+		/// <returns></returns>
+		[HttpGet("secondary_handling_report")]
         public async Task<List<SecondaryHandlingVo>> SecondaryHandlingReport([FromQuery] QuerySecondaryHandlingRequest dto)
         {
             var query = _orderSecondaryHandlingApplication.SecondaryHandlingReport(dto, HttpContext.RequestAborted);

+ 256 - 0
src/Hotline.Api/Controllers/CallNativeContrroller.cs

@@ -0,0 +1,256 @@
+using Hotline.Api.Filter;
+using Hotline.Application.CallCenter;
+using Hotline.Caching.Interfaces;
+using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Configs;
+using Hotline.CallCenter.Tels;
+using Hotline.EventBus;
+using Hotline.Repository.SqlSugar;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.CallNative;
+using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using SqlSugar;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using XingTang.Sdk;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Requests;
+using Hotline.Share.Dtos.Order;
+using Hotline.CallCenter.BlackLists;
+
+namespace Hotline.Api.Controllers
+{
+    public class CallNativeContrroller : BaseController
+    {
+        private readonly ICallApplication _callApplication;
+        private readonly Publisher _publisher;
+        private readonly IMapper _mapper;
+        private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IRepository<TelOperationXthx> _telOperationXthxRepository;
+        private readonly IUserCacheManager _userCacheManager;
+        private readonly ISessionContext _sessionContext;
+        private readonly IRepository<TelActionRecord> _telActionRecordRepository;
+        private readonly ITelRestRepository _telRestRepository;
+        private readonly ISqlSugarClient _db;
+        private readonly IRepository<WhiteBlackLog> _whiteBlackLogRepository;
+
+        public CallNativeContrroller(
+            ICallApplication callApplication,
+            Publisher publisher,
+            IMapper mapper,
+            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
+            ISystemSettingCacheManager systemSettingCacheManager,
+            IRepository<TelOperationXthx> telOperationXthxRepository,
+            IUserCacheManager userCacheManager,
+            ISessionContext sessionContext,
+            IRepository<TelActionRecord> telActionRecordRepository,
+            ITelRestRepository telRestRepository,
+            ISugarUnitOfWork<XingTangDbContext> uow,
+            IRepository<WhiteBlackLog> whiteBlackLogRepository)
+        {
+            _callApplication = callApplication;
+            _publisher = publisher;
+            _mapper = mapper;
+            _callcenterOptions = callcenterOptions;
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _telOperationXthxRepository = telOperationXthxRepository;
+            _userCacheManager = userCacheManager;
+            _sessionContext = sessionContext;
+            _telActionRecordRepository = telActionRecordRepository;
+            _telRestRepository = telRestRepository;
+            _db = uow.Db;
+            _whiteBlackLogRepository = whiteBlackLogRepository;
+        }
+
+        #region 黑白名单处理
+        #region 黑名单
+        /// <summary>
+        /// 获取黑名单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("get-black-phone-list")]
+        public async Task<PagedDto<BlackPhoneListDto>> GetBlackPhoneList([FromQuery] PagedKeywordRequest dto)
+        {
+            var (total, items) = await _db.Queryable<XingtangBlackPhone>()
+                .WhereIF(!string.IsNullOrEmpty(dto.Keyword), p => p.StartPhone.Contains(dto.Keyword))
+                .ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<BlackPhoneListDto>(total, _mapper.Map<IReadOnlyList<BlackPhoneListDto>>(items));
+        }
+
+        /// <summary>
+        /// 删除黑名单
+        /// </summary>
+        /// <param name="phone"></param>
+        /// <returns></returns>
+        [HttpDelete("delete-black-phone/{phone}")]
+        [LogFilter("删除黑名单")]
+        public async Task DeleteBlackPhone(string phone)
+        {
+            var row = await _db.Deleteable<XingtangBlackPhone>().Where(p => p.StartPhone == phone).ExecuteCommandAsync();
+            if (row >= 0)
+            {
+                WhiteBlackLog whiteBlackLog = new()
+                {
+                    LogType = "1",
+                    LogAction = "删除",
+                    PhoneNum = phone
+                };
+                await _whiteBlackLogRepository.AddAsync(whiteBlackLog, HttpContext.RequestAborted);
+            }
+        }
+
+        /// <summary>
+        /// 新增黑名单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("add-black-phone")]
+        [LogFilter("新增黑名单")]
+        public async Task AddBlackPhone([FromBody] AddBlackPhoneDto dto)
+        {
+            if (string.IsNullOrEmpty(dto.PhoneNum))
+                throw UserFriendlyException.SameMessage("电话号码不能为空!");
+
+            if (dto.ValidDateTime.HasValue == false)
+                throw UserFriendlyException.SameMessage("有效期不能为空!");
+
+            var isEx = await _db.Queryable<XingtangBlackPhone>().Where(p => p.StartPhone == dto.PhoneNum).AnyAsync();
+            if (isEx)
+                throw UserFriendlyException.SameMessage("此号码已经是黑名单!");
+
+            var id = await _db.Queryable<XingtangBlackPhone>().MaxAsync(p => p.BlackPhoneId);
+            XingtangBlackPhone xingtangBlackPhone = new XingtangBlackPhone()
+            {
+                BlackPhoneId = id + 1,
+                StartPhone = dto.PhoneNum,
+                PhoneType = 0,
+                ValidDateTime = dto.ValidDateTime,
+                Enabled = 1,
+                CreateDate = DateTime.Now,
+                CreateUserId = _sessionContext.UserId
+            };
+            var row = await _db.Insertable(xingtangBlackPhone).ExecuteCommandAsync();
+            if (row >= 0)
+            {
+                WhiteBlackLog whiteBlackLog = new()
+                {
+                    LogType = "1",
+                    LogAction = "新增",
+                    PhoneNum = dto.PhoneNum,
+                    ValidDateTime = dto.ValidDateTime
+                };
+                await _whiteBlackLogRepository.AddAsync(whiteBlackLog, HttpContext.RequestAborted);
+            }
+        }
+        #endregion
+
+        #region 白名单
+        /// <summary>
+        /// 获取白名单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("get-white-phone-list")]
+        public async Task<PagedDto<WhitePhoneListDto>> GetWhitePhoneList([FromQuery] PagedKeywordRequest dto)
+        {
+            var (total, items) = await _db.Queryable<XingtangWhitePhone>()
+                .WhereIF(!string.IsNullOrEmpty(dto.Keyword), p => p.Phone.Contains(dto.Keyword))
+                .ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<WhitePhoneListDto>(total, _mapper.Map<IReadOnlyList<WhitePhoneListDto>>(items));
+        }
+
+        /// <summary>
+        /// 删除白名单
+        /// </summary>
+        /// <param name="phone"></param>
+        /// <returns></returns>
+        [HttpDelete("delete-white-phone/{phone}")]
+        [LogFilter("删除白名单")]
+        public async Task DeleteWhitePhone(string phone)
+        {
+            var row = await _db.Deleteable<XingtangWhitePhone>().Where(p => p.Phone == phone).ExecuteCommandAsync();
+            if (row >= 0)
+            {
+                WhiteBlackLog whiteBlackLog = new()
+                {
+                    LogType = "2",
+                    LogAction = "删除",
+                    PhoneNum = phone
+                };
+                await _whiteBlackLogRepository.AddAsync(whiteBlackLog, HttpContext.RequestAborted);
+            }
+        }
+
+        /// <summary>
+        /// 新增白名单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("add-white-phone")]
+        [LogFilter("新增白名单")]
+        public async Task AddWhitePhone([FromBody] AddBlackPhoneDto dto)
+        {
+            if (string.IsNullOrEmpty(dto.PhoneNum))
+                throw UserFriendlyException.SameMessage("电话号码不能为空!");
+
+            if (dto.ValidDateTime.HasValue == false)
+                throw UserFriendlyException.SameMessage("有效期不能为空!");
+
+            var isEx = await _db.Queryable<XingtangWhitePhone>().Where(p => p.Phone == dto.PhoneNum).AnyAsync();
+            if (isEx)
+                throw UserFriendlyException.SameMessage("此号码已经是白名单!");
+
+            var id = await _db.Queryable<XingtangWhitePhone>().MaxAsync(p => p.Id);
+            XingtangWhitePhone xingtangPhone = new XingtangWhitePhone()
+            {
+                Id = id + 1,
+                Phone = dto.PhoneNum,
+                ValidDateTime = dto.ValidDateTime,
+                Enabled = 1
+            };
+            var row = await _db.Insertable(xingtangPhone).ExecuteCommandAsync();
+            if (row >= 0)
+            {
+                WhiteBlackLog whiteBlackLog = new()
+                {
+                    LogType = "2",
+                    LogAction = "新增",
+                    PhoneNum = dto.PhoneNum,
+                    ValidDateTime = dto.ValidDateTime
+                };
+                await _whiteBlackLogRepository.AddAsync(whiteBlackLog, HttpContext.RequestAborted);
+            }
+        }
+        #endregion
+
+        /// <summary>
+        /// 查询黑白名单操作记录
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("get-white-black-log-list")]
+        public async Task<PagedDto<WhiteBlackLog>> GetWhiteBlackLogList([FromQuery] BlackPhoneListQuery dto)
+        {
+            var (total, items) = await _whiteBlackLogRepository.Queryable()
+                .WhereIF(dto.StartTime.HasValue, p => p.CreationTime >= dto.StartTime)
+                .WhereIF(dto.EndTime.HasValue, p => p.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.LogType), p => p.LogType == dto.LogType)
+                .WhereIF(!string.IsNullOrEmpty(dto.LogAction), p => p.LogAction == dto.LogAction)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserName), p => p.CreatorName == dto.UserName)
+                .WhereIF(!string.IsNullOrEmpty(dto.PhoneNum), p => p.PhoneNum == dto.PhoneNum)
+                .ToPagedListAsync(dto, HttpContext.RequestAborted);
+            return new PagedDto<WhiteBlackLog>(total, items);
+        }
+        #endregion
+    }
+}

+ 429 - 11
src/Hotline.Api/Controllers/FwThirdController.cs

@@ -7,6 +7,7 @@ using Hotline.Caching.Interfaces;
 using Hotline.Configurations;
 using Hotline.KnowledgeBase;
 using Hotline.Orders;
+using Hotline.Push.Notifies;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
@@ -16,6 +17,8 @@ using Hotline.Share.Dtos.Schedulings;
 using Hotline.Share.Dtos.WebPortal;
 using Hotline.Share.Enums.KnowledgeBase;
 using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Push;
+using Hotline.Share.Tools;
 using Hotline.Tools;
 using Hotline.Users;
 using Hotline.WebPortal;
@@ -26,6 +29,10 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using NPOI.XWPF.UserModel;
 using SqlSugar;
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Text;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Exceptions;
@@ -44,6 +51,7 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<WebUserRegister> _webUserRegisterRepository;
         private readonly IRepository<WebUserAuth> _webUserAuthRepository;
         private readonly IRepository<WebFlowAccept> _webFlowAcceptRepository;
+        private readonly ITypedCache<VerificationPic> _verificationPic;
         private readonly ITypedCache<WriteLettersSendSmsDto> _writeLettersSendSms;
         private readonly IRepository<Hotspot> _hotspotRepository;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
@@ -73,6 +81,7 @@ namespace Hotline.Api.Controllers
             IRepository<WebUserRegister> webUserRegisterRepository,
             IRepository<WebUserAuth> webUserAuthRepository,
             IRepository<WebFlowAccept> webFlowAcceptRepository,
+            ITypedCache<VerificationPic> verificationPic,
             ITypedCache<WriteLettersSendSmsDto> writeLettersSendSms,
             IRepository<Hotspot> hotspotRepository,
             ISystemSettingCacheManager systemSettingCacheManager,
@@ -103,6 +112,7 @@ namespace Hotline.Api.Controllers
             _webUserRegisterRepository = webUserRegisterRepository;
             _webUserAuthRepository = webUserAuthRepository;
             _webFlowAcceptRepository = webFlowAcceptRepository;
+            _verificationPic = verificationPic;
             _writeLettersSendSms = writeLettersSendSms;
             _hotspotRepository = hotspotRepository;
             _systemSettingCacheManager = systemSettingCacheManager;
@@ -143,9 +153,9 @@ namespace Hotline.Api.Controllers
 
         #region 自贡门户查询接口
 
-        #region 受理统计
+        #region 4.2 受理统计
 
-        /// <summary>getacceptancetypestatisticsbymonth
+        /// <summary>
         /// 受理统计
         /// </summary>
         /// <param name="dto"></param>
@@ -199,7 +209,7 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
-        #region 办理统计
+        #region 4.3 办理统计
 
         /// <summary>
         /// 办理统计
@@ -253,7 +263,7 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
-        #region 公告通知、工作简报
+        #region 4.4~5 公告通知、工作简报
 
         /// <summary>
         /// 公告通知、工作简报
@@ -327,7 +337,7 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
-        #region 信件选登
+        #region 4.6 信件选登
 
         /// <summary>
         /// 查询工单发布后公开的数据
@@ -457,7 +467,7 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
-        #region 知识库
+        #region 4.7 知识库
 
         #region 知识库分类
 
@@ -523,7 +533,7 @@ namespace Hotline.Api.Controllers
                 .Where(p => p.IsPublic == true && p.Status == EKnowledgeStatus.OnShelf)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Title.Contains(dto.Title))
                 .WhereIF(!string.IsNullOrEmpty(typeSpliceNameTags), p => SqlFunc.JsonArrayAny(p.Keywords, typeSpliceNameTags) == true)
-                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith(typeSpliceName)))
+                .WhereIF(!string.IsNullOrEmpty(typeSpliceName), x => x.KnowledgeType.Any(t => t.KnowledgeTypeSpliceName.EndsWith("营商环境")))
                 .OrderByDescending(p => p.CreationTime)
                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
 
@@ -563,7 +573,7 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
-        #region 工单写信
+        #region 4.8.1 工单写信(老版)
 
         /// <summary>
         /// 工单写信
@@ -583,6 +593,22 @@ namespace Hotline.Api.Controllers
                 return OpenResponse.Ok(WebPortalDeResponse<OrderAcceptanceReturnDto>.Failed("数据验证不通过!"));
             }
 
+            var order = _orderListRepository.Queryable()
+                            .Where(x => x.Title == dto.Title)
+                            .Where(x => x.SourceChannelCode == "YTW")
+                            .Where(x => x.Contact == dto.Mobile)
+                            .Select(x => new
+                            {
+                                No = x.No,
+                                Second = SqlFunc.DateDiff(DateType.Second, x.CreationTime, DateTime.Now)
+                            }).MergeTable()
+                            .Where(x => x.Second < 180)
+                            .Any();
+            if (order)
+            {
+                return OpenResponse.Ok(WebPortalDeResponse<OrderAcceptanceReturnDto>.Failed("3分钟内,请勿重复提交工单!"));
+            }
+
             var data = _mapper.Map<Hotline.Share.Dtos.Order.AddOrderDto>(dto);
             data.Source = ESource.WebPortal;
             data.SourceChannel = "因特网";
@@ -639,13 +665,12 @@ namespace Hotline.Api.Controllers
             else
                 returnDto.State = "0";
 
-
             return OpenResponse.Ok(WebPortalDeResponse<OrderAcceptanceReturnDto>.Success(returnDto));
         }
 
         #endregion
 
-        #region 附件上传
+        #region 4.8.2 附件上传
 
         /// <summary>
         /// 附件上传
@@ -694,7 +719,101 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
-        #region 工单查询
+        #region 4.8.3 工单写信(带验证码)
+
+        /// <summary>
+        /// 工单写信
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("orderacceptancesms")]
+        public async Task<OrderAcceptanceReturnDto> OrderAcceptanceSms([FromBody] WebFlowAcceptSmsDto dto)
+        {
+            //电话号码去空格
+            if (!string.IsNullOrEmpty(dto.Mobile))
+                dto.Mobile = dto.Mobile.Trim();
+
+            string strResult = dto.ValidateObject();
+            if (!string.IsNullOrEmpty(strResult))
+            {
+                throw UserFriendlyException.SameMessage("数据验证不通过");
+            }
+
+            if (dto.Mobile.IsPhoneNumber() == false)
+            {
+                throw UserFriendlyException.SameMessage("请输入正确的手机号");
+            }
+
+            string keyTokenSms = "SmsUserWriteKey_" + dto.Mobile + "_" + DateTime.Now.ToString("yyyyMMdd");
+            var dataSms = await _writeLettersSendSms.GetAsync(keyTokenSms, HttpContext.RequestAborted);
+            if (dataSms != null)
+            {
+                if (dataSms.SmsCode != dto.SmsCode)
+                    throw UserFriendlyException.SameMessage("短信验证码错误!");
+            }
+            else
+            {
+                throw UserFriendlyException.SameMessage("短信验证码错误!");
+            }
+
+            var order = _orderListRepository.Queryable()
+                            .Where(x => x.Title == dto.Title)
+                            .Where(x => x.SourceChannelCode == "YTW")
+                            .Where(x => x.Contact == dto.Mobile)
+                            .Select(x => new
+                            {
+                                No = x.No,
+                                Second = SqlFunc.DateDiff(DateType.Second, x.CreationTime, DateTime.Now)
+                            }).MergeTable()
+                            .Where(x => x.Second < 180)
+                            .Any();
+            if (order)
+            {
+                throw UserFriendlyException.SameMessage("3分钟内,请勿重复提交工单!");
+            }
+
+            var data = _mapper.Map<Hotline.Share.Dtos.Order.AddOrderDto>(dto);
+            data.Source = ESource.WebPortal;
+            data.SourceChannel = "因特网";
+            data.SourceChannelCode = "YTW";
+
+            if (!string.IsNullOrEmpty(data.LicenceNo))
+            {
+                data.LicenceTypeCode = "10";
+                data.LicenceType = "中华人民共和国居民身份证";
+            }
+            data.ExternalId = Guid.NewGuid().ToString();
+
+            data.IdentityType = dto.IdentityType;
+            data.Transpond = false;
+            data.IsEnforcementOrder = false;
+
+
+            var result = await _orderApplication.ReceiveOrderFromExternalAsync(data, HttpContext.RequestAborted);
+
+            OrderAcceptanceReturnDto returnDto = new();
+            if (result != null)
+            {
+                returnDto.PWD = result.Password;
+                returnDto.Code = result.No;
+                returnDto.State = "1";
+
+                dto.Pwd = result.Password;
+                dto.Code = result.No;
+                dto.OrderId = result.Id;
+                var dtoData = _mapper.Map<WebFlowAccept>(dto);
+                await _webFlowAcceptRepository.AddAsync(dtoData, HttpContext.RequestAborted);
+
+            }
+            else
+                returnDto.State = "0";
+
+            return returnDto;
+        }
+
+        #endregion
+
+        #region 4.9 工单查询
 
         /// <summary>
         /// 根据编号和密码查询信件ID
@@ -751,10 +870,125 @@ namespace Hotline.Api.Controllers
         }
 
 
+        #endregion
+
+        #region 4.10 图片验证码
+
+        /// <summary>
+        /// 图片验证码
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("getverificationcodebypic")]
+        public async Task<VerificationPic> GetVerificationCodeByPic()
+        {
+            var verifyCode = GetVerifyCodeText(5, true, false);                // 验证码
+            var base64String = CreateImageAndConvertToBase64(verifyCode);      // 图片Base64
+            var guidString = Guid.NewGuid().ToString();                        // Guid
+
+            var data = new VerificationPic()
+            {
+                base64 = base64String,
+                guid = guidString
+            };
+
+            // 保存数据到Readis
+            string keyToken = "PicUserWriteKey_" + guidString + "_" + verifyCode + "_" + DateTime.Now.ToString("yyyyMMdd");
+            await _verificationPic.SetAsync(keyToken, data, TimeSpan.FromHours(1), HttpContext.RequestAborted);
+
+            return data;
+        }
+
+        #endregion
+
+        #region 4.11 短信验证码
+
+        /// <summary>
+        /// 短信验证码
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getverificationcodebysms")]
+        public async Task<VerificationSms> GetVerificationCodeBySms([FromBody] VerificationPicDto dto)
+        {
+            if (dto.TelNum.IsPhoneNumber() == false)
+            {
+                throw UserFriendlyException.SameMessage("请输入正确的手机号");
+            }
+
+            // 验证图片验证码是否对
+            string keyToken = "PicUserWriteKey_" + dto.guid + "_" + dto.verCode + "_" + DateTime.Now.ToString("yyyyMMdd");
+            var data = await _verificationPic.GetAsync(keyToken, HttpContext.RequestAborted);
+            if (data == null)
+            {
+                throw UserFriendlyException.SameMessage("验证码错误");
+            }
+
+            var SmsCodeRand = RandomNum();
+
+            // 发送短信验证码
+            string keyTokenSms = "SmsUserWriteKey_" + dto.TelNum + "_" + DateTime.Now.ToString("yyyyMMdd");
+            var dataSms = await _writeLettersSendSms.GetAsync(keyTokenSms, HttpContext.RequestAborted);
+            if (dataSms != null)//已经发过短信
+            {
+                //是否可以继续发送短信(10次)
+                if (dataSms.SendCount > 10)
+                    //短信发送超过10条
+                    throw UserFriendlyException.SameMessage("短信发送超过10条");
+
+                // 验证是否在两分钟之内
+                TimeSpan duration = DateTime.Now - dataSms.AddTime; // 计算时间差
+                if ((int)duration.TotalSeconds < 120)
+                    // 距离上次发送时间不足两分钟
+                    throw UserFriendlyException.SameMessage("距离上次发送时间不足两分钟");
+
+                //修改缓存信息
+                dataSms.SendCount++;
+                dataSms.SmsCode = SmsCodeRand;
+                dataSms.AddTime = DateTime.Now;
+            }
+            else
+            {
+                //没有发过短信,直接写入数据
+                dataSms = new WriteLettersSendSmsDto
+                {
+                    AddTime = DateTime.Now,
+                    SmsCode = SmsCodeRand,
+                    MobileNum = dto.TelNum,
+                    SendCount = 1
+                };
+            }
+            //这里发送短信
+            var messageDto = new Share.Dtos.Push.MessageDto
+            {
+                PushBusiness = EPushBusiness.MsgCode,
+                ExternalId = "",
+                OrderId = "",
+                PushPlatform = EPushPlatform.Web,
+                Remark = "",
+                Name = dto.TelNum,
+                TemplateCode = "1008",
+                Params = new List<string>() { SmsCodeRand },
+                TelNumber = dto.TelNum,
+
+            };
+            await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
+
+            //保存短信
+            await _writeLettersSendSms.SetAsync(keyTokenSms, dataSms, TimeSpan.FromDays(1), HttpContext.RequestAborted);
+
+            return new VerificationSms()
+            {
+                TelNum = dto.TelNum,
+                SmsCode = SmsCodeRand
+            };
+        }
+
         #endregion
 
         #region 私有方法
 
+        #region 将文件转化为文件流
+
         /// <summary>
         /// 将文件转化为文件流
         /// </summary>
@@ -771,6 +1005,190 @@ namespace Hotline.Api.Controllers
 
         #endregion
 
+        #region 获取验证码字符串
+        /// <summary>
+        /// 获取验证码字符串
+        /// </summary>
+        /// <param name="codeLen">验证码长度</param>
+        /// <param name="addUpper">是否添加大写</param>
+        /// <param name="addLower">是否添加小写</param>
+        /// <returns></returns>
+        private string GetVerifyCodeText(int codeLen = 4, bool addUpper = true, bool addLower = true)
+        {
+            // 返回对象
+            string codeText = "";
+            if (codeLen < 4)
+            {
+                codeLen = 4;
+            }
+            StringBuilder objChars = new StringBuilder();
+            StringBuilder objNums = new StringBuilder();
+            // 加入数字1-9
+            for (int i = 1; i <= 9; i++)
+            {
+                objNums.Append(i.ToString());
+            }
+            List<char> notExists = new List<char>();
+            notExists.Add('O');
+            notExists.Add('o');
+            notExists.Add('I');
+            notExists.Add('i');
+            notExists.Add('L');
+            notExists.Add('l');
+            notExists.Add('J');
+            notExists.Add('j');
+            notExists.Add('P');
+            notExists.Add('p');
+            notExists.Add('G');
+            notExists.Add('g');
+            // 加入大写字母A-Z,不包括O
+            if (addUpper == true)
+            {
+                char temp = ' ';
+
+                for (int i = 0; i < 26; i++)
+                {
+                    temp = Convert.ToChar(i + 65);
+
+                    // 如果生成的字母不是'O','o','I','i','L','l','J','j'
+                    if (notExists.Contains(temp) == false)
+                    {
+                        objChars.Append(temp);
+                    }
+                }
+            }
+
+            // 加入小写字母a-z,不包括o
+            if (addLower == true)
+            {
+                char temp = ' ';
+
+                for (int i = 0; i < 26; i++)
+                {
+                    temp = Convert.ToChar(i + 97);
+
+                    // 如果生成的字母不是'O','o','I','i','L','l','J','j'
+                    if (notExists.Contains(temp) == false)
+                    {
+                        objChars.Append(temp);
+                    }
+                }
+            }
+            // 生成验证码字符串
+            {
+                int index = 0;
+                Random objRandom = new Random();
+                StringBuilder objCodes = new StringBuilder();
+                for (int i = 0; i < 2; i++)
+                {
+                    index = objRandom.Next(0, objNums.Length);
+                    objCodes.Append(objNums[index]);
+                    objNums.Remove(index, 1);
+                }
+                for (int i = 0; i < codeLen - 2; i++)
+                {
+                    index = objRandom.Next(0, objChars.Length);
+                    objCodes.Append(objChars[index]);
+                    objChars.Remove(index, 1);
+                }
+                for (int i = 0; i < codeLen; i++)
+                {
+                    index = objRandom.Next(0, objCodes.Length);
+                    codeText += objCodes[index];
+                    objCodes.Remove(index, 1);
+                }
+            }
+            return codeText;
+        }
+        #endregion
+
+        #region 创建图片并且转换成base64
+
+        /// <summary>
+        /// 创建图片并且转换成base64
+        /// </summary>
+        /// <param name="verifyCode"></param>
+        /// <returns></returns>
+        private string CreateImageAndConvertToBase64(string verifyCode)
+        {
+            var base64String = "";
+            try
+            {
+                // 1. 创建一个新的位图 (100x100像素)
+                using (var bitmap = new Bitmap((int)Math.Ceiling((verifyCode.Length * 16.5)), 25))
+                {
+                    // 2. 使用Graphics对象绘制内容
+                    using (var graphics = Graphics.FromImage(bitmap))
+                    {
+                        Random random = new Random();
+
+                        graphics.Clear(Color.White);
+
+                        for (int i = 0; i < 25; i++)
+                        {
+                            int x1 = random.Next(bitmap.Width);
+                            int x2 = random.Next(bitmap.Width);
+                            int y1 = random.Next(bitmap.Height);
+                            int y2 = random.Next(bitmap.Height);
+                            graphics.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
+                        }
+
+                        // 添加一些文本
+                        var font = new Font("Arial", 14, (FontStyle.Bold | FontStyle.Italic));
+                        System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, bitmap.Width, bitmap.Height), Color.Blue, Color.DarkRed, 1.2f, true);
+                        graphics.DrawString(verifyCode, font, brush, 10, 0);
+
+                        for (int i = 0; i < 100; i++)
+                        {
+                            int x = random.Next(bitmap.Width);
+                            int y = random.Next(bitmap.Height);
+
+                            bitmap.SetPixel(x, y, Color.FromArgb(random.Next()));
+                        }
+
+                        graphics.DrawRectangle(new Pen(Color.Silver), 0, 0, bitmap.Width - 1, bitmap.Height - 1);
+                    }
+
+                    // 保存图片
+                    //bitmap.Save("C:\\Users\\PC\\Desktop\\1x1.png", ImageFormat.Png);
+
+                    // 3. 将图像保存到内存流
+                    using (var memoryStream = new MemoryStream())
+                    {
+                        bitmap.Save(memoryStream, ImageFormat.Png);
+
+                        // 4. 将内存流转换为字节数组
+                        byte[] imageBytes = memoryStream.ToArray();
+
+                        // 5. 将字节数组转换为Base64字符串
+                        base64String = Convert.ToBase64String(imageBytes);
+                    }
+                }
+            }
+            catch (Exception)
+            {
+            }
+            return base64String;
+        }
+
+        #endregion
+
+        #region 随机生成6位数字
+
+        /// <summary>
+        /// 随机生成6位数字
+        /// </summary>
+        /// <returns></returns>
+        private string RandomNum()
+        {
+            var random = new Random();
+            return random.Next(100000, 999999).ToString();
+        }
+
+        #endregion
+
+        #endregion
+
         #endregion
 
         #region 泸州电信查询接口

+ 63 - 0
src/Hotline.Api/Controllers/IPPbxController.cs

@@ -683,6 +683,69 @@ namespace Hotline.Api.Controllers
             {
                 //await _iPPbxApplication.ResetTelStatus(null, dto.TelNo, CancellationToken.None);
             }
+
+            #region 处理动作数据
+            TelOperation telOperation = new()
+            {
+                StaffNo = dto.StaffNo,
+                TelNo = dto.TelNo,
+                OperateState = dto.Status,
+                OperateTime = DateTime.Now,
+            };
+
+            switch (dto.Status)
+            {
+                case 0:
+                    telOperation.OperateStateText = "签出";
+                    break;
+                case 100:
+                    telOperation.OperateStateText = "签入";
+                    break;
+                case 200:
+                    telOperation.OperateStateText = "空闲";
+                    break;
+                case 201:
+                    telOperation.OperateStateText = "小休";
+                    break;
+                case 202:
+                    telOperation.OperateStateText = "繁忙";
+                    break;
+                case 300:
+                    telOperation.OperateStateText = "呼入振铃";
+                    break;
+                case 301:
+                    telOperation.OperateStateText = "呼入通话";
+                    break;
+                case 302:
+                    telOperation.OperateStateText = "呼出振铃";
+                    break;
+                case 303:
+                    telOperation.OperateStateText = "呼出通话";
+                    break;
+                case 310:
+                    telOperation.OperateStateText = "通话保持";
+                    break;
+                case 320:
+                    telOperation.OperateStateText = "会议";
+                    break;
+                case 330:
+                    telOperation.OperateStateText = "咨询";
+                    break;
+                case 400:
+                    telOperation.OperateStateText = "整理";
+                    break;
+                case 900:
+                    telOperation.OperateStateText = "注册";
+                    break;
+                case 901:
+                    telOperation.OperateStateText = "注销";
+                    break;
+                default:
+                    break;
+            }
+            await _telOperationRepository.AddAsync(telOperation);
+            #endregion
+
             dto.Status = await _callTelClient.GetStatusAsync(dto.Status);
             await _callApplication.EndActionAsync(dto.Adapt<EndActionInDto>());
 

+ 14 - 0
src/Hotline.Api/Controllers/IdentityController.cs

@@ -121,6 +121,20 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
     public async Task<Dictionary<string, object>> GetThirdTokenAsync([FromBody] ThirdTokenInDto dto)
         => await _identityAppService.GetThredTokenAsync(dto, HttpContext.RequestAborted);
 
+
+    /// <summary>
+    /// 第三方登录
+    /// </summary>
+    /// <param name=""></param>
+    /// <param name="_identityAppService"></param>
+    /// <param name=""></param>
+    /// <param name=""></param>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [HttpPost("third/login")]
+    public async Task<Dictionary<string, object>> GetThirdLoginAsync([FromBody] ThirdOpenIdInDto dto)
+            => await _identityAppService.GetThredTokenAsync(dto, HttpContext.RequestAborted);
+
     /// <summary>
     /// 根据OpenId刷新令牌
     /// </summary>

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

@@ -2890,16 +2890,51 @@ public class OrderController : BaseController
         return rsp;
     }
 
-    #endregion
-
-    #region 工单甄别
-
-    /// <summary>
-    /// 工单甄别待申请列表
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <returns></returns>
-    [HttpGet("mayscreen")]
+	/// <summary>
+	/// 自动延期写入
+	/// </summary>
+	/// <returns></returns>
+	[HttpPost("delay/automatic")]
+	[AllowAnonymous]
+	public async Task OrderDelayAutomatic() {
+
+        await _orderApplication.OrderDelayAutomatic();
+	}
+
+	/// <summary>
+	/// 延期短信执行
+	/// </summary>
+	/// <param name="type"></param>
+	/// <returns></returns>
+	[HttpPost("delay/automatic/handle/sms")]
+	[AllowAnonymous]
+	public async Task OrderDelayAutomaticHandleSms()
+	{
+		await _orderApplication.OrderDelayAutomaticHandle(EOrderDelayAutomaticType.Sms);
+	}
+	/// <summary>
+	/// 自动延期执行
+	/// </summary>
+	/// <param name="type"></param>
+	/// <returns></returns>
+	[HttpPost("delay/automatic/handle")]
+	[AllowAnonymous]
+	public async Task OrderDelayAutomaticHandleAutomatic()
+	{
+		await _orderApplication.OrderDelayAutomaticHandle(EOrderDelayAutomaticType.Automatic);
+	}
+
+
+	#endregion
+
+	#region 工单甄别
+
+	/// <summary>
+	/// 工单甄别待申请列表
+	/// </summary>
+	/// <param name="dto"></param>
+	/// <returns></returns>
+	[HttpGet("mayscreen")]
     public async Task<PagedDto<OrderVisitDetailDto>> MayScreenList([FromQuery] MayScreenListDto dto)
     {
         if (_appOptions.Value.IsYiBin) dto.ScreenType = EOrderScreenType.Org;
@@ -3413,6 +3448,105 @@ public class OrderController : BaseController
         await _workflowDomainService.NextAsync(dto, cancellationToken: HttpContext.RequestAborted);
     }
 
+    /// <summary>
+    /// 批量审批甄别
+    /// </summary>
+    [HttpPost("screen/batch_audit")]
+    [LogFilter("批量审批甄别")]
+    public async Task<string> BatchAuditScreen([FromBody] BatchScreenNextFlowDto dto)
+    {
+        var result = new StringBuilder();
+        var fail = 0;
+        var success = 0;
+        var workflow = dto.NextWorkflow;
+        foreach (var item in dto.ScreenId)
+        {
+            try
+            {
+                var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order).Where(x => x.Id == item)
+                    .FirstAsync(HttpContext.RequestAborted);
+                workflow.WorkflowId = screen.WorkflowId;
+                var workflowEntuty = await _workflowDomainService.GetWorkflowAsync(workflow.WorkflowId, withDefine: true, withSteps: true,
+                    cancellationToken: HttpContext.RequestAborted);
+                var currentStep = workflowEntuty.Steps.FirstOrDefault(d =>
+                    d.Status == EWorkflowStepStatus.WaitForAccept || d.Status == EWorkflowStepStatus.WaitForHandle);
+
+                NextStepsWithOpinionDto<NextStepOption> next = null;
+
+                try
+                {
+                    next = await _workflowApplication.GetNextStepsAsync(screen.WorkflowId, HttpContext.RequestAborted);
+                }
+                catch (UserFriendlyException e)
+                {
+                    if (e.Message.Contains("未找到对应节点"))
+                    {
+                        result.Append("无权审核:" + screen.No);
+                        fail++;
+                    }
+                    else
+                    {
+                        throw;
+                    }
+                }
+
+                if (next == null) continue;
+
+                if (!screen.Order.IsProvince)
+                {
+                    if (next.Steps.Any(x => x.Value == "省审批"))
+                    {
+                        next.Steps.Remove(next.Steps.First(x => x.Value == "省审批"));
+                    }
+                }
+
+                if (!_sessionContext.OrgIsCenter && currentStep.Name != "中心初审")
+                {
+                    if (next.Steps.Any(x => x.Value == "中心终审"))
+                    {
+                        next.Steps.Remove(next.Steps.First(x => x.Value == "中心终审"));
+                    }
+                }
+
+                workflow.StepId = next.StepId;
+                workflow.ReviewResult = dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed;
+
+                if (workflow.ReviewResult == EReviewResult.Approval)
+                {
+                    var isBatch = next.Steps.Where(x => x.Value == workflow.NextStepName).Any();
+                    if (isBatch)
+                    {
+                        var step = next.Steps.Where(x => x.Value == workflow.NextStepName).FirstOrDefault();
+                        workflow.NextStepCode = step.Key;
+                        workflow.NextStepName = step.Value;
+                    }
+                    else
+                    {
+                        result.Append("无权审核:" + screen.No);
+                        fail++;
+                        continue;
+                    }
+
+                    await _workflowDomainService.NextAsync(workflow, cancellationToken: HttpContext.RequestAborted);
+                }
+                else
+                {
+                    var reject = workflow.Adapt<RejectDto>();
+                    await _workflowApplication.RejectAsync(reject, HttpContext.RequestAborted);
+                }
+
+                success++;
+            }
+            catch (UserFriendlyException e)
+            {
+                result.Append(e.Message);
+                fail++;
+            }
+        }
+
+        return $"总共: {dto.ScreenId.Length}, 成功: {success}, 失败: {fail}, 失败原因: {result.ToString()}";
+    }
+
     /// <summary>
     /// 甄别审批退回(返回前一节点)
     /// </summary>

+ 8 - 8
src/Hotline.Api/StartupHelper.cs

@@ -327,14 +327,14 @@ namespace Hotline.Api
                             .WithCronSchedule("0/5 * * * * ?")
                         );
 
-                        var getOperationsJobKey = new JobKey(nameof(XingTangTelOperationSyncJob));
-                        d.AddJob<XingTangTelOperationSyncJob>(getOperationsJobKey);
-                        d.AddTrigger(t => t
-                            .WithIdentity("get-operationsxt-trigger")
-                            .ForJob(getOperationsJobKey)
-                            .StartNow()
-                            .WithCronSchedule("0/10 * * * * ?")
-                        );
+                        //var getOperationsJobKey = new JobKey(nameof(XingTangTelOperationSyncJob));
+                        //d.AddJob<XingTangTelOperationSyncJob>(getOperationsJobKey);
+                        //d.AddTrigger(t => t
+                        //    .WithIdentity("get-operationsxt-trigger")
+                        //    .ForJob(getOperationsJobKey)
+                        //    .StartNow()
+                        //    .WithCronSchedule("0/10 * * * * ?")
+                        //);
 
                         var getCallSatisfactionJobKey = new JobKey(nameof(XingTangCallSatisfactionSyncJob));
                         d.AddJob<XingTangCallSatisfactionSyncJob>(getCallSatisfactionJobKey);

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

@@ -1,7 +1,7 @@
 {
   "AllowedHosts": "*",
   "AppConfiguration": {
-    "AppScope": "ZiGong",
+    "AppScope": "LuZhou",
     "YiBin": {
       "AreaCode": "511500",
       "CallCenterType": "TianRun", //XunShi、WeiErXin、TianRun、XingTang

+ 5 - 1
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -358,6 +358,10 @@ public abstract class DefaultCallApplication : ICallApplication
         query = query.WhereIF(dto.Type == 3, (d, o, v) => d.AnsweredTime == null);
         query = query.WhereIF(dto.Type == 1, (d, o, v) => d.Direction == ECallDirection.In && d.AnsweredTime != null);
         query = query.WhereIF(dto.Type == 2, (d, o, v) => d.Direction == ECallDirection.Out && d.AnsweredTime != null);
+
+        query = query.WhereIF(dto.Type == 4, (d, o, v) => d.CallIdentity == ECallIdentity.White);
+        query = query.WhereIF(dto.Type == 5, (d, o, v) => d.CallIdentity == ECallIdentity.Black);
+
         query = query.WhereIF(dto.Type != 3 && !string.IsNullOrEmpty(dto.StaffNo), d => d.StaffNo == dto.StaffNo);
 
         if (dto.Type == 2)
@@ -376,7 +380,7 @@ public abstract class DefaultCallApplication : ICallApplication
 #endif
             return d;
         }
-        if (dto.Type == 3)
+        if (dto.Type == 3 || dto.Type == 5)
         {
             return query.Select((d, o, v) => new CallNativeDto
             {

+ 8 - 0
src/Hotline.Application/Identity/IIdentityAppService.cs

@@ -22,6 +22,14 @@ namespace Hotline.Application.Identity
         /// <exception cref="UserFriendlyException"></exception>
         Task<Dictionary<string, object>> GetThredTokenAsync(ThirdTokenInDto dto, CancellationToken token);
 
+        /// <summary>
+        /// 第三方登录
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="token"></param>
+        /// <returns></returns>
+        Task<Dictionary<string, object>> GetThredTokenAsync(ThirdOpenIdInDto dto, CancellationToken token);
+
         /// <summary>
         /// 根据OpenId刷新令牌
         /// </summary>

+ 16 - 3
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -336,14 +336,27 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         thirdDto = await _thirdAccountDomainFactory.GetThirdParameterAsync(thirdDto, token);
         var thirdToken = await _thirdIdentiyFactory.GetTokenAsync(thirdDto, token);
         var phone = await _thirdIdentiyFactory.GetPhoneNumberAsync(thirdDto, token);
-        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(thirdToken.OpenId, token);
+
+        var inDto = new ThirdOpenIdInDto
+        { 
+            OpenId = thirdToken.OpenId,
+            UnionId = thirdToken.UnIonId,
+            AppType = dto.AppType,
+            ThirdType = dto.ThirdType,
+            PhoneNumber = phone.PhoneNumber
+        };
+        return await GetThredTokenAsync(inDto, token);
+    }
+
+    public async Task<Dictionary<string, object>> GetThredTokenAsync(ThirdOpenIdInDto dto, CancellationToken token)
+    {
+        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(dto.OpenId, token);
 
         // 新用户注册
         if (thirdAccount is null)
         {
             thirdAccount = dto.Adapt<ThirdAccount>();
-            thirdToken.Adapt(thirdAccount);
-            thirdAccount.PhoneNumber = phone.PhoneNumber;
+            thirdAccount.PhoneNumber = dto.PhoneNumber;
             thirdAccount.Id = await _thirdAccountRepository.AddAsync(thirdAccount);
             await _thirdAccountDomainFactory.RegisterAsync(thirdAccount, token);
             thirdAccount = await _thirdAccountRepository.GetAsync(thirdAccount.Id, token);

+ 12 - 3
src/Hotline.Application/Mappers/CallMapperConfigs.cs

@@ -85,7 +85,8 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.WaitDuration, s => s.WaitTime)
                 .Map(d => d.AudioFile, s => s.AudioFile)
                 .Map(d => d.AgentTransferNumber, s => s.TransCalled)
-                .AfterMapping((s, d) => {
+                .AfterMapping((s, d) =>
+                {
                     if (s.CallState == 0 || s.CallState == 4 || s.CallState == 5 || s.CallState == 8)
                         d.CallState = ECallState.On;
                     if (s.CallState == 1)
@@ -94,13 +95,21 @@ namespace Hotline.Application.Mappers
                         d.CallState = ECallState.Missed;
                     if (s.CallState == 6)
                         d.CallState = ECallState.IVRNoAccept;
-                    })
+                })
                 .AfterMapping((s, d) =>
                 {
                     d.EndBy = d.Direction == ECallDirection.In
                         ? EEndBy.From
                         : EEndBy.To;
-                });
+                })
+                .AfterMapping((s, d) =>
+                {
+                    if (s.BlackList == 1)
+                        d.CallIdentity = ECallIdentity.Black;
+                    if (s.WhiteList == 1)
+                        d.CallIdentity = ECallIdentity.White;
+                })
+                ;
 
             config.ForType<XingtangSatisfaction, CallSatisfaction>()
                 .Map(d => d.CallNo, s => s.CallNo)

+ 21 - 6
src/Hotline.Application/OrderApp/IOrderApplication.cs

@@ -25,12 +25,14 @@ namespace Hotline.Application.OrderApp
         /// <returns></returns>
         Task<List<SendOrderReportOutDto>> SendOrderReportAsync(QuerySendOrderRequest dto);
 
-        /// <summary>
-        /// 更新工单办理期满时间
-        /// 1.更新工单 2.更新流程
-        /// </summary>
-        /// <returns></returns>
-        Task DelayOrderExpiredTimeAsync(string orderId, int timeCount, ETimeType timeType, bool IsProDelay, CancellationToken cancellationToken);
+        ISugarQueryable<Order> QuerySendOrderDetail(QuerySendOrderDetailRequest dto);
+
+		/// <summary>
+		/// 更新工单办理期满时间
+		/// 1.更新工单 2.更新流程
+		/// </summary>
+		/// <returns></returns>
+		Task DelayOrderExpiredTimeAsync(string orderId, int timeCount, ETimeType timeType, bool IsProDelay, CancellationToken cancellationToken);
 
         // /// <summary>
         // /// 新增工单办理流程记录
@@ -513,5 +515,18 @@ namespace Hotline.Application.OrderApp
         /// <returns></returns>
         ISugarQueryable<OrderDto> SeatSendBackStatisticsDetail(SeatSendBackStatisticsDetail dto);
 
+		/// <summary>
+		/// 自动延期记录写入
+		/// </summary>
+		/// <returns></returns>
+		Task OrderDelayAutomatic();
+
+		/// <summary>
+		/// 自动延期处理
+		/// </summary>
+		/// <param name="type"></param>
+		/// <returns></returns>
+		Task OrderDelayAutomaticHandle(EOrderDelayAutomaticType type);
+
 	}
 }

+ 524 - 111
src/Hotline.Application/OrderApp/OrderApplication.cs

@@ -1,4 +1,6 @@
 using DocumentFormat.OpenXml.Drawing.Diagrams;
+using DocumentFormat.OpenXml.Office.CustomUI;
+using DocumentFormat.OpenXml.Office2010.CustomUI;
 using DocumentFormat.OpenXml.Spreadsheet;
 using DotNetCore.CAP;
 using FluentValidation;
@@ -57,6 +59,7 @@ using PanGu;
 using SqlSugar;
 using System.Data;
 using System.Dynamic;
+using System.Threading;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -118,8 +121,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly ISessionContextManager _sessionContextManager;
     private readonly IOrderVisitApplication _orderVisitApplication;
     private readonly IRepository<OrderVisitDetailCopy> _orderVisitDetailCopyRepository;
+	private readonly IRepository<OrderDelayAutomatic> _orderDelayAutomaticRepository;
 
-    public OrderApplication(
+	public OrderApplication(
         IOrderDomainService orderDomainService,
         IOrderRepository orderRepository,
         IWorkflowDomainService workflowDomainService,
@@ -170,8 +174,9 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         ISessionContextManager sessionContextManager,
         IOrderVisitApplication orderVisitApplication,
         IRepository<Role> roleRepository,
-        IRepository<OrderVisitDetailCopy> orderVisitDetailCopyRepository
-        )
+        IRepository<OrderVisitDetailCopy> orderVisitDetailCopyRepository,
+		IRepository<OrderDelayAutomatic> orderDelayAutomaticRepository
+		)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -223,7 +228,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _orderVisitApplication = orderVisitApplication;
         _roleRepository = roleRepository;
         _orderVisitDetailCopyRepository = orderVisitDetailCopyRepository;
-    }
+		_orderDelayAutomaticRepository = orderDelayAutomaticRepository;
+	}
 
     /// <summary>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -767,8 +773,15 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         {
             List<FileJson> file = order.FileJson;
             List<FileJson> fileNew = await _fileRepository.AddFileAsync(dto.Files, order.Id, cancellationToken);
-            file.AddRange(fileNew);
-            order.FileJson = file;
+            if (file != null)
+            {
+                file.AddRange(fileNew);
+                order.FileJson = file;
+            }
+            else
+            {
+                order.FileJson = fileNew;
+            }
             await _orderRepository.UpdateAsync(order, cancellationToken);
         }
     }
@@ -2431,7 +2444,13 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .Select((o, w) => new Order { DaysOverdueOrgName = w.HandlerOrgName, Id = o.Id.SelectAll() });
         quer = _orderRepository.UnionAll(quer, queryCountersignOrder).MergeTable()
             .InnerJoin<SystemOrganize>((x, so) => x.ActualHandleOrgCode == so.Id)
-            .WhereIF(!string.IsNullOrEmpty(dto.No), (x, so) => x.No == dto.No);
+            .WhereIF(!string.IsNullOrEmpty(dto.No), (x, so) => x.No == dto.No)
+			.OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
+			.OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
+			.OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
+			.OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
+			.OrderByIF(dto is { SortField: "expiredTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
+			.OrderByIF(dto is { SortField: "expiredTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc);
         return quer;
     }
 
@@ -5489,6 +5508,10 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     /// <returns></returns>
     public async Task<List<SendOrderReportOutDto>> SendOrderReportAsync(QuerySendOrderRequest dto)
     {
+        if (_appOptions.Value.IsLuZhou)
+        {
+           return  await SendOrderReportAsync_LZ(dto);
+        }
         var itemsHandled = _workflowTraceRepository.Queryable()
             .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
             .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
@@ -5577,13 +5600,186 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return res;
     }
 
+	/// <summary>
+	/// 泸州派单量统计
+	/// </summary>
+	/// <param name="dto"></param>
+	/// <returns></returns>
+	public async Task<List<SendOrderReportOutDto>> SendOrderReportAsync_LZ(QuerySendOrderRequest dto)
+    {
+		var itemsHandled = _workflowTraceRepository.Queryable()
+			   .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+			   .Where((x, w) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled && (x.Name =="派单组" || x.Name == "班长审批"))
+			   .Where((x, w) => x.HandleTime >= dto.StartTime.Value)
+			   .Where((x, w) => x.HandleTime <= dto.EndTime.Value)
+			   .WhereIF(!string.IsNullOrEmpty(dto.UserName), (x, w) => x.HandlerName == dto.UserName)
+			   .GroupBy((x, w) => new { x.HandlerId,x.HandlerName,x.Name})
+			   .Select((x, w) => new BiOrderSendVo
+			   {
+				   UserId = x.HandlerId,
+				   UserName = x.HandlerName,
+                   StepName = x.Name,
+				   SendOrderNum = SqlFunc.AggregateDistinctCount(w.ExternalId),
+				   NoSendOrderNum = 0,
+				   ReSendOrderNum = 0,
+			   });
+		var itemsNo = _workflowTraceRepository.Queryable()
+			.LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+			.Where((x, w) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status != EWorkflowStepStatus.Handled && (x.Name == "派单组" || x.Name == "班长审批"))
+			.Where((x, w) => x.CreationTime >= dto.StartTime.Value)
+			.Where((x, w) => x.CreationTime <= dto.EndTime.Value)
+			.WhereIF(!string.IsNullOrEmpty(dto.UserName), (x, w) => x.HandlerName == dto.UserName)
+			.GroupBy((x, w) => new { x.HandlerId, x.HandlerName, x.Name })
+			.Select((x, w) => new BiOrderSendVo
+			{
+				UserId = x.HandlerId,
+				UserName = x.HandlerName,
+				StepName = x.Name,
+				SendOrderNum = 0,
+				ReSendOrderNum =0,
+				NoSendOrderNum = SqlFunc.AggregateDistinctCount(w.ExternalId),
+			});
+        
+		var items2 =  _workflowTraceRepository.Queryable()
+			.LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+			.Where((x, w) => w.ModuleCode == WorkflowModuleConsts.OrderHandle && x.BusinessType == EBusinessType.Send &&
+								 x.Status == EWorkflowStepStatus.Handled
+								 && !string.IsNullOrEmpty(x.NextMainHandler) && x.NextMainHandler != OrgSeedData.CenterId && (x.Name == "派单组" || x.Name == "班长审批"))
+			.Where((x, w) => x.CreationTime >= dto.StartTime.Value)
+			.Where((x, w) => x.CreationTime <= dto.EndTime.Value)
+			.GroupBy((x, w) => x.WorkflowId)
+			.Having((x, w) => SqlFunc.AggregateCount(x.WorkflowId) > 1)
+			.Select((x, w) => new { Id = x.WorkflowId, CreationTime = SqlFunc.AggregateMin(x.CreationTime) })
+			.MergeTable()
+			.LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
+			.WhereIF(!string.IsNullOrEmpty(dto.UserName), ((a, wt) => wt.HandlerName == dto.UserName))
+			.GroupBy((a, wt) => new { wt.HandlerId, wt.HandlerName ,wt.Name })
+			.Select((a, wt) => new BiOrderSendVo
+			{
+				UserId = wt.HandlerId,
+				UserName = wt.HandlerName,
+				StepName = wt.Name,
+				SendOrderNum = 0,
+				NoSendOrderNum = 0,
+				ReSendOrderNum = SqlFunc.AggregateDistinctCount(wt.ExternalId),
+			});
+
+        var items = await _orderRepository.UnionAll(itemsHandled, itemsNo, itemsHandled)
+         .GroupBy(x => new { x.UserId, x.UserName, x.StepName })
+         .Select(x => new BiOrderSendVo
+         {
+             UserId = x.UserId,
+             UserName = x.UserName,
+             StepName = x.StepName,
+             SendOrderNum = SqlFunc.AggregateSum(x.SendOrderNum),
+             NoSendOrderNum = SqlFunc.AggregateSum(x.NoSendOrderNum),
+             ReSendOrderNum = SqlFunc.AggregateSum(x.ReSendOrderNum)
+         }).ToListAsync();
+        var res = items.Select(p=> new SendOrderReportOutDto {
+             UserId =p.UserId,
+			 UserName = p.UserName,
+			 StepName = p.StepName,
+			 SendOrderNum = p.SendOrderNum,
+             NoSendOrderNum = p.NoSendOrderNum,
+			 ReSendOrderNum = p.ReSendOrderNum,
+             ChainRate = p.AccuracyRate
+		 }).ToList();
+		return res;
+	}
+
+
+    public ISugarQueryable<Order> QuerySendOrderDetail(QuerySendOrderDetailRequest dto) 
+    {
+        if (_appOptions.Value.IsLuZhou)
+        {
+            return QuerySendOrderDetail_LZ(dto);
 
-    /// <summary>
-    /// 扭转信件统计
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <returns></returns>
-    public ISugarQueryable<OrderVisitJudeStatisticsRep> OrderVisitJudeStatistics(OrderVisitJudeStatisticsReq dto)
+		}
+		if (dto.TitleCode.ToUpper() == "RESENDORDERNUM")
+        {
+           var query = _workflowTraceRepository.Queryable()
+                         .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+                         .InnerJoin<SchedulingUser>((x, w, su) => x.HandlerId == su.UserId)
+                         .Where((x, w, su) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled
+                                              && !string.IsNullOrEmpty(x.NextMainHandler) && x.NextMainHandler != OrgSeedData.CenterId)
+                         .Where((x, w, su) => x.CreationTime >= dto.StartTime.Value)
+                         .Where((x, w, su) => x.CreationTime <= dto.EndTime.Value)
+                         .GroupBy((x, w, su) => x.WorkflowId)
+                         .Having((x, w, su) => SqlFunc.AggregateCount(x.WorkflowId) > 1)
+                          .Select((x, w, su) => new { Id = x.WorkflowId, CreationTime = SqlFunc.AggregateMin(x.CreationTime) })
+                         .MergeTable()
+                         .LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
+                         .LeftJoin<Workflow>((a, wt, wf) => wt.WorkflowId == wf.Id)
+                         .InnerJoin<SchedulingUser>((a, wt, wf, su) => wt.HandlerId == su.UserId)
+                         .Where((a, wt, wf, su) => su.UserId == dto.UserId)
+                         .GroupBy((a, wt, wf, su) => wf.ExternalId)
+                         .Select((a, wt, wf, su) => new { Id = wf.ExternalId })
+                         .MergeTable()
+                         .LeftJoin<Order>((a, b) => a.Id == b.Id)
+                         .Select((a, b) => b);
+            return query;
+        }
+        var  query2 = _workflowTraceRepository.Queryable()
+                .InnerJoin<SchedulingUser>((x, su) => x.HandlerId == su.UserId)
+                .Where((x, su) => x.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send)
+                .Where((x, su) => x.CreationTime >= dto.StartTime.Value && x.CreationTime <= dto.EndTime.Value && su.UserId == dto.UserId)
+                .WhereIF(dto.TitleCode.ToUpper() == "NOSENDORDERNUM", (x, su) => x.Status != EWorkflowStepStatus.Handled)
+                .WhereIF(dto.TitleCode.ToUpper() == "SENDORDERNUM", (x, su) => x.Status == EWorkflowStepStatus.Handled)
+                .GroupBy((x, su) => x.ExternalId)
+                .Select((x, su) => new { Id = x.ExternalId })
+                .MergeTable()
+                .LeftJoin<Order>((a, b) => a.Id == b.Id)
+                .Select((a, b) => b);
+        return query2;
+	}
+
+	public ISugarQueryable<Order> QuerySendOrderDetail_LZ(QuerySendOrderDetailRequest dto)
+	{
+		if (dto.TitleCode.ToUpper() == "RESENDORDERNUM")
+		{
+			var query = _workflowTraceRepository.Queryable()
+						  .LeftJoin<Workflow>((x, w) => x.WorkflowId == w.Id)
+						  .Where((x, w) => w.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && x.Status == EWorkflowStepStatus.Handled
+											   && !string.IsNullOrEmpty(x.NextMainHandler) && x.NextMainHandler != OrgSeedData.CenterId && (x.Name == "派单组" || x.Name == "班长审批"))
+						  .Where((x, w) => x.CreationTime >= dto.StartTime.Value)
+						  .Where((x, w) => x.CreationTime <= dto.EndTime.Value)
+                          .WhereIF(dto.RoleName is "派单员", (x, w) => x.Name == "派单组")
+						  .WhereIF(dto.RoleName is "中心班长", (x, w) => x.Name == "班长审批")
+						  .GroupBy((x, w) => x.WorkflowId)
+						  .Having((x, w) => SqlFunc.AggregateCount(x.WorkflowId) > 1)
+						   .Select((x, w) => new { Id = x.WorkflowId, CreationTime = SqlFunc.AggregateMin(x.CreationTime) })
+						  .MergeTable()
+						  .LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
+						  .LeftJoin<Workflow>((a, wt, wf) => wt.WorkflowId == wf.Id)
+						  .Where((a, wt, wf) => wt.HandlerId == dto.UserId)
+						  .GroupBy((a, wt, wf) => wf.ExternalId)
+						  .Select((a, wt, wf) => new { Id = wf.ExternalId })
+						  .MergeTable()
+						  .LeftJoin<Order>((a, b) => a.Id == b.Id)
+						  .Select((a, b) => b);
+			return query;
+		}
+        var query2 = _workflowTraceRepository.Queryable()
+                .Where(x => x.ModuleCode == "OrderHandle" && x.BusinessType == EBusinessType.Send && (x.Name == "派单组" || x.Name == "班长审批"))
+                .Where(x => x.CreationTime >= dto.StartTime.Value && x.CreationTime <= dto.EndTime.Value && x.HandlerId == dto.UserId)
+                .WhereIF(dto.TitleCode.ToUpper() == "NOSENDORDERNUM", x => x.Status != EWorkflowStepStatus.Handled)
+                .WhereIF(dto.TitleCode.ToUpper() == "SENDORDERNUM", x => x.Status == EWorkflowStepStatus.Handled)
+                .WhereIF(dto.RoleName is "派单员", x => x.Name == "派单组")
+                .WhereIF(dto.RoleName is "中心班长", x => x.Name == "班长审批")
+                .GroupBy(x => x.ExternalId)
+                .Select(x => new { Id = x.ExternalId })
+                .MergeTable()
+                .LeftJoin<Order>((a, b) => a.Id == b.Id)
+                .Select((a, b) => b);
+		return query2;
+	}
+
+	/// <summary>
+	/// 扭转信件统计
+	/// </summary>
+	/// <param name="dto"></param>
+	/// <returns></returns>
+	public ISugarQueryable<OrderVisitJudeStatisticsRep> OrderVisitJudeStatistics(OrderVisitJudeStatisticsReq dto)
     {
         if (!dto.EndTime.HasValue)
         {
@@ -6558,15 +6754,15 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         //.GroupBy((x, o) => new { o.AcceptorId})
         //.Select((x, o) => new { UserId = o.AcceptorId, SendOrderBackNum = SqlFunc.AggregateDistinctCount(x.ExternalId), SendOrderBackNumber = SqlFunc.AggregateCount(x.ExternalId) });
         var sendBack = _orderSendBackAuditRepository.Queryable()
-			.LeftJoin<Order>((x, o) => x.OrderId == o.Id)
-			.Where((x, o) =>  (x.SendBackStepName == "话务部") && o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime)
-			.GroupBy((x, o) => new { o.AcceptorId })
-			.Select((x, o) => new SeatSendBackStatisticsVo { UserId = o.AcceptorId, SendOrderBackNum = SqlFunc.AggregateDistinctCount(x.OrderId), SendOrderBackNumber = SqlFunc.AggregateCount(x.Id) });
+            .LeftJoin<Order>((x, o) => x.OrderId == o.Id)
+            .Where((x, o) => (x.SendBackStepName == "话务部") && o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime)
+            .GroupBy((x, o) => new { o.AcceptorId })
+            .Select((x, o) => new SeatSendBackStatisticsVo { UserId = o.AcceptorId, SendOrderBackNum = SqlFunc.AggregateDistinctCount(x.OrderId), SendOrderBackNumber = SqlFunc.AggregateCount(x.Id) });
 
 
-		var filed = _orderRepository.Queryable()
-            .Where(x=> x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime && x.Status >= EOrderStatus.Filed && x.FileOrgIsCenter == true && x.FileUserRole == EFileUserType.Seat)
-            .GroupBy(x=>x.AcceptorId)
+        var filed = _orderRepository.Queryable()
+            .Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime && x.Status >= EOrderStatus.Filed && x.FileOrgIsCenter == true && x.FileUserRole == EFileUserType.Seat)
+            .GroupBy(x => x.AcceptorId)
             .Select(x => new SeatSendBackStatisticsVo { UserId = x.AcceptorId, CentreFileNum = SqlFunc.AggregateDistinctCount(x.Id) });
 
         var back = _orderSpecialRepository.Queryable()
@@ -6625,97 +6821,97 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
         else if (dto.StatisticsType == "sendOrderBackNum")
         {
-			var query = _orderRepository.Queryable().Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime )
-				.WhereIF(!string.IsNullOrEmpty(dto.UserId), x => x.AcceptorId == dto.UserId)
-				.WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No!))
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.AcceptTypeCode == dto.AcceptType) //受理类型
-				.WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.SourceChannelCode == dto.Channel) //来源渠道
-				 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
-					x => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
-				.Where(x => SqlFunc.Subqueryable<OrderSendBackAudit>().Where(os => os.OrderId == x.Id && (os.SendBackStepName == "话务部")).Any())
-				.Select(x => new OrderDto() { Id = x.Id.SelectAll() })
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
-			return query;
-		}
-		else if (dto.StatisticsType == "sendOrderBackNumber")
-		{
-			var query = _orderRepository.Queryable()
-                .LeftJoin<OrderSendBackAudit>((x, os) => x.Id == os.OrderId  && (os.SendBackStepName == "话务部"))
-				.WhereIF(!string.IsNullOrEmpty(dto.No), (x, os) => x.No.Contains(dto.No!))
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), (x, os) => x.Title.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), (x, os) => x.AcceptTypeCode == dto.AcceptType) //受理类型
-				.WhereIF(!string.IsNullOrEmpty(dto.Channel), (x, os) => x.SourceChannelCode == dto.Channel) //来源渠道
-				 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
-					(x, os) => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
-				.Where((x, os) => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
-				.WhereIF(!string.IsNullOrEmpty(dto.UserId), (x, os) => x.AcceptorId == dto.UserId)
-				.Select((x, os) => new OrderDto() { Id = x.Id.SelectAll(), SendBackOpinion = os.Content, SendBackAuditTime = os.AuditTime })
+            var query = _orderRepository.Queryable().Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserId), x => x.AcceptorId == dto.UserId)
+                .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.AcceptTypeCode == dto.AcceptType) //受理类型
+                .WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.SourceChannelCode == dto.Channel) //来源渠道
+                 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
+                    x => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
+                .Where(x => SqlFunc.Subqueryable<OrderSendBackAudit>().Where(os => os.OrderId == x.Id && (os.SendBackStepName == "话务部")).Any())
+                .Select(x => new OrderDto() { Id = x.Id.SelectAll() })
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
+            return query;
+        }
+        else if (dto.StatisticsType == "sendOrderBackNumber")
+        {
+            var query = _orderRepository.Queryable()
+                .LeftJoin<OrderSendBackAudit>((x, os) => x.Id == os.OrderId && (os.SendBackStepName == "话务部"))
+                .WhereIF(!string.IsNullOrEmpty(dto.No), (x, os) => x.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), (x, os) => x.Title.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), (x, os) => x.AcceptTypeCode == dto.AcceptType) //受理类型
+                .WhereIF(!string.IsNullOrEmpty(dto.Channel), (x, os) => x.SourceChannelCode == dto.Channel) //来源渠道
+                 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
+                    (x, os) => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
+                .Where((x, os) => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserId), (x, os) => x.AcceptorId == dto.UserId)
+                .Select((x, os) => new OrderDto() { Id = x.Id.SelectAll(), SendBackOpinion = os.Content, SendBackAuditTime = os.AuditTime })
                 .MergeTable()
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
-			return query;
-		}
-		else if (dto.StatisticsType == "centreFileNum")
-		{
-			var query = _orderRepository.Queryable().Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime && x.FileUserRole == EFileUserType.Seat )
-				.WhereIF(!string.IsNullOrEmpty(dto.UserId), x => x.AcceptorId == dto.UserId)
-				.WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No!))
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.AcceptTypeCode == dto.AcceptType) //受理类型
-				.WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.SourceChannelCode == dto.Channel) //来源渠道
-				 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
-					x => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
-				.Where(x => x.Status >= EOrderStatus.Filed && x.FileOrgIsCenter == true).Select(x => new OrderDto() { Id = x.Id.SelectAll() })
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
-			return query;
-		}
-		else if (dto.StatisticsType == "centreFileBackNum")
-		{
-			var query = _orderRepository.Queryable().Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime )
-				.WhereIF(!string.IsNullOrEmpty(dto.UserId), x => x.AcceptorId == dto.UserId)
-				.WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No!))
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.AcceptTypeCode == dto.AcceptType) //受理类型
-				.WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.SourceChannelCode == dto.Channel) //来源渠道
-				 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
-					x => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
-				.Where(x => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == x.Id && os.SpecialType == ESpecialType.SendBack && os.NextStepCode == "start").Any())
-				.Select(x => new OrderDto() { Id = x.Id.SelectAll() })
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
-				.OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
-			return query;
-		}
-		else if (dto.StatisticsType == "centreFileBackNumber")
-		{
-			var query = _orderRepository.Queryable().LeftJoin<OrderSpecial>((x, os) => x.Id == os.OrderId &&  os.SpecialType == ESpecialType.SendBack && os.NextStepCode == "start")
-				.WhereIF(!string.IsNullOrEmpty(dto.No), (x, os) => x.No.Contains(dto.No!))
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), (x, os) => x.Title.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), (x, os) => x.AcceptTypeCode == dto.AcceptType) //受理类型
-				.WhereIF(!string.IsNullOrEmpty(dto.Channel), (x, os) => x.SourceChannelCode == dto.Channel) //来源渠道
-				 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
-					(x, os) => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
-				.Where((x, os) => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
-				.WhereIF(!string.IsNullOrEmpty(dto.UserId), (x, os) => x.AcceptorId == dto.UserId)
-				.Select((x, os) => new OrderDto() { Id = x.Id.SelectAll() , SendBackOpinion = os.Reason , SendBackAuditTime = os.CreationTime })
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
+            return query;
+        }
+        else if (dto.StatisticsType == "centreFileNum")
+        {
+            var query = _orderRepository.Queryable().Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime && x.FileUserRole == EFileUserType.Seat)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserId), x => x.AcceptorId == dto.UserId)
+                .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.AcceptTypeCode == dto.AcceptType) //受理类型
+                .WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.SourceChannelCode == dto.Channel) //来源渠道
+                 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
+                    x => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
+                .Where(x => x.Status >= EOrderStatus.Filed && x.FileOrgIsCenter == true).Select(x => new OrderDto() { Id = x.Id.SelectAll() })
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
+            return query;
+        }
+        else if (dto.StatisticsType == "centreFileBackNum")
+        {
+            var query = _orderRepository.Queryable().Where(x => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserId), x => x.AcceptorId == dto.UserId)
+                .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Title.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.AcceptTypeCode == dto.AcceptType) //受理类型
+                .WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.SourceChannelCode == dto.Channel) //来源渠道
+                 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
+                    x => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
+                .Where(x => SqlFunc.Subqueryable<OrderSpecial>().Where(os => os.OrderId == x.Id && os.SpecialType == ESpecialType.SendBack && os.NextStepCode == "start").Any())
+                .Select(x => new OrderDto() { Id = x.Id.SelectAll() })
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 0 }, x => x.FiledTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "filedTime", SortRule: 1 }, x => x.FiledTime, OrderByType.Desc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 0 }, x => x.SendBackAuditTime, OrderByType.Asc)
+                .OrderByIF(dto is { SortField: "sendBackAuditTime", SortRule: 1 }, x => x.SendBackAuditTime, OrderByType.Desc);
+            return query;
+        }
+        else if (dto.StatisticsType == "centreFileBackNumber")
+        {
+            var query = _orderRepository.Queryable().LeftJoin<OrderSpecial>((x, os) => x.Id == os.OrderId && os.SpecialType == ESpecialType.SendBack && os.NextStepCode == "start")
+                .WhereIF(!string.IsNullOrEmpty(dto.No), (x, os) => x.No.Contains(dto.No!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), (x, os) => x.Title.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), (x, os) => x.AcceptTypeCode == dto.AcceptType) //受理类型
+                .WhereIF(!string.IsNullOrEmpty(dto.Channel), (x, os) => x.SourceChannelCode == dto.Channel) //来源渠道
+                 .WhereIF(!string.IsNullOrEmpty(dto.Hotspot),
+                    (x, os) => x.HotspotSpliceName != null && x.HotspotSpliceName.Contains(dto.Hotspot)) //热点分类
+                .Where((x, os) => x.CreationTime >= dto.StartTime && x.CreationTime <= dto.EndTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserId), (x, os) => x.AcceptorId == dto.UserId)
+                .Select((x, os) => new OrderDto() { Id = x.Id.SelectAll(), SendBackOpinion = os.Reason, SendBackAuditTime = os.CreationTime })
                 .MergeTable()
                 .OrderByIF(dto is { SortField: "startTime", SortRule: 0 }, x => x.StartTime, OrderByType.Asc)
                 .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, x => x.StartTime, OrderByType.Desc)
@@ -6727,5 +6923,222 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
         return null;
     }
-    #endregion
+
+
+	#region 自动延期
+
+    /// <summary>
+    /// 自动延期记录写入
+    /// </summary>
+    /// <returns></returns>
+	public async Task OrderDelayAutomatic()
+	{
+
+		var data = new List<OrderDelayAutomatic>();
+		var automatic = await _orderRepository.Queryable()
+		   .Where(x => x.Status < EOrderStatus.Filed && x.ExpiredTime <= DateTime.Now.AddHours(1) && x.ExpiredTime >= DateTime.Now)
+		   .Where(x => SqlFunc.Subqueryable<OrderDelay>().Where(od => od.OrderId == x.Id && od.DelayState == EDelayState.Examining).NotAny())
+		   .Where(x => SqlFunc.Subqueryable<OrderDelayAutomatic>().Where(oda => oda.OrderId == x.Id && oda.Status == EOrderDelayAutomaticStatus.Pending && oda.Type == EOrderDelayAutomaticType.Automatic).NotAny())
+		   .Select(x => new OrderDelayAutomatic
+		   {
+			   OrderId = x.Id,
+			   WorkflowId = x.WorkflowId,
+			   Title = x.Title,
+			   No = x.No,
+			   ExpiredTime = x.ExpiredTime,
+			   CenterToOrgTime =x.CenterToOrgTime,
+			   AcceptTypeCode =x.AcceptTypeCode,
+			   Type = EOrderDelayAutomaticType.Automatic
+		   })
+		   .ToListAsync();
+		if (automatic.Any())
+		{
+			data.AddRange(automatic);
+		}
+		var automaticSMS = await _orderRepository.Queryable()
+			.Where(x => x.Status < EOrderStatus.Filed && x.ExpiredTime <= DateTime.Now.AddHours(2) && x.ExpiredTime >= DateTime.Now.AddHours(1) && x.ExpiredTime >= DateTime.Now)
+			.Where(x => SqlFunc.Subqueryable<OrderDelay>().Where(od => od.OrderId == x.Id && od.DelayState == EDelayState.Examining).NotAny())
+			.Where(x => SqlFunc.Subqueryable<OrderDelayAutomatic>().Where(oda => oda.OrderId == x.Id && oda.Status == EOrderDelayAutomaticStatus.Pending && oda.Type == EOrderDelayAutomaticType.Sms).NotAny())
+			.Select(x => new OrderDelayAutomatic
+			{
+				OrderId = x.Id,
+				WorkflowId = x.WorkflowId,
+				Title = x.Title,
+				No = x.No,
+				ExpiredTime = x.ExpiredTime,
+				CenterToOrgTime = x.CenterToOrgTime,
+				AcceptTypeCode = x.AcceptTypeCode,
+				Type = EOrderDelayAutomaticType.Sms
+			})
+			.ToListAsync();
+		if (automaticSMS.Any())
+		{
+			data.AddRange(automaticSMS);
+		}
+		if (data.Any())
+		{
+			await _orderDelayAutomaticRepository.AddRangeAsync(data);
+		}
+	}
+
+	/// <summary>
+	/// 自动延期处理
+	/// </summary>
+	/// <param name="type"></param>
+	/// <returns></returns>
+	public async Task OrderDelayAutomaticHandle(EOrderDelayAutomaticType type)
+	{
+        var copy = new List<OrderDelayAutomatic>();
+		var tasks = await _orderDelayAutomaticRepository.Queryable()
+			.Where(x => x.Status == EOrderDelayAutomaticStatus.Pending && x.Type == type)
+			.ToListAsync();
+		foreach (var task in tasks)
+        {
+            task.Status = EOrderDelayAutomaticStatus.BeingProcessed;
+            var row =  await _orderDelayAutomaticRepository.Updateable(task).ExecuteCommandWithOptLockAsync();
+            if (row > 0)
+            {
+                copy.Add(task);
+			}
+		}
+		if (type == EOrderDelayAutomaticType.Sms)
+		{
+			if (copy.Any())
+			{
+				foreach (var item in copy)
+				{
+					var workflow = await _workflowDomainService.GetWorkflowAsync(item.WorkflowId, withSteps: true);
+					var steps = workflow.Steps.Where(x => x.Status == EWorkflowStepStatus.WaitForAccept || x.Status == EWorkflowStepStatus.WaitForHandle).ToList();
+					if (steps.Any())
+					{
+						foreach (var step in steps)
+						{
+							var setting = step.HandlerOrgId == OrgSeedData.CenterId ? SettingConstants.AutomaticDelayCenterRoles : SettingConstants.AutomaticDelayDepartmentRoles;
+							var roleIds = _systemSettingCacheManager.GetSetting(setting)?.SettingValue;
+							if (step.HandlerOrgId == OrgSeedData.CenterId && string.IsNullOrEmpty(step.RoleId))
+							{
+								roleIds.Add(step.RoleId);
+							}
+							var userList = await _userRepository.Queryable().Where(x => x.OrgId == step.HandlerOrgId && x.Roles.Any(r => roleIds.Contains(r.Name))).ToListAsync();
+							foreach (var user in userList)
+							{
+								if (!string.IsNullOrEmpty(user.PhoneNo))
+								{
+									//发送短信
+									var messageDto = new Share.Dtos.Push.MessageDto
+									{
+										PushBusiness = EPushBusiness.AutomaticDelay,
+										ExternalId = item.Id,
+										OrderId = item.Id,
+										PushPlatform = EPushPlatform.Sms,
+										Remark = item.Title,
+										Name = user.Name,
+										TemplateCode = "1015",
+										Params = new List<string>() { item.No },
+										TelNumber = user.PhoneNo,
+									};
+									await _mediator.Publish(new PushMessageNotify(messageDto));
+								}
+							}
+						}
+					}
+					await _orderDelayAutomaticRepository.Updateable().SetColumns(x=>x.Status == EOrderDelayAutomaticStatus.Processed).Where(x=>x.Id == item.Id).ExecuteCommandAsync();
+				}
+			}
+		}
+		if (type == EOrderDelayAutomaticType.Automatic)
+		{
+			if (copy.Any())
+			{
+				foreach (var item in copy)
+				{
+					var delayAny = await _orderDelayRepository.Queryable().Where(x => x.OrderId == item.OrderId && x.DelayState == EDelayState.Examining).AnyAsync();
+					if (!delayAny)
+					{
+						var delays = await _orderDelayRepository.Queryable().Where(x => x.OrderId == item.OrderId && x.AutomaticDelayNum > 0).ToListAsync();
+						var delay = new OrderDelay();
+						if (delays.Any())
+						{
+							delay = delays.First();
+							var startTime = DateTime.Now;
+							if (item.CenterToOrgTime.HasValue)
+							{
+								startTime = item.CenterToOrgTime.Value;
+							}
+							var beforeDelay = DateTime.Now;
+							if (!_appOptions.Value.IsYiBin)
+							{
+								beforeDelay = item.ExpiredTime.Value;
+							}
+							delay.AfterDelay = (await _expireTime
+								.CalcEndTime(beforeDelay, startTime, delay.DelayUnit, delay.DelayNum, item.AcceptTypeCode))?.EndTime; //todo
+							await _orderDelayRepository.Updateable().SetColumns(x => new OrderDelay() { AutomaticDelayNum = x.AutomaticDelayNum + 1, ApplyDelayTime = DateTime.Now, AfterDelay = delay.AfterDelay })
+								.Where(x => x.Id == delay.Id).ExecuteCommandAsync();
+						}
+						else
+						{
+							delay.OrderId = item.OrderId;
+							delay.EmployeeId = "";
+							delay.EmployeeName = "系统自动延期";
+							delay.ApplyOrgName = OrgSeedData.CenterName;
+							delay.ApplyOrgCode = OrgSeedData.CenterId;
+							delay.DelayApplyType = EDelayApplyType.LocalApply;
+							delay.BeforeDelay = item.ExpiredTime;
+							delay.DelayState = EDelayState.Pass;
+							delay.DelayReason = "系统自动延期";
+							delay.ApplyDelayTime = DateTime.Now;
+							delay.No = item.No;
+							delay.AutomaticDelayNum = 1;
+							delay.DelayNum = 1;
+							delay.DelayUnit = Share.Enums.Settings.ETimeType.WorkDay;
+							delay.IsProDelay = false;
+							delay.CreatorOrgId = OrgSeedData.CenterId;
+							delay.CreatorOrgName = OrgSeedData.CenterName;
+							delay.CreatorName = "系统自动延期";
+							var startTime = DateTime.Now;
+							if (item.CenterToOrgTime.HasValue)
+							{
+								startTime = item.CenterToOrgTime.Value;
+							}
+							if (delay.BeforeDelay != null)
+							{
+								delay.AfterDelay = (await _expireTime
+									.CalcEndTime(delay.BeforeDelay.Value, startTime, delay.DelayUnit, delay.DelayNum, item.AcceptTypeCode))?.EndTime; //todo
+							}
+							await _orderDelayRepository.AddAsync(delay, false);
+						}
+						//处理工单延期
+						await DelayOrderExpiredTimeAsync(item.OrderId, delay.DelayNum, delay.DelayUnit, delay.IsProDelay, default);
+					}
+					await _orderDelayAutomaticRepository.Updateable().SetColumns(x => x.Status == EOrderDelayAutomaticStatus.Processed).Where(x => x.Id == item.Id).ExecuteCommandAsync();
+				}
+			}
+		}
+	}
+
+	/// <summary>
+	/// 批量发送短信
+	/// </summary>
+	/// <param name="dto"></param>
+	/// <param name="cancellationToken"></param>
+	/// <returns></returns>
+	public async Task SendLeaderSMS(PublishLeaderSMSDto dto, CancellationToken cancellationToken)
+	{
+		//发送短信
+		var messageDto = new Share.Dtos.Push.MessageDto
+		{
+			PushBusiness = EPushBusiness.OrderSend,
+			ExternalId = dto.OrderId,
+			OrderId = dto.OrderId,
+			PushPlatform = EPushPlatform.Sms,
+			Remark = string.Empty,
+			Name = dto.Name,
+			TemplateCode = "1014",
+			Params = new(),
+			TelNumber = dto.TelNumber,
+		};
+		await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
+	}
+	#endregion
+	#endregion
 }

+ 26 - 2
src/Hotline.Application/Snapshot/RedPackApplication.cs

@@ -88,6 +88,26 @@ public class RedPackApplication : IRedPackApplication, IScopeDependency
             status = ERedPackAuditStatus.Agree;
         var redPackAudit = await _redPackAuditRepository.GetAsync(dto.RedPackAuditId, token) ?? throw UserFriendlyException.SameMessage("审核记录不存在");
         if (redPackAudit.Status != ERedPackAuditStatus.Pending) throw UserFriendlyException.SameMessage("已审核, 不可重复审核");
+
+        var industry = await _industryRepository.Queryable(includeDeleted: true)
+          .LeftJoin<OrderSnapshot>((i, o) => i.Id == o.IndustryId)
+          .Where((i, o) => o.Id == redPackAudit.OrderId)
+          .Select((i, o) => new {
+              i.Id,
+              i.CitizenReadPackAmount,
+              i.ArgeePoints,
+              i.ExtraDeductedPoints,
+              i.RefusePoints,
+              i.IsPoints,
+              o.IsSafetyDepartment,
+              i.Name
+          })
+          .FirstAsync(token);
+        redPackAudit.ApprovedAmount = redPackAudit.ShouldAmount;
+        if (industry.Name == "安全隐患" && industry.IsSafetyDepartment.HasValue && industry.IsSafetyDepartment == true)
+        {
+            redPackAudit.ApprovedAmount = 20;
+        }
         redPackAudit.SMSTemplateId = dto.SMSTemplateId;
         redPackAudit.Status = status;
         redPackAudit.AuditRemark = dto.Opinion;
@@ -97,7 +117,6 @@ public class RedPackApplication : IRedPackApplication, IScopeDependency
         redPackAudit.AuditTime = DateTime.Now;
         redPackAudit.AuditOrgId = _sessionContext.OrgId;
         redPackAudit.AuditOrgName = _sessionContext.OrgName;
-        redPackAudit.ApprovedAmount = redPackAudit.ShouldAmount;
         redPackAudit.Points = dto.Points;
         redPackAudit.PointsStatus = dto.PointsStatus;
         redPackAudit.PointsOpinion = dto.PointsOpinion;
@@ -247,9 +266,14 @@ public class RedPackApplication : IRedPackApplication, IScopeDependency
         var industry = await _industryRepository.Queryable(includeDeleted: true)
             .LeftJoin<OrderSnapshot>((i, o) => i.Id == o.IndustryId)
             .Where((i, o) => o.Id == id)
-            .Select((i, o) => new { i.Id, i.CitizenReadPackAmount, i.ArgeePoints, i.ExtraDeductedPoints, i.RefusePoints, i.IsPoints })
+            .Select((i, o) => new { i.Id, i.CitizenReadPackAmount, i.ArgeePoints, i.ExtraDeductedPoints, i.RefusePoints, i.IsPoints,
+            o.IsSafetyDepartment, i.Name})
             .FirstAsync();
         outDto.Amount = industry.CitizenReadPackAmount;
+        if (industry.Name == "安全隐患" && industry.IsSafetyDepartment.HasValue && industry.IsSafetyDepartment == true)
+        {
+            outDto.Amount = 20;
+        }
         outDto.ArgeePoints = industry.ArgeePoints;
         outDto.ExtraDeductedPoints = industry.ExtraDeductedPoints;
         outDto.RefusePoints = industry.RefusePoints;

+ 2 - 0
src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs

@@ -69,6 +69,8 @@ namespace Hotline.Share.Dtos.CallCenter
         /// 1: 呼入
         /// 2: 呼出
         /// 3: 未接
+        /// 4:呼入白名单
+        /// 5:呼入黑名单
         /// </summary>
         public int Type { get; set; }
 

+ 85 - 0
src/Hotline.Share/Dtos/CallNative/BlackPhoneListDto.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.CallNative
+{
+    /// <summary>
+    /// 新增
+    /// </summary>
+    public class AddBlackPhoneDto
+    {
+        /// <summary>
+        /// 黑名单号码
+        /// </summary>
+        public string PhoneNum { get; set; }
+
+        /// <summary>
+        /// 有效期
+        /// </summary>
+        public DateTime? ValidDateTime { get; set; }
+
+    }
+
+    /// <summary>
+    /// 黑名单
+    /// </summary>
+    public class BlackPhoneListDto
+    {
+        /// <summary>
+        /// 黑名单号码ID
+        /// </summary>
+        public int BlackPhoneId { get; set; }
+
+        /// <summary>
+        /// 黑名单号码起始号段
+        /// </summary>
+        public string? StartPhone { get; set; }
+
+        /// <summary>
+        /// 有效期
+        /// </summary>
+        public DateTime? ValidDateTime { get; set; }
+
+        /// <summary>
+        /// 是否生效(0:不生效,1:生效)
+        /// </summary>
+        public int? Enabled { get; set; }
+
+        public string? EnabledText => Enabled == 1 ? "有效" : "无效";
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime? CreateDate { get; set; }
+    }
+
+    /// <summary>
+    /// 白名单
+    /// </summary>
+    public class WhitePhoneListDto
+    {
+        /// <summary>
+        /// 号码ID
+        /// </summary>
+        public int Id { get; set; }
+
+        /// <summary>
+        /// 号码
+        /// </summary>
+        public string? Phone { get; set; }
+
+        /// <summary>
+        /// 有效期
+        /// </summary>
+        public DateTime? ValidDateTime { get; set; }
+
+        /// <summary>
+        /// 是否生效(0:不生效,1:生效)
+        /// </summary>
+        public int? Enabled { get; set; }
+        public string? EnabledText => Enabled == 1 ? "有效" : "无效";
+    }
+}

+ 27 - 0
src/Hotline.Share/Dtos/CallNative/BlackPhoneListQuery.cs

@@ -0,0 +1,27 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.CallNative
+{
+    public record BlackPhoneListQuery : PagedKeywordRequest
+    {
+        /// <summary>
+        /// 操作类型 1:黑名单 2:白名单
+        /// </summary>
+        public string LogType { get; set; }
+
+        /// <summary>
+        /// 操作动作
+        /// </summary>
+        public string LogAction { get; set; }
+
+        /// <summary>
+        /// 操作号码
+        /// </summary>
+        public string PhoneNum { get; set; }
+
+        /// <summary>
+        /// 操作人
+        /// </summary>
+        public string? UserName { get; set; }
+    }
+}

+ 27 - 9
src/Hotline.Share/Dtos/FlowEngine/Workflow/NextWorkflowDto.cs

@@ -29,15 +29,33 @@ public class NextWorkflowDto<TData>
     public NextWorkflowDto Workflow { get; set; }
 }
 
-public class OrderScreenNextWorkflowDto : NextWorkflowDto {
+public class OrderScreenNextWorkflowDto : NextWorkflowDto
+{
+
+    /// <summary>
+    /// 省附件
+    /// </summary>
+    public List<FileDto> ProvinceFiles { get; set; } = new();
 
-	/// <summary>
-	/// 省附件
-	/// </summary>
-	public List<FileDto> ProvinceFiles { get; set; } = new();
+    /// <summary>
+    /// 甄别申请id
+    /// </summary>
+    public string ScreenId { get; set; }
+}
 
-	/// <summary>
-	/// 甄别申请id
-	/// </summary>
-	public string ScreenId { get; set; } 
+public class BatchScreenNextFlowDto
+{
+    /// <summary>
+    /// 省附件
+    /// </summary>
+    public List<FileDto> ProvinceFiles { get; set; } = new();
+
+    public string[] ScreenId { get; set; }
+
+    public NextWorkflowDto NextWorkflow { get; set; }
+
+    /// <summary>
+    /// 是否通过
+    /// </summary>
+    public bool IsPass { get; set; }
 }

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

@@ -914,10 +914,12 @@ namespace Hotline.Share.Dtos.Order
         public string UserId { get; set; }
         public string UserName { get; set; }
 
-        /// <summary>
-        /// 派单量
-        /// </summary>
-        public int SendOrderNum { get; set; }
+        public string StepName { get; set; }
+
+		/// <summary>
+		/// 派单量
+		/// </summary>
+		public int SendOrderNum { get; set; }
 
         /// <summary>
         /// 待派单量
@@ -929,8 +931,23 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public int ReSendOrderNum { get; set; }
 
-        public string AccuracyRate { get; set; }
-    }
+        //public string AccuracyRate { get; set; }
+
+		public string AccuracyRate => GetAccuracyRateRate();
+
+		public string GetAccuracyRateRate()
+		{
+            if (ReSendOrderNum == 0)
+            {
+                return "100%";
+            }
+			if (SendOrderNum > 0 &&  ReSendOrderNum > 0)
+			{
+				return Math.Round(((SendOrderNum- ReSendOrderNum) / (double)SendOrderNum) * 100, 2).ToString() +"%";
+			}
+			return "0%";
+		}
+	}
 
     public class OrderReTransactVo
     {

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

@@ -27,4 +27,9 @@ public class SendOrderReportOutDto
     public int ReSendOrderNum { get; set; }
 
     public string ChainRate {get;set; }
+
+
+	public string StepName { get; set; }
+
+    public string RoldName => string.IsNullOrEmpty(StepName) ? "" : StepName == "派单组" ? "派单员" : "中心班长";
 }

+ 37 - 0
src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs

@@ -6,6 +6,43 @@ using System.ComponentModel.DataAnnotations;
 
 namespace Hotline.Share.Dtos.Snapshot;
 
+public class ThirdOpenIdInDto
+{
+    /// <summary>
+    /// OpenId
+    /// </summary>
+    public string OpenId { get; set; }
+
+    /// <summary>
+    /// UnionId
+    /// </summary>
+    public string UnionId { get; set; }
+
+    /// <summary>
+    /// 第三方平台类型(不传默认微信)
+    /// 0: 微信
+    /// </summary>
+    public EThirdType ThirdType { get; set; } = EThirdType.WeChat;
+
+    /// <summary>
+    /// 登录app(不传默认随手拍)
+    /// 1: 随手拍
+    /// 2: 部门办件app
+    /// 3: 市民办件app
+    /// </summary>
+    public EAppType AppType { get; set; } = EAppType.Snapshot;
+
+    /// <summary>
+    /// 接口地址前缀
+    /// </summary>
+    public string? WebApiHost { get; set; }
+
+    /// <summary>
+    /// 电话号码
+    /// </summary>
+    public string? PhoneNumber { get; set; }
+}
+
 public class ThirdTokenInDto
 {
     /// <summary>

+ 43 - 0
src/Hotline.Share/Dtos/WebPortal/GetOrderCodePwd.cs

@@ -633,4 +633,47 @@ namespace Hotline.Share.Dtos.WebPortal
         public string? Name { get; set; }
     }
 
+    public class VerificationPic
+    {
+        /// <summary>
+        /// 图片
+        /// </summary>
+        public string? base64 { get; set; }
+
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string? guid { get; set; }
+    }
+
+    public class VerificationPicDto
+    {
+        /// <summary>
+        /// 验证码
+        /// </summary>
+        public string verCode { get; set; }
+
+        /// <summary>
+        /// Guid
+        /// </summary>
+        public string guid { get; set; }
+
+        /// <summary>
+        /// 号码
+        /// </summary>
+        public string TelNum { get; set; }
+    }
+
+    public class VerificationSms
+    {
+        /// <summary>
+        /// 号码
+        /// </summary>
+        public string TelNum { get; set; }
+
+        /// <summary>
+        /// 验证码
+        /// </summary>
+        public string SmsCode { get; set; }
+    }
 }

+ 8 - 0
src/Hotline.Share/Dtos/WebPortal/WebFlowAcceptDto.cs

@@ -137,4 +137,12 @@ namespace Hotline.Share.Dtos.WebPortal
         /// </summary>
         public string? AreaCode { get; set; }
     }
+
+    public class WebFlowAcceptSmsDto : WebFlowAcceptDto
+    {
+        /// <summary>
+        /// 短信验证码
+        /// </summary>
+        public string SmsCode { get; set; }
+    }
 }

+ 22 - 0
src/Hotline.Share/Enums/CallCenter/ECallIdentity.cs

@@ -0,0 +1,22 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.CallCenter
+{
+    /// <summary>
+    /// 通话身份
+    /// </summary>
+    public enum ECallIdentity
+    {
+        /// <summary>
+        /// 黑名单
+        /// </summary>
+        [Description("黑名单")]
+        Black = 0,
+
+        /// <summary>
+        /// 白名单
+        /// </summary>
+        [Description("白名单")]
+        White = 1,
+    }
+}

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

@@ -218,16 +218,8 @@ public record OrgDataListDetailRequest : PagedRequest
 /// <summary>
 /// 部门超期全部数据
 /// </summary>
-public record OrgDataListAllDetailRequest : PagedRequest
+public record OrgDataListAllDetailRequest : PagedKeywordRequest
 {
-    /// <summary>
-    /// 开始时间
-    /// </summary>
-    public DateTime StartTime { get; set; }
-    /// <summary>
-    /// 结束时间
-    /// </summary>
-    public DateTime EndTime { get; set; }
     /// <summary>
     /// 部门Code
     /// </summary>
@@ -523,6 +515,8 @@ public record QuerySendOrderDetailRequest : ReportPagedRequest
     public string UserId { get; set; }
 
     public string TitleCode { get; set; }
+
+    public string RoleName { get; set; }
 }
 
 public record QueryUnsignedOrdersRequest : ReportPagedRequest

+ 36 - 0
src/Hotline/CallCenter/BlackLists/WhiteBlackLog.cs

@@ -0,0 +1,36 @@
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.BlackLists
+{
+    /// <summary>
+    /// 黑白名单操作记录
+    /// </summary>
+    [Description("黑白名单操作记录")]
+    public class WhiteBlackLog : CreationEntity
+    {
+        /// <summary>
+        /// 操作类型 1:黑名单 2:白名单
+        /// </summary>
+        [SugarColumn(ColumnDescription = "操作类型", ColumnDataType = "varchar(50)")]
+        public string LogType { get; set; }
+
+        /// <summary>
+        /// 操作动作
+        /// </summary>
+        [SugarColumn(ColumnDescription = "操作类型", ColumnDataType = "varchar(50)")]
+        public string LogAction { get; set; }
+
+        /// <summary>
+        /// 操作号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "操作号码")]
+        public string PhoneNum { get; set; }
+
+        /// <summary>
+        /// 有效期
+        /// </summary>
+        public DateTime? ValidDateTime { get; set; }
+    }
+}

+ 5 - 0
src/Hotline/CallCenter/Calls/CallNative.cs

@@ -178,5 +178,10 @@ namespace Hotline.CallCenter.Calls
         /// </summary>
         [SugarColumn(ColumnDescription = "软删除", DefaultValue = "f")]
         public bool IsDeleted { get; set; }
+
+        /// <summary>
+        /// 通话类型,黑白名单
+        /// </summary>
+        public ECallIdentity? CallIdentity {  get; set; }
     }
 }

+ 106 - 0
src/Hotline/Orders/OrderDelayAutomatic.cs

@@ -0,0 +1,106 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Orders
+{
+	/// <summary>
+	/// 工单自动延期
+	/// </summary>
+	[Description("工单自动延期")]
+	public class OrderDelayAutomatic : CreationEntity
+	{
+		/// <summary>
+		/// 工单Id
+		/// </summary>
+		public string OrderId { get; set; }
+
+		/// <summary>
+		/// 流程Id
+		/// </summary>
+		public string WorkflowId { get; set; }
+
+		public string Title { get; set; }
+
+		public string No { get; set; }
+
+		/// <summary>
+		/// 交办时间(中心交部门办理时间)
+		/// </summary>
+		[SugarColumn(ColumnDescription = "交办时间")]
+		public DateTime? CenterToOrgTime { get; set; }
+
+		/// <summary>
+		/// 超期时间(期满时间)
+		/// </summary>
+		[SugarColumn(ColumnDescription = "超期时间")]
+		public DateTime? ExpiredTime { get; set; }
+
+		/// <summary>
+		/// 受理类型代码
+		/// </summary>
+		[SugarColumn(ColumnDescription = "受理类型代码")]
+		public string? AcceptTypeCode { get; set; }
+
+		/// <summary>
+		/// 延期后超期时间(期满时间)
+		/// </summary>
+		[SugarColumn(ColumnDescription = "延期后超期时间")]
+		public DateTime? DelayExpiredTime { get; set; }
+
+		/// <summary>
+		/// 记录类型
+		/// </summary>
+		public EOrderDelayAutomaticType Type { get; set; }
+
+		/// <summary>
+		/// 记录状态
+		/// </summary>
+		public EOrderDelayAutomaticStatus Status { get; set; }
+
+		/// <summary>
+		/// 处理时间
+		/// </summary>
+		public DateTime ProcessedTime { get; set; }
+
+
+		[SqlSugar.SugarColumn(IsEnableUpdateVersionValidation = true)]
+		public Guid Ver { get; set; }
+	}
+
+
+	public enum EOrderDelayAutomaticStatus
+	{
+		/// <summary>
+		/// 待处理
+		/// </summary>
+		Pending = 0,
+
+		/// <summary>
+		/// 处理中
+		/// </summary>
+		BeingProcessed = 1,
+
+		/// <summary>
+		/// 已处理
+		/// </summary>
+		Processed = 2,
+	}
+
+	public enum EOrderDelayAutomaticType	
+	{
+		/// <summary>
+		/// 自动延期
+		/// </summary>
+		Automatic = 0,
+		/// <summary>
+		/// 延期短信
+		/// </summary>
+		Sms = 1,
+	}
+}

+ 9 - 4
src/Hotline/Orders/OrderDomainService.cs

@@ -41,6 +41,9 @@ using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Enums.Settings;
 using Hotline.Settings.TimeLimitDomain;
 using Hotline.Snapshot.IRepository;
+using Hotline.Share.Dtos.Push;
+using System.Threading;
+using XF.Domain.Entities;
 
 namespace Hotline.Orders;
 
@@ -73,8 +76,9 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     private readonly ICalcExpireTime _expireTime;
     private readonly ICallDomainService _callDomainService;
     private readonly IOrderVisitDomainService _orderVisitDomainService;
+	
 
-    public OrderDomainService(
+	public OrderDomainService(
         IOrderRepository orderRepository,
         IRepository<OrderRedo> orderRedoRepository,
         IRepository<OrderPublish> orderPublishRepository,
@@ -131,7 +135,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         _expireTime = expireTime;
         _callDomainService = callDomainService;
         _orderVisitDomainService = orderVisitDomainService;
-    }
+	}
 
     /// <summary>
     /// 归档后自动发布
@@ -973,9 +977,10 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     }
     #endregion
 
-    #region private
 
-    private async Task<Order> GetOrderByFlowIdAsync(string workflowId, CancellationToken cancellationToken)
+	#region private
+
+	private async Task<Order> GetOrderByFlowIdAsync(string workflowId, CancellationToken cancellationToken)
     {
         if (string.IsNullOrEmpty(workflowId))
             throw UserFriendlyException.SameMessage("无效流程编号");

+ 29 - 21
src/Hotline/Push/FWMessage/PushDomainService.cs

@@ -3,18 +3,12 @@ using Hotline.EventBus;
 using Hotline.Orders;
 using Hotline.Push.Notifies;
 using Hotline.Share.Dtos.Push;
-using Hotline.Share.Dtos.SendSms;
 using Hotline.Share.Enums.Push;
+using Hotline.Share.Tools;
 using Mapster;
 using MapsterMapper;
-using MediatR;
-using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using System.Net;
-using System.Net.Http.Json;
 using System.Text.RegularExpressions;
-using System.Xml;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
@@ -107,19 +101,33 @@ public class PushDomainService : IPushDomainService, IScopeDependency
         }
         #endregion
         var message = _mapper.Map<Message>(messageDto);
+        //验证手机号是否正确
+        var isRight = message.TelNumber.IsPhoneNumber();
+        //如果不正确直接写入失败,不往短信平台推送
+        if (isRight == false)
+        {
+            message.Status = EPushStatus.Failed;
+            message.SendState = ESendState.Failed;
+            message.Reason = "手机号码格式错误";
+        }
+
         var id = await _messageRepository.AddAsync(message, cancellation);//写入本地数据库
 
-        PushMessageDto pushMessage = new()
+        //手机号码正确推送短信
+        if (isRight)
         {
-            ClientId = "Hotline",
-            ExternalId = id,
-            Content = message.Content,
-            Remark = message.Remark,
-            Name = message.Name,
-            TelNumber = message.TelNumber
-        };
-        //消息队列
-        await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.SendSms, pushMessage, cancellationToken: cancellation);
+            PushMessageDto pushMessage = new()
+            {
+                ClientId = "Hotline",
+                ExternalId = id,
+                Content = message.Content,
+                Remark = message.Remark,
+                Name = message.Name,
+                TelNumber = message.TelNumber
+            };
+            //消息队列
+            await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.SendSms, pushMessage, cancellationToken: cancellation);
+        }
     }
 
     /// <summary>
@@ -171,11 +179,11 @@ public class PushDomainService : IPushDomainService, IScopeDependency
                 messData.SmsReplyTime = Convert.ToDateTime(dto.SmsReplyTime);
                 messData.SmsReplyContent = dto.SmsReplyContent;
                 await _messageRepository.UpdateAsync(messData, cancellation);
-            }
 
-            var notify = messData.Adapt<ReceiveMessageNotify>();
-            notify.NotifyDto.Type = dto.Type;
-            await _publisher.PublishAsync(notify, PublishStrategy.ParallelWhenAll, cancellation);
+                var notify = messData.Adapt<ReceiveMessageNotify>();
+                notify.NotifyDto.Type = dto.Type;
+                await _publisher.PublishAsync(notify, PublishStrategy.ParallelWhenAll, cancellation);
+            }
         }
 
     }

+ 8 - 0
src/Hotline/Snapshot/Contracts/ISnapshotPointsDomainService.cs

@@ -12,5 +12,13 @@ namespace Hotline.Snapshot.Contracts;
 /// </summary>
 public interface ISnapshotPointsDomainService
 {
+    /// <summary>
+    /// 新增积分
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="source"></param>
+    /// <param name="status"></param>
+    /// <param name="extraDeductedPoints"></param>
+    /// <returns></returns>
     Task AddPointsAsync(string orderId, EPointsSource source, ESnapshotSMSStatus? status, int? extraDeductedPoints);
 }

+ 15 - 10
src/Hotline/Snapshot/Services/SnapshotPointsDomainService.cs

@@ -36,19 +36,24 @@ public class SnapshotPointsDomainService : ISnapshotPointsDomainService, IScopeD
         if (order.ReportPoints.HasValue == false)
             throw new UserFriendlyException($"{order.Name} 行业未配置积分");
 
-        var point = 0;
-        if (source == EPointsSource.Report)
-            point = order.ReportPoints.Value;
-        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Agree)
-            point = order.ArgeePoints ?? 0;
-        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Refuse)
-            point = order.RefusePoints ?? 0 + extraDeductedPoints ?? 0;
-        await _pointsRecordRepository.AddAsync(new SnapshotPointsRecord
+        var points = new SnapshotPointsRecord
         {
             UserId = order.CreatorId,
             OrderId = orderId,
             Points = order.ReportPoints.Value,
-            Source = source
-        });
+            Source = source,
+            Direction = Share.Enums.CallCenter.EPointsDirection.In
+        };
+        if (source == EPointsSource.Report)
+            points.Points = order.ReportPoints.Value;
+        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Agree)
+            points.Points = order.ArgeePoints ?? 0;
+        if (source == EPointsSource.Audit && status == ESnapshotSMSStatus.Refuse)
+        {
+            points.Direction = Share.Enums.CallCenter.EPointsDirection.Out;
+            points.Points = order.RefusePoints ?? 0 + extraDeductedPoints ?? 0;
+            points.Points *= -1;
+        }
+        await _pointsRecordRepository.AddAsync(points);
     }
 }

+ 1 - 1
src/TianQue.Sdk/Models/ApiReponse.cs

@@ -43,5 +43,5 @@ public class AcceptInfoSuccessDto
     /// <summary>
     /// OrgId
     /// </summary>
-    public uint OrgId { get; set; }
+    public string OrgId { get; set; }
 }

+ 77 - 0
src/XingTang.Sdk/XingtangBlackPhone.cs

@@ -0,0 +1,77 @@
+using SqlSugar;
+
+namespace XingTang.Sdk
+{
+    /// <summary>
+    /// 兴唐黑名单
+    /// </summary>
+    [SugarTable("sys_blackphone")]
+    public class XingtangBlackPhone
+    {
+        [SugarColumn(IsPrimaryKey = true)]
+        /// <summary>
+        /// 黑名单号码ID
+        /// </summary>
+        public int BlackPhoneId { get; set; }
+
+        /// <summary>
+        /// 黑名单号码起始号段
+        /// </summary>
+        public string? StartPhone { get; set; }
+
+        /// <summary>
+        /// 黑名单号码结束号段
+        /// </summary>
+        public string? EndPhone { get; set; }
+
+        /// <summary>
+        /// 类别(分号段和单号,单号即起始号段) 直接填0
+        /// </summary>
+        public int? PhoneType { get; set; }
+
+        /// <summary>
+        /// 黑名单组ID
+        /// </summary>
+        public string? BlackGroupid { get; set; }
+
+        /// <summary>
+        /// 有效期
+        /// </summary>
+        public DateTime? ValidDateTime { get; set; }
+
+        /// <summary>
+        /// 是否生效(0:不生效,1:生效)
+        /// </summary>
+        public int? Enabled { get; set; }
+
+        /// <summary>
+        /// 排序码
+        /// </summary>
+        public int? SortCode { get; set; }
+
+        /// <summary>
+        /// 删除标记
+        /// </summary>
+        public int? DeleteMark { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime? CreateDate { get; set; }
+
+        /// <summary>
+        /// 创建用户主键
+        /// </summary>
+        public string? CreateUserId { get; set; }
+
+        /// <summary>
+        /// 修改时间
+        /// </summary>
+        public DateTime? ModifyDate { get; set; }
+
+        /// <summary>
+        /// 修改用户主键
+        /// </summary>
+        public string? ModifyUserId { get; set; }
+    }
+}

+ 10 - 0
src/XingTang.Sdk/XingtangCall.cs

@@ -127,6 +127,16 @@ public class XingtangCall
     /// </summary>
     public string? OrgCaller { get; set; }
 
+    /// <summary>
+    /// 黑名单
+    /// </summary>
+    public int? BlackList { get; set; }
+
+    /// <summary>
+    /// 白名单
+    /// </summary>
+    public int? WhiteList { get; set; }
+
     #region 未启用
 
     public int? MutiCall { get; set; }

+ 33 - 0
src/XingTang.Sdk/XingtangWhitePhone.cs

@@ -0,0 +1,33 @@
+using SqlSugar;
+
+namespace XingTang.Sdk
+{
+    /// <summary>
+    /// 兴唐白名单
+    /// </summary>
+    [SugarTable("sys_whitephone")]
+    public class XingtangWhitePhone
+    {
+        [SugarColumn(IsPrimaryKey = true)]
+        /// <summary>
+        /// 号码ID
+        /// </summary>
+        public int Id { get; set; }
+
+        /// <summary>
+        /// 号码
+        /// </summary>
+        public string? Phone { get; set; }
+
+        /// <summary>
+        /// 有效期
+        /// </summary>
+        public DateTime? ValidDateTime { get; set; }
+
+        /// <summary>
+        /// 是否生效(0:不生效,1:生效)
+        /// </summary>
+        public int? Enabled { get; set; }
+
+    }
+}

+ 9 - 1
test/Hotline.Tests/Infrastructure/TianQueTest.cs

@@ -1,14 +1,20 @@
 using Hotline.File;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.IRepository;
+using Shouldly;
 using TianQue.Sdk;
+using TianQue.Sdk.Models;
 
 namespace Hotline.Tests.Infrastructure;
 public class TianQueTest
 {
     private readonly IFileDomainService _fileDomainService;
+    private readonly IGuiderSystemService _tiqnQueService;
 
-    public TianQueTest(IFileDomainService fileDomainService)
+    public TianQueTest(IFileDomainService fileDomainService, IGuiderSystemService tiqnQueService)
     {
         _fileDomainService = fileDomainService;
+        _tiqnQueService = tiqnQueService;
     }
 
     [Fact]
@@ -34,6 +40,8 @@ public class TianQueTest
 
         //// Assert
         //Assert.Equal("ok", result);
+
+        //await _tiqnQueService.PostOrder(null, null, null);
     }
 
     [Fact]