xf преди 9 месеца
родител
ревизия
20635da5c7

+ 27 - 1
src/Hotline.Api/Controllers/CallController.cs

@@ -3,7 +3,9 @@ using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Calls;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
 using Microsoft.AspNetCore.Mvc;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Api.Controllers
 {
@@ -73,7 +75,31 @@ namespace Hotline.Api.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpGet("calls-fixed")]
-        public Task<IReadOnlyList<CallNative>> QueryCallsFixed([FromQuery]QueryCallsFixedDto dto)
+        public Task<IReadOnlyList<CallNativeDto>> QueryCallsFixed([FromQuery] QueryCallsFixedDto dto)
             => _callApplication.QueryCallsFixedAsync(dto, HttpContext.RequestAborted);
+
+        /// <summary>
+        /// 通话记录基础数据
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("base-data")]
+        public async Task<object> BaseData()
+        {
+            return new
+            {
+                OnState = EnumExts.GetDescriptions<EOnState>(),
+                CallDirection = EnumExts.GetDescriptions<ECallDirection>(),
+                EndBy = EnumExts.GetDescriptions<EEndBy>(),
+                PhoneTypes = EnumExts.GetDescriptions<EPhoneTypes>()
+            };
+        }
+
+        /// <summary>
+        /// 关联通话记录与工单或回访
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("relate-call-to-order")]
+        public Task RelateCallToOrder([FromBody] LinkCallRecordDto dto) =>
+            _callApplication.RelateCallToOrderAsync(dto, HttpContext.RequestAborted);
     }
 }

+ 1 - 1
src/Hotline.Api/Controllers/OrderController.cs

@@ -2313,7 +2313,7 @@ public class OrderController : BaseController
         var count = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.FixedQueryCount)
             .SettingValue[0]);
         var query = _orderApplication.QueryOrders(dto);
-        var orders = await query.Skip(dto.QueryIndex * count).Take(count).ToListAsync(HttpContext.RequestAborted);
+        var orders = await query.ToFixedListAsync(dto.QueryIndex, count, HttpContext.RequestAborted);
         return _mapper.Map<IReadOnlyList<OrderDto>>(orders);
     }
 

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

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

+ 7 - 1
src/Hotline.Application/CallCenter/ICallApplication.cs

@@ -7,6 +7,7 @@ using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Calls;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.TrCallCenter;
+using XingTang.Sdk;
 
 namespace Hotline.Application.CallCenter
 {
@@ -51,6 +52,11 @@ namespace Hotline.Application.CallCenter
         /// <summary>
         /// 定量查询通话记录
         /// </summary>
-        Task<IReadOnlyList<CallNative>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken);
+        Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// 关联通话记录与工单或回访
+        /// </summary>
+        Task RelateCallToOrderAsync(LinkCallRecordDto dto, CancellationToken cancellationToken);
     }
 }

+ 76 - 4
src/Hotline.Application/CallCenter/XingTangCallApplication.cs

@@ -3,16 +3,24 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using DotNetCore.CAP;
 using Hotline.Caching.Interfaces;
 using Hotline.Caching.Services;
 using Hotline.CallCenter.BlackLists;
 using Hotline.CallCenter.Calls;
 using Hotline.CallCenter.Tels;
+using Hotline.Orders;
 using Hotline.Repository.SqlSugar.CallCenter;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Repository.SqlSugar.Orders;
 using Hotline.Settings;
 using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Enums.CallCenter;
 using Hotline.Users;
+using MapsterMapper;
+using Microsoft.AspNetCore.Http;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Exceptions;
@@ -27,10 +35,14 @@ namespace Hotline.Application.CallCenter
         private readonly IWorkRepository _workRepository;
         private readonly ITelRestRepository _telRestRepository;
         private readonly IRepository<CallNative> _callNativeRepository;
+        private readonly IOrderRepository _orderRepository;
+        private readonly IRepository<OrderVisit> _orderVisitRepository;
         private readonly ITypedCache<Work> _cacheWork;
         private readonly IUserCacheManager _userCacheManager;
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly ICapPublisher _capPublisher;
         private readonly ISessionContext _sessionContext;
