Pārlūkot izejas kodu

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

Dun.Jason 9 mēneši atpakaļ
vecāks
revīzija
0500c3e009
26 mainītis faili ar 980 papildinājumiem un 305 dzēšanām
  1. 56 5
      src/Hotline.Api/Controllers/CallController.cs
  2. 136 134
      src/Hotline.Api/Controllers/OrderController.cs
  3. 1 1
      src/Hotline.Api/StartupExtensions.cs
  4. 2 2
      src/Hotline.Api/StartupHelper.cs
  5. 1 1
      src/Hotline.Api/config/appsettings.Development.json
  6. 2 1
      src/Hotline.Api/config/appsettings.shared.Development.json
  7. 22 0
      src/Hotline.Application/CallCenter/ICallApplication.cs
  8. 118 59
      src/Hotline.Application/CallCenter/TianRunCallApplication.cs
  9. 112 6
      src/Hotline.Application/CallCenter/XingTangCallApplication.cs
  10. 20 32
      src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs
  11. 36 0
      src/Hotline.Application/Jobs/XingTangTelOperationSyncJob.cs
  12. 30 16
      src/Hotline.Application/Mappers/CallMapperConfigs.cs
  13. 33 33
      src/Hotline.Application/StatisticalReport/OrderReportApplication.cs
  14. 20 0
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarRepositoryExtensions.cs
  15. 123 0
      src/Hotline.Share/Dtos/CallCenter/CallNativeDto.cs
  16. 44 0
      src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs
  17. 31 0
      src/Hotline.Share/Dtos/CallCenter/QueryTelOperationsFixedDto.cs
  18. 1 1
      src/Hotline.Share/Dtos/CallCenter/TelDto.cs
  19. 1 1
      src/Hotline.Share/Dtos/Order/QueryOrderFixedDto.cs
  20. 15 0
      src/Hotline.Share/Enums/CallCenter/EHangupBy.cs
  21. 21 0
      src/Hotline.Share/QueryFixedDto.cs
  22. 25 5
      src/Hotline/CallCenter/Calls/CallNative.cs
  23. 44 0
      src/Hotline/CallCenter/Tels/TelOperation.cs
  24. 0 5
      src/Hotline/Settings/SettingConstants.cs
  25. 3 3
      src/XingTang.Sdk/CallXingtang.cs
  26. 83 0
      src/XingTang.Sdk/SeatOperation.cs

+ 56 - 5
src/Hotline.Api/Controllers/CallController.cs

@@ -1,8 +1,12 @@
 using Hotline.Application.CallCenter;
 using Hotline.CallCenter.BlackLists;
+using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Tels;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
 using Microsoft.AspNetCore.Mvc;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Api.Controllers
 {
@@ -19,14 +23,14 @@ namespace Hotline.Api.Controllers
         /// 查询分机
         /// </summary>
         [HttpGet("tels")]
-        public Task<IReadOnlyList<TelDto>> QueryTelsAsync()
+        public Task<IReadOnlyList<TelDto>> QueryTels()
             => _callApplication.QueryTelsAsync(HttpContext.RequestAborted);
 
         /// <summary>
         /// 查询分机组
         /// </summary>
         [HttpGet("groups")]
-        public Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync()
+        public Task<IReadOnlyList<TelGroupDto>> QueryTelGroups()
             => _callApplication.QueryTelGroupsAsync(HttpContext.RequestAborted);
 
         #region 黑名单
@@ -41,14 +45,61 @@ namespace Hotline.Api.Controllers
         /// 签入
         /// </summary>
         [HttpPost("signin")]
-        public Task<TrOnDutyResponseDto> SignInAsync([FromBody] SignInDto dto)
+        public Task<TrOnDutyResponseDto> SignIn([FromBody] SignInDto dto)
             => _callApplication.SignInAsync(dto, HttpContext.RequestAborted);
 
         /// <summary>
         /// 签出
         /// </summary>
         [HttpPost("signout")]
-        public Task SingOutAsync()
-            => _callApplication.QueryTelsAsync(HttpContext.RequestAborted);
+        public Task SignOut()
+            => _callApplication.SingOutAsync(HttpContext.RequestAborted);
+
+        /// <summary>
+        /// 签出
+        /// </summary>
+        [HttpPost("signout/{telNo}")]
+        public Task SignOut(string telNo)
+            => _callApplication.SingOutAsync(telNo, HttpContext.RequestAborted);
+
+        /// <summary>
+        /// 查询当前用户的分机状态
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("tel-state")]
+        public Task GetTelState()
+            => _callApplication.GetTelStateAsync(HttpContext.RequestAborted);
+
+        /// <summary>
+        /// 查询通话记录(固定数据量)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("calls-fixed")]
+        public Task<IReadOnlyList<CallNativeDto>> QueryCallsFixed([FromQuery] QueryCallsFixedDto dto)
+            => _callApplication.QueryCallsFixedAsync(dto, HttpContext.RequestAborted);
+
+        /// <summary>
+        /// 通话记录基础数据
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("base-data")]
+        public async Task<object> BaseData()
+        {
+            return new
+            {
+                Direction = EnumExts.GetDescriptions<ECallDirection>(),
+                EndBy = EnumExts.GetDescriptions<EEndBy>(),
+            };
+        }
+
+        /// <summary>
+        /// 查询坐席操作记录(固定数据量)
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("tel-operations-fixed")]
+        public Task<IReadOnlyList<TelOperation>> QueryTelOperationsFixed([FromQuery] QueryTelOperationsFixedDto dto) => 
+            _callApplication.QueryTelOperationsAsync(dto, HttpContext.RequestAborted);
     }
 }

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

@@ -1660,8 +1660,8 @@ public class OrderController : BaseController
             .Includes(d => d.VisitDetail)
             .Includes(d => d.Visit, v => v.Order)
             .Includes(d => d.Workflow)
-			.WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Visit.Order.Title.Contains(dto.Title!) )
-			.WhereIF(!string.IsNullOrEmpty(dto.No),  d => d.Visit.Order.No.Contains(dto.No!));
+            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Visit.Order.Title.Contains(dto.Title!))
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Visit.Order.No.Contains(dto.No!));
         if (dto.Status is EScreenStatus.Apply)
         {
             query.Where(d => (d.Status == EScreenStatus.Apply || d.Status == EScreenStatus.Approval));
@@ -1676,7 +1676,7 @@ public class OrderController : BaseController
             .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart)
             .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd)
             .WhereIF(!string.IsNullOrEmpty(dto.OrderId), d => d.OrderId == dto.OrderId)
-            .WhereIF(!string.IsNullOrEmpty(dto.CreatorOrgName),d=>d.CreatorOrgName == dto.CreatorOrgName)
+            .WhereIF(!string.IsNullOrEmpty(dto.CreatorOrgName), d => d.CreatorOrgName == dto.CreatorOrgName)
             .OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
         return new PagedDto<OrderScreenListDto>(total, _mapper.Map<IReadOnlyList<OrderScreenListDto>>(items));
@@ -2318,13 +2318,11 @@ public class OrderController : BaseController
     /// <summary>
     /// 查询工单(固定数据量)
     /// </summary>
-    [HttpGet("constant")]
-    public async Task<IReadOnlyList<OrderDto>> QueryConstant([FromQuery] QueryOrderConstantDto dto)
+    [HttpGet("fixed")]
+    public async Task<IReadOnlyList<OrderDto>> QueryFixed([FromQuery] QueryOrderFixedDto dto)
     {
-        var count = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.ConstantQueryCount)
-            .SettingValue[0]);
         var query = _orderApplication.QueryOrders(dto);
-        var orders = await query.Skip(dto.QueryIndex * count).Take(count).ToListAsync(HttpContext.RequestAborted);
+        var orders = await query.ToFixedListAsync(dto.QueryIndex, cancellationToken: HttpContext.RequestAborted);
         return _mapper.Map<IReadOnlyList<OrderDto>>(orders);
     }
 
@@ -2332,7 +2330,7 @@ public class OrderController : BaseController
     /// 查询总数
     /// </summary>
     [HttpGet("count")]
