Jelajahi Sumber

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

Dun.Jason 9 bulan lalu
induk
melakukan
e7bae8597a
26 mengubah file dengan 1636 tambahan dan 589 penghapusan
  1. 34 55
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  2. 1 5
      src/Hotline.Api/Controllers/Bigscreen/JudicialManagementScreenController.cs
  3. 84 28
      src/Hotline.Api/Controllers/CommonPController.cs
  4. 257 132
      src/Hotline.Api/Controllers/OrderController.cs
  5. 75 20
      src/Hotline.Api/Controllers/QualityController.cs
  6. 3 1
      src/Hotline.Api/StartupExtensions.cs
  7. 10 1
      src/Hotline.Api/StartupHelper.cs
  8. 361 0
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  9. 65 2
      src/Hotline.Application/CallCenter/ICallApplication.cs
  10. 123 114
      src/Hotline.Application/CallCenter/TianRunCallApplication.cs
  11. 313 167
      src/Hotline.Application/CallCenter/XingTangCallApplication.cs
  12. 32 6
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  13. 44 11
      src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs
  14. 12 3
      src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs
  15. 79 2
      src/Hotline.Application/Jobs/XingTangTelOperationSyncJob.cs
  16. 18 6
      src/Hotline.Application/Mappers/CallMapperConfigs.cs
  17. 3 1
      src/Hotline.Share/Dtos/CallCenter/CallNativeDto.cs
  18. 12 2
      src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs
  19. 31 0
      src/Hotline/AppDefaults.cs
  20. 49 0
      src/Hotline/CallCenter/Calls/CallIdManager.cs
  21. 20 0
      src/Hotline/CallCenter/Calls/CallidRelation.cs
  22. 2 2
      src/Hotline/CallCenter/Tels/TelDomainService.cs
  23. 0 25
      src/Hotline/Orders/OrderDefaults.cs
  24. 5 5
      src/Hotline/Orders/OrderDomainService.cs
  25. 1 0
      src/XF.Domain/XF.Domain.csproj
  26. 2 1
      src/XingTang.Sdk/XingtangSeatOperation.cs

+ 34 - 55
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -3137,7 +3137,7 @@ namespace Hotline.Api.Controllers.Bi
         /// <param name="StartTime">开始时间</param>
         /// <param name="EndTime">结束时间</param>
         /// <param name="TypeId">0:全部 ,1:市民,2:企业</param>
-        /// <param name="AreaCode"></param>
+        /// <param name="AreaCode">上级区域Id</param>
         /// <returns></returns>
         [HttpGet("area_subordinate")]
         public async Task<object> AreaSubordinate(DateTime StartTime, DateTime EndTime, int TypeId, string? AreaCode)
@@ -3146,63 +3146,42 @@ namespace Hotline.Api.Controllers.Bi
 
             var IsCenter = _sessionContext.OrgIsCenter;
 
-            if (string.IsNullOrEmpty(AreaCode))
-            {
-                var list =  _systemAreaRepository.Queryable()
-                .LeftJoin<Order>((it, o) => it.Id == o.AreaCode)
-                .Where((it, o) => o.CreationTime >= StartTime && o.CreationTime <= EndTime && o.Id != null && it.ParentId == "510000")
-                .WhereIF(TypeId == 1, (it, o) => o.IdentityType == EIdentityType.Citizen)
-                .WhereIF(TypeId == 2, (it, o) => o.IdentityType == EIdentityType.Enterprise)
-                .WhereIF(IsCenter == false, (it, o) => o.ActualHandleOrgCode.StartsWith(_sessionContext.RequiredOrgId))
-                .GroupBy((it, o) => new { Id = it.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("6")) })
-                .Select((it, o) => new
-                {
-                    AreaCode = it.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>("2")),
-                    SumCount = SqlFunc.AggregateCount(it.AreaName)
-                })
-                .MergeTable()
-                .LeftJoin<SystemArea>((x, q) => x.AreaCode == q.Id)
-                .Select((x, q) => new
-                {
-                    AreaCode = x.AreaCode,
-                    SumCount = x.SumCount,
-                    AreaName = q.AreaName,
-                    HasChild = SqlFunc.Subqueryable<SystemArea>().Where(d => d.ParentId == x.AreaCode).Any()
-                }).ToSqlString();
-             //   .ToListAsync();
+            string count = "6";
+            if (!string.IsNullOrEmpty(AreaCode) && AreaCode!= "511500")
+                count = (AreaCode.Length + 2).ToString();
 
-                return null;
-            }
-            else
-            {
-                string count = (AreaCode.Length + 2).ToString();
-                string countx = AreaCode.Length.ToString();
-                var list = await _systemAreaRepository.Queryable()
-                .LeftJoin<Order>((it, o) => it.Id == o.HotspotId)
-                .Where((it, o) => o.CreationTime >= StartTime && o.CreationTime <= EndTime
-                && it.ParentId.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(countx)) == AreaCode)
-                .WhereIF(TypeId == 1, (it, o) => o.IdentityType == EIdentityType.Citizen)
-                .WhereIF(TypeId == 2, (it, o) => o.IdentityType == EIdentityType.Enterprise)
-                .WhereIF(IsCenter == false, (it, o) => o.ActualHandleOrgCode.StartsWith(_sessionContext.RequiredOrgId))
-                .GroupBy((it, o) => new { Id = it.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(count)) })
-                .Select((it, o) => new
-                {
-                    AreaCode = it.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(count)),
-                    SumCount = SqlFunc.AggregateCount(it.AreaName)
-                })
-                .MergeTable()
-               .LeftJoin<SystemArea>((x, q) => x.AreaCode == q.Id)
-                .Select((x, q) => new
-                {
-                    AreaCode = x.AreaCode,
-                    SumCount = x.SumCount,
-                    AreaName = q.AreaName,
-                    HasChild = SqlFunc.Subqueryable<SystemArea>().Where(d => d.ParentId == x.AreaCode).Any()
-                })
+            if (string.IsNullOrEmpty(AreaCode))
+                AreaCode = "510000";
+
+            var query = _orderRepository.Queryable()
+                  .Where(p => p.CreationTime >= StartTime && p.CreationTime <= EndTime)
+                    .WhereIF(TypeId == 1, p => p.IdentityType == EIdentityType.Citizen)
+                  .WhereIF(TypeId == 2, p => p.IdentityType == EIdentityType.Enterprise)
+                  .WhereIF(IsCenter == false, p => p.ActualHandleOrgCode.StartsWith(_sessionContext.RequiredOrgId))
+                   .GroupBy(p => new { Id = p.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(count)) })
+                  .Select(p => new
+                  {
+                      AreaCode = p.AreaCode.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(count)),
+                      SumCount = SqlFunc.AggregateCount(p.AreaCode)
+                  })
+                  .MergeTable();
+
+            var list = await _systemAreaRepository.Queryable()
+                  .LeftJoin(query, (s, o) => s.Id == o.AreaCode)
+                  .Where((s, o) => s.ParentId == AreaCode)
+                  .Select((s, o) => new
+                  {
+                      AreaCode = s.Id,
+                      SumCount = o.SumCount,
+                      AreaName = s.AreaName,
+                      HasChild = SqlFunc.Subqueryable<SystemArea>().Where(d => d.ParentId == o.AreaCode).Any()
+                  })
                 .ToListAsync();
-                return list;
 
-            }
+            return list;
+
+
+
         }
     }
 }

+ 1 - 5
src/Hotline.Api/Controllers/Bigscreen/JudicialManagementScreenController.cs