+        private readonly IMapper _mapper;
 
         public XingTangCallApplication(
             IRepository<Tel> telRepository,
@@ -38,20 +50,28 @@ namespace Hotline.Application.CallCenter
             IWorkRepository workRepository,
             ITelRestRepository telRestRepository,
             IRepository<CallNative> callNativeRepository,
+            IOrderRepository orderRepository,
+            IRepository<OrderVisit> orderVisitRepository,
             ITypedCache<Work> cacheWork,
             IUserCacheManager userCacheManager,
             ISystemSettingCacheManager systemSettingCacheManager,
-            ISessionContext sessionContext)
+            ICapPublisher capPublisher,
+            ISessionContext sessionContext,
+            IMapper mapper)
         {
             _telRepository = telRepository;
             _telGroupRepository = telGroupRepository;
             _workRepository = workRepository;
             _telRestRepository = telRestRepository;
             _callNativeRepository = callNativeRepository;
+            _orderRepository = orderRepository;
+            _orderVisitRepository = orderVisitRepository;
             _cacheWork = cacheWork;
             _userCacheManager = userCacheManager;
             _systemSettingCacheManager = systemSettingCacheManager;
+            _capPublisher = capPublisher;
             _sessionContext = sessionContext;
+            _mapper = mapper;
         }
 
         /// <summary>
@@ -184,12 +204,64 @@ namespace Hotline.Application.CallCenter
         /// <summary>
         /// 定量查询通话记录
         /// </summary>
-        public async Task<IReadOnlyList<CallNative>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
+        public async Task<IReadOnlyList<CallNativeDto>> QueryCallsFixedAsync(QueryCallsFixedDto dto, CancellationToken cancellationToken)
         {
             var count = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.FixedQueryCount).SettingValue[0]);
-            var calls = await _callNativeRepository.Queryable()
-                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), d=>d.OrderNo == dto.OrderNo)
+            return await _callNativeRepository.Queryable()
+                .LeftJoin<Order>((d, o) => d.CallNo == o.CallId)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrderNo), (d, o) => o.No == dto.OrderNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.FromNo), d => d.FromNo == dto.FromNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.ToNo), d => d.FromNo == dto.ToNo)
+                .WhereIF(!string.IsNullOrEmpty(dto.UserName), d => d.FromNo == dto.UserName)
+                .WhereIF(!string.IsNullOrEmpty(dto.TelNo), d => d.FromNo == dto.TelNo)
+                .WhereIF(dto.HangupBy != null, d => d.HangupBy == dto.HangupBy)
+                .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, count, cancellationToken);
+        }
+
+        /// <summary>
+        /// 关联通话记录与工单或回访
+        /// </summary>
+        public async Task RelateCallToOrderAsync(LinkCallRecordDto dto, CancellationToken cancellationToken)
+        {
+            var call = await _callNativeRepository.GetAsync(d => d.CallNo == dto.CallId, cancellationToken);
+            if (call is null) throw UserFriendlyException.SameMessage("无效通话记录编号");
+            if (dto.IsOrder)
+            {
+                //工单
+                var order = await _orderRepository.GetAsync(x => x.Id == dto.Id, cancellationToken);
+                if (order is null) throw UserFriendlyException.SameMessage("无效工单编号");
+                if (!string.IsNullOrEmpty(order.CallId))
+                    throw UserFriendlyException.SameMessage("通话记录已经关联工单");
+
+                order.CallId = dto.CallId;
+                order.FromPhone = call.FromNo;
+                order.TransferPhone = call.ToNo;
+                await _orderRepository.UpdateAsync(order, cancellationToken);
 
+                //推省上
+                await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineCallConnectWithOrder, 
+                    new PublishCallRecrodDto() { 
+                        Order = _mapper.Map<OrderDto>(order), 
+                        TrCallRecordDto = _mapper.Map<TrCallDto>(call) });
+            }
+            else
+            {
+                //回访
+                var visit = await _orderVisitRepository.GetAsync(x => x.Id == dto.Id, cancellationToken);
+                if (visit is null) throw UserFriendlyException.SameMessage("无效回访记录编号");
+                visit.CallId = dto.CallId;
+                await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
+            }
         }
     }
 }