-    public async Task<int> Count([FromQuery] QueryOrderConstantDto dto)
+    public async Task<int> Count([FromQuery] QueryOrderFixedDto dto)
     {
         var query = _orderApplication.QueryOrders(dto);
         return await query.CountAsync(HttpContext.RequestAborted);
@@ -2431,40 +2429,42 @@ public class OrderController : BaseController
         if (!string.IsNullOrEmpty(order.WorkflowId))
         {
             bool canInsteadHandle = false;
-			//班长代办
-			var settingEnable = _systemSettingCacheManager.GetSetting(SettingConstants.ChargeDAffaires);
+            //班长代办
+            var settingEnable = _systemSettingCacheManager.GetSetting(SettingConstants.ChargeDAffaires);
             var isEnable = settingEnable != null && settingEnable.SettingValue.Any() ? int.Parse(settingEnable?.SettingValue[0]) : 0;
-			if (isEnable > 0)
-			{
-				var setting = _systemSettingCacheManager.GetSetting(SettingConstants.SeatsMonitor);
-				var settingStr = setting?.SettingValue;
-				var roles = _sessionContext.Roles;
-				foreach (var item in settingStr)
-				{
+            if (isEnable > 0)
+            {
+                var setting = _systemSettingCacheManager.GetSetting(SettingConstants.SeatsMonitor);
+                var settingStr = setting?.SettingValue;
+                var roles = _sessionContext.Roles;
+                foreach (var item in settingStr)
+                {
                     if (roles != null && roles.Contains(item))
-                    { 
-                        canInsteadHandle = true; 
-                    }else {
+                    {
+                        canInsteadHandle = true;
+                    }
+                    else
+                    {
                         canInsteadHandle = false;
                     };
-				}
-				if (canInsteadHandle)
-				{
-					var unhandleSteps =
-						await _workflowDomainService.GetUnhandleStepsByOthersAsync(order.WorkflowId, HttpContext.RequestAborted);
-					// 会签多节点 不允许班长代办
-					if (unhandleSteps.Count > 1)
-						canInsteadHandle = false;
-					if (isEnable < 3 && unhandleSteps.Count == 1)
+                }
+                if (canInsteadHandle)
+                {
+                    var unhandleSteps =
+                        await _workflowDomainService.GetUnhandleStepsByOthersAsync(order.WorkflowId, HttpContext.RequestAborted);
+                    // 会签多节点 不允许班长代办
+                    if (unhandleSteps.Count > 1)
+                        canInsteadHandle = false;
+                    if (isEnable < 3 && unhandleSteps.Count == 1)
                     {
                         var type = isEnable - 1;
-						var step = unhandleSteps.FirstOrDefault(d => d.BusinessType == (EBusinessType)type);
-						canInsteadHandle = step != null && !string.IsNullOrEmpty(step.Id);
-						if (canInsteadHandle && step.IsInCountersign())
-							canInsteadHandle = false;
-					}
-				}
-			}
+                        var step = unhandleSteps.FirstOrDefault(d => d.BusinessType == (EBusinessType)type);
+                        canInsteadHandle = step != null && !string.IsNullOrEmpty(step.Id);
+                        if (canInsteadHandle && step.IsInCountersign())
+                            canInsteadHandle = false;
+                    }
+                }
+            }
             var result = await _workflowDomainService.GetWorkflowHandlePermissionAsync(
                 order.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, _sessionContext.Roles,
                 cancellationToken: HttpContext.RequestAborted);
@@ -2475,25 +2475,25 @@ public class OrderController : BaseController
             dto.CanInsteadHandle = !dto.CanHandle && canInsteadHandle;
             if (result.Trace is not null)
             {
-	            dto.PreviousOpinion = result.Trace!.Opinion;
-				var sendBack = await _orderSendBackAuditRepository.Queryable().Where(x => x.OrderId == dto.Id && x.TraceId == result.Trace.Id).OrderByDescending(x => x.CreationTime).FirstAsync();
-	            if (sendBack is { Id: not null })
-	            {
-					dto.SendBackOpinion = !string.IsNullOrEmpty(sendBack.Content) ? sendBack.Content : string.Empty;
-					if (sendBack.State == ESendBackAuditState.Refuse)
-					{
-						dto.SendBackRefuseOpinion = sendBack.AuditContent;
+                dto.PreviousOpinion = result.Trace!.Opinion;
+                var sendBack = await _orderSendBackAuditRepository.Queryable().Where(x => x.OrderId == dto.Id && x.TraceId == result.Trace.Id).OrderByDescending(x => x.CreationTime).FirstAsync();
+                if (sendBack is { Id: not null })
+                {
+                    dto.SendBackOpinion = !string.IsNullOrEmpty(sendBack.Content) ? sendBack.Content : string.Empty;
+                    if (sendBack.State == ESendBackAuditState.Refuse)
+                    {
+                        dto.SendBackRefuseOpinion = sendBack.AuditContent;
                         dto.SendBackAuditTime = sendBack.AuditTime;
-					}
-				}
-			}
+                    }
+                }
+            }
 
-			await _mediator.Publish(new GetOrderDetailNotify(result.Workflow,
+            await _mediator.Publish(new GetOrderDetailNotify(result.Workflow,
                 _sessionContext.RequiredUserId, _sessionContext.UserName,
                 _sessionContext.RequiredOrgId, _sessionContext.OrgName,
                 _sessionContext.OrgAreaCode, _sessionContext.OrgAreaName));
         }
-       
+
 
         //var dto = _mapper.Map<OrderDto>(order!);
         //dto.CountersignId = countersignId;
@@ -2565,8 +2565,10 @@ public class OrderController : BaseController
         {
             var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
                 cancellationToken: HttpContext.RequestAborted);
-            var centerOpinion = workflow.Steps.Where(x => x.HandlerOrgId == OrgSeedData.CenterId).OrderByDescending(x => x.CreationTime).Select(x => x.Opinion).First();
-            dto.CenterOpinion = string.IsNullOrEmpty(centerOpinion) ? string.Empty : centerOpinion;
+            //  var centerOpinion = workflow.Steps.Where(x => x.HandlerOrgId == OrgSeedData.CenterId).OrderByDescending(x => x.CreationTime).Select(x => x.Opinion).FirstOrDefault();
+            var centerOpinion = workflow.Steps.Where(x => x.HandlerOrgId == OrgSeedData.CenterId).MaxBy(d => d.CreationTime)?.Opinion ?? string.Empty;
+
+            dto.CenterOpinion = centerOpinion;//string.IsNullOrEmpty(centerOpinion) ? string.Empty : centerOpinion;
             List<OrderRemarksDto> remarks = workflow.Steps.Where(x => !string.IsNullOrEmpty(x.Remark)).Select(x => new OrderRemarksDto { Remark = x.Remark, RemarkTime = x.CreationTime, RemarkUser = x.CreatorName }).ToList();
             dto.OrderRemarks = remarks;
         }
@@ -2624,7 +2626,7 @@ public class OrderController : BaseController
         }
 
         //内容分词
-        await _orderApplication.OrderParticiple(dto.Content, order.Id,order.CreationTime, HttpContext.RequestAborted);
+        await _orderApplication.OrderParticiple(dto.Content, order.Id, order.CreationTime, HttpContext.RequestAborted);
         //敏感分词
         await _orderApplication.OrderSensitiveParticiple(dto.Content, order.Id, HttpContext.RequestAborted);
         //sms
