Ver Fonte

Merge branch 'master' of http://110.188.24.182:10023/Fengwo/hotline

田爽 há 1 ano atrás
pai
commit
f6bf970edc

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

@@ -0,0 +1,114 @@
+using Hotline.Permissions;
+using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Users;
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using Tr.Sdk;
+using Tr.Sdk.Blacklist;
+using Tr.Sdk.Tels;
+using XF.Domain.Authentications;
+
+namespace Hotline.Api.Controllers
+{
+    public class IPPbxController:BaseController
+    {
+        private readonly ITrClient _trClient;
+        private readonly IMapper _mapper;
+        private readonly IUserDomainService _userDomainService;
+        private readonly ISessionContext _sessionContext;
+
+        public IPPbxController(ITrClient trClient,IMapper mapper,IUserDomainService userDomainService,ISessionContext sessionContext)
+        {
+            _trClient = trClient;
+            _mapper = mapper;
+            _userDomainService = userDomainService;
+            _sessionContext = sessionContext;
+        }
+
+        #region 添添呼
+
+        #region 分机
+        /// <summary>
+        /// 查询所有分机
+        /// </summary>
+        /// <returns></returns>
+        [Permission(EPermission.QueryTels)]
+        [HttpGet("query-tels")]
+        public async Task<IReadOnlyList<TrTelDto>> TrQueryTels()
+        {
+            var tels = await _trClient.QueryTelsAsync(new QueryTelRequest() { }, HttpContext.RequestAborted);
+            return _mapper.Map<IReadOnlyList<TrTelDto>>(tels);
+        }
+
+        #endregion
+
+        #region 黑白名单
+
+        /// <summary>
+        /// 新增黑白名单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [Permission(EPermission.AddBlackList)]
+        [HttpPost("add-blacklist")]
+        public async Task AddBlacklist([FromBody]TrAddBlacklistDto dto)
+        {
+            await _trClient.AddBlacklistAsync(new AddBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 删除黑白名单
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [Permission(EPermission.RemoveBlacklist)]
+        [HttpPost("remove-blacklist")]
+        public async Task DelBlacklist([FromBody]TrDelBlacklistDto dto)
+        {
+            await _trClient.DelBlacklistAsync(new DelBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 查询黑白名单
+        /// </summary>
+        /// <returns></returns>
+        [Permission(EPermission.QueryBulletinList)]
+        [HttpGet("query-blacklist")]
+        public async Task<IReadOnlyList<TrQueryBlacklistResponseDto>> QueryBlacklist([FromQuery]TrQueryBlacklistDto dto)
+        {
+            var blacklist = await _trClient.QueryBlacklistAsync(new QueryBlacklistRequest() { Phone = dto.Phone, SpecialFlag = dto.SpecialFlag }, HttpContext.RequestAborted);
+            return _mapper.Map<IReadOnlyList<TrQueryBlacklistResponseDto>>(blacklist);
+        }
+
+        #endregion
+
+        #region 签入后调用
+
+        /// <summary>
+        /// 上班
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [Permission(EPermission.OnDuty)]
+        [HttpPost("on-duty")]
+        public async Task OnDuty([FromBody] TrOnDutyDto dto)
+        {
+            await _userDomainService.TrOnDutyAsync(_sessionContext.RequiredUserId, dto.TelId, dto.TelNo, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 下班
+        /// </summary>
+        /// <returns></returns>
+        [Permission(EPermission.OffDuty)]
+        [HttpPost("off-duty")]
+        public async Task OffDuty()
+        {
+            await _userDomainService.TrOffDutyAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
+        }
+
+        #endregion
+
+        #endregion
+    }
+}

+ 5 - 0
src/Hotline.Api/Controllers/PbxController.cs

@@ -27,6 +27,7 @@ using Hotline.FlowEngine.WorkflowModules;
 using Hotline.Share.Dtos;
 using Microsoft.AspNetCore.Authorization;
 using XF.Domain.Repository;
+using Tr.Sdk;
 
 namespace Hotline.Api.Controllers
 {
@@ -59,6 +60,7 @@ namespace Hotline.Api.Controllers
         private readonly IWexClient _wexClient;
         private readonly IWexTelGroupRepository _wexTelGroupRepository;
         private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+        
 
         public PbxController(
             ITelRepository telRepository,
@@ -112,6 +114,9 @@ namespace Hotline.Api.Controllers
             _systemDicDataCacheManager = systemDicDataCacheManager;
         }
 
+        
+
+
         #region 话机
 
 

+ 2 - 1
src/Hotline.Api/Controllers/TestController.cs

@@ -136,7 +136,8 @@ public class TestController : BaseController
 
         //var rsp = await _daprClient.InvokeMethodAsync<ApiResponse<string>>(HttpMethod.Get, "identity", "api/v1/Test/time", HttpContext.RequestAborted);
         //var rsp1 = await _daprClient.InvokeMethodAsync<int, ApiResponse<string>>(HttpMethod.Post, "identity", "api/v1/Test/time1", 222, HttpContext.RequestAborted);
-        
+        var a = await _trClient.QueryTelsAsync(new Tr.Sdk.Tels.QueryTelRequest() { }, HttpContext.RequestAborted);
+
         return OpenResponse.Ok(DateTime.Now.ToString("F"));
     }
 

+ 6 - 3
src/Hotline.Api/config/appsettings.Development.json

@@ -90,9 +90,12 @@
     "ServiceName": "hotline"
   },
   "Tr": {
-    "Address": "http://internal.ttf-cti.com:8080",
-    "Username": "yscs",
-    "Password": "123456"
+    //"Address": "http://internal.ttf-cti.com:8080",
+    //"Username": "yscs",
+    //"Password": "123456",
+    "Address": "http://222.213.23.229:29003/",
+    "Username": "root",
+    "Password": "12345678aa"
   }
 
 }

+ 6 - 3
src/Hotline.Api/config/appsettings.json

@@ -90,8 +90,11 @@
     "ServiceName": "hotline"
   },
   "Tr": {
-    "Address": "http://internal.ttf-cti.com:8080",
-    "Username": "yscs",
-    "Password": "123456"
+    //"Address": "http://internal.ttf-cti.com:8080",
+    //"Username": "yscs",
+    //"Password": "123456"
+    "Address": "http://222.213.23.229:29003/",
+    "Username": "root",
+    "Password": "12345678aa"
   }
 }

+ 4 - 4
src/Hotline.Application/Mappers/WorkflowMapperConfigs.cs

@@ -18,9 +18,9 @@ public class WorkflowMapperConfigs : IRegister
             .Ignore(d => d.PrevStepId)
             .Ignore(d => d.IsMain)
             .Ignore(d => d.Status)
-            .Ignore(d => d.ParentId)
+            //.Ignore(d => d.ParentId)
             .Ignore(d => d.Handlers)
-            .Ignore(d => d.Steps)
+            //.Ignore(d => d.Steps)
             .Ignore(d => d.StartCountersignId)
             .Ignore(d => d.CountersignId)
             .Ignore(d => d.IsStartedCountersignEnd)
@@ -81,8 +81,8 @@ public class WorkflowMapperConfigs : IRegister
             .Map(d => d.RealCommunicationAddress, s => s.RealCommunicationAddress)
             .IgnoreNonMapped(true);
 
-        config.ForType<WorkflowDefinition, WorkflowStep>()
-            .Ignore(d => d.Steps);
+        //config.ForType<WorkflowDefinition, WorkflowStep>()
+        //    .Ignore(d => d.Steps);
 
     }
 }

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

