Browse Source

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

libin 1 week ago
parent
commit
0e6d09176e
29 changed files with 1302 additions and 57 deletions
  1. 256 0
      src/Hotline.Api/Controllers/CallNativeContrroller.cs
  2. 63 0
      src/Hotline.Api/Controllers/IPPbxController.cs
  3. 14 0
      src/Hotline.Api/Controllers/IdentityController.cs
  4. 144 10
      src/Hotline.Api/Controllers/OrderController.cs
  5. 8 8
      src/Hotline.Api/StartupHelper.cs
  6. 5 1
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  7. 8 0
      src/Hotline.Application/Identity/IIdentityAppService.cs
  8. 16 3
      src/Hotline.Application/Identity/IdentityAppService.cs
  9. 12 3
      src/Hotline.Application/Mappers/CallMapperConfigs.cs
  10. 13 0
      src/Hotline.Application/OrderApp/IOrderApplication.cs
  11. 228 5
      src/Hotline.Application/OrderApp/OrderApplication.cs
  12. 26 2
      src/Hotline.Application/Snapshot/RedPackApplication.cs
  13. 2 0
      src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs
  14. 85 0
      src/Hotline.Share/Dtos/CallNative/BlackPhoneListDto.cs
  15. 27 0
      src/Hotline.Share/Dtos/CallNative/BlackPhoneListQuery.cs
  16. 27 9
      src/Hotline.Share/Dtos/FlowEngine/Workflow/NextWorkflowDto.cs
  17. 37 0
      src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs
  18. 22 0
      src/Hotline.Share/Enums/CallCenter/ECallIdentity.cs
  19. 36 0
      src/Hotline/CallCenter/BlackLists/WhiteBlackLog.cs
  20. 5 0
      src/Hotline/CallCenter/Calls/CallNative.cs
  21. 106 0
      src/Hotline/Orders/OrderDelayAutomatic.cs
  22. 9 4
      src/Hotline/Orders/OrderDomainService.cs
  23. 8 0
      src/Hotline/Snapshot/Contracts/ISnapshotPointsDomainService.cs
  24. 15 10
      src/Hotline/Snapshot/Services/SnapshotPointsDomainService.cs
  25. 1 1
      src/TianQue.Sdk/Models/ApiReponse.cs
  26. 77 0
      src/XingTang.Sdk/XingtangBlackPhone.cs
  27. 10 0
      src/XingTang.Sdk/XingtangCall.cs
  28. 33 0
      src/XingTang.Sdk/XingtangWhitePhone.cs
  29. 9 1
      test/Hotline.Tests/Infrastructure/TianQueTest.cs

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

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

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

+ 13 - 0
src/Hotline.Application/OrderApp/IOrderApplication.cs

@@ -513,5 +513,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);
+
 	}
 }

+ 228 - 5
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>
     /// 更新工单办理期满时间(延期调用,其他不调用)
@@ -6734,5 +6740,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; }
 }

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

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

+ 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("无效流程编号");

+ 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]