@@ -17,7 +17,6 @@ namespace Hotline.Api.Controllers.Bigscreen
     public class JudicialManagementScreenController : BaseController
     {
         private readonly IMapper _mapper;
-        private readonly IRepository<EnforcementOrders> _enforcementOrdersRepository;
         private readonly IRepository<SystemArea> _systemAreaRepository;
         private readonly IRepository<JudicialManagementOrders> _judicialManagementOrdersRepository;
 
@@ -25,16 +24,13 @@ namespace Hotline.Api.Controllers.Bigscreen
         /// 
         /// </summary>
         /// <param name="mapper"></param>
-        /// <param name="enforcementOrdersRepository"></param>
         /// <param name="systemAreaRepository"></param>
         /// <param name="judicialManagementOrdersRepository"></param>
         public JudicialManagementScreenController(IMapper mapper,
-            IRepository<EnforcementOrders> enforcementOrdersRepository,
             IRepository<SystemArea> systemAreaRepository,
             IRepository<JudicialManagementOrders> judicialManagementOrdersRepository)
         {
             _mapper = mapper;
-            _enforcementOrdersRepository = enforcementOrdersRepository;
             _systemAreaRepository = systemAreaRepository;
             _judicialManagementOrdersRepository = judicialManagementOrdersRepository;
         }
@@ -217,7 +213,7 @@ namespace Hotline.Api.Controllers.Bigscreen
         {
             var list = await _judicialManagementOrdersRepository.Queryable()
                   .LeftJoin<SystemArea>((o, s) => o.AreaCode == s.Id)
-                  .Where((o, s) => o.CreationTime.Date >= DateTime.Now.AddDays(-30).Date)
+                  .Where((o, s) => o.CreationTime.Date >= DateTime.Now.Date)
                   .Select((o, s) => new
                   {
                       o.Id,

+ 84 - 28
src/Hotline.Api/Controllers/CommonPController.cs

@@ -10,9 +10,13 @@ using Microsoft.AspNetCore.Mvc;
 using MongoDB.Driver;
 using SqlSugar;
 using System.Reflection.Metadata;
+using Hotline.Application.CallCenter;
+using Hotline.CallCenter.Configs;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Settings.TimeLimits;
+using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.Order;
+using Microsoft.Extensions.Options;
 using XF.Domain.Authentications;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
@@ -28,40 +32,46 @@ namespace Hotline.Api.Controllers
         private readonly ISystemAreaDomainService _systemAreaDomainService;
         private readonly IMapper _mapper;
         private readonly ISessionContext _sessionContext;
-        private readonly IRepository<TrCallRecord> _trCallRecordRepository;
+        // private readonly IRepository<TrCallRecord> _trCallRecordRepository;
         private readonly IOrderRepository _orderRepository;
         private readonly IOrderDelayRepository _orderDelayRepository;
         private readonly ITimeLimitDomainService _timeLimitDomainService;
         private readonly IOrderScreenRepository _orderScreenRepository;
         private readonly IRepository<OrderVisitDetail> _orderVisitedDetailRepository;
+        private readonly ICallApplication _callApplication;
+        private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
 
         public CommonPController(
             ISystemCommonOpinionDomainService commonOpinionDomainService,
             ISystemAreaDomainService systemAreaDomainService,
             ISessionContext sessionContext,
-            IRepository<TrCallRecord> trCallRecordRepository,
+            // IRepository<TrCallRecord> trCallRecordRepository,
             IOrderRepository orderRepository,
             IMapper mapper,
             IOrderDelayRepository orderDelayRepository,
             ITimeLimitDomainService timeLimitDomainService,
             IOrderScreenRepository orderScreenRepository,
-            IRepository<OrderVisitDetail> orderVisitedDetailRepository)
+            IRepository<OrderVisitDetail> orderVisitedDetailRepository,
+            ICallApplication callApplication,
+            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions)
         {
             _commonOpinionDomainService = commonOpinionDomainService;
             _systemAreaDomainService = systemAreaDomainService;
             _mapper = mapper;
             _sessionContext = sessionContext;
-            _trCallRecordRepository = trCallRecordRepository;
+            // _trCallRecordRepository = trCallRecordRepository;
             _orderRepository = orderRepository;
             _orderDelayRepository = orderDelayRepository;
             _timeLimitDomainService = timeLimitDomainService;
             _orderScreenRepository = orderScreenRepository;
             _orderVisitedDetailRepository = orderVisitedDetailRepository;
+            _callApplication = callApplication;
+            _callcenterOptions = callcenterOptions;
         }
 
 
-
         #region 省市区
+
         /// <summary>
         /// 获取省市区树形
         /// </summary>
@@ -89,46 +99,88 @@ namespace Hotline.Api.Controllers
                 var orderQuery = _orderRepository.Queryable(false, false, false)
                     .Includes(o => o.Workflow, w => w.Steps);
                 //今日来电
-                var tadayCalls = await _trCallRecordRepository.Queryable()
-                    .Where(x => x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && tadayTime.Equals(x.CreatedTime.ToString("yyyy-MM-dd"))).ToListAsync();
-                var callNum = tadayCalls.Count();
-                var validCallNum = tadayCalls.Where(x => x.Duration > 0 || x.QueueTims > 0 || x.RingTimes > 0).Count();
-                //今日接通率
-                var answeredNum = tadayCalls.Where(x => x.Duration > 0).Count();
-                var answeredRate = validCallNum > 0 ? Math.Round((double.Parse(answeredNum.ToString()) / double.Parse(validCallNum.ToString())) * 100, 2) + "%" : "-";
+                //  var tadayCalls = await _trCallRecordRepository.Queryable()
+                //      .Where(x => x.CallDirection == Share.Enums.CallCenter.ECallDirection.In && tadayTime.Equals(x.CreatedTime.ToString("yyyy-MM-dd"))).ToListAsync();
+                //  var callNum = tadayCalls.Count();
+                //  var validCallNum = tadayCalls.Where(x => x.Duration > 0 || x.QueueTims > 0 || x.RingTimes > 0).Count();
+                //  //今日接通率
+                //  var answeredNum = tadayCalls.Where(x => x.Duration > 0).Count();
+                // var answeredRate = validCallNum > 0 ? Math.Round((double.Parse(answeredNum.ToString()) / double.Parse(validCallNum.ToString())) * 100, 2) + "%" : "-";
+
+                int callNum = 0, validCallNum = 0, answeredNum = 0;
+                var answeredRate = string.Empty;
+                var today = DateTime.Today.Date;
+                if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.TianRun)
+                {
+                    var calls = await _callApplication.QueryTianrunCallsAsync(
+                        direction: ECallDirection.In,
+                        callStartTimeStart: today,
+                        callStartTimeEnd: today.AddDays(1).AddSeconds(-1),
+                        cancellationToken: HttpContext.RequestAborted);
+                    callNum = calls.Count();
+                    validCallNum = calls.Where(x => x.Duration > 0 || x.QueueTims > 0 || x.RingTimes > 0).Count();
+                    //今日接通率
+                    answeredNum = calls.Where(x => x.Duration > 0).Count();
+                    answeredRate = validCallNum > 0
+                        ? Math.Round((double.Parse(answeredNum.ToString()) / double.Parse(validCallNum.ToString())) * 100, 2) + "%"
+                        : "-";
+                }
+                else if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.XingTang)
+                {
+                    var calls = await _callApplication.QueryCallsAsync(
+                        callStartTimeStart: today,
+                        callStartTimeEnd: today.AddDays(1).AddSeconds(-1),
+                        cancellationToken: HttpContext.RequestAborted);
+                    callNum = calls.Count();
+                    validCallNum = calls.Where(x => x.Duration > 0 || x.WaitDuration > 0 || x.RingDuration > 0).Count();
+                    //今日接通率
+                    answeredNum = calls.Where(x => x.Duration > 0).Count();
+                    answeredRate = validCallNum > 0
+                        ? Math.Round((double.Parse(answeredNum.ToString()) / double.Parse(validCallNum.ToString())) * 100, 2) + "%"
+                        : "-";
+                }
+
                 //今日受理工单
                 var tadayOrders = await orderQuery
                     .Where(o => tadayTime.Equals(o.CreationTime.ToString("yyyy-MM-dd"))).ToListAsync();
                 var orderNum = tadayOrders.Count();
                 var directlyNum = tadayOrders.Where(o => o.ProcessType == Share.Enums.Order.EProcessType.Zhiban).Count();
-                return new { CallNum = callNum, ValidCallNum = validCallNum, AnsweredNum = answeredNum, AnsweredRate = answeredRate, OrderNum = orderNum, DirectlyNum = directlyNum };
+                return new
+                {
+                    CallNum = callNum, ValidCallNum = validCallNum, AnsweredNum = answeredNum, AnsweredRate = answeredRate, OrderNum = orderNum,
+                    DirectlyNum = directlyNum
+                };
             }
+
             //部门
             //今日待办 tasksOkNum
             //var time = DateTime.Parse(tadayTime);
             //工单
             var order = await _orderRepository.Queryable()
-               .Where(o => SqlFunc.JsonListObjectAny(o.HandlerUsers, "Key", _sessionContext.RequiredUserId) || SqlFunc.JsonListObjectAny(o.HandlerOrgs, "Key", _sessionContext.RequiredOrgId))
-               .GroupBy(o => o.Id).MergeTable()
-               .Select(o => new
-               {
-                   aboutExpire = SqlFunc.AggregateSum(SqlFunc.IIF(DateTime.Now > o.NearlyExpiredTime!.Value && DateTime.Now < o.ExpiredTime!.Value, 1, 0)),
-                   havExpired = SqlFunc.AggregateSum(SqlFunc.IIF(DateTime.Now > o.ExpiredTime!.Value, 1, 0)),
-                   countersignHandle = SqlFunc.AggregateSum(SqlFunc.IIF(o.CounterSignType.HasValue, 1, 0)),
-
-               }).FirstAsync();
+                .Where(o => SqlFunc.JsonListObjectAny(o.HandlerUsers, "Key", _sessionContext.RequiredUserId) ||
+                            SqlFunc.JsonListObjectAny(o.HandlerOrgs, "Key", _sessionContext.RequiredOrgId))
+                .GroupBy(o => o.Id).MergeTable()
+                .Select(o => new
+                {
+                    aboutExpire = SqlFunc.AggregateSum(SqlFunc.IIF(DateTime.Now > o.NearlyExpiredTime!.Value && DateTime.Now < o.ExpiredTime!.Value,
+                        1, 0)),
+                    havExpired = SqlFunc.AggregateSum(SqlFunc.IIF(DateTime.Now > o.ExpiredTime!.Value, 1, 0)),
+                    countersignHandle = SqlFunc.AggregateSum(SqlFunc.IIF(o.CounterSignType.HasValue, 1, 0)),
+                }).FirstAsync();
             var aboutExpire = order?.aboutExpire ?? 0;
             var havExpired = order?.havExpired ?? 0;
             var countersignHandle = order?.countersignHandle ?? 0;
             //延期
             var delay = await _orderDelayRepository.Queryable()
                 .Includes(x => x.Workflow)
-                .Where(x => SqlFunc.JsonListObjectAny(x.HandlerUsers, "Key", _sessionContext.RequiredUserId) || SqlFunc.JsonListObjectAny(x.HandlerOrgs, "Key", _sessionContext.RequiredOrgId))
+                .Where(x => SqlFunc.JsonListObjectAny(x.HandlerUsers, "Key", _sessionContext.RequiredUserId) ||
+                            SqlFunc.JsonListObjectAny(x.HandlerOrgs, "Key", _sessionContext.RequiredOrgId))
                 .Where(x => x.DelayState == EDelayState.Examining).CountAsync();
             //甄别
             var screenAudit = await _orderScreenRepository.Queryable()
                 .Includes(x => x.Workflow)
-                 .Where(x => SqlFunc.JsonListObjectAny(x.HandlerUsers, "Key", _sessionContext.RequiredUserId) || SqlFunc.JsonListObjectAny(x.HandlerOrgs, "Key", _sessionContext.RequiredOrgId))
+                .Where(x => SqlFunc.JsonListObjectAny(x.HandlerUsers, "Key", _sessionContext.RequiredUserId) ||
+                            SqlFunc.JsonListObjectAny(x.HandlerOrgs, "Key", _sessionContext.RequiredOrgId))
                 .Where(x => x.Status == EScreenStatus.Apply)
                 .CountAsync();
             var workTime = _timeLimitDomainService.CalcWorkTimeReduce(DateTime.Now, 5);
@@ -139,9 +191,13 @@ namespace Hotline.Api.Controllers
                 .Where(x => x.OrderVisit.VisitTime < DateTime.Now && x.OrderVisit.VisitTime > workTime)
                 .Where((x, s) => x.OrderVisit.VisitState == EVisitState.Visited && x.OrderVisit.IsCanHandle)
                 .Where((x, s) => x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode == _sessionContext.OrgId && (
-                SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" || SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
-                SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" || SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2")).CountAsync();
-            return new { AboutExpire = aboutExpire, HavExpired = havExpired, CountersignHandle = countersignHandle, ScreenAudit = screenAudit, Delay = delay, ScreenHandle = screenHandle };
+                    SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" || SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
+                    SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" || SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2")).CountAsync();
+            return new
+            {
+                AboutExpire = aboutExpire, HavExpired = havExpired, CountersignHandle = countersignHandle, ScreenAudit = screenAudit, Delay = delay,
+                ScreenHandle = screenHandle
+            };
         }
     }
-}
+}

File diff ditekan karena terlalu besar
+ 257 - 132
src/Hotline.Api/Controllers/OrderController.cs


+ 75 - 20
src/Hotline.Api/Controllers/QualityController.cs

@@ -25,6 +25,9 @@ using Hotline.Ai.Quality;
 using Newtonsoft.Json;
 using Polly;
 using Hotline.Api.Filter;
+using Hotline.Application.CallCenter;
+using Hotline.CallCenter.Configs;
+using Microsoft.Extensions.Options;
 using XF.Domain.Constants;
 
 namespace Hotline.Api.Controllers
@@ -46,6 +49,8 @@ namespace Hotline.Api.Controllers
 		private readonly IAiQualityService _aiQualityService;
 		private readonly ILogger<QualityController> _logger;
 		private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+		private readonly ICallApplication _callApplication;