@@ -64,4 +64,6 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public List<TelGroupDto> Groups { get; set; }
     }
+
+   
 }

+ 68 - 0
src/Hotline.Share/Dtos/TrCallCenter/TrTelDao.cs

@@ -0,0 +1,68 @@
+
+namespace Hotline.Share.Dtos.TrCallCenter
+{
+    #region 分机
+    public class TrTelDto
+    {
+        public string Id { get; set; }
+
+        public string Name { get; set; }
+
+        public string TelNo { get; set; }
+
+        public string Description { get; set; }
+    }
+
+    #endregion
+
+    #region 黑白名单
+    public class TrAddBlacklistDto
+    {
+        public string Phone { get; set; }
+
+        public int SpecialFlag { get; set; }
+    }
+
+    public class TrDelBlacklistDto
+    {
+        public string Phone { get; set; }
+
+        public int? SpecialFlag { get; set; }
+    }
+
+    public class TrQueryBlacklistDto
+    {
+        public string? Phone { get; set; }
+
+        /// <summary>
+        /// 1:白名单;2:呼入黑名单;3:呼出黑名单;4:呼入呼出黑名单;
+        /// </summary>
+        public int? SpecialFlag { get; set; }
+    }
+
+    public class TrQueryBlacklistResponseDto
+    {
+        public string Id { get; set; }
+
+        public string UserId { get; set; }
+
+        public DateTime CreationTime { get; set; }
+
+        public string Phone { get; set; }
+
+        public int SpecialFlag { get; set; }
+
+        public int? Priority { get; set; }
+    }
+    #endregion
+
+    #region 登录
+
+    public class TrOnDutyDto
+    {
+        public string TelId { get; set; }
+        public string TelNo { get; set; }
+    }
+
+    #endregion
+}