+ 18 - 25
src/Hotline.Application/Jobs/GetCallsJob.cs → src/Hotline.Application/Jobs/GetXingTangCallsJob.cs

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

+ 27 - 15
src/Hotline.Application/Mappers/CallMapperConfigs.cs

@@ -61,29 +61,41 @@ namespace Hotline.Application.Mappers
                 .Map(d => d.CallAccept, x => x.call_accept)
                 .Map(d => d.Type, x => x.type);
 
-            config.ForType<XingtangCall, TrCallRecord>()
-                .Map(d => d.OtherAccept, s => s.CallGuid)
-                .Map(d => d.CallDirection, s => s.CallType)
-                .Map(d => d.CPN, s => s.Caller)
-                .Map(d => d.CDPN, s => s.Called)
+            config.ForType<CallXingtang, CallNative>()
+                .Map(d => d.CallNo, s => s.CallGuid)
+                .Map(d => d.Direction, s => s.CallType)
+                .Map(d => d.FromNo, s => s.Caller)
+                .Map(d => d.ToNo, s => s.Called)
                 .Map(d => d.TelNo, s => s.Ext)
-                .Map(d => d.StaffNo, s => s.UserCode)
-                .Map(d => d.AnsweredTime, s => s.ReceiveEndTime)
-                .Map(d => d.OverTime, s => s.CallEndTime)
                 .Map(d => d.BeginIvrTime, s => s.CallStartTime)
                 .Map(d => d.EndIvrTime, s => s.EnqueueTime)
+                .Map(d => d.BeginQueueTime, s => s.EnqueueTime)
+                .Map(d => d.EndQueueTime, s => s.DequeueTime)
                 .Map(d => d.BeginRingTime, s => s.RingStartTime)
-                .Map(d => d.EndRingTimg, s => s.ReceiveEndTime.HasValue ? s.ReceiveEndTime : s.CallEndTime)
-                .Map(d => d.OlaQueue, s => s.SkillId.ToString())
+                .Map(d => d.EndRingTime, s => s.ReceiveEndTime.HasValue ? s.ReceiveEndTime : s.CallEndTime)
+                .Map(d => d.AnsweredTime, s => s.ReceiveEndTime)
+                .Map(d => d.EndTime, s => s.CallEndTime)
+                .Map(d => d.GroupId, s => s.SkillId.ToString())
+                .Map(d => d.StaffNo, s => s.UserCode)
                 .Map(d => d.Duration, s => s.Duration)
-                .Map(d => d.RingTimes, s => s.RingTime)
-                .Map(d => d.QueueTims, s => s.WaitTime)
-                .Map(d => d.RecordingAbsolutePath, s => s.AudioFile)
+                .Map(d => d.RingDuration, s => s.RingTime)
+                .Map(d => d.WaitDuration, s => s.WaitTime)
+                .Map(d => d.AudioFile, s => s.AudioFile)
                 .AfterMapping((s, d) =>
                 {
-                    d.CreatedTime = DateTime.Now;
-                    d.OnState = s.ReceiveEndTime.HasValue ? EOnState.On : EOnState.NoOn;
+                    d.HangupBy = EHangupBy.Outside;
                 });
+
+            config.ForType<CallNative, TrCallDto>()
+                .Map(d => d.CallDirection, s => s.Direction)
+                .Map(d => d.CPN, s => s.FromNo)
+                .Map(d => d.CDPN, s => s.ToNo)
+                .Map(d => d.CreatedTime, s => s.CreationTime)
+                .Map(d => d.OverTime, s => s.EndTime)
+                .Map(d => d.Gateway, s => s.ToNo)
+                .Map(d => d.EndRingTimg, s => s.EndRingTime)
+                ;
+
         }
 
         private DateTime? FormatDateTime(string? time)

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

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

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

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