+		private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
 
 		public QualityController(
 			ISessionContext sessionContext,
@@ -57,13 +62,14 @@ namespace Hotline.Api.Controllers
 			IRepository<QualityTemplateDetail> qualiteyTemplateDetail,
 			IRepository<QualityProhibited> qualiteyProhibited,
 			ISystemDicDataCacheManager systemDicDataCacheManager,
-			IRepository<TrCallRecord> trCallRecordRepository,
+			 IRepository<TrCallRecord> trCallRecordRepository,
 			IQualityApplication qualityApplication,
 			IOrderRepository orderRepository,
 			IAiQualityService aiQualityService,
 			ILogger<QualityController> logger,
-			ISystemSettingCacheManager systemSettingCacheManager
-		)
+			ISystemSettingCacheManager systemSettingCacheManager,
+			ICallApplication callApplication,
+			IOptionsSnapshot<CallCenterConfiguration> callcenterOptions)
 		{
 			_sessionContext = sessionContext;
 			_mapper = mapper;
@@ -80,6 +86,8 @@ namespace Hotline.Api.Controllers
 			_aiQualityService = aiQualityService;
 			_logger = logger;
 			_systemSettingCacheManager = systemSettingCacheManager;
+			_callApplication = callApplication;
+			_callcenterOptions = callcenterOptions;
 		}
 		#region 质检管理
 		/// <summary>
@@ -152,25 +160,72 @@ namespace Hotline.Api.Controllers
 				.Includes(x => x.QualityDetails)
 				.FirstAsync(x => x.Id == id);
 			var qualityDto = _mapper.Map<QualityDto>(quality);
-			if (qualityDto.Order != null) {
-                //var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == qualityDto.Order.CallId).FirstAsync(); //由CallAccept改为OtherAccept
-                var call = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == qualityDto.Order.CallId).FirstAsync();
-                if (call != null)
-                {
-					qualityDto.Order.RecordingBaseAddress = call.RecordingBaseAddress;
-					qualityDto.Order.RecordingAbsolutePath = call.RecordingAbsolutePath;
-                }
+			// if (qualityDto.Order != null) {
+   //              //var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == qualityDto.Order.CallId).FirstAsync(); //由CallAccept改为OtherAccept
+   //              var call = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == qualityDto.Order.CallId).FirstAsync();
+   //              if (call != null)
+   //              {
+			// 		qualityDto.Order.RecordingBaseAddress = call.RecordingBaseAddress;
+			// 		qualityDto.Order.RecordingAbsolutePath = call.RecordingAbsolutePath;
+   //              }
+			// }
+			// if (qualityDto.Visit != null)
+			// {
+   //              //var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == qualityDto.Visit.CallId).FirstAsync(); //由CallAccept改为OtherAccept
+   //              var call = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == qualityDto.Visit.CallId).FirstAsync();
+   //              if (call != null)
+   //              {
+   //                  qualityDto.Visit.RecordingBaseAddress = call.RecordingBaseAddress;
+   //                  qualityDto.Visit.RecordingAbsolutePath = call.RecordingAbsolutePath;
+   //              }
+   //          }
+			
+			if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.TianRun)
+			{
+				if (qualityDto.Order != null) {
+					//var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == qualityDto.Order.CallId).FirstAsync(); //由CallAccept改为OtherAccept
+					//var call = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == qualityDto.Order.CallId).FirstAsync();
+					var call = await _callApplication.GetTianrunCallAsync(qualityDto.Order.CallId, HttpContext.RequestAborted); 
+					if (call != null)
+					{
+						qualityDto.Order.RecordingBaseAddress = call.RecordingBaseAddress;
+						qualityDto.Order.RecordingAbsolutePath = call.RecordingAbsolutePath;
+					}
+				}
+				if (qualityDto.Visit != null)
+				{
+					//var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == qualityDto.Visit.CallId).FirstAsync(); //由CallAccept改为OtherAccept
+					// var call = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == qualityDto.Visit.CallId).FirstAsync();
+					var call = await _callApplication.GetTianrunCallAsync(qualityDto.Visit.CallId, HttpContext.RequestAborted);
+					if (call != null)
+					{
+						qualityDto.Visit.RecordingBaseAddress = call.RecordingBaseAddress;
+						qualityDto.Visit.RecordingAbsolutePath = call.RecordingAbsolutePath;
+					}
+				}
 			}
-			if (qualityDto.Visit != null)
+			else if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.XingTang)
 			{
-                //var call = await _trCallRecordRepository.Queryable().Where(x => x.CallAccept == qualityDto.Visit.CallId).FirstAsync(); //由CallAccept改为OtherAccept
-                var call = await _trCallRecordRepository.Queryable().Where(x => x.OtherAccept == qualityDto.Visit.CallId).FirstAsync();
-                if (call != null)
-                {
-                    qualityDto.Visit.RecordingBaseAddress = call.RecordingBaseAddress;
-                    qualityDto.Visit.RecordingAbsolutePath = call.RecordingAbsolutePath;
-                }
-            }
+				if (!string.IsNullOrEmpty(qualityDto?.Order?.CallId))
+				{
+					var call = await _callApplication.GetCallAsync(qualityDto.Order.CallId, HttpContext.RequestAborted); 
+					if (call != null)
+					{
+						qualityDto.Order.RecordingBaseAddress = call.AudioFile;
+						qualityDto.Order.RecordingAbsolutePath = call.AudioFile;
+					}
+				}
+				if (!string.IsNullOrEmpty(qualityDto.Visit.CallId))
+				{
+					var call = await _callApplication.GetCallAsync(qualityDto.Visit.CallId, HttpContext.RequestAborted);
+					if (call != null)
+					{
+						qualityDto.Visit.RecordingBaseAddress = call.AudioFile;
+						qualityDto.Visit.RecordingAbsolutePath = call.AudioFile;
+					}
+				}
+			}
+			
 			return qualityDto;
 		}
 		#endregion

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

@@ -25,6 +25,7 @@ using Hotline.Share.Dtos.SendSms;
 using Hotline.Wex;
 using Hotline.Application.CallCenter.Calls;
 using Hotline.Application.CallCenter;
+using Hotline.CallCenter.Calls;
 
 namespace Hotline.Api;
 
@@ -113,7 +114,7 @@ internal static class StartupExtensions
                 break;
             case "TianRun":
                 services
-                    //.AddScoped<ICallApplication, TianRunCallApplication>()
+                    .AddScoped<ICallApplication, TianRunCallApplication>()
                     .AddScoped<ITrApplication, TrApplication>()
                     .AddHostedService<CurrentWaitNumService>()
                     .AddHostedService<TelsStatusRefreshService>()
@@ -124,6 +125,7 @@ internal static class StartupExtensions
             case "XingTang":
                 services.AddXingTangDb(callCenterConfiguration.XingTang)
                     .AddScoped<ICallApplication, XingTangCallApplication>()
+                    .AddScoped<CallIdManager>()
                     ;
                 break;
             default:

+ 10 - 1
src/Hotline.Api/StartupHelper.cs

@@ -262,7 +262,7 @@ namespace Hotline.Api
 
                 switch (callCenterConfiguration.CallCenterType)
                 {
-                    case "XingTang":
+                    case AppDefaults.CallCenterType.XingTang:
                         var getCallsJobKey = new JobKey(nameof(XingTangCallsSyncJob));
                         d.AddJob<XingTangCallsSyncJob>(getCallsJobKey);
                         d.AddTrigger(d => d
@@ -271,6 +271,15 @@ namespace Hotline.Api
                             .StartNow()
                             .WithCronSchedule("0/5 * * * * ?")
                         );
+
+                        var getOperationsJobKey = new JobKey(nameof(XingTangTelOperationSyncJob));
+                        d.AddJob<XingTangTelOperationSyncJob>(getOperationsJobKey);
+                        d.AddTrigger(d => d
+                            .WithIdentity("get-operationsxt-trigger")
+                            .ForJob(getOperationsJobKey)
+                            .StartNow()
+                            .WithCronSchedule("0/10 * * * * ?")
+                        );
                         break;
                 }
             });