@@ -2985,38 +2987,38 @@ public class OrderController : BaseController
     [HttpGet("nextsteps_commission/{orderId}")]
     public async Task<NextStepsWithOpinionDto<RecommendStepOption>> GetNextStepsWithRecommendCommission(string orderId)
     {
-	    var order = await _orderDomainService.GetOrderAsync(orderId, cancellationToken: HttpContext.RequestAborted);
-	    if (string.IsNullOrEmpty(order.WorkflowId))
-		    throw UserFriendlyException.SameMessage("该工单未开启流程");
-	    var unhandleSteps =
-		    await _workflowDomainService.GetUnhandleStepsByOthersAsync(order.WorkflowId, HttpContext.RequestAborted);
-	    if (unhandleSteps.Count > 1)
-		    throw UserFriendlyException.SameMessage("会签工单不允许班长代办");
-	    if (unhandleSteps.Count < 1)
-		    throw UserFriendlyException.SameMessage("未查询到流程信息");
+        var order = await _orderDomainService.GetOrderAsync(orderId, cancellationToken: HttpContext.RequestAborted);
+        if (string.IsNullOrEmpty(order.WorkflowId))
+            throw UserFriendlyException.SameMessage("该工单未开启流程");
+        var unhandleSteps =
+            await _workflowDomainService.GetUnhandleStepsByOthersAsync(order.WorkflowId, HttpContext.RequestAborted);
+        if (unhandleSteps.Count > 1)
+            throw UserFriendlyException.SameMessage("会签工单不允许班长代办");
+        if (unhandleSteps.Count < 1)
+            throw UserFriendlyException.SameMessage("未查询到流程信息");
         var stepOne = unhandleSteps.First();
         if (stepOne.IsInCountersign())
-	        throw UserFriendlyException.SameMessage("会签工单不允许班长代办");
-		var dto = await _workflowApplication.GetNextStepsAsync(order.WorkflowId, stepOne.Id, HttpContext.RequestAborted);
-	    dto.ExpiredTime = order.ExpiredTime;
-	    var rsp = _mapper.Map<NextStepsWithOpinionDto<RecommendStepOption>>(dto);
-	    foreach (var step in rsp.Steps)
-	    {
-		    if (dto.CurrentStepBusinessType is not EBusinessType.Send ||
-		        step.BusinessType is not EBusinessType.Department) continue;
-		    var org = await _organizeRepository.GetAsync(d => d.AreaCode == order.AreaCode, HttpContext.RequestAborted);
-		    if (org is null) continue;
-		    step.RecommendOrgId = org.Id;
-		    step.RecommendOrgName = org.Name;
-	    }
-
-	    return rsp;
-    }
-
-	/// <summary>
-	/// 结束会签
-	/// </summary>
-	[HttpPost("endcs")]
+            throw UserFriendlyException.SameMessage("会签工单不允许班长代办");
+        var dto = await _workflowApplication.GetNextStepsAsync(order.WorkflowId, stepOne.Id, HttpContext.RequestAborted);
+        dto.ExpiredTime = order.ExpiredTime;
+        var rsp = _mapper.Map<NextStepsWithOpinionDto<RecommendStepOption>>(dto);
+        foreach (var step in rsp.Steps)
+        {
+            if (dto.CurrentStepBusinessType is not EBusinessType.Send ||
+                step.BusinessType is not EBusinessType.Department) continue;
+            var org = await _organizeRepository.GetAsync(d => d.AreaCode == order.AreaCode, HttpContext.RequestAborted);
+            if (org is null) continue;
+            step.RecommendOrgId = org.Id;
+            step.RecommendOrgName = org.Name;
+        }
+
+        return rsp;
+    }
+
+    /// <summary>
+    /// 结束会签
+    /// </summary>
+    [HttpPost("endcs")]
     public async Task EndCountersign([FromBody] EndCountersignDto dto)
     {
         var workflow = await _workflowDomainService.TerminalCountersignAsync(dto.CountersignId, HttpContext.RequestAborted);
@@ -3249,7 +3251,7 @@ public class OrderController : BaseController
         //}
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
-    
+
     /// <summary>
     /// 查询坐席待办
     /// </summary>
@@ -3266,7 +3268,7 @@ public class OrderController : BaseController
             dto.EndTime = dto.EndTime.Value.AddDays(1).AddSeconds(-1);
 
         var query = _orderRepository.Queryable();
-        if(dto.IsHandled.HasValue)
+        if (dto.IsHandled.HasValue)
         {
             var hasHandled = dto.IsHandled.Value;
             query = query.Where(d => SqlFunc.Subqueryable<WorkflowTrace>()
@@ -3275,8 +3277,8 @@ public class OrderController : BaseController
                                (!hasHandled || step.Status == EWorkflowStepStatus.Handled && step.TraceState != EWorkflowTraceState.StepRemoveByPrevious) &&
                                ((step.FlowAssignType == EFlowAssignType.User && !string.IsNullOrEmpty(step.HandlerId) && step.HandlerId == _sessionContext.RequiredUserId) ||
                                 (step.FlowAssignType == EFlowAssignType.Org && !string.IsNullOrEmpty(step.HandlerOrgId) && step.HandlerOrgId == _sessionContext.RequiredOrgId) ||
-                                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId)))).Any() || 
-                                     (string.IsNullOrEmpty(d.WorkflowId) && 
+                                (step.FlowAssignType == EFlowAssignType.Role && !string.IsNullOrEmpty(step.RoleId) && _sessionContext.Roles.Contains(step.RoleId)))).Any() ||
+                                     (string.IsNullOrEmpty(d.WorkflowId) &&
                                       (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContext.RequiredUserId))
             );
         }
@@ -3333,7 +3335,7 @@ public class OrderController : BaseController
 
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
-    
+
     /// <summary>
     /// 查询中心待办
     /// </summary>
@@ -3396,7 +3398,7 @@ public class OrderController : BaseController
             .WhereIF(dto.ExpiredStatus is EExpiredStatus.Normal, d => DateTime.Now < d.NearlyExpiredTime)
             .WhereIF(dto.ExpiredStatus is EExpiredStatus.GoingToExpired, d => DateTime.Now > d.NearlyExpiredTime && DateTime.Now < d.ExpiredTime)
             .WhereIF(dto.ExpiredStatus is EExpiredStatus.Expired, d => DateTime.Now >= d.ExpiredTime)
-            .WhereIF(!string.IsNullOrEmpty(dto.CenterToOrgHandlerName),d=>d.CenterToOrgHandlerName == dto.CenterToOrgHandlerName)
+            .WhereIF(!string.IsNullOrEmpty(dto.CenterToOrgHandlerName), d => d.CenterToOrgHandlerName == dto.CenterToOrgHandlerName)
             .WhereIF(dto.IsUrgent.HasValue, d => d.IsUrgent == dto.IsUrgent!.Value)
             .OrderBy(d => d.Status)
             .OrderBy(d => d.CreationTime, OrderByType.Desc)
@@ -3543,7 +3545,7 @@ public class OrderController : BaseController
                     WorkflowUserId = _sessionContext.RequiredUserId,
                     WorkflowRoleIds = _sessionContext.Roles.ToList(),
                     TraceId = currentStep.Id
-				};
+                };
                 await _orderSendBackAuditRepository.AddAsync(audit, HttpContext.RequestAborted);
             }
             else
@@ -3661,45 +3663,45 @@ public class OrderController : BaseController
         return new PagedDto<SendBackDto>(total, _mapper.Map<IReadOnlyList<SendBackDto>>(items));
     }
 
-	/// <summary>
-	/// 工单业务退回审批列表导出
-	/// </summary>
-	/// <returns></returns>
-	[HttpPost("order_previous_list/_export")]
+    /// <summary>
+    /// 工单业务退回审批列表导出
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("order_previous_list/_export")]
     public async Task<FileStreamResult> OrgDataListDetailExport([FromBody] ExportExcelDto<SendBackListDto> dto)
     {
-	    var query = _orderSendBackAuditApplication.AuditList(dto.QueryDto);
-	    List<OrderSendBackAudit> data;
-	    if (dto.IsExportAll)
-	    {
-		    data = await query.ToListAsync(HttpContext.RequestAborted);
-	    }
-	    else
-	    {
-		    var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
-		    data = items;
-	    }
+        var query = _orderSendBackAuditApplication.AuditList(dto.QueryDto);
+        List<OrderSendBackAudit> data;
+        if (dto.IsExportAll)
+        {
+            data = await query.ToListAsync(HttpContext.RequestAborted);
+        }
+        else
+        {
+            var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+            data = items;
+        }
 
-	    var dataDtos = _mapper.Map<ICollection<SendBackDto>>(data);
+        var dataDtos = _mapper.Map<ICollection<SendBackDto>>(data);
 
-	    dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
 
-	    var dtos = dataDtos
-		    .Select(stu => _mapper.Map(stu, typeof(SendBackDto), dynamicClass))
-		    .Cast<object>()
-		    .ToList();
+        var dtos = dataDtos
+            .Select(stu => _mapper.Map(stu, typeof(SendBackDto), dynamicClass))
+            .Cast<object>()
+            .ToList();
 
-	    var stream = ExcelHelper.CreateStream(dtos);
+        var stream = ExcelHelper.CreateStream(dtos);
 
-	    return ExcelStreamResult(stream, "工单退回列表数据");
+        return ExcelStreamResult(stream, "工单退回列表数据");
     }
 