+ 21 - 5
src/Hotline.Share/Dtos/CallCenter/QueryCallsFixedDto.cs

@@ -19,10 +19,26 @@ namespace Hotline.Share.Dtos.CallCenter
 
         public string? TelNo { get; set; }
 
-        public EEndBy? EndBy { get; set; }
-
-        public DateTime? StartTime { get; set; }
-
-        public DateTime? EndTime { get; set; }
+        public EHangupBy? HangupBy { get; set; }
+
+        /// <summary>
+        /// 呼入时间
+        /// </summary>
+        public DateTime? CallStartTimeBT { get; set; }
+
+        /// <summary>
+        /// 呼入时间
+        /// </summary>
+        public DateTime? CallStartTimeLT { get; set; }
+
+        /// <summary>
+        /// 是否接通
+        /// </summary>
+        public bool? IsConnected { get; set; }
+
+        /// <summary>
+        /// 通话方向
+        /// </summary>
+        public ECallDirection? Direction { get; set; }
     }
 }

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

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

+ 9 - 8
src/Hotline/CallCenter/Calls/CallNative.cs

@@ -13,13 +13,13 @@ namespace Hotline.CallCenter.Calls
     /// <summary>
     /// 本地通话记录
     /// </summary>
-    [SugarIndex("index_call_callid", nameof(CallNative.CallId), OrderByType.Asc)]
+    [SugarIndex("index_call_callid", nameof(CallNative.CallNo), OrderByType.Asc)]
     public class CallNative : CreationEntity
     {
         /// <summary>
-        /// 通话id
+        /// 通话记录编号
         /// </summary>
-        public string CallId { get; set; }
+        public string CallNo { get; set; }
 
         public ECallDirection Direction { get; set; }
 
@@ -41,7 +41,7 @@ namespace Hotline.CallCenter.Calls
         /// <summary>
         /// 挂断方
         /// </summary>
-        public EEndBy EndBy { get; set; }
+        public EHangupBy? HangupBy { get; set; }
 
         /// <summary>
         /// IVR开始时间
@@ -121,10 +121,11 @@ namespace Hotline.CallCenter.Calls
         /// </summary>
         public string AudioFile { get; set; }
 
-        public string? ExternalId { get; set; }
+        //public string? ExternalId { get; set; }
+
+        //public string? OrderNo { get; set; }
+
+        //public string? Title { get; set; }
 
-        public string? OrderNo { get; set; }
-        
-        public string? Title { get; set; }
     }
 }

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

@@ -0,0 +1,56 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.CallCenter.Tels
+{
+    [Description("话机操作记录")]
+    public class TelOperation : CreationEntity
+    {
+        /// <summary>
+        /// 工号
+        /// </summary>
+        public string? StaffNo { get; set; }
+
+        public string? UserId { get; set; }
+
+        public string? UserName { get; set; }
+
+        /// <summary>
+        /// 分机号
+        /// </summary>
+        public string? TelNo { get; set; }
+
+        public string? GroupId { get; set; }
+
+        /// <summary>
+        /// 执行状态
+        /// 0:签入 1:接听 2:置忙 3:置闲 16:签出 17:呼出 30:挂机
+        /// </summary>
+        public int? OperateState { get; set; }
+
+        public string? OperateStateText => OperateState.HasValue
+            ? OperateState switch
+            {
+                0 => "签入",
+                1 => "接听",
+                2 => "置忙",
+                3 => "置闲",
+                16 => "签出",
+                17 => "呼出",
+                30 => "挂机",
+                _ => "未知"
+            }
+            : string.Empty;
+
+        public DateTime? OperateTime { get; set; }
+        public int? Channel { get; set; }
+        public string? CompanyId { get; set; }
+        public string? Remark { get; set; }
+    }
+}

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

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

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

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