+ 361 - 0
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -0,0 +1,361 @@
+using Hotline.Caching.Interfaces;
+using Hotline.CallCenter.BlackLists;
+using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Tels;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
+using Hotline.Users;
+using MapsterMapper;
+using Microsoft.Extensions.Logging;
+using XF.Domain.Authentications;
+using XF.Domain.Cache;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.CallCenter;
+
+public abstract class DefaultCallApplication : ICallApplication
+{
+    private readonly IRepository<Tel> _telRepository;
+    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 IRepository<CallidRelation> _callIdRelationRepository;
+    private readonly ITypedCache<Work> _cacheWork;
+    private readonly IUserCacheManager _userCacheManager;
+    private readonly ISessionContext _sessionContext;
+    private readonly IMapper _mapper;
+    private readonly ILogger<DefaultCallApplication> _logger;
+
+    public DefaultCallApplication(
+        IRepository<Tel> telRepository,
+        IRepository<TelGroup> telGroupRepository,
+        IWorkRepository workRepository,
+        ITelRestRepository telRestRepository,
+        IRepository<CallNative> callNativeRepository,
+        IRepository<TelOperation> telOperationRepository,
+        IRepository<CallidRelation> callIdRelationRepository,
+        ITypedCache<Work> cacheWork,
+        IUserCacheManager userCacheManager,
+        ISessionContext sessionContext,
+        IMapper mapper,
+        ILogger<DefaultCallApplication> logger)
+    {
+        _telRepository = telRepository;
+        _telGroupRepository = telGroupRepository;
+        _workRepository = workRepository;
+        _telRestRepository = telRestRepository;
+        _callNativeRepository = callNativeRepository;
+        _telOperationRepository = telOperationRepository;
+        _callIdRelationRepository = callIdRelationRepository;
+        _cacheWork = cacheWork;
+        _userCacheManager = userCacheManager;
+        _sessionContext = sessionContext;
+        _mapper = mapper;
+        _logger = logger;
+    }
+
+    public DefaultCallApplication()
+    {
+    }
+
+    /// <summary>
+    /// 查询分机
+    /// </summary>
+    public async Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
+    {
+        return await _telRepository.Queryable()
+            .Select<TelDto>()
+            .ToListAsync(cancellationToken);
+    }
+
+    /// <summary>
+    /// 查询分机组
+    /// </summary>
+    public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
+    {
+        return await _telGroupRepository.Queryable()
+            .Select<TelGroupDto>()
+            .ToListAsync(cancellationToken);
+    }
+
+    /// <summary>
+    /// 新增黑名单
+    /// </summary>
+    public abstract Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 删除黑名单
+    /// </summary>
+    public abstract Task RemoveBlackListAsync(string id, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 查询黑名单
+    /// </summary>
+    public abstract Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 签入
+    /// </summary>
+    public async Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken)
+    {
+        if (string.IsNullOrEmpty(dto.TelNo))
+            throw UserFriendlyException.SameMessage("无效分机号");
+        var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
+        if (work is not null)
+        {
+            //if (work.TelNo != dto.TelNo)
+            //{
+            //    throw UserFriendlyException.SameMessage("当前用户已签入其他分机");
+            //}
+            throw UserFriendlyException.SameMessage("当前用户已签入");
+        }
+
+        var telWork = _userCacheManager.GetWorkByTelNoExp(dto.TelNo);
+        if (telWork is not null)
+        {
+            throw UserFriendlyException.SameMessage("当前分机已被占用");
+        }
+
+        work = new Work(_sessionContext.RequiredUserId, _sessionContext.UserName,
+            dto.TelNo, dto.TelNo, null, null,
+            dto.GroupId, _sessionContext.StaffNo, null);
+        await _workRepository.AddAsync(work, cancellationToken);
+
+        return new TrOnDutyResponseDto
+        {
+            TelNo = dto.TelNo,
+            QueueId = dto.GroupId,
+            StartTime = work.StartTime,
+        };
+    }
+
+    /// <summary>
+    /// 签出
+    /// </summary>
+    public async Task SingOutAsync(CancellationToken cancellationToken)
+    {
+        var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
+        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>
+    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(includeDeleted: true)
+            .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.CallStartTimeStart != null, d => d.BeginIvrTime >= dto.CallStartTimeStart)
+            .WhereIF(dto.CallStartTimeEnd != null, d => d.BeginIvrTime <= dto.CallStartTimeEnd)
+            .WhereIF(dto.IsConnected != null, d => d.AnsweredTime != null)
+            .WhereIF(dto.Direction != null, d => d.Direction == dto.Direction)
+            .WhereIF(dto.WaitDurationStart != null && dto.WaitDurationStart > 0, d => d.WaitDuration >= dto.WaitDurationStart)
+            .WhereIF(dto.WaitDurationEnd != null && dto.WaitDurationEnd > 0, d => d.WaitDuration <= dto.WaitDurationEnd)
+            .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);
+    }
+
+    /// <summary>
+    /// 依据通话记录编号获取映射后的callId
+    /// </summary>
+    public async Task<string> GetOrSetCallIdAsync(string callNo, CancellationToken cancellationToken)
+    {
+        var callOrder = await _callIdRelationRepository.GetAsync(callNo, cancellationToken);
+        if (callOrder == null)
+        {
+            callOrder = new CallidRelation
+            {
+                Id = callNo,
+                CallId = Ulid.NewUlid().ToString(),
+            };
+            try
+            {
+                await _callIdRelationRepository.AddAsync(callOrder, cancellationToken);
+            }
+            catch (Exception e)
+            {
+                _logger.LogError($"写入callidRelation失败:{e.Message}");
+            }
+        }
+
+        return callOrder.CallId;
+    }
+
+    /// <summary>
+    /// 批量获取callId
+    /// </summary>
+    public async Task<List<(string callNo, string callId)>> GetOrSetCallIdRangeAsync(List<string> callNos,
+        CancellationToken cancellationToken)
+    {
+        var relations = await _callIdRelationRepository.Queryable()
+            .Where(d => callNos.Contains(d.Id))
+            .ToListAsync(cancellationToken);
+
+        var rsp = new List<(string callNo, string callId)>();
+        var newRelations = new List<CallidRelation>();
+        foreach (var callNo in callNos)
+        {
+            var relation = relations.FirstOrDefault(d => d.Id == callNo);
+            if (relation is null)
+            {
+                relation = new CallidRelation
+                {
+                    Id = callNo,
+                    CallId = Ulid.NewUlid().ToString(),
+                };
+                newRelations.Add(relation);
+                rsp.Add(new(relation.Id, relation.CallId));
+            }
+            else
+            {
+                rsp.Add(new(relation.Id, relation.CallId));
+            }
+        }
+
+        await _callIdRelationRepository.AddRangeAsync(newRelations, cancellationToken);
+        return rsp;
+    }
+
+    /// <summary>
+    /// 查询通话记录
+    /// </summary>
+    public Task<CallNative?> GetCallAsync(string callId, CancellationToken cancellationToken)
+    {
+        if (string.IsNullOrEmpty(callId)) return null;
+        return _callNativeRepository.GetAsync(callId, cancellationToken);
+    }
+
+    /// <summary>
+    /// 查询通话记录
+    /// </summary>
+    public async Task<List<CallNative>> QueryCallsAsync(
+        string? phone,
+        ECallDirection? direction,
+        DateTime? callStartTimeStart,
+        DateTime? callStartTimeEnd,
+        CancellationToken cancellationToken)
+    {
+        if (string.IsNullOrEmpty(phone))
+            return new List<CallNative>();
+        return await _callNativeRepository.Queryable()
+            .WhereIF(direction.HasValue, d => d.Direction == direction)
+            .WhereIF(callStartTimeStart != null, d => d.BeginIvrTime >= callStartTimeStart)
+            .WhereIF(callStartTimeEnd != null, d => d.BeginIvrTime <= callStartTimeEnd)
+            .Where(d => d.FromNo == phone || d.ToNo == phone)
+            .OrderBy(d => d.CreationTime)
+            .ToListAsync(cancellationToken);
+    }
+
+    #region tianrun 临时方案
+
+    public Task<TrCallRecord?> GetTianrunCallAsync(string callId, CancellationToken cancellationToken)
+    {
+        throw new NotImplementedException();
+    }
+
+    /// <summary>
+    /// 关联通话记录与order(添润)
+    /// </summary>
+    public Task RelateTianrunCallWithOrderAsync(string callId, string orderId,
+        CancellationToken cancellationToken)
+    {
+        throw new NotImplementedException();
+    }
+
+    /// <summary>
+    /// 查询通话记录
+    /// </summary>
+    public Task<List<TrCallRecord>> QueryTianrunCallsAsync(
+        string? phone = null, 
+        ECallDirection? direction = null,
+        DateTime? callStartTimeStart = null,
+        DateTime? callStartTimeEnd = null,
+        CancellationToken cancellationToken = default)
+    {
+        throw new NotImplementedException();
+    }
+
+    #endregion
+}

+ 65 - 2
src/Hotline.Application/CallCenter/ICallApplication.cs

@@ -6,8 +6,10 @@ using System.Threading.Tasks;
 using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Tels;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
 using XingTang.Sdk;
 
 namespace Hotline.Application.CallCenter
@@ -26,8 +28,19 @@ namespace Hotline.Application.CallCenter
 
         #region 黑名单
 
+        /// <summary>
+        /// 新增黑名单
+        /// </summary>
         Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 删除黑名单
+        /// </summary>
         Task RemoveBlackListAsync(string id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 查询黑名单
+        /// </summary>
         Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken);
 
         #endregion
@@ -41,6 +54,10 @@ namespace Hotline.Application.CallCenter
         /// 签出
         /// </summary>
         Task SingOutAsync(CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 签出
+        /// </summary>
         Task SingOutAsync(string telNo, CancellationToken cancellationToken);
 
         /// <summary>
@@ -54,11 +71,57 @@ namespace Hotline.Application.CallCenter
         /// 定量查询通话记录
         /// </summary>
         Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken);