-	/// <summary>
-	/// 退回详情
-	/// </summary>
-	/// <param name="id"></param>
-	/// <returns></returns>
-	[HttpGet("order_previous/{id}")]
+    /// <summary>
+    /// 退回详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("order_previous/{id}")]
     public async Task<OrderSendBackAudit> OrderSendBackEntity(string id)
     {
         return await _orderSendBackAuditRepository.Queryable()
@@ -3714,24 +3716,24 @@ public class OrderController : BaseController
     [HttpGet("order_previous/base-data")]
     public async Task<object> OrderSendBackBaseData()
     {
-	    var rsp = new
-	    {
-		    AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
-		    ESendBackAuditState = EnumExts.GetDescriptions<ESendBackAuditState>(),
-	    };
-	    return rsp;
+        var rsp = new
+        {
+            AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
+            ESendBackAuditState = EnumExts.GetDescriptions<ESendBackAuditState>(),
+        };
+        return rsp;
     }
 
-	#endregion
+    #endregion
 
-	#region 省工单退回
+    #region 省工单退回
 
-	/// <summary>
-	/// 工单退回列表
-	/// </summary>
-	/// <param name="dto"></param>
-	/// <returns></returns>
-	[Permission(EPermission.SendBackOrder)]
+    /// <summary>
+    /// 工单退回列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [Permission(EPermission.SendBackOrder)]
     [HttpGet("send_back")]
     public async Task<PagedDto<OrderSendBackDto>> UrgeList([FromQuery] OrderSendBackListDto dto)
     {

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

@@ -113,7 +113,7 @@ internal static class StartupExtensions
                 break;
             case "TianRun":
                 services
-                    .AddScoped<ICallApplication, TianRunCallApplication>()
+                    //.AddScoped<ICallApplication, TianRunCallApplication>()
                     .AddScoped<ITrApplication, TrApplication>()
                     .AddHostedService<CurrentWaitNumService>()
                     .AddHostedService<TelsStatusRefreshService>()

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

@@ -263,8 +263,8 @@ namespace Hotline.Api
                 switch (callCenterConfiguration.CallCenterType)
                 {
                     case "XingTang":
-                        var getCallsJobKey = new JobKey(nameof(GetCallsJob));
-                        d.AddJob<GetCallsJob>(getCallsJobKey);
+                        var getCallsJobKey = new JobKey(nameof(XingTangCallsSyncJob));
+                        d.AddJob<XingTangCallsSyncJob>(getCallsJobKey);
                         d.AddTrigger(d => d
                             .WithIdentity("get-callsxt-trigger")
                             .ForJob(getCallsJobKey)

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

@@ -1,7 +1,7 @@
 {
   "AllowedHosts": "*",
   "CallCenterConfiguration": {
-    "CallCenterType": "TianRun", //XunShi、WeiErXin、TianRun、XingTang
+    "CallCenterType": "XingTang", //XunShi、WeiErXin、TianRun、XingTang
     "NewRock": {
       "Address": "http://192.168.100.100/xml",
       "Authorize": true,

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

@@ -54,7 +54,8 @@
       //  "Name": "File",
       //  "Args": {
       //    "path": "logs/log-.txt",
-      //    "rollingInterval": "Day"
+      //    "rollingInterval": "Day",
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
       //  }
       //},
       //{

+ 22 - 0
src/Hotline.Application/CallCenter/ICallApplication.cs

@@ -4,8 +4,11 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Hotline.CallCenter.BlackLists;
+using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Tels;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.TrCallCenter;
+using XingTang.Sdk;
 
 namespace Hotline.Application.CallCenter
 {
@@ -38,5 +41,24 @@ namespace Hotline.Application.CallCenter
         /// 签出
         /// </summary>
         Task SingOutAsync(CancellationToken cancellationToken);
+        Task SingOutAsync(string telNo, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 查询当前用户的分机状态
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<TrOnDutyResponseDto> GetTelStateAsync(CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 定量查询通话记录
+        /// </summary>
+        Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken);
+        
+        /// <summary>
+        /// 查询分机操作记录(定量)
+        /// </summary>
+        Task<IReadOnlyList<TelOperation>> QueryTelOperationsAsync(QueryTelOperationsFixedDto dto,
+            CancellationToken cancellationToken);
     }
 }

+ 118 - 59
src/Hotline.Application/CallCenter/TianRunCallApplication.cs

@@ -1,68 +1,127 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Hotline.Application.CallCenter.Calls;
-using Hotline.Application.Tels;
-using Hotline.CallCenter.BlackLists;
-using Hotline.Share.Dtos.CallCenter;
-using Hotline.Share.Dtos.TrCallCenter;
-using Hotline.Share.Enums.CallCenter;
-using Microsoft.AspNetCore.Http;
-using XF.Domain.Authentications;
+//using System;
+//using System.Collections.Generic;
+//using System.Linq;
+//using System.Text;
+//using System.Threading.Tasks;
+//using Hotline.Application.CallCenter.Calls;
+//using Hotline.Application.Tels;
+//using Hotline.Caching.Interfaces;
+//using Hotline.CallCenter.BlackLists;
+//using Hotline.CallCenter.Calls;
+//using Hotline.CallCenter.Tels;
+//using Hotline.Settings;
+//using Hotline.Share.Dtos.CallCenter;
+//using Hotline.Share.Dtos.TrCallCenter;
+//using Hotline.Share.Enums.CallCenter;
+//using Microsoft.AspNetCore.Http;
+//using XF.Domain.Authentications;
 
-namespace Hotline.Application.CallCenter
-{
-    public class TianRunCallApplication : ICallApplication
-    {
-        private readonly ISessionContext _sessionContext;
-        private readonly ITrApplication _trApplication;
-        private readonly ITelApplication _telApplication;
+//namespace Hotline.Application.CallCenter
+//{
+//    public class TianRunCallApplication : ICallApplication
+//    {
+//        private readonly ISessionContext _sessionContext;
+//        private readonly ITrApplication _trApplication;
+//        private readonly ITelApplication _telApplication;
+//        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
 
-        public TianRunCallApplication(
-            ISessionContext sessionContext,
-            ITrApplication trApplication,
-            ITelApplication telApplication)
-        {
-            _sessionContext = sessionContext;
-            _trApplication = trApplication;
-            _telApplication = telApplication;
-        }
+//        public TianRunCallApplication(
+//            ISessionContext sessionContext,
+//            ITrApplication trApplication,
+//            ITelApplication telApplication,
+//            ISystemSettingCacheManager systemSettingCacheManager
+//            )
+//        {
+//            _sessionContext = sessionContext;
+//            _trApplication = trApplication;
+//            _telApplication = telApplication;
+//            _systemSettingCacheManager = systemSettingCacheManager;
+//        }
 
-        public Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
+//        /// <summary>
+//        /// 查询分机
+//        /// </summary>
+//        public Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
 
-        public Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
+//        /// <summary>
+//        /// 查询分机组
+//        /// </summary>
+//        public Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
 
-        public Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
+//        public Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
 
-        public Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
+//        public Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
 
-        public Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
+//        public Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
 
-        public async Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken)
-        {
-            return await _trApplication.OnSign(_sessionContext.RequiredUserId, dto.TelNo, (ETelModel)dto.TelModelState, cancellationToken);
-        }
+//        /// <summary>
+//        /// 签入
+//        /// </summary>
+//        public Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken) =>
+//            _trApplication.OnSign(_sessionContext.RequiredUserId, dto.TelNo, (ETelModel)dto.TelModelState, cancellationToken);
 
-        public async Task SingOutAsync(CancellationToken cancellationToken)
-        {
-            await _telApplication.SignOutAsync(_sessionContext.RequiredUserId, cancellationToken);
-        }
-    }
-}
+//        /// <summary>
+//        /// 签出
+//        /// </summary>
+//        public Task SingOutAsync(CancellationToken cancellationToken) =>
+//            _telApplication.SignOutAsync(_sessionContext.RequiredUserId, cancellationToken);
+
+//        public Task SingOutAsync(string telNo, CancellationToken cancellationToken) =>
+//            _telApplication.SignOutByTelNoAsync(telNo, cancellationToken);
+
+//        /// <summary>
+//        /// 查询当前用户的分机状态
+//        /// </summary>
+//        /// <param name="cancellationToken"></param>
+//        /// <returns></returns>
+//        public Task<TrOnDutyResponseDto> GetTelStateAsync(CancellationToken cancellationToken) =>
+//            _trApplication.TelState(_sessionContext.RequiredUserId, cancellationToken);
+
+//        /// <summary>
+//        /// 定量查询通话记录
+//        /// </summary>
+//        Task<IReadOnlyList<CallNativeDto>> ICallApplication.QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
+
+//        /// <summary>
+//        /// 关联通话记录与工单或回访
+//        /// </summary>
+//        public Task RelateCallToOrderAsync(LinkCallRecordDto dto, CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
+
+//        /// <summary>
+//        /// 查询分机操作记录(定量)
+//        /// </summary>
+//        public async Task<IReadOnlyList<TelOperation>> QueryTelOperationsAsync(QueryTelOperationsFixedDto dto, CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
+
+//        /// <summary>
+//        /// 定量查询通话记录
+//        /// </summary>
+//        public async Task<IReadOnlyList<CallNative>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
+//        {
+//            throw new NotImplementedException();
+//        }
+//    }
+//}

+ 112 - 6
src/Hotline.Application/CallCenter/XingTangCallApplication.cs

@@ -3,13 +3,24 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using DotNetCore.CAP;
 using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
 using Hotline.CallCenter.BlackLists;
+using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Tels;
+using Hotline.Orders;
 using Hotline.Repository.SqlSugar.CallCenter;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Repository.SqlSugar.Orders;
+using Hotline.Settings;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
 using Hotline.Users;
+using MapsterMapper;
+using Microsoft.AspNetCore.Http;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Exceptions;
@@ -23,28 +34,40 @@ namespace Hotline.Application.CallCenter
         private readonly IRepository<TelGroup> _telGroupRepository;
         private readonly IWorkRepository _workRepository;
         private readonly ITelRestRepository _telRestRepository;
+        private readonly IRepository<CallNative> _callNativeRepository;
+        private readonly IRepository<TelOperation> _teloperationRepository;
         private readonly ITypedCache<Work> _cacheWork;
         private readonly IUserCacheManager _userCacheManager;
         private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
 
         public XingTangCallApplication(
             IRepository<Tel> telRepository,
             IRepository<TelGroup> telGroupRepository,
             IWorkRepository workRepository,
             ITelRestRepository telRestRepository,
+            IRepository<CallNative> callNativeRepository,
+            IRepository<TelOperation> teloperationRepository,
             ITypedCache<Work> cacheWork,
             IUserCacheManager userCacheManager,
-            ISessionContext sessionContext)
+            ISessionContext sessionContext,
+            IMapper mapper)
         {
             _telRepository = telRepository;
             _telGroupRepository = telGroupRepository;
             _workRepository = workRepository;
             _telRestRepository = telRestRepository;
+            _callNativeRepository = callNativeRepository;
+            _teloperationRepository = teloperationRepository;
             _cacheWork = cacheWork;
             _userCacheManager = userCacheManager;
             _sessionContext = sessionContext;
+            _mapper = mapper;
         }
 
+        /// <summary>
+        /// 查询分机
+        /// </summary>
         public async Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
         {
             return await _telRepository.Queryable()
@@ -52,6 +75,9 @@ namespace Hotline.Application.CallCenter
                 .ToListAsync(cancellationToken);
         }
 
+        /// <summary>
+        /// 查询分机组
+        /// </summary>
         public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
         {
             return await _telGroupRepository.Queryable()
@@ -59,21 +85,24 @@ namespace Hotline.Application.CallCenter
                 .ToListAsync(cancellationToken);
         }
 
-        public Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
+        public async Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
         {
             throw new NotImplementedException();
         }
 
-        public Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
+        public async Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
         {
             throw new NotImplementedException();
         }
 
-        public Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
+        public async Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
         {
             throw new NotImplementedException();
         }
 
+        /// <summary>
+        /// 签入
+        /// </summary>
         public async Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken)
         {
             if (string.IsNullOrEmpty(dto.TelNo))
@@ -107,6 +136,9 @@ namespace Hotline.Application.CallCenter
             };
         }
 
+        /// <summary>
+        /// 签出
+        /// </summary>
         public async Task SingOutAsync(CancellationToken cancellationToken)
         {
             var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
@@ -121,8 +153,82 @@ namespace Hotline.Application.CallCenter
 
             work.OffDuty();
             await _workRepository.UpdateAsync(work, cancellationToken);
-            _cacheWork.Remove(work.GetKey(KeyMode.UserId));
-            _cacheWork.Remove(work.GetKey(KeyMode.TelNo));
+            await _cacheWork.RemoveAsync(work.GetKey(KeyMode.UserId), cancellationToken);
+            await _cacheWork.RemoveAsync(work.GetKey(KeyMode.TelNo), cancellationToken);
+        }
+
+        public async Task SingOutAsync(string telNo, CancellationToken cancellationToken)
+        {
+            var work = _userCacheManager.GetWorkByTelNoExp(telNo);
+            if (work is null) return;
+
+            var telRest = await _telRestRepository.GetAsync(x => x.TelNo == work.TelNo && !x.EndTime.HasValue, cancellationToken);
+            if (telRest is not null)
+            {
+                telRest.EndRest();
+                await _telRestRepository.UpdateAsync(telRest, cancellationToken);
+            }
+
+            work.OffDuty();
+            await _workRepository.UpdateAsync(work, cancellationToken);
+            await _cacheWork.RemoveAsync(work.GetKey(KeyMode.UserId), cancellationToken);
+            await _cacheWork.RemoveAsync(work.GetKey(KeyMode.TelNo), cancellationToken);
+        }
+
+        /// <summary>
+        /// 查询当前用户的分机状态
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<TrOnDutyResponseDto> GetTelStateAsync(CancellationToken cancellationToken)
+        {
+            var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
+            if (work is null) return null;
+            return await Task.FromResult(new TrOnDutyResponseDto
+            {
+                TelNo = work.TelNo,
+                QueueId = work.QueueId,
+                StartTime = work.StartTime,
+            });
+        }
+
+        /// <summary>
+        /// 定量查询通话记录
+        /// </summary>
+        public async Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
+        {
+            return await _callNativeRepository.Queryable()
+                .LeftJoin<Order>((d, o) => d.CallNo == o.CallId)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), (d, o) => o.No == dto.OrderNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.FromNo), d => d.FromNo == dto.FromNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.ToNo), d => d.ToNo == dto.ToNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserName), d => d.UserName == dto.UserName)
+                .WhereIF(!string.IsNullOrEmpty(dto.TelNo), d => d.TelNo == dto.TelNo)
+                .WhereIF(dto.EndBy != null, d => d.EndBy == dto.EndBy)
+                .WhereIF(dto.CallStartTimeBT != null, d => d.BeginIvrTime >= dto.CallStartTimeBT)
+                .WhereIF(dto.CallStartTimeLT != null, d => d.BeginIvrTime <= dto.CallStartTimeLT)
+                .WhereIF(dto.IsConnected != null, d => d.AnsweredTime != null)
+                .WhereIF(dto.Direction != null, d => d.Direction == dto.Direction)
+                .Select((d, o) => new CallNativeDto
+                {
+                    OrderId = o.Id,
+                    OrderNo = o.No,
+                    Title = o.Title,
+                }, true)
+                .ToFixedListAsync(dto, cancellationToken);
+        }
+        
+        /// <summary>
+        /// 查询分机操作记录(定量)
+        /// </summary>
+        public async Task<IReadOnlyList<TelOperation>> QueryTelOperationsAsync(QueryTelOperationsFixedDto dto, CancellationToken cancellationToken)
+        {
+            return await _teloperationRepository.Queryable()
+                .WhereIF(!string.IsNullOrEmpty(dto.UserName), d => d.UserName == dto.UserName)
+                .WhereIF(!string.IsNullOrEmpty(dto.StaffNo), d => d.StaffNo == dto.StaffNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.GroupId), d => d.GroupId == dto.GroupId)
+                .WhereIF(dto.OperateState != null, d => d.OperateState == dto.OperateState)
+                .ToFixedListAsync(dto, cancellationToken);
         }
     }
 }