+ 61 - 55
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -253,18 +253,33 @@ namespace Hotline.FlowEngine.Workflows
             var currentStep = GetUnHandleStep(workflow.Steps, _sessionContext.RequiredOrgId, _sessionContext.RequiredUserId);
             if (currentStep.Status is not EWorkflowStepStatus.WaitForAccept) return;
 
-            if (currentStep.HandlerType is EHandlerType.AssignedUser or EHandlerType.Role
-            && !currentStep.IsInCountersign()
-            && currentStep.InstanceMode is EInstanceMode.Config)
+            if(currentStep.HandlerType is EHandlerType.AssignedOrg or EHandlerType.OrgLevel or EHandlerType.OrgType
+               || (currentStep.InstanceMode is EInstanceMode.Dynamic && !currentStep.DynamicShouldTerminal())//动态并且非结束节点
+                   || (currentStep.IsInCountersign() && !currentStep.IsCountersignEndStep)//会签并且非会签节点
+                   )
             {
-                //userId
-                if (currentStep.Handlers.All(d => d.Key != userId)) return;
+                //orgId
+                if (currentStep.Handlers.All(d => d.Key != orgId)) return;
             }
             else
             {
-                //orgId
-                if (currentStep.Handlers.All(d => d.Key != orgId)) return;
+                //userId
+                if (currentStep.Handlers.All(d => d.Key != userId)) return;
             }
+
+            //if (currentStep.HandlerType is EHandlerType.AssignedUser or EHandlerType.Role
+            //&& !currentStep.IsInCountersign()
+            //&& currentStep.InstanceMode is EInstanceMode.Config
+            //)
+            //{
+            //    //userId
+            //    if (currentStep.Handlers.All(d => d.Key != userId)) return;
+            //}
+            //else
+            //{
+            //    //orgId
+            //    if (currentStep.Handlers.All(d => d.Key != orgId)) return;
+            //}
             if (currentStep.StepType is EStepType.End)
                 throw new UserFriendlyException("当前流程已流转到最终步骤");
 
@@ -413,7 +428,7 @@ namespace Hotline.FlowEngine.Workflows
             //todo 计算办理工作时长
 
             //更新当前办理节点信息
-            workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, currentStep, nextSteps?.First());
+            workflow.UpdateWorkflowCurrentStepInfo(dto.IsStartCountersign, currentStep, nextSteps?.FirstOrDefault());
 
             //发起会签时记录顶层会签节点
             if (dto.IsStartCountersign && !workflow.IsInCountersign())