-        
+
         /// <summary>
         /// 查询分机操作记录(定量)
         /// </summary>
         Task<IReadOnlyList<TelOperation>> QueryTelOperationsAsync(QueryTelOperationsFixedDto dto,
             CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 依据通话记录编号获取映射后的callId
+        /// </summary>
+        Task<string> GetOrSetCallIdAsync(string callNo, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 批量获取callId
+        /// </summary>
+        Task<List<(string callNo, string callId)>> GetOrSetCallIdRangeAsync(List<string> callNos, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 查询通话记录
+        /// </summary>
+        Task<CallNative?> GetCallAsync(string callId, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 查询通话记录
+        /// </summary>
+        Task<List<CallNative>> QueryCallsAsync(
+            string? phone = null,
+            ECallDirection? direction = null,
+            DateTime? callStartTimeStart = null,
+            DateTime? callStartTimeEnd = null,
+            CancellationToken cancellationToken = default);
+
+        #region tianrun
+
+        Task<TrCallRecord?> GetTianrunCallAsync(string callId, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 关联通话记录与order(添润)
+        /// </summary>
+        Task RelateTianrunCallWithOrderAsync(string callId, string orderId, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 查询通话记录
+        /// </summary>
+        Task<List<TrCallRecord>> QueryTianrunCallsAsync(
+            string? phone = null, 
+            ECallDirection? direction = null,
+            DateTime? callStartTimeStart = null,
+            DateTime? callStartTimeEnd = null,
+            CancellationToken cancellationToken = default);
+
+        #endregion
     }
-}
+}

+ 123 - 114
src/Hotline.Application/CallCenter/TianRunCallApplication.cs

@@ -1,127 +1,136 @@
-//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;
+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;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
 
-//namespace Hotline.Application.CallCenter
-//{
-//    public class TianRunCallApplication : ICallApplication
-//    {
-//        private readonly ISessionContext _sessionContext;
-//        private readonly ITrApplication _trApplication;
-//        private readonly ITelApplication _telApplication;
-//        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+namespace Hotline.Application.CallCenter
+{
+    public class TianRunCallApplication : DefaultCallApplication
+    {
+        private readonly ISessionContext _sessionContext;
+        private readonly IRepository<TrCallRecord> _trCallRecordRepository;
+        private readonly ITrApplication _trApplication;
+        private readonly ITelApplication _telApplication;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
 
-//        public TianRunCallApplication(
-//            ISessionContext sessionContext,
-//            ITrApplication trApplication,
-//            ITelApplication telApplication,
-//            ISystemSettingCacheManager systemSettingCacheManager
-//            )
-//        {
-//            _sessionContext = sessionContext;
-//            _trApplication = trApplication;
-//            _telApplication = telApplication;
-//            _systemSettingCacheManager = systemSettingCacheManager;
-//        }
+        public TianRunCallApplication(
+            ISessionContext sessionContext,
+            IRepository<TrCallRecord> trCallRecordRepository,
+            ITrApplication trApplication,
+            ITelApplication telApplication,
+            ISystemSettingCacheManager systemSettingCacheManager
+        )
+        {
+            _sessionContext = sessionContext;
+            _trCallRecordRepository = trCallRecordRepository;
+            _trApplication = trApplication;
+            _telApplication = telApplication;
+            _systemSettingCacheManager = systemSettingCacheManager;
+        }
 
-//        /// <summary>
-//        /// 查询分机
-//        /// </summary>
-//        public Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
-//        {
-//            throw new NotImplementedException();
-//        }
+        /// <summary>
+        /// 新增黑名单
+        /// </summary>
+        public override async Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
 
-//        /// <summary>
-//        /// 查询分机组
-//        /// </summary>
-//        public Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
-//        {
-//            throw new NotImplementedException();
-//        }
+        /// <summary>
+        /// 删除黑名单
+        /// </summary>
+        public override async Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
 
-//        public Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
-//        {
-//            throw new NotImplementedException();
-//        }
+        /// <summary>
+        /// 查询黑名单
+        /// </summary>
+        public override async Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
+        {
+            throw new NotImplementedException();
+        }
 
-//        public Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
-//        {
-//            throw new NotImplementedException();
-//        }
+        /// <summary>
+        /// 签入
+        /// </summary>
+        public new async Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken) =>
+            await _trApplication.OnSign(_sessionContext.RequiredUserId, dto.TelNo, (ETelModel)dto.TelModelState, cancellationToken);
 
-//        public Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
-//        {
-//            throw new NotImplementedException();
-//        }
+        /// <summary>
+        /// 签出
+        /// </summary>
+        public new Task SingOutAsync(CancellationToken cancellationToken) =>
+            _telApplication.SignOutAsync(_sessionContext.RequiredUserId, cancellationToken);
 
-//        /// <summary>
-//        /// 签入
-//        /// </summary>
-//        public Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken) =>
-//            _trApplication.OnSign(_sessionContext.RequiredUserId, dto.TelNo, (ETelModel)dto.TelModelState, cancellationToken);
+        public new Task SingOutAsync(string telNo, CancellationToken cancellationToken) =>
+            _telApplication.SignOutByTelNoAsync(telNo, cancellationToken);
 
-//        /// <summary>
-//        /// 签出
-//        /// </summary>
-//        public Task SingOutAsync(CancellationToken cancellationToken) =>
-//            _telApplication.SignOutAsync(_sessionContext.RequiredUserId, cancellationToken);
+        /// <summary>
+        /// 查询当前用户的分机状态
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public new Task<TrOnDutyResponseDto> GetTelStateAsync(CancellationToken cancellationToken) =>
+            _trApplication.TelState(_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);
+        public new async Task<TrCallRecord?> GetTianrunCallAsync(string callId, CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrEmpty(callId)) return null;
+            var callRecord = await _trCallRecordRepository.GetAsync(
+                x => x.OtherAccept == callId && string.IsNullOrEmpty(x.OtherAccept) == false, cancellationToken);
+            return callRecord;
+        }
 
-//        /// <summary>
-//        /// 定量查询通话记录
-//        /// </summary>
-//        Task<IReadOnlyList<CallNativeDto>> ICallApplication.QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
-//        {
-//            throw new NotImplementedException();
-//        }
+        /// <summary>
+        /// 关联通话记录与order(添润)
+        /// </summary>
+        public new async Task RelateTianrunCallWithOrderAsync(string callId, string orderId, CancellationToken cancellationToken)
+        {
+            var callRecord = await _trCallRecordRepository.GetAsync(
+                p => p.OtherAccept == callId && string.IsNullOrEmpty(p.OtherAccept) == false, cancellationToken);
+            if (callRecord != null && string.IsNullOrEmpty(callRecord.ExternalId))
+            {
+                callRecord.ExternalId = orderId;
+                callRecord.CallOrderType = Share.Enums.CallCenter.ECallOrderType.Order;
+                await _trCallRecordRepository.UpdateAsync(callRecord, cancellationToken);
+            }
+        }
 
-//        /// <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();
-//        }
-//    }
-//}
+        /// <summary>
+        /// 查询通话记录
+        /// </summary>
+        public new async Task<List<TrCallRecord>> QueryTianrunCallsAsync(
+            string? phone,
+            ECallDirection? direction,
+            DateTime? callStartTimeStart,
+            DateTime? callStartTimeEnd,
+            CancellationToken cancellationToken)
+        {
+            return await _trCallRecordRepository.Queryable()
+                .WhereIF(!string.IsNullOrEmpty(phone), d => d.CPN == phone || d.CDPN == phone)
+                .WhereIF(direction != null, d => d.CallDirection == direction)
+                .WhereIF(callStartTimeStart != null, d => d.BeginIvrTime >= callStartTimeStart)
+                .WhereIF(callStartTimeEnd != null, d => d.BeginIvrTime <= callStartTimeEnd)
+                .OrderBy(x => x.CreatedTime)
+                .ToListAsync(cancellationToken);
+        }
+    }
+}

+ 313 - 167
src/Hotline.Application/CallCenter/XingTangCallApplication.cs

@@ -14,6 +14,7 @@ using Hotline.Repository.SqlSugar.CallCenter;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Repository.SqlSugar.Orders;
 using Hotline.Settings;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.TrCallCenter;
@@ -21,6 +22,7 @@ using Hotline.Share.Enums.CallCenter;
 using Hotline.Users;
 using MapsterMapper;
 using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Exceptions;
@@ -28,18 +30,20 @@ using XF.Domain.Repository;
 
 namespace Hotline.Application.CallCenter
 {
-    public class XingTangCallApplication : ICallApplication
+    public class XingTangCallApplication : DefaultCallApplication
     {
-        private readonly IRepository<Tel> _telRepository;
-        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;
+        // private readonly IRepository<Tel> _telRepository;
+        // 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 IRepository<CallidRelation> _callidRelationRepository;
+        // private readonly ITypedCache<Work> _cacheWork;
+        // private readonly IUserCacheManager _userCacheManager;
+        // private readonly ISessionContext _sessionContext;
+        // private readonly IMapper _mapper;
+        // private readonly ILogger<XingTangCallApplication> _logger;
 
         public XingTangCallApplication(
             IRepository<Tel> telRepository,
@@ -48,187 +52,329 @@ namespace Hotline.Application.CallCenter
             ITelRestRepository telRestRepository,
             IRepository<CallNative> callNativeRepository,
             IRepository<TelOperation> teloperationRepository,
+            IRepository<CallidRelation> callidRelationRepository,
             ITypedCache<Work> cacheWork,
             IUserCacheManager userCacheManager,
             ISessionContext sessionContext,
-            IMapper mapper)
+            IMapper mapper,
+            ILogger<XingTangCallApplication> logger
+        ) : base(telRepository, telGroupRepository, workRepository, telRestRepository, callNativeRepository,
+            teloperationRepository, callidRelationRepository, cacheWork, userCacheManager, sessionContext, mapper,
+            logger)
         {
-            _telRepository = telRepository;
-            _telGroupRepository = telGroupRepository;
-            _workRepository = workRepository;
-            _telRestRepository = telRestRepository;
-            _callNativeRepository = callNativeRepository;
-            _teloperationRepository = teloperationRepository;
-            _cacheWork = cacheWork;
-            _userCacheManager = userCacheManager;
-            _sessionContext = sessionContext;
-            _mapper = mapper;
+            // _telRepository = telRepository;
+            // _telGroupRepository = telGroupRepository;
+            // _workRepository = workRepository;
+            // _telRestRepository = telRestRepository;
+            // _callNativeRepository = callNativeRepository;
+            // _teloperationRepository = teloperationRepository;
+            // _callidRelationRepository = callidRelationRepository;
+            // _cacheWork = cacheWork;
+            // _userCacheManager = userCacheManager;
+            // _sessionContext = sessionContext;
+            // _mapper = mapper;
+            // _logger = logger;
         }
 
-        /// <summary>
-        /// 查询分机
-        /// </summary>
-        public async Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
-        {
-            return await _telRepository.Queryable()
-                .Select<TelDto>()
-                .ToListAsync(cancellationToken);
-        }
-
-        /// <summary>
-        /// 查询分机组
-        /// </summary>
-        public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
-        {
-            return await _telGroupRepository.Queryable()
-                .Select<TelGroupDto>()
-                .ToListAsync(cancellationToken);
-        }
+        // /// <summary>
+        // /// 查询分机
+        // /// </summary>
+        // public async Task<IReadOnlyList<TelDto>> QueryTelsAsync(CancellationToken cancellationToken)
+        // {
+        //     return await _telRepository.Queryable()
+        //         .Select<TelDto>()
+        //         .ToListAsync(cancellationToken);
+        // }
+        //
+        // /// <summary>
+        // /// 查询分机组
+        // /// </summary>
+        // public async Task<IReadOnlyList<TelGroupDto>> QueryTelGroupsAsync(CancellationToken cancellationToken)
+        // {
+        //     return await _telGroupRepository.Queryable()
+        //         .Select<TelGroupDto>()
+        //         .ToListAsync(cancellationToken);
+        // }
 
-        public async Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
-
-        public async Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
-
-        public async Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
-        {
-            throw new NotImplementedException();
-        }
+        // /// <summary>
+        // /// 新增黑名单
+        // /// </summary>
+        // public async Task<string> AddBlackListAsync(AddBlacklistDto dto, CancellationToken cancellationToken)
+        // {
+        //     throw new NotImplementedException();
+        // }
+        //
+        // /// <summary>
+        // /// 删除黑名单
+        // /// </summary>
+        // public async Task RemoveBlackListAsync(string id, CancellationToken cancellationToken)
+        // {
+        //     throw new NotImplementedException();
+        // }
+        //
+        // /// <summary>
+        // /// 查询黑名单
+        // /// </summary>
+        // 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))
-                throw UserFriendlyException.SameMessage("无效分机号");
-            var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
-            if (work is not null)
-            {
-                //if (work.TelNo != dto.TelNo)
-                //{
-                //    throw UserFriendlyException.SameMessage("当前用户已签入其他分机");
-                //}
-                throw UserFriendlyException.SameMessage("当前用户已签入");
-            }
 
-            var telWork = _userCacheManager.GetWorkByTelNoExp(dto.TelNo);
-            if (telWork is not null)
-            {
-                throw UserFriendlyException.SameMessage("当前分机已被占用");
-            }
+        // /// <summary>
+        // /// 签入
+        // /// </summary>
+        // public async Task<TrOnDutyResponseDto> SignInAsync(SignInDto dto, CancellationToken cancellationToken)
+        // {
+        //     if (string.IsNullOrEmpty(dto.TelNo))
+        //         throw UserFriendlyException.SameMessage("无效分机号");
+        //     var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
+        //     if (work is not null)
+        //     {
+        //         //if (work.TelNo != dto.TelNo)
+        //         //{
+        //         //    throw UserFriendlyException.SameMessage("当前用户已签入其他分机");
+        //         //}
+        //         throw UserFriendlyException.SameMessage("当前用户已签入");
+        //     }
+        //
+        //     var telWork = _userCacheManager.GetWorkByTelNoExp(dto.TelNo);
+        //     if (telWork is not null)
+        //     {
+        //         throw UserFriendlyException.SameMessage("当前分机已被占用");
+        //     }
+        //
+        //     work = new Work(_sessionContext.RequiredUserId, _sessionContext.UserName,
+        //         dto.TelNo, dto.TelNo, null, null,
+        //         dto.GroupId, _sessionContext.StaffNo, null);
+        //     await _workRepository.AddAsync(work, cancellationToken);
+        //
+        //     return new TrOnDutyResponseDto
+        //     {
+        //         TelNo = dto.TelNo,
+        //         QueueId = dto.GroupId,
+        //         StartTime = work.StartTime,
+        //     };
+        // }
+        //
+        // /// <summary>
+        // /// 签出
+        // /// </summary>
+        // public async Task SingOutAsync(CancellationToken cancellationToken)
+        // {
+        //     var work = _userCacheManager.GetWorkByUserNoExp(_sessionContext.RequiredUserId);
+        //     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);
+        // }
+        //
+        // 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(includeDeleted: true)
+        //         .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);
+        // }
 
-            work = new Work(_sessionContext.RequiredUserId, _sessionContext.UserName,
-                dto.TelNo, dto.TelNo, null, null,
-                dto.GroupId, _sessionContext.StaffNo, null);
-            await _workRepository.AddAsync(work, cancellationToken);
+        // /// <summary>
+        // /// 依据通话记录编号获取映射后的callId
+        // /// </summary>
+        // public async Task<string> GetOrSetCallIdAsync(string callNo, CancellationToken cancellationToken)
+        // {
+        //     var callOrder = await _callidRelationRepository.GetAsync(callNo, cancellationToken);
+        //     if (callOrder == null)
+        //     {
+        //         callOrder = new CallidRelation
+        //         {
+        //             Id = callNo,
+        //             CallId = Ulid.NewUlid().ToString(),
+        //         };
+        //         try
+        //         {
+        //             await _callidRelationRepository.AddAsync(callOrder, cancellationToken);
+        //         }
+        //         catch (Exception e)
+        //         {
+        //             _logger.LogError($"写入callidRelation失败:{e.Message}");
+        //         }
+        //     }
+        //     return callOrder.CallId;
+        // }
 
-            return new TrOnDutyResponseDto
-            {
-                TelNo = dto.TelNo,
-                QueueId = dto.GroupId,
-                StartTime = work.StartTime,
-            };
-        }
+        // /// <summary>
+        // /// 批量获取callId
+        // /// </summary>
+        // public async Task<List<(string callNo, string callId)>> GetOrSetCallIdRangeAsync(List<string> callNos, CancellationToken cancellationToken)
+        // {
+        //     var relations = await _callidRelationRepository.Queryable()
+        //         .Where(d => callNos.Contains(d.Id))
+        //         .ToListAsync(cancellationToken);
+        //
+        //     var rsp = new List<(string callNo, string callId)>();
+        //     var newRelations = new List<CallidRelation>();
+        //     foreach (var callNo in callNos)
+        //     {
+        //         var relation = relations.FirstOrDefault(d => d.Id == callNo);
+        //         if (relation is null)
+        //         {
+        //             relation = new CallidRelation
+        //             {
+        //                 Id = callNo,
+        //                 CallId = Ulid.NewUlid().ToString(),
+        //             };
+        //             newRelations.Add(relation);
+        //             rsp.Add(new(relation.Id, relation.CallId));
+        //         }
+        //         else
+        //         {
+        //             rsp.Add(new(relation.Id, relation.CallId));
+        //         }
+        //     }
+        //
+        //     await _callidRelationRepository.AddRangeAsync(newRelations, cancellationToken);
+        //     return rsp;
+        // }
+        //
+        // /// <summary>
+        // /// 查询通话记录
+        // /// </summary>
+        // public Task<CallNative?> GetCallAsync(string callId, CancellationToken cancellationToken)
+        // {
+        //     if (string.IsNullOrEmpty(callId)) return null;
+        //     return _callNativeRepository.GetAsync(callId, cancellationToken);
+        // }
+        //
+        // public async Task<List<CallNative>> QueryCallsAsync(string phone, ECallDirection? direction, CancellationToken cancellationToken)
+        // {
+        //     if (string.IsNullOrEmpty(phone))
+        //         return new List<CallNative>();
+        //     return await _callNativeRepository.Queryable()
+        //         .WhereIF(direction.HasValue, d=>d.Direction == direction)
+        //         .Where(d=>d.FromNo == phone || d.ToNo == phone)
+        //         .OrderBy(d=>d.CreationTime)
+        //         .ToListAsync(cancellationToken);
+        // }
 
+        // public async Task<TrCallRecord?> GetTianrunCallAsync(string callId, CancellationToken cancellationToken)
+        // {
+        //     throw new NotImplementedException();
+        // }
+        //
+        // /// <summary>
+        // /// 关联通话记录与order(添润)
+        // /// </summary>
+        // public async Task RelateTianrunCallWithOrderAsync(string callId, string orderId, CancellationToken cancellationToken)
+        // {
+        //     throw new NotImplementedException();
+        // }
+        //
+        // /// <summary>
+        // /// 查询通话记录
+        // /// </summary>
+        // public async Task<List<TrCallRecord>> QueryTianrunCallsAsync(string phone, ECallDirection? direction, CancellationToken cancellationToken)
+        // {
+        //     throw new NotImplementedException();
+        // }
         /// <summary>
-        /// 签出
+        /// 新增黑名单
         /// </summary>
-        public async Task SingOutAsync(CancellationToken cancellationToken)
-        {
-            var work = _userCacheManager.GetWorkByUser(_sessionContext.RequiredUserId);
-            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);
-        }
-
-        public async Task SingOutAsync(string telNo, CancellationToken cancellationToken)
+        public override async Task<string> AddBlackListAsync(AddBlacklistDto dto, 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);
+            throw new NotImplementedException();
         }
 
         /// <summary>