+ 20 - 32
src/Hotline.Application/Jobs/GetCallsJob.cs → src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs

@@ -1,41 +1,34 @@
 using Quartz;
 using SqlSugar;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Hotline.CallCenter.Calls;
 using Hotline.Repository.SqlSugar;
-using Hotline.Share.Enums.CallCenter;
 using Hotline.Users;
 using MapsterMapper;
 using Microsoft.Extensions.Logging;
-using XF.Domain.Cache;
 using XF.Domain.Repository;
 using XingTang.Sdk;
 
 namespace Hotline.Application.Jobs
 {
     /// <summary>
-    /// 查询通话记录
+    /// 查询兴唐通话记录
     /// </summary>
-    public class GetCallsJob : IJob, IDisposable
+    public class XingTangCallsSyncJob : IJob, IDisposable
     {
-        private readonly IRepository<TrCallRecord> _trcallRepository;
+        private readonly IRepository<CallNative> _callRepository;
         private readonly IRepository<User> _userRepository;
         private readonly IMapper _mapper;
-        private readonly ILogger<GetCallsJob> _logger;
+        private readonly ILogger<XingTangCallsSyncJob> _logger;
         private readonly ISqlSugarClient _db;
 
-        public GetCallsJob(
+        public XingTangCallsSyncJob(
             ISugarUnitOfWork<XingTangDbContext> uow,
-            IRepository<TrCallRecord> trcallRepository,
+            IRepository<CallNative> callRepository,
             IRepository<User> userRepository,
             IMapper mapper,
-            ILogger<GetCallsJob> logger)
+            ILogger<XingTangCallsSyncJob> logger)
         {
-            _trcallRepository = trcallRepository;
+            _callRepository = callRepository;
             _userRepository = userRepository;
             _mapper = mapper;
             _logger = logger;
@@ -44,48 +37,43 @@ namespace Hotline.Application.Jobs
 
         public async Task Execute(IJobExecutionContext context)
         {
-            var calls = await _db.Queryable<XingtangCall>()
+            var xingtangCalls = await _db.Queryable<CallXingtang>()
                 .Where(d => (d.IsSync == null || !d.IsSync) && (d.Tries == null || d.Tries <= 50))
                 .Take(10)
                 .ToListAsync(context.CancellationToken);
 
-            var occupyCalls = new List<XingtangCall>();
-            foreach (var call in calls)
+            var occupyCalls = new List<CallXingtang>();
+            foreach (var call in xingtangCalls)
             {
                 call.IsSync = true;
                 call.Tries += 1;
 
                 var rows = await _db.Updateable(call)
-                    .ExecuteCommandWithOptLockAsync(true);
+                    .ExecuteCommandWithOptLockAsync();
                 if (rows > 0)
                     occupyCalls.Add(call);
             }
 
             try
             {
-                var trCalls = _mapper.Map<List<TrCallRecord>>(occupyCalls);
+                var calls = _mapper.Map<List<CallNative>>(occupyCalls);
                 //填充user信息
-                var staffNos = calls.Select(d => d.UserCode).ToList();
+                var staffNos = calls.Select(d => d.StaffNo).ToList();
                 var users = await _userRepository.Queryable()
                     .Where(d => staffNos.Contains(d.StaffNo))
                     .ToListAsync(context.CancellationToken);
 
-                foreach (var trCall in trCalls)
+                foreach (var call in calls)
                 {
-                    //if (trCall.CallDirection is ECallDirection.Out &&
-                    //    string.IsNullOrEmpty(trCall.RecordingAbsolutePath))
-                    //    continue;
-                    Console.WriteLine(trCall.OtherAccept);
-
-                    var user = users.FirstOrDefault(d => d.StaffNo == trCall.StaffNo);
+                    var user = users.FirstOrDefault(d => d.StaffNo == call.StaffNo);
                     if (user is not null)
                     {
-                        trCall.UserId = user.Id;
-                        trCall.UserName = user.Name;
+                        call.UserId = user.Id;
+                        call.UserName = user.Name;
                     }
                 }
 
-                await _trcallRepository.AddRangeAsync(trCalls, context.CancellationToken);
+                await _callRepository.AddRangeAsync(calls, context.CancellationToken);
             }
             catch (Exception e)
             {
@@ -105,7 +93,7 @@ namespace Hotline.Application.Jobs
         /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
         public void Dispose()
         {
-            _logger.LogInformation($"{nameof(GetCallsJob)} disposed");
+            _logger.LogInformation($"{nameof(XingTangCallsSyncJob)} disposed");
         }
     }
 }

+ 36 - 0
src/Hotline.Application/Jobs/XingTangTelOperationSyncJob.cs

@@ -0,0 +1,36 @@
+using Quartz;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Jobs
+{
+    public class XingTangTelOperationSyncJob : IJob, IDisposable
+    {
+        /// <summary>
+        /// Called by the <see cref="T:Quartz.IScheduler" /> when a <see cref="T:Quartz.ITrigger" />
+        /// fires that is associated with the <see cref="T:Quartz.IJob" />.
+        /// </summary>
+        /// <remarks>
+        /// The implementation may wish to set a  result object on the
+        /// JobExecutionContext before this method exits.  The result itself
+        /// is meaningless to Quartz, but may be informative to
+        /// <see cref="T:Quartz.IJobListener" />s or
+        /// <see cref="T:Quartz.ITriggerListener" />s that are watching the job's
+        /// execution.
+        /// </remarks>
+        /// <param name="context">The execution context.</param>
+        public async Task Execute(IJobExecutionContext context)
+        {
+            throw new NotImplementedException();
+        }
+
+        /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+        public void Dispose()
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 30 - 16
src/Hotline.Application/Mappers/CallMapperConfigs.cs

@@ -61,30 +61,44 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.CallAccept, x => x.call_accept)
                 .Map(d => d.Type, x => x.type);
 
-            config.ForType<XingtangCall, TrCallRecord>()
-                .Map(d => d.OtherAccept, s => s.CallGuid)
-                .Map(d => d.CallDirection, s => s.CallType)
-                .Map(d => d.CPN, s => s.Caller)
-                .Map(d => d.CDPN, s => s.Called)
+            config.ForType<CallXingtang, CallNative>()
+                .Map(d => d.CallNo, s => s.CallGuid)
+                .Map(d => d.Direction, s => s.CallType)
+                .Map(d => d.FromNo, s => s.Caller)
+                .Map(d => d.ToNo, s => s.Called)
                 .Map(d => d.TelNo, s => s.Ext)
-                .Map(d => d.StaffNo, s => s.UserCode)
-                .Map(d => d.AnsweredTime, s => s.ReceiveEndTime)
-                .Map(d => d.OverTime, s => s.CallEndTime)
                 .Map(d => d.BeginIvrTime, s => s.CallStartTime)
                 .Map(d => d.EndIvrTime, s => s.EnqueueTime)
+                .Map(d => d.BeginQueueTime, s => s.EnqueueTime)
+                .Map(d => d.EndQueueTime, s => s.DequeueTime)
                 .Map(d => d.BeginRingTime, s => s.RingStartTime)
-                .Map(d => d.EndRingTimg, s => s.ReceiveEndTime.HasValue ? s.ReceiveEndTime : s.CallEndTime)
-                .Map(d => d.OlaQueue, s => s.SkillId.ToString())
+                .Map(d => d.EndRingTime, s => s.ReceiveEndTime.HasValue ? s.ReceiveEndTime : s.CallEndTime)
+                .Map(d => d.AnsweredTime, s => s.ReceiveEndTime)
+                .Map(d => d.EndTime, s => s.CallEndTime)
+                .Map(d => d.GroupId, s => s.SkillId.ToString())
+                .Map(d => d.StaffNo, s => s.UserCode)
                 .Map(d => d.Duration, s => s.Duration)
-                .Map(d => d.RingTimes, s => s.RingTime)
-                .Map(d => d.QueueTims, s => s.WaitTime)
-                .Map(d => d.RecordingAbsolutePath, s => s.AudioFile)
-                .Map(d => d.Gateway, s => s.Called)
+                .Map(d => d.RingDuration, s => s.RingTime)
+                .Map(d => d.WaitDuration, s => s.WaitTime)
+                .Map(d => d.AudioFile, s => s.AudioFile)
                 .AfterMapping((s, d) =>
                 {
-                    d.CreatedTime = DateTime.Now;
-                    d.OnState = s.ReceiveEndTime.HasValue ? EOnState.On : EOnState.NoOn;
+                    //todo 等待兴唐补全Disposition字段
+                    d.EndBy = d.Direction == ECallDirection.In
+                        ? EEndBy.From
+                        : EEndBy.To;
                 });
+
+            config.ForType<CallNative, TrCallDto>()
+                .Map(d => d.CallDirection, s => s.Direction)
+                .Map(d => d.CPN, s => s.FromNo)
+                .Map(d => d.CDPN, s => s.ToNo)
+                .Map(d => d.CreatedTime, s => s.CreationTime)
+                .Map(d => d.OverTime, s => s.EndTime)
+                .Map(d => d.Gateway, s => s.ToNo)
+                .Map(d => d.EndRingTimg, s => s.EndRingTime)
+                ;
+
         }
 
         private DateTime? FormatDateTime(string? time)

+ 33 - 33
src/Hotline.Application/StatisticalReport/OrderReportApplication.cs

@@ -526,13 +526,13 @@ order by ""su"".""OrgCode""";
                     break;
                 case EStatisticsType.OrderDelayCount://延期次数
                     query = _orderDelayRepository.Queryable()
-                        .Includes(x => x.Order)
-                        .Where(x => x.Order.CreationTime >= dto.StartTime && x.Order.CreationTime <= dto.EndTime && x.DelayState == EDelayState.Pass)
-                        .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.Source == ESource.ProvinceStraight)
-                        .WhereIF(dto.OrgCode == "001", x => x.ApplyOrgCode == dto.OrgCode)
-                        .WhereIF(dto.OrgCode != "001", x => x.ApplyOrgCode.StartsWith(dto.OrgCode))
-                        .OrderByDescending(x => x.Order.CreationTime)
-                        .Select(x => new SelectOrderId { Id = x.Order.Id })
+                          .LeftJoin<Order>((d, o) => d.OrderId == o.Id)
+                        .Where((d, o) => o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime && d.DelayState == EDelayState.Pass)
+                        .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, (d, o) => o.Source == ESource.ProvinceStraight)
+                        .WhereIF(dto.OrgCode == "001", (d, o) => d.ApplyOrgCode == dto.OrgCode)
+                        .WhereIF(dto.OrgCode != "001", (d, o) => d.ApplyOrgCode.StartsWith(dto.OrgCode))
+                        .OrderByDescending((d, o) => o.CreationTime)
+                        .Select((d, o) => new SelectOrderId { Id = o.Id })
                        .MergeTable();
                     break;
                 case EStatisticsType.YBOverdue://已办超期
@@ -677,44 +677,44 @@ order by ""su"".""OrgCode""";
                     break;
                 case EStatisticsType.ScreenCount://甄别总量
                     query = _orderScreenRepository.Queryable()
-                        .Includes(x => x.Order)
-                       .Where(x => x.Order.CreationTime >= dto.StartTime && x.Order.CreationTime <= dto.EndTime && x.Order.Id != null)
-                       .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.Source == ESource.ProvinceStraight)
-                       .WhereIF(dto.OrgCode == "001", x => x.CreatorOrgId == dto.OrgCode)
-                       .WhereIF(dto.OrgCode != "001", x => x.CreatorOrgId.StartsWith(dto.OrgCode))
-                        .Select(x => new SelectOrderId { Id = x.Order.Id })
+                        .LeftJoin<Order>((d, o) => d.OrderId == o.Id)
+                       .Where((d, o) => o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime && o.Id != null)
+                       .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, (d, o) => o.Source == ESource.ProvinceStraight)
+                       .WhereIF(dto.OrgCode == "001", (d, o) => d.CreatorOrgId == dto.OrgCode)
+                       .WhereIF(dto.OrgCode != "001", (d, o) => d.CreatorOrgId.StartsWith(dto.OrgCode))
+                        .Select((d, o) => new SelectOrderId { Id = o.Id })
                        .MergeTable();
                     break;
                 case EStatisticsType.ScreenApproval://带甄别
                     query = _orderScreenRepository.Queryable()
-                        .Includes(x => x.Order)
-                       .Where(x => x.Order.CreationTime >= dto.StartTime && x.Order.CreationTime <= dto.EndTime && x.Order.Id != null
-                       && (x.Status == EScreenStatus.Approval || x.Status == EScreenStatus.Apply))
-                       .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.Source == ESource.ProvinceStraight)
-                       .WhereIF(dto.OrgCode == "001", x => x.CreatorOrgId == dto.OrgCode)
-                       .WhereIF(dto.OrgCode != "001", x => x.CreatorOrgId.StartsWith(dto.OrgCode))
-                        .Select(x => new SelectOrderId { Id = x.Order.Id })
+                       .LeftJoin<Order>((d, o) => d.OrderId == o.Id)
+                       .Where((d, o) => o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime && o.Id != null
+                       && (d.Status == EScreenStatus.Approval || d.Status == EScreenStatus.Apply))
+                       .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, (d, o) => o.Source == ESource.ProvinceStraight)
+                       .WhereIF(dto.OrgCode == "001", (d, o) => d.CreatorOrgId == dto.OrgCode)
+                       .WhereIF(dto.OrgCode != "001", (d, o) => d.CreatorOrgId.StartsWith(dto.OrgCode))
+                        .Select((d, o) => new SelectOrderId { Id = o.Id })
                        .MergeTable();
                     break;
                 case EStatisticsType.ScreenPass://甄别通过
                     query = _orderScreenRepository.Queryable()
-                        .Includes(x => x.Order)
-                       .Where(x => x.Order.CreationTime >= dto.StartTime && x.Order.CreationTime <= dto.EndTime && x.Order.Id != null && x.Status == EScreenStatus.End)
-                       .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.Source == ESource.ProvinceStraight)
-                       .WhereIF(dto.OrgCode == "001", x => x.CreatorOrgId == dto.OrgCode)
-                       .WhereIF(dto.OrgCode != "001", x => x.CreatorOrgId.StartsWith(dto.OrgCode))
-                        .Select(x => new SelectOrderId { Id = x.Order.Id })
+                       .LeftJoin<Order>((d, o) => d.OrderId == o.Id)
+                       .Where((d, o) => o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime && o.Id != null && d.Status == EScreenStatus.End)
+                       .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, (d, o) =>o.Source == ESource.ProvinceStraight)
+                       .WhereIF(dto.OrgCode == "001", (d, o) => d.CreatorOrgId == dto.OrgCode)
+                       .WhereIF(dto.OrgCode != "001", (d, o) => d.CreatorOrgId.StartsWith(dto.OrgCode))
+                        .Select((d, o) => new SelectOrderId { Id = o.Id })
                        .MergeTable();
                     break;
                 case EStatisticsType.ScreenNotPass://甄别不通过
                     query = _orderScreenRepository.Queryable()
-                       .Includes(x => x.Order)
-                      .Where(x => x.Order.CreationTime >= dto.StartTime && x.Order.CreationTime <= dto.EndTime && x.Order.Id != null && x.Status == EScreenStatus.Refuse)
-                      .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.Source == ESource.ProvinceStraight)
-                      .WhereIF(dto.OrgCode == "001", x => x.CreatorOrgId == dto.OrgCode)
-                      .WhereIF(dto.OrgCode != "001", x => x.CreatorOrgId.StartsWith(dto.OrgCode))
-                       .Select(x => new SelectOrderId { Id = x.Order.Id })
-                      .MergeTable();
+                      .LeftJoin<Order>((d, o) => d.OrderId == o.Id)
+                      .Where((d, o) => o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime && o.Id != null && d.Status == EScreenStatus.Refuse)
+                      .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, (d, o) => o.Source == ESource.ProvinceStraight)
+                      .WhereIF(dto.OrgCode == "001", (d, o) => d.CreatorOrgId == dto.OrgCode)
+                       .WhereIF(dto.OrgCode != "001", (d, o) => d.CreatorOrgId.StartsWith(dto.OrgCode))
+                        .Select((d, o) => new SelectOrderId { Id = o.Id })
+                       .MergeTable();
                     break;
                 default:
                     break;

+ 20 - 0
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarRepositoryExtensions.cs

@@ -4,7 +4,9 @@ using System.Linq;
 using System.Linq.Expressions;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Share;
 using Hotline.Share.Requests;
+using Microsoft.AspNetCore.Http;
 using SqlSugar;
 using XF.Domain.Entities;
 
@@ -27,5 +29,23 @@ namespace Hotline.Repository.SqlSugar.Extensions
             var items = await query.ToPageListAsync(dto.PageIndex, dto.PageSize, total, cancellationToken);
             return (total.Value, items);
         }
+
+        /// <summary>
+        /// 分批次查询固定数量
+        /// </summary>
+        /// <returns></returns>
+        public static Task<List<TEntity>> ToFixedListAsync<TEntity>(this ISugarQueryable<TEntity> query, QueryFixedDto dto, CancellationToken cancellationToken)
+        where TEntity : class, new()
+        {
+            if (dto.QueryCount == 0) dto.QueryCount = 50;
+            return query.Skip(dto.QueryIndex * dto.QueryCount).Take(dto.QueryCount).ToListAsync(cancellationToken);
+        }
+
+        public static Task<List<TEntity>> ToFixedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int queryIndex, int? queryCount = null, CancellationToken cancellationToken = default)
+            where TEntity : class, new()
+        {
+            if (queryCount is null or 0) queryCount = 50;
+            return query.Skip(queryIndex * queryCount.Value).Take(queryCount.Value).ToListAsync(cancellationToken);
+        }
     }
 }

+ 123 - 0
src/Hotline.Share/Dtos/CallCenter/CallNativeDto.cs