@@ -459,18 +474,7 @@ namespace Hotline.FlowEngine.Workflows
                         && !workflow.Steps.Any(d => d.IsCountersignEndStep && d.CountersignStartStepId == countersignStartStep.Id))
                     {
                         //todo 创建会签汇总节点
-                        var countersignEndStep = _mapper.Map<WorkflowStep>(countersignStartStep);
-                        countersignEndStep.IsCountersignEndStep = true;
-                        countersignEndStep.CountersignStartStepId = countersignStartStep.Id;
-                        countersignEndStep.CountersignPosition = ECountersignPosition.Outer;
-                        countersignEndStep.CountersignId = countersignStartStep.StartCountersignId;
-                        countersignEndStep.Handlers = new List<Kv>
-                            { new(countersignStartStep.HandlerId, countersignStartStep.HandlerName) };
-                        countersignEndStep.TimeLimit = GetTimeLimit(""); //todo 过期时间
-                        //countersignEndStep.Name = $"{countersignStartStep.Name}汇总";
-                        countersignEndStep.Name = dto.NextStepName;
-
-                        await _workflowStepRepository.AddAsync(countersignEndStep, cancellationToken);
+                        var countersignEndStep = await CreateCountersignEndStepAsync(countersignStartStep, dto, cancellationToken);
                         nextSteps = new List<WorkflowStep> { countersignEndStep };
 
                         //create trace
@@ -485,7 +489,7 @@ namespace Hotline.FlowEngine.Workflows
                     //考虑没有下级部门情况
                     nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
                         EWorkflowStepStatus.WaitForAccept, currentStep.GetNextStepCountersignPosition(),
-                        expiredTime, cancellationToken);
+                        expiredTime, false, cancellationToken);
                 }
             }
             else if (dto.IsStartCountersign)
@@ -493,14 +497,14 @@ namespace Hotline.FlowEngine.Workflows
                 //todo 依据会签策略创建会签下一级节点
                 nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
                     EWorkflowStepStatus.WaitForAccept, currentStep.GetNextStepCountersignPosition(),
-                        expiredTime, cancellationToken);
+                        expiredTime, false, cancellationToken);
             }
             else if (currentStep.InstanceMode is EInstanceMode.Dynamic && !currentStep.DynamicShouldTerminal())
             {
                 //todo 创建动态下一级节点
                 nextSteps = await CreateStepsAsync(workflow, nextStepDefine, currentStep, dto, dto.NextHandlers,
                     EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
-                    expiredTime, cancellationToken);
+                    expiredTime, false, cancellationToken);
             }
             else
             {
@@ -511,6 +515,31 @@ namespace Hotline.FlowEngine.Workflows
             return nextSteps;
         }
 
+        private async Task<WorkflowStep> CreateCountersignEndStepAsync(WorkflowStep countersignStartStep,
+            BasicWorkflowDto dto, CancellationToken cancellationToken)
+        {
+            var csEndStep = _mapper.Map<WorkflowStep>(countersignStartStep);
+            _mapper.Map(dto, csEndStep);
+            csEndStep.Status = EWorkflowStepStatus.WaitForAccept;
+            csEndStep.NextSteps = new();
+            csEndStep.PrevStepId = null;
+            csEndStep.PrevStepCode = null;
+            csEndStep.IsOrigin = false;
+            csEndStep.CountersignId = countersignStartStep.StartCountersignId;
+            csEndStep.CountersignPosition = ECountersignPosition.Outer;
+            csEndStep.CountersignSteps = new();
+            csEndStep.IsCountersignEndStep = true;
+            csEndStep.CountersignStartStepId = countersignStartStep.Id;
+            csEndStep.Name = dto.NextStepName;
+            csEndStep.Handlers = new List<Kv> { new(countersignStartStep.HandlerId, countersignStartStep.HandlerName) };
+            csEndStep.TimeLimit = GetTimeLimit(""); //todo 过期时间
+
+            csEndStep.Reset();
+
+            await _workflowStepRepository.AddAsync(csEndStep, cancellationToken);
+            return csEndStep;
+        }
+
         /// <summary>
         /// 退回(返回前一节点)
         /// </summary>