-        /// 查询当前用户的分机状态
+        /// 删除黑名单
         /// </summary>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
-        public async Task<TrOnDutyResponseDto> GetTelStateAsync(CancellationToken cancellationToken)
+        public override async Task RemoveBlackListAsync(string id, 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,
-            });
+            throw new NotImplementedException();
         }
 
         /// <summary>
-        /// 定量查询通话记录
+        /// 查询黑名单
         /// </summary>
-        public async Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
+        public override async Task<List<Blacklist>> QueryBlackListsAsync(CancellationToken cancellationToken)
         {
-            return await _callNativeRepository.Queryable(includeDeleted: true)
-                .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);
+            throw new NotImplementedException();
         }
 
-        /// <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);
-        }
+       
     }
-}
+}

+ 32 - 6
src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs

@@ -1,9 +1,11 @@
 using DotNetCore.CAP;
+using Hotline.Application.CallCenter;
 using Hotline.Application.JudicialManagement;
 using Hotline.Application.Orders;
 using Hotline.Application.Quality;
 using Hotline.Article;
 using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Configs;
 using Hotline.CallCenter.Tels;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
@@ -19,6 +21,7 @@ using Hotline.Share.Enums.Quality;
 using MapsterMapper;
 using MediatR;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using StackExchange.Redis;
 using XF.Domain.Authentications;
 using XF.Domain.Repository;
@@ -49,9 +52,11 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
     private readonly ILogger<WorkflowEndHandler> _logger;
     private readonly IKnowledgeRepository _knowledgeRepository;
     private readonly IKnowledgeWorkFlowRepository _knowledgeWorkFlowRepository;
-    private readonly IRepository<TrCallRecord> _trCallRecordRepository;
+    // private readonly IRepository<TrCallRecord> _trCallRecordRepository;
     private readonly IQualityApplication _qualityApplication;
     private readonly IEnforcementApplication _enforcementApplication;
+    private readonly ICallApplication _callApplication;
+    private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
     private readonly ISessionContext _sessionContext;
 
     public WorkflowEndHandler(
@@ -77,9 +82,11 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
         IKnowledgeRepository knowledgeRepository,
         IKnowledgeWorkFlowRepository knowledgeWorkFlowRepository,
         ILogger<WorkflowEndHandler> logger,
-        IRepository<TrCallRecord> trCallRecordRepository,
+        // IRepository<TrCallRecord> trCallRecordRepository,
          IQualityApplication qualityApplication,
         IEnforcementApplication enforcementApplication,
+        ICallApplication callApplication,
+        IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
         ISessionContext sessionContext)
     {
         _knowledgeDomainService = knowledgeDomainService;
@@ -103,9 +110,11 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
         _logger = logger;
         _knowledgeRepository = knowledgeRepository;
         _knowledgeWorkFlowRepository = knowledgeWorkFlowRepository;
-        _trCallRecordRepository = trCallRecordRepository;
+        // _trCallRecordRepository = trCallRecordRepository;
         _qualityApplication = qualityApplication;
         _enforcementApplication = enforcementApplication;
+        _callApplication = callApplication;
+        _callcenterOptions = callcenterOptions;
         _sessionContext = sessionContext;
     }
 
@@ -193,16 +202,33 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
 
                 await _orderRepository.UpdateAsync(order, cancellationToken);
                 //var callRecord = await _trCallRecordRepository.GetAsync(p => p.CallAccept == order.CallId, cancellationToken); //由CallAccept改为OtherAccept
-                var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
+                //var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
                 var orderFlowDto = new OrderFlowDto
                 {
                     Order = _mapper.Map<OrderDto>(order),
                     WorkflowTrace = _mapper.Map<WorkflowTraceDto>(notification.Trace)
                 };
-                if (callRecord != null)
+                // if (callRecord != null)
+                // {
+                //     orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+                // }
+                
+                if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.TianRun)
                 {
-                    orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+                    // var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
+                    var callRecord = await _callApplication.GetTianrunCallAsync(order.CallId, cancellationToken);
+                    if (callRecord != null)
+                    {
+                        orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+                    }
+                }
+                else if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.XingTang)
+                {
+                    var call = await _callApplication.GetCallAsync(order.CallId, cancellationToken);
+                    if(call is not null)
+                        orderFlowDto.TrCallRecordDto = _mapper.Map<TrCallDto>(call);
                 }
+                
                 //这里需要判断是否是警情退回
                 orderFlowDto.IsNonPoliceReturn = notification.Dto.External == null ? false : notification.Dto.External.IsPoliceReturn;
                 await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderFiled, orderFlowDto, cancellationToken: cancellationToken);

+ 44 - 11
src/Hotline.Application/Handlers/FlowEngine/WorkflowStartHandler.cs

@@ -1,6 +1,8 @@
 using DotNetCore.CAP;
+using Hotline.Application.CallCenter;
 using Hotline.Application.Quality;
 using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Configs;
 using Hotline.CallCenter.Tels;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
@@ -18,6 +20,7 @@ using MapsterMapper;
 using MediatR;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 using XF.Domain.Entities;
 using XF.Domain.Repository;
 
@@ -33,10 +36,12 @@ namespace Hotline.Application.Handlers.FlowEngine
         private readonly IMapper _mapper;
         private readonly ILogger<WorkflowStartHandler> _logger;
         private readonly IQualityApplication _qualityApplication;
-        private readonly IRepository<TrCallRecord> _trCallRecordRepository;
+        // private readonly IRepository<TrCallRecord> _trCallRecordRepository;
         private readonly IOrderScreenRepository _orderScreenRepository;
         private readonly IOrderDelayRepository _orderDelayRepository;
         private readonly IRepository<User> _userRepository;