@@ -0,0 +1,123 @@
+using Hotline.Share.Enums.CallCenter;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.CallCenter
+{
+    public class CallNativeDto
+    {
+        /// <summary>
+        /// 通话记录编号
+        /// </summary>
+        public string CallNo { get; set; }
+
+        public ECallDirection Direction { get; set; }
+
+        /// <summary>
+        /// 主叫
+        /// </summary>
+        public string FromNo { get; set; }
+
+        /// <summary>
+        /// 被叫
+        /// </summary>
+        public string ToNo { get; set; }
+
+        /// <summary>
+        /// 响应分机号
+        /// </summary>
+        public string TelNo { get; set; }
+
+        /// <summary>
+        /// 挂断方
+        /// </summary>
+        public EHangupBy? HangupBy { get; set; }
+
+        /// <summary>
+        /// IVR开始时间
+        /// </summary>
+        public DateTime? BeginIvrTime { get; set; }
+        /// <summary>
+        /// IVR结束时间
+        /// </summary>
+        public DateTime? EndIvrTime { get; set; }
+        /// <summary>
+        /// 开始等待时间
+        /// </summary>
+        public DateTime? BeginQueueTime { get; set; }
+        /// <summary>
+        /// 结束等待时间
+        /// </summary>
+        public DateTime? EndQueueTime { get; set; }
+        /// <summary>
+        /// 开始振铃时间
+        /// </summary>
+        public DateTime? BeginRingTime { get; set; }
+        /// <summary>
+        /// 结束振铃时间
+        /// </summary>
+        public DateTime? EndRingTime { get; set; }
+        /// <summary>
+        /// 接听时间
+        /// </summary>
+        public DateTime? AnsweredTime { get; set; }
+        /// <summary>
+        /// 挂机时间
+        /// </summary>
+        public DateTime EndTime { get; set; }
+
+        /// <summary>
+        /// 分机组id(技能组Id)
+        /// </summary>
+        public string? GroupId { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string? StaffNo { get; set; }
+
+        /// <summary>
+        /// 话务员id
+        /// </summary>
+        public string? UserId { get; set; }
+
+        /// <summary>
+        /// 话务员姓名
+        /// </summary>
+        public string? UserName { get; set; }
+
+        /// <summary>
+        /// 评分
+        /// </summary>
+        public int Score { get; set; }
+
+        /// <summary>
+        /// 通话时长(秒)
+        /// </summary>
+        public int Duration { get; set; }
+
+        /// <summary>
+        /// 响铃时长(秒)
+        /// </summary>
+        public int RingDuration { get; set; }
+
+        /// <summary>
+        /// 等待时长
+        /// </summary>
+        public int WaitDuration { get; set; }
+
+        /// <summary>
+        /// 通话录音
+        /// </summary>
+        public string AudioFile { get; set; }
+
+        public string? OrderId { get; set; }
+
+        public string? OrderNo { get; set; }
+
+        public string? Title { get; set; }
+    }
+}

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

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Share.Enums.CallCenter;
+
+namespace Hotline.Share.Dtos.CallCenter
+{
+    public class QueryCallsFixedDto : QueryFixedDto
+    {
+        public string? OrderNo { get; set; }
+
+        public string? FromNo { get; set; }
+
+        public string? ToNo { get; set; }
+
+        public string? UserName { get; set; }
+
+        public string? TelNo { get; set; }
+
+        public EEndBy? EndBy { get; set; }
+
+        /// <summary>
+        /// 呼入时间
+        /// </summary>
+        public DateTime? CallStartTimeBT { get; set; }
+
+        /// <summary>
+        /// 呼入时间
+        /// </summary>
+        public DateTime? CallStartTimeLT { get; set; }
+
+        /// <summary>
+        /// 是否接通
+        /// </summary>
+        public bool? IsConnected { get; set; }
+
+        /// <summary>
+        /// 通话方向
+        /// </summary>
+        public ECallDirection? Direction { get; set; }
+    }
+}

+ 31 - 0
src/Hotline.Share/Dtos/CallCenter/QueryTelOperationsFixedDto.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.CallCenter
+{
+    public class QueryTelOperationsFixedDto : QueryFixedDto
+    {
+        /// <summary>
+        /// 姓名
+        /// </summary>
+        public string? UserName { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string? StaffNo { get; set; }
+
+        /// <summary>
+        /// 工作组
+        /// </summary>
+        public string? GroupId { get; set; }
+
+        /// <summary>
+        /// 操作类型
+        /// </summary>
+        public int? OperateState { get; set; }
+    }
+}

+ 1 - 1
src/Hotline.Share/Dtos/CallCenter/TelDto.cs

@@ -64,7 +64,7 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public List<TelGroupDto> Groups { get; set; }
 
-        #region 兼容添润字段
+        #region 添润
         
         public string Name { get; set; }
 

+ 1 - 1
src/Hotline.Share/Dtos/Order/QueryOrderConstantDto.cs → src/Hotline.Share/Dtos/Order/QueryOrderFixedDto.cs

@@ -3,7 +3,7 @@
 /// <summary>
 /// 定量查询工单
 /// </summary>
-public record QueryOrderConstantDto : QueryOrderDto
+public record QueryOrderFixedDto : QueryOrderDto
 {
     /// <summary>
     /// 查询批次

+ 15 - 0
src/Hotline.Share/Enums/CallCenter/EHangupBy.cs

@@ -0,0 +1,15 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.CallCenter;
+
+/// <summary>
+/// 通话结束方
+/// </summary>
+public enum EHangupBy
+{
+    [Description("外线")]
+    Outside = 0,
+
+    [Description("坐席")]
+    Seat = 1
+}

+ 21 - 0
src/Hotline.Share/QueryFixedDto.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share
+{
+    public class QueryFixedDto
+    {
+        /// <summary>
+        /// 查询批次
+        /// </summary>
+        public int QueryIndex { get; set; }
+
+        /// <summary>
+        /// 单次总数据量
+        /// </summary>
+        public int QueryCount { get; set; } = 50;
+    }
+}

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

@@ -3,7 +3,9 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Orders;
 using Hotline.Share.Enums.CallCenter;
+using SqlSugar;
 using XF.Domain.Repository;
 
 namespace Hotline.CallCenter.Calls
@@ -11,12 +13,13 @@ namespace Hotline.CallCenter.Calls
     /// <summary>
     /// 本地通话记录
     /// </summary>
+    [SugarIndex("index_call_callid", nameof(CallNative.CallNo), OrderByType.Asc)]
     public class CallNative : CreationEntity
     {
         /// <summary>
-        /// 通话id
+        /// 通话记录编号
         /// </summary>
-        public string CallId { get; set; }
+        public string CallNo { get; set; }
 
         public ECallDirection Direction { get; set; }
 
@@ -33,7 +36,12 @@ namespace Hotline.CallCenter.Calls
         /// <summary>
         /// 响应分机号
         /// </summary>
-        public string Ext { get; set; }
+        public string TelNo { get; set; }
+
+        /// <summary>
+        /// 挂断方
+        /// </summary>
+        public EEndBy? EndBy { get; set; }
 
         /// <summary>
         /// IVR开始时间
@@ -79,9 +87,14 @@ namespace Hotline.CallCenter.Calls
         public string? StaffNo { get; set; }
 
         /// <summary>
-        /// 挂断方
+        /// 话务员id
+        /// </summary>
+        public string? UserId { get; set; }
+
+        /// <summary>
+        /// 话务员姓名
         /// </summary>
-        public EEndBy EndBy { get; set; }
+        public string? UserName { get; set; }
 
         /// <summary>
         /// 评分
@@ -107,5 +120,12 @@ namespace Hotline.CallCenter.Calls
         /// 通话录音
         /// </summary>
         public string AudioFile { get; set; }
+
+        //public string? ExternalId { get; set; }
+
+        //public string? OrderNo { get; set; }
+
+        //public string? Title { get; set; }
+
     }
 }

+ 44 - 0
src/Hotline/CallCenter/Tels/TelOperation.cs

@@ -0,0 +1,44 @@
+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.CallCenter.Tels
+{
+    [Description("话机操作记录")]
+    public class TelOperation : CreationEntity
+    {
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string? StaffNo { get; set; }
+
+        public string? UserId { get; set; }
+
+        public string? UserName { get; set; }
+
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        public string? TelNo { get; set; }
+
+        public string? GroupId { get; set; }
+
+        /// <summary>
+        /// 执行状态
+        /// 0:签入 1:接听 2:置忙 3:置闲 16:签出 17:呼出 30:挂机
+        /// </summary>
+        public int? OperateState { get; set; }
+
+        public string? OperateStateText { get; set; }
+
+        public DateTime? OperateTime { get; set; }
+        public int? Channel { get; set; }
+        public string? CompanyId { get; set; }
+        public string? Remark { get; set; }
+    }
+}

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

@@ -345,11 +345,6 @@ namespace Hotline.Settings
         /// </summary>
         public const string CanUpdateOrderSender = "CanUpdateOrderSender";
 
-        /// <summary>
-        /// 定量查询数据条数上限
-        /// </summary>
-        public const string ConstantQueryCount = "ConstantQueryCount";
-
         /// <summary>
         /// 是否开启重复工单
         /// </summary>

+ 3 - 3
src/XingTang.Sdk/XingtangCall.cs → src/XingTang.Sdk/CallXingtang.cs

@@ -3,9 +3,9 @@
 namespace XingTang.Sdk;
 
 [SugarTable("call_cti_trafficlist")]
-public class XingtangCall
+public class CallXingtang
 {
-    [SugarColumn(IsPrimaryKey = true, ColumnName = "ID")]
+    [SugarColumn(ColumnName = "ID", IsPrimaryKey = true)]
     public int Id { get; set; }
 
     /// <summary>
@@ -157,7 +157,7 @@ public class XingtangCall
     public int Tries { get; set; }
 
     [SugarColumn(IsEnableUpdateVersionValidation = true)]
-    public long Ver { get; set; }
+    public string Ver { get; set; }
 
     #endregion
 }

+ 83 - 0
src/XingTang.Sdk/SeatOperation.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SqlSugar;
+
+namespace XingTang.Sdk
+{
+    /// <summary>
+    /// 坐席操作记录
+    /// </summary>
+    [SugarTable("call_cti_seatingoperation")]
+    public class SeatOperation
+    {
+        [SugarColumn(ColumnName = "ID", IsPrimaryKey = true)]
+        public int Id { get; set; }
+
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string? UserCode { get; set; }
+
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        public string? Ext { get; set; }
+
+        /// <summary>
+        /// 执行状态
+        /// 0:签入 1:接听 2:置忙 3:置闲 16:签出 17:呼出 30:挂机
+        /// </summary>
+        public int? ExecutionState { get; set; }
+
+        public string? ExtutionStateText => ExecutionState.HasValue
+            ? ExecutionState switch
+            {
+                0 => "签入",
+                1 => "接听",
+                2 => "置忙",
+                3 => "置闲",
+                16 => "签出",
+                17 => "呼出",
+                30 => "挂机",
+                _ => "未知"
+            }
+            : string.Empty;
+
+        /// <summary>
+        /// 操作时间
+        /// </summary>
+        public DateTime? ExecutionTime { get; set; }
+
+        /// <summary>
+        /// 通道号
+        /// </summary>
+        public int? Channel { get; set; }
+
+        /// <summary>
+        /// 公司号
+        /// </summary>
+        public string? CompanyId { get; set; }
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        public string? Remark { get; set; }
+
+        //弃用
+        public DateTime? EndTime { get; set; }
+
+        #region 自建
+
+        public bool IsSync { get; set; }
+
+        public int Tries { get; set; }
+
+        [SugarColumn(IsEnableUpdateVersionValidation = true)]
+        public long Ver { get; set; }
+
+        #endregion
+    }
+}