@@ -756,7 +785,6 @@ namespace Hotline.FlowEngine.Workflows
                 return new(new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), new List<Kv>());
             var steps = workflow.Steps
                 .Where(d => d.StepType is EStepType.Normal && d.BusinessType is EBusinessType.Department)
-                .SelectMany(d => d.Steps)
                 .ToList();
             var items = steps.Select(d => new Kv(d.HandlerOrgId, d.HandlerOrgName)).ToList();
             return (new Kv(workflow.ActualHandleOrgCode, workflow.ActualHandleOrgName), items);
@@ -1015,7 +1043,7 @@ namespace Hotline.FlowEngine.Workflows
             newStep.Status = EWorkflowStepStatus.WaitForAccept;
             newStep.PrevStepId = step.PrevStepId;
             newStep.IsMain = step.IsMain;
-            newStep.ParentId = step.ParentId;
+            //newStep.ParentId = step.ParentId;
             newStep.Handlers = step.Handlers;
             newStep.StartCountersignId = step.StartCountersignId;
             newStep.CountersignId = step.CountersignId;
@@ -1057,30 +1085,6 @@ namespace Hotline.FlowEngine.Workflows
         }
 
 
-        /// <summary>
-        /// 更新下级汇总节点可办理状态
-        /// </summary>
-        /// <param name="nextStepBox"></param>
-        /// <param name="currentStep"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        private async Task SetNextCountersignEndAssignedAsync(WorkflowStep nextStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
-        {
-            var nextSteps = currentStep.CountersignPosition is ECountersignPosition.Inner
-                ? nextStepBox.Steps.Where(d => d.CountersignId == currentStep.CountersignId).ToList()
-                : nextStepBox.Steps.Where(d => d.PrevStepId == currentStep.Id).ToList();
-
-            if (!nextSteps.Any())
-                throw new UserFriendlyException($"未查询到下一节点, currentStepId: {currentStep.Id}");
-
-            foreach (var nextStep in nextSteps)
-            {
-                nextStep.SetAssigned();
-            }
-
-            await _workflowStepRepository.UpdateRangeAsync(nextSteps, cancellationToken);
-        }
-
         private async Task JumpTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
         {
             //未办理的traces
@@ -1325,7 +1329,7 @@ namespace Hotline.FlowEngine.Workflows
 
             var step = CreateStep(endStepDefine, prevStep, workflow.Id, new List<Kv> { handler },
                  null, null, null,
-                     EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, DateTime.Now, endStepDefine.Name);
+                     EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None, DateTime.Now, endStepDefine.Name, true);
 
             //step.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName,
             //    _sessionContext.RequiredOrgId, _sessionContext.OrgName,
@@ -1410,7 +1414,7 @@ namespace Hotline.FlowEngine.Workflows
 
             return await CreateStepsAsync(workflow, stepDefine, prevStep, dto, handlers,
                 EWorkflowStepStatus.WaitForAccept, ECountersignPosition.None,
-                expiredTime, cancellationToken);
+                expiredTime, true, cancellationToken);
         }
 
         //new
@@ -1424,6 +1428,7 @@ namespace Hotline.FlowEngine.Workflows
             EWorkflowStepStatus stepStatus,
             ECountersignPosition csPosition,
             DateTime expiredTime,
+            bool isOrigin,
             CancellationToken cancellationToken
             )
         {
@@ -1436,7 +1441,7 @@ namespace Hotline.FlowEngine.Workflows
                 {
                     var step = CreateStep(stepDefine, prevStep, workflow.Id, new List<Kv> { handler },
                         dto.NextStepCode, dto.NextMainHandler, countersignId,
-                        stepStatus, csPosition, expiredTime, dto.NextStepName);
+                        stepStatus, csPosition, expiredTime, dto.NextStepName, isOrigin);
 
                     steps.Add(step);
                 }
@@ -1445,7 +1450,7 @@ namespace Hotline.FlowEngine.Workflows
             {
                 var step = CreateStep(stepDefine, prevStep, workflow.Id, handlers,
                     dto.NextStepCode, dto.NextMainHandler, countersignId,
-                    stepStatus, csPosition, expiredTime, dto.NextStepName);
+                    stepStatus, csPosition, expiredTime, dto.NextStepName, isOrigin);
 
                 steps.Add(step);
             }
@@ -1700,7 +1705,8 @@ namespace Hotline.FlowEngine.Workflows
             EWorkflowStepStatus stepStatus,
             ECountersignPosition countersignPosition,
             DateTime expiredTime,
-            string stepName
+            string stepName,
+            bool isOrigin
             )
         {
             if (!handlers.Any())
@@ -1720,7 +1726,7 @@ namespace Hotline.FlowEngine.Workflows
             step.CountersignPosition = countersignPosition;
             step.StepExpiredTime = expiredTime;
             step.TimeLimit = GetTimeLimit("");//todo 过期时间
-            step.IsOrigin = true;
+            step.IsOrigin = isOrigin;
             step.Name = stepName;
 
             return step;

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

@@ -39,16 +39,16 @@ public class WorkflowStep : StepBasicEntity
 
     #region 会签
 
-    /// <summary>
-    /// 会签流程生命周期上级节点Id(会签流程中的节点才有)
-    /// </summary>
-    public string? ParentId { get; set; }
+    ///// <summary>
+    ///// 会签流程生命周期上级节点Id(会签流程中的节点才有)
+    ///// </summary>
+    //public string? ParentId { get; set; }
 
-    /// <summary>
-    /// 会签流程生命周期下级节点
-    /// </summary>
-    [SugarColumn(IsIgnore = true)]
-    public List<WorkflowStep> Steps { get; set; } = new();
+    ///// <summary>
+    ///// 会签流程生命周期下级节点
+    ///// </summary>
+    //[SugarColumn(IsIgnore = true)]
+    //public List<WorkflowStep> Steps { get; set; } = new();
 
     /// <summary>
     /// 会签id(或外层会签的id)
@@ -141,18 +141,6 @@ public class WorkflowStep : StepBasicEntity
             NextSteps.First(d => d.Code == nextStepCode).Selected = true;
     }
 
-    /// <summary>
-    /// 检查stepBox状态,如果子节点全都办理,则stepBox办理完成
-    /// </summary>
-    public void CheckStepBoxStatusAndUpdate()
-    {
-        if (Status is not EWorkflowStepStatus.Handled && Steps.All(d => d.Status is EWorkflowStepStatus.Handled))
-        {
-            Status = EWorkflowStepStatus.Handled;
-            HandleTime = Steps.Max(d => d.HandleTime);
-        }
-    }
-
     /// <summary>
     /// 会签结束
     /// </summary>
@@ -194,7 +182,7 @@ public class WorkflowStep : StepBasicEntity
     public bool IsOrg() => BusinessType is EBusinessType.Department;
 
     /// <summary>
-    /// 重置节点,只清除办理痕迹(退回场景,将上一级节点重置等待重新办理)
+    /// 重置节点,只清除办理痕迹(退回场景/依据会签开始节点复制会签汇总节点,将上一级节点重置等待重新办理)
     /// </summary>
     public void Reset()
     {
@@ -202,12 +190,16 @@ public class WorkflowStep : StepBasicEntity
         HandlerName = null;
         HandlerOrgId = null;
         HandlerOrgName = null;
+        HandlerOrgAreaCode = null;
+        HandlerOrgAreaName = null;
         HandleTime = null;
 
         AcceptorId = null;
         AcceptorName = null;
         AcceptorOrgId = null;
         AcceptorOrgName = null;
+        AcceptorOrgAreaCode = null;
+        AcceptorOrgAreaName = null;
         AcceptTime = null;
     }
 

+ 21 - 0
src/Hotline/Users/IUserDomainService.cs

@@ -21,5 +21,26 @@ namespace Hotline.Users
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         Task<WorkDto?> OffDutyAsync(string userId, CancellationToken cancellationToken);
+
+        #region 添添呼
+        /// <summary>
+        /// 上班
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="TelId"></param>
+        /// <param name="TelNo"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task TrOnDutyAsync(string userId, string TelId, string TelNo, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 下班
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task TrOffDutyAsync(string userId, CancellationToken cancellationToken);
+
+        #endregion
     }
 }

+ 47 - 2
src/Hotline/Users/UserDomainService.cs

@@ -74,8 +74,6 @@ namespace Hotline.Users
 
         }
 
-
-
         /// <summary>
         /// 下班
         /// </summary>
@@ -104,5 +102,52 @@ namespace Hotline.Users
             #endregion
             return _mapper.Map<WorkDto>(work);
         }
+
+
+        #region 添添呼
+
+        /// <summary>
+        /// 上班
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="TelId"></param>
+        /// <param name="TelNo"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task TrOnDutyAsync(string userId,string TelId,string TelNo,CancellationToken cancellationToken)
+        {
+            var user = await _userRepository.GetAsync(userId, cancellationToken);
+            if (user is null)
+                throw UserFriendlyException.SameMessage("无效的用户编号");
+
+            var workingTel = await _workRepository.GetCurrentWorkByUserAsync(userId, cancellationToken);
+            if (workingTel is not null)
+                throw UserFriendlyException.SameMessage($"当前用户已上班,userId:{userId}");
+
+            var telIsWorking = await _workRepository.AnyAsync(d => d.TelId == TelId && !d.EndTime.HasValue, cancellationToken);
+            if (telIsWorking)
+                throw UserFriendlyException.SameMessage("当前分机已被占用");
+
+            var work = new Work(userId, user.Name, TelId, TelNo);
+            await _workRepository.AddAsync(work, cancellationToken);
+        }
+
+        /// <summary>
+        /// 下班
+        /// </summary>
+        /// <param name="userId"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task TrOffDutyAsync(string userId,CancellationToken cancellationToken)
+        {
+            var work = _userCacheManager.GetWorkByUser(userId);
+            work.OffDuty();
+            await _workRepository.UpdateAsync(work, cancellationToken);
+            _cacheWork.Remove(work.GetKey(KeyMode.UserId));
+            _cacheWork.Remove(work.GetKey(KeyMode.TelNo));
+        }
+
+
+        #endregion
     }
 }

+ 3 - 0
src/Tr.Sdk/Blacklist/AddBlacklistRequest.cs

@@ -13,6 +13,9 @@ namespace Tr.Sdk.Blacklist
         [RequestProperty(Name = "phone")]
         public string Phone { get; set; }
 
+        /// <summary>
+        /// 1:白名单;2:呼入黑名单;3:呼出黑名单;4:呼入呼出黑名单;
+        /// </summary>
         [RequestProperty(Name = "special_flag")]
         public int SpecialFlag { get; set; }
 

+ 24 - 0
src/Tr.Sdk/Blacklist/DelBlacklistRequest.cs

@@ -0,0 +1,24 @@
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tr.Sdk.Blacklist
+{
+    public class DelBlacklistRequest: TrRequest
+    {
+        public override string Path() => "api/special_phone/delete";
+
+        public override Method HttpMethod() => Method.Post;
+
+        public string Phone { get; set; }
+
+        /// <summary>
+        /// 1:白名单;2:呼入黑名单;3:呼出黑名单;4:呼入呼出黑名单;
+        /// </summary>
+        [RequestProperty(Name = "special_flag")]
+        public int? SpecialFlag { get; set; }
+    }
+}

+ 42 - 0
src/Tr.Sdk/Blacklist/QueryBlacklistRequest.cs

@@ -0,0 +1,42 @@
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace Tr.Sdk.Blacklist
+{
+    public class QueryBlacklistRequest: TrRequest
+    {
+        public override string Path() => "api/special_phone/delete";
+
+        public override Method HttpMethod() => Method.Get;
+
+        public string? Phone { get; set; }
+
+        /// <summary>
+        /// 1:白名单;2:呼入黑名单;3:呼出黑名单;4:呼入呼出黑名单;
+        /// </summary>
+        public int? SpecialFlag { get; set; }
+    }
+
+    public class QueryBlacklistResponse
+    {
+        [JsonPropertyName("uuid")]
+        public string Id { get; set; }
+
+        [JsonPropertyName("user_uuid")]
+        public string UserId { get; set; }
+
+        public DateTime CreationTime { get; set; }
+
+        public string Phone { get; set; }
+
+        [JsonPropertyName("special_flag")]
+        public int SpecialFlag { get; set; }
+
+        public int? Priority { get; set; }
+    }
+}

+ 3 - 0
src/Tr.Sdk/ITrClient.cs

@@ -19,5 +19,8 @@ namespace Tr.Sdk
         /// <returns></returns>
         Task<TResponse> ExecuteAsync<TRequest, TResponse>(TRequest request, CancellationToken cancellationToken)
             where TRequest : TrRequest;
+
+        Task ExecuteAsync<TRequest>(TRequest request, CancellationToken cancellationToken)
+            where TRequest : TrRequest;
     }
 }

+ 16 - 0
src/Tr.Sdk/Tels/ITrClient.Tel.cs

@@ -3,13 +3,29 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Tr.Sdk.Blacklist;
 using Tr.Sdk.Tels;
 
 namespace Tr.Sdk
 {
     public partial interface ITrClient
     {
+        #region 分机
         Task<List<QueryTelResponse>> QueryTelsAsync(QueryTelRequest request, CancellationToken cancellationToken) =>
             ExecuteAsync<QueryTelRequest, List<QueryTelResponse>>(request, cancellationToken);
+
+        #endregion
+
+        #region 黑白名单
+
+        Task AddBlacklistAsync(AddBlacklistRequest request,CancellationToken cancellationToken) =>
+            ExecuteAsync<AddBlacklistRequest>(request, cancellationToken);
+
+        Task DelBlacklistAsync(DelBlacklistRequest request, CancellationToken cancellationToken) =>
+            ExecuteAsync<DelBlacklistRequest>(request, cancellationToken);
+
+        Task<List<QueryBlacklistResponse>> QueryBlacklistAsync(QueryBlacklistRequest request, CancellationToken cancellationToken) =>
+            ExecuteAsync<QueryBlacklistRequest,List<QueryBlacklistResponse>>(request, cancellationToken);
+        #endregion
     }
 }

+ 9 - 0
src/Tr.Sdk/Tels/QueryTelRequest.cs

@@ -9,6 +9,12 @@ namespace Tr.Sdk.Tels
 
 
         public override Method HttpMethod() => Method.Get;
+
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        [RequestProperty(Name = "extn")]
+        public string? TelNo { get; set; }
     }
 
     public class QueryTelResponse
@@ -16,5 +22,8 @@ namespace Tr.Sdk.Tels
         [JsonPropertyName("uuid")]
         public string Id { get; set; }
         public string Name { get; set; }
+        [JsonPropertyName("nbr")]
+        public string TelNo { get; set; }
+        public string Description { get; set; }
     }
 }

+ 26 - 0
src/Tr.Sdk/TrClient.cs

@@ -44,6 +44,32 @@ public class TrClient : ITrClient, IDisposable
         }
     }
 
+    /// <summary>
+    /// 执行操作呼叫中心请求
+    /// </summary>
+    /// <typeparam name="TRequest"></typeparam>
+    /// <param name="request"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    /// <exception cref="HttpRequestException"></exception>
+    public async Task ExecuteAsync<TRequest>(TRequest request, CancellationToken cancellationToken)
+        where TRequest : TrRequest
+    {
+        var req = new RestRequest(request.Path(), request.HttpMethod())
+            .AddObject(request);
+        try
+        {
+            var response = await _client.ExecuteAsync(req, cancellationToken);
+            if (!response?.IsSuccessful ?? false)
+                throw new HttpRequestException($"请求呼叫中心服务失败, HttpCode: {response.StatusCode}, Error: {response.ErrorMessage}");
+        }
+        catch (Exception e)
+        {
+            throw new HttpRequestException($"呼叫中心请求失败,Error: {e.Message}");
+        }
+    }
+
+
     /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
     public void Dispose()
     {