+        private readonly ICallApplication _callApplication;
+        private readonly IOptionsSnapshot<CallCenterConfiguration> _callcenterOptions;
         private readonly IMediator _mediator;
 
         public WorkflowStartHandler(
@@ -48,10 +53,12 @@ namespace Hotline.Application.Handlers.FlowEngine
             IQualityApplication qualityApplication,
             IMapper mapper,
             ILogger<WorkflowStartHandler> logger,
-            IRepository<TrCallRecord> trCallRecordRepository,
+            // IRepository<TrCallRecord> trCallRecordRepository,
             IOrderScreenRepository orderScreenRepository,
             IOrderDelayRepository orderDelayRepository,
             IRepository<User> userRepository,
+            ICallApplication callApplication,
+            IOptionsSnapshot<CallCenterConfiguration> callcenterOptions,
             IMediator mediator
         )
         {
@@ -63,10 +70,12 @@ namespace Hotline.Application.Handlers.FlowEngine
             _qualityApplication = qualityApplication;
             _mapper = mapper;
             _logger = logger;
-            _trCallRecordRepository = trCallRecordRepository;
+            // _trCallRecordRepository = trCallRecordRepository;
             _orderScreenRepository = orderScreenRepository;
             _orderDelayRepository = orderDelayRepository;
             _userRepository = userRepository;
+            _callApplication = callApplication;
+            _callcenterOptions = callcenterOptions;
             _mediator = mediator;
         }
 
@@ -95,12 +104,30 @@ namespace Hotline.Application.Handlers.FlowEngine
                     var publishCallRecordDto = new PublishCallRecrodDto() { };
                     //查询通话记录
                     //var callRecord = await _trCallRecordRepository.GetAsync(p => p.CallAccept == order.CallId, cancellationToken); //由CallAccept改为OtherAccept
-                    var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
-                    if (callRecord != null)
+                    // var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
+                    // if (callRecord != null)
+                    // {
+                    //     publishCallRecordDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+                    // }
+
+
+                    if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.TianRun)
+                    {
+                        // var callRecord = await _trCallRecordRepository.GetAsync(p => p.OtherAccept == order.CallId, cancellationToken);
+                        var callRecord = await _callApplication.GetTianrunCallAsync(order.CallId, cancellationToken);
+                        if (callRecord != null)
+                        {
+                            publishCallRecordDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+                        }
+                    }
+                    else if (_callcenterOptions.Value.CallCenterType == AppDefaults.CallCenterType.XingTang)
                     {
-                        publishCallRecordDto.TrCallRecordDto = _mapper.Map<TrCallDto>(callRecord);
+                        var call = await _callApplication.GetCallAsync(order.CallId, cancellationToken);
+                        if(call is not null)
+                            publishCallRecordDto.TrCallRecordDto = _mapper.Map<TrCallDto>(call);
                     }
 
+
                     publishCallRecordDto.Order = _mapper.Map<OrderDto>(order);
                     publishCallRecordDto.WorkflowTrace = _mapper.Map<WorkflowTraceDto>(notification.Trace);
 
@@ -115,7 +142,8 @@ namespace Hotline.Application.Handlers.FlowEngine
                             {
                                 case EFlowAssignType.Org:
                                     var orgCodes = notification.FlowAssignInfo.HandlerObjects.Select(x => x.Key);
-                                    var orgList = await _userRepository.Queryable().Where(x => orgCodes.Contains(x.OrgId) && x.Roles.Any(d => d.Id == "08dae71e-0eca-4bc4-89fe-7eaefae8a98e")).ToListAsync();
+                                    var orgList = await _userRepository.Queryable().Where(x =>
+                                        orgCodes.Contains(x.OrgId) && x.Roles.Any(d => d.Id == "08dae71e-0eca-4bc4-89fe-7eaefae8a98e")).ToListAsync();
                                     foreach (var item in orgList)
                                     {
                                         if (!string.IsNullOrEmpty(item.PhoneNo))
@@ -131,15 +159,16 @@ namespace Hotline.Application.Handlers.FlowEngine
                                                 TemplateCode = "1007",
                                                 Params = new List<string>() { order.No },
                                                 TelNumber = item.PhoneNo,
-
                                             };
                                             await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
                                         }
                                     }
+
                                     break;
                                 case EFlowAssignType.User:
                                     var userCodes = notification.FlowAssignInfo.HandlerObjects.Select(x => x.Key);
-                                    var userList = await _userRepository.Queryable().Where(x => userCodes.Contains(x.Id) && x.Roles.Any(d => d.Id == "08dae71e-0eca-4bc4-89fe-7eaefae8a98e")).ToListAsync();
+                                    var userList = await _userRepository.Queryable().Where(x =>
+                                        userCodes.Contains(x.Id) && x.Roles.Any(d => d.Id == "08dae71e-0eca-4bc4-89fe-7eaefae8a98e")).ToListAsync();
                                     foreach (var item in userList)
                                     {
                                         var messageDto = new Share.Dtos.Push.MessageDto
@@ -153,17 +182,19 @@ namespace Hotline.Application.Handlers.FlowEngine
                                             TemplateCode = "1007",
                                             Params = new List<string>() { order.No },
                                             TelNumber = item.PhoneNo,
-
                                         };
                                         await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
                                     }
+
                                     break;
                                 default:
                                     break;
                             }
                         }
                     }
-                    catch{}
+                    catch
+                    {
+                    }
 
                     //写入质检
                     await _qualityApplication.AddQualityAsync(EQualitySource.Accepted, order.Id, cancellationToken);
@@ -184,6 +215,7 @@ namespace Hotline.Application.Handlers.FlowEngine
                         screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
                         await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
                     }
+
                     break;
                 case WorkflowModuleConsts.OrderDelay:
                     var orderDelay = await _orderDelayRepository.Queryable()
@@ -194,6 +226,7 @@ namespace Hotline.Application.Handlers.FlowEngine
                         orderDelay.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
                         await _orderDelayRepository.UpdateAsync(orderDelay, cancellationToken);
                     }
+
                     break;
             }
         }

+ 12 - 3
src/Hotline.Application/Jobs/XingTangCallsSyncJob.cs

@@ -1,4 +1,5 @@
-using Quartz;
+using Hotline.Application.CallCenter;
+using Quartz;
 using SqlSugar;
 using Hotline.CallCenter.Calls;
 using Hotline.Repository.SqlSugar;
@@ -17,6 +18,7 @@ namespace Hotline.Application.Jobs
     {
         private readonly IRepository<CallNative> _callRepository;
         private readonly IRepository<User> _userRepository;
+        private readonly ICallApplication _callApplication;
         private readonly IMapper _mapper;
         private readonly ILogger<XingTangCallsSyncJob> _logger;
         private readonly ISqlSugarClient _db;
@@ -25,11 +27,13 @@ namespace Hotline.Application.Jobs
             ISugarUnitOfWork<XingTangDbContext> uow,
             IRepository<CallNative> callRepository,
             IRepository<User> userRepository,
+            ICallApplication callApplication,
             IMapper mapper,
             ILogger<XingTangCallsSyncJob> logger)
         {
             _callRepository = callRepository;
             _userRepository = userRepository;
+            _callApplication = callApplication;
             _mapper = mapper;
             _logger = logger;
             _db = uow.Db;
@@ -39,7 +43,7 @@ namespace Hotline.Application.Jobs
         {
             var xingtangCalls = await _db.Queryable<XingtangCall>()
                 .Where(d => (d.IsSync == null || !d.IsSync) && (d.Tries == null || d.Tries <= 50))
-                .Take(10)
+                .Take(50)
                 .ToListAsync(context.CancellationToken);
 
             var occupyCalls = new List<XingtangCall>();
@@ -63,8 +67,13 @@ namespace Hotline.Application.Jobs
                     .Where(d => staffNos.Contains(d.StaffNo))
                     .ToListAsync(context.CancellationToken);
 
+                var relations = await _callApplication.GetOrSetCallIdRangeAsync(
+                    calls.Select(d => d.CallNo).ToList(),
+                    context.CancellationToken);
+
                 foreach (var call in calls)
                 {
+                    call.Id = relations.First(d => d.callNo == call.CallNo).callId;
                     var user = users.FirstOrDefault(d => d.StaffNo == call.StaffNo);
                     if (user is not null)
                     {
@@ -93,7 +102,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(XingTangCallsSyncJob)} disposed");
+            //_logger.LogInformation($"{nameof(XingTangCallsSyncJob)} disposed");
         }
     }
 }

+ 79 - 2
src/Hotline.Application/Jobs/XingTangTelOperationSyncJob.cs

@@ -4,29 +4,106 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.Caching.Interfaces;
 using Microsoft.Extensions.Logging;
+using Hotline.Repository.SqlSugar;
+using SqlSugar;
+using XingTang.Sdk;
+using Hotline.CallCenter.Calls;
+using Hotline.CallCenter.Tels;
+using MapsterMapper;
+using Hotline.Users;
+using XF.Domain.Repository;
 
 namespace Hotline.Application.Jobs
 {
     public class XingTangTelOperationSyncJob : IJob, IDisposable
     {
+        private readonly IRepository<User> _userRepository;
+        private readonly IRepository<TelOperation> _telOperationRepository;
+        private readonly IUserCacheManager _userCacheManager;
+        private readonly IMapper _mapper;
         private readonly ILogger<XingTangTelOperationSyncJob> _logger;
+        private readonly ISqlSugarClient _db;
 
         public XingTangTelOperationSyncJob(
+            ISugarUnitOfWork<XingTangDbContext> uow,
+            IRepository<User> userRepository,
+            IRepository<TelOperation> telOperationRepository,
+            IUserCacheManager userCacheManager,
+            IMapper mapper,
             ILogger<XingTangTelOperationSyncJob> logger)
         {
+            _db = uow.Db;
+            _userRepository = userRepository;
+            _telOperationRepository = telOperationRepository;
+            _userCacheManager = userCacheManager;
+            _mapper = mapper;
             _logger = logger;
         }
 
         public async Task Execute(IJobExecutionContext context)
         {
-            throw new NotImplementedException();
+           var xingtangOperations = await _db.Queryable<XingtangSeatOperation>()
+                .Where(d => (d.IsSync == null || !d.IsSync) && (d.Tries == null || d.Tries <= 20))
+                .Take(50)
+                .ToListAsync(context.CancellationToken);
+
+            var occupyOperations = new List<XingtangSeatOperation>();
+            foreach (var operation in xingtangOperations)
+            {
+                operation.IsSync = true;
+                operation.Tries += 1;
+
+                var rows = await _db.Updateable(operation)
+                    .ExecuteCommandWithOptLockAsync();
+                if (rows > 0)
+                    occupyOperations.Add(operation);
+            }
+
+            try
+            {
+                var operations = _mapper.Map<List<TelOperation>>(occupyOperations);
+                //填充user信息
+                var staffNos = operations.Select(d => d.StaffNo).ToList();
+                var users = await _userRepository.Queryable()
+                    .Where(d => staffNos.Contains(d.StaffNo))
+                    .ToListAsync(context.CancellationToken);
+
+                foreach (var operation in operations)
+                {
+                    var user = users.FirstOrDefault(d => d.StaffNo == operation.StaffNo);
+                    if (user is not null)
+                    {
+                        operation.UserId = user.Id;
+                        operation.UserName = user.Name;
+                        var work = _userCacheManager.GetWorkByUserNoExp(user.Id);
+                        if (work is not null)
+                            operation.GroupId = work.QueueId;
+                    }
+                    
+                }
+
+                await _telOperationRepository.AddRangeAsync(operations, context.CancellationToken);
+            }
+            catch (Exception e)
+            {
+                _logger.LogError($"获取分机操作记录异常:{e.Message} \n {e.StackTrace}");
+                foreach (var occupyOperation in occupyOperations)
+                {
+                    occupyOperation.IsSync = false;
+                }
+
+                await _db.Updateable(occupyOperations)
+                    .UpdateColumns(d => new { d.IsSync })
+                    .ExecuteCommandAsync(context.CancellationToken);
+            }
         }
 
         /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
         public void Dispose()
         {
-            _logger.LogInformation($"{nameof(XingTangTelOperationSyncJob)} disposed");
+            //_logger.LogInformation($"{nameof(XingTangTelOperationSyncJob)} disposed");
         }
     }
 }

+ 18 - 6
src/Hotline.Application/Mappers/CallMapperConfigs.cs

@@ -7,6 +7,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Hotline.CallCenter.Tels;
 using XingTang.Sdk;
 
 namespace Hotline.Application.Mappers
@@ -89,16 +90,27 @@ namespace Hotline.Application.Mappers
                         : EEndBy.To;
                 });
 
+            config.ForType<XingtangSeatOperation, TelOperation>()
+                .Map(d => d.StaffNo, s => s.UserCode)
+                .Map(d => d.TelNo, s => s.Ext)
+                .Map(d => d.OperateState, s => s.ExecutionState)
+                .Map(d => d.OperateStateText, s => s.ExtutionStateText)
+                .Map(d => d.OperateTime, s => s.ExecutionTime)
+                ;
+
             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.OnState, s => s.AnsweredTime.HasValue ? EOnState.On : EOnState.NoOn)
+                .Map(d => d.CallDirection, s => s.Direction)
+                .Map(d => d.AnsweredTime, s => s.AnsweredTime)
                 .Map(d => d.OverTime, s => s.EndTime)
-                .Map(d => d.Gateway, s => s.ToNo)
-                .Map(d => d.EndRingTimg, s => s.EndRingTime)
+                .Map(d => d.BeginIvrTime, s => s.BeginIvrTime)
+                .Map(d => d.BeginQueueTime, s => s.BeginQueueTime)
+                .Map(d => d.BeginRingTime, s => s.BeginRingTime)
+                .Map(d => d.Duration, s => s.Duration)
+                .Map(d => d.TelNo, s => s.TelNo)
+                .Map(d => d.RecordingFileUrl, s => s.AudioFile)
                 ;
-
         }
 
         private DateTime? FormatDateTime(string? time)

+ 3 - 1
src/Hotline.Share/Dtos/CallCenter/CallNativeDto.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Share.Dtos.CallCenter
 {
@@ -34,7 +35,8 @@ namespace Hotline.Share.Dtos.CallCenter
         /// <summary>
         /// 挂断方
         /// </summary>
-        public EHangupBy? HangupBy { get; set; }
+        public EEndBy? EndBy { get; set; }
+        public string? EndByText => EndBy?.GetDescription() ?? string.Empty;
 
         /// <summary>
         /// IVR开始时间

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

@@ -24,12 +24,12 @@ namespace Hotline.Share.Dtos.CallCenter
         /// <summary>
         /// 呼入时间
         /// </summary>
-        public DateTime? CallStartTimeBT { get; set; }
+        public DateTime? CallStartTimeStart { get; set; }
 
         /// <summary>
         /// 呼入时间
         /// </summary>
-        public DateTime? CallStartTimeLT { get; set; }
+        public DateTime? CallStartTimeEnd { get; set; }
 
         /// <summary>
         /// 是否接通
@@ -40,5 +40,15 @@ namespace Hotline.Share.Dtos.CallCenter
         /// 通话方向
         /// </summary>
         public ECallDirection? Direction { get; set; }
+
+        /// <summary>
+        /// 等待时长
+        /// </summary>
+        public int? WaitDurationStart { get; set; }
+        
+        /// <summary>
+        /// 等待时长
+        /// </summary>
+        public int? WaitDurationEnd { get; set; }
     }
 }

+ 31 - 0
src/Hotline/AppDefaults.cs

@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline
+{
+    public class AppDefaults
+    {
+        /// <summary>
+        /// 派单池id(用作派单池的用户id)
+        /// </summary>
+        public const string SendPoolId = "08dc592a-ecce-4d32-88d0-03eeae3c41c6";
+
+        public const string TrafficTrunkNum = "12328";
+
+        public class SourceChannel
+        {
+            public const string DianHua = "RGDH";
+        }
+
+        public class CallCenterType
+        {
+            public const string XunShi = "XunShi";
+            public const string WeiErXin = "WeiErXin";
+            public const string TianRun = "TianRun";
+            public const string XingTang = "XingTang";
+        }
+    }
+}

+ 49 - 0
src/Hotline/CallCenter/Calls/CallIdManager.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Calls
+{
+    /// <summary>
+    /// 生成CallId与CallNo形成映射并持久化
+    /// </summary>
+    public class CallIdManager
+    {
+        private readonly IRepository<CallidRelation> _callorderRepository;
+        private readonly ILogger<CallIdManager> _logger;
+
+        public CallIdManager(
+            IRepository<CallidRelation> callorderRepository,
+            ILogger<CallIdManager> logger)
+        {
+            _callorderRepository = callorderRepository;
+            _logger = logger;
+        }
+
+        public async Task<string> GetOrSetAsync(string callNo, CancellationToken cancellationToken)
+        {
+            var callOrder = await _callorderRepository.GetAsync(callNo, cancellationToken);
+            if (callOrder == null)
+            {
+                callOrder = new CallidRelation
+                {
+                    Id = callNo,
+                    CallId = Ulid.NewUlid().ToString(),
+                };
+                try
+                {
+                    await _callorderRepository.AddAsync(callOrder, cancellationToken);
+                }
+                catch (Exception e)
+                {
+                    _logger.LogError($"写入callidRelation失败:{e.Message}");
+                }
+            }
+            return callOrder.CallId;
+        }
+    }
+}

+ 20 - 0
src/Hotline/CallCenter/Calls/CallidRelation.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SqlSugar;
+using XF.Domain.Entities;
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Calls
+{
+    /// <summary>
+    /// callId与callNo关系
+    /// Id: CallNo
+    /// </summary>
+    public class CallidRelation : CreationEntity
+    {   
+        public string CallId { get; set; }
+    }
+}

+ 2 - 2
src/Hotline/CallCenter/Tels/TelDomainService.cs

@@ -129,7 +129,7 @@ public class TelDomainService : ITelDomainService, IScopeDependency
 
         //await _deviceManager.TelRestAsync(telRest.TelNo, cancellationToken);
 
-        if (_options.Value.CallCenterType!= "WeiErXin")
+        if (_options.Value.CallCenterType == AppDefaults.CallCenterType.XunShi)
         {
             telRest.StartTime = DateTime.Now;
             telRest.ApplyStatus = ETelRestApplyStatus.Resting;
@@ -183,7 +183,7 @@ public class TelDomainService : ITelDomainService, IScopeDependency
             throw new UserFriendlyException("未查询到分机休息信息");
         restingTel.EndRest();
         await _telRestRepository.UpdateAsync(restingTel, cancellationToken);
-        if (_options.Value.CallCenterType != "WeiErXin")
+        if (_options.Value.CallCenterType == AppDefaults.CallCenterType.XunShi)
         {
             #region 处理设备
             var telCache = _telCacheManager.GetTel(tel.No);

+ 0 - 25
src/Hotline/Orders/OrderDefaults.cs

@@ -1,25 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hotline.Orders
-{
-    internal class OrderDefaults
-    {
-        internal class SourceChannel
-        {
-            public const string DianHua = "RGDH";
-
-            /// <summary>
-            /// 派单池id(用作派单池的用户id)
-            /// </summary>
-            public const string SendPoolId = "08dc592a-ecce-4d32-88d0-03eeae3c41c6";
-
-            public const string TrafficTrunkNum = "12328";
-
-
-		}
-	}
-}

+ 5 - 5
src/Hotline/Orders/OrderDomainService.cs

@@ -197,9 +197,9 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         if (scheduling is null)
             return new FlowStepHandler
             {
-                Key = OrderDefaults.SourceChannel.SendPoolId,
+                Key = AppDefaults.SendPoolId,
                 Value = "待派单池",
-                UserId = OrderDefaults.SourceChannel.SendPoolId,
+                UserId = AppDefaults.SendPoolId,
                 Username = "待派单池",
                 OrgId = OrgSeedData.CenterId,
                 OrgName = "市民热线服务中心"
@@ -229,7 +229,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         //2.获取今天上班的人员
         //3.给当前这个用户平均派单
 
-        var steps = await _workflowDomainService.GetStepsBelongsToAsync(OrderDefaults.SourceChannel.SendPoolId,
+        var steps = await _workflowDomainService.GetStepsBelongsToAsync(AppDefaults.SendPoolId,
             cancellationToken);
 
         var user = await _userRepository.Queryable()
@@ -271,7 +271,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
 
         if (schedulings.Any())
         {
-            var steps = await _workflowDomainService.GetStepsBelongsToAsync(OrderDefaults.SourceChannel.SendPoolId,
+            var steps = await _workflowDomainService.GetStepsBelongsToAsync(AppDefaults.SendPoolId,
                 cancellationToken);
             if (steps.Any())
             {
@@ -310,7 +310,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     {
         var valid = new OrderValidation { Validation = false, Result = "" };
         var hotspot = await _hotspotRepository.GetAsync(dto.HotspotId, cancellationToken);
-        if (hotspot.TrunkNum.Equals(OrderDefaults.SourceChannel.TrafficTrunkNum))
+        if (hotspot.TrunkNum.Equals(AppDefaults.TrafficTrunkNum))
         {
             switch (dto.AcceptTypeCode)
             {

+ 1 - 0
src/XF.Domain/XF.Domain.csproj

@@ -16,6 +16,7 @@
     <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
     <PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
     <PackageReference Include="Serilog.Enrichers.Span" Version="2.3.0" />
+    <PackageReference Include="Ulid" Version="1.3.3" />
     <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.25.1" />
   </ItemGroup>
 

+ 2 - 1
src/XingTang.Sdk/XingtangSeatOperation.cs

@@ -32,6 +32,7 @@ namespace XingTang.Sdk
         /// </summary>
         public int? ExecutionState { get; set; }
 
+        [SugarColumn(IsIgnore = true)]
         public string? ExtutionStateText => ExecutionState.HasValue
             ? ExecutionState switch
             {
@@ -76,7 +77,7 @@ namespace XingTang.Sdk
         public int Tries { get; set; }
 
         [SugarColumn(IsEnableUpdateVersionValidation = true)]
-        public long Ver { get; set; }
+        public string Ver { get; set; }
 
         #endregion
     }

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini