Ver Fonte

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

xf há 1 ano atrás
pai
commit
8acb6e1964

+ 7 - 0
src/Hotline.Ai.Jths/AiJthsStartupExtensions.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Hotline.Ai.Quality;
+using Hotline.Ai.Visit;
 using Microsoft.Extensions.DependencyInjection;
 
 namespace Hotline.Ai.Jths
@@ -13,7 +14,13 @@ namespace Hotline.Ai.Jths
 		public static IServiceCollection AddAiJths(this IServiceCollection services, string baseUrl)
 		{
 			services.AddSingleton<IAiQualityService, AiQualityService>(_ => new AiQualityService(baseUrl));
+			
+			return services;
+		}
 
+		public static IServiceCollection AddAiVisitService(this IServiceCollection services,string baseUrl,string appkey,string serviceversion,string sceneuid,string ruleuid)
+		{
+			services.AddSingleton<IAiVisitService, AiVisitService>(_ => new AiVisitService(baseUrl, appkey, serviceversion, sceneuid, ruleuid));
 			return services;
 		}
 	}

+ 12 - 0
src/Hotline.Ai.Jths/AiVisitConfig.cs

@@ -0,0 +1,12 @@
+
+namespace Hotline.Ai.Jths
+{
+    public class AiVisitConfig
+    {
+        public string Url { get; set; }
+        public string Appkey { get; set; }
+        public string ServiceVersion { get; set; }
+        public string SceneUid { get; set; }
+        public string RuleUid { get;set; }
+    }
+}

+ 121 - 0
src/Hotline.Ai.Jths/AiVisitService.cs

@@ -0,0 +1,121 @@
+using Fw.Utility.UnifyResponse;
+using Hotline.Ai.Jths.OrderVisits;
+using Hotline.Ai.Visit;
+using Hotline.Orders;
+using Hotline.Share.Enums.Order;
+using Newtonsoft.Json;
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Ai.Jths
+{
+    public class AiVisitService : IAiVisitService
+    {
+        private readonly RestClient _client;
+        private readonly string _baseUrl;
+        private readonly string _appkey;
+        private readonly string _serviceversion;
+        private readonly string _sceneuid;
+        private readonly string _ruleuid;
+        public AiVisitService(string baseUrl, string appkey, string serviceversion, string sceneuid, string ruleuid)
+        {
+         
+            _baseUrl = baseUrl;
+            _appkey = appkey;
+            _serviceversion = serviceversion;
+            _sceneuid = sceneuid;
+            _ruleuid = ruleuid;
+            var options = new RestClientOptions(_baseUrl);
+            _client = new RestClient(options);
+        }
+
+        public async Task<AiOrderVisit> CreateAiOrderVisitTask(AiOrderVisit aiOrderVisit, CancellationToken cancellationToken)
+        {
+            var requestData = new AiVisitServiceRequest()
+            {
+                BatchName = aiOrderVisit.Name,
+                BatchStatus = 1,
+                SceneUid = _sceneuid,
+                Priority = 1,
+                StartDate = aiOrderVisit.BeginTime,
+                EndDate = aiOrderVisit.EndTime,
+                FestivalBan = aiOrderVisit.FestivalBan,
+                RuleType = aiOrderVisit.RuleType,
+                RuleUid = aiOrderVisit.RuleUid,
+            };
+            var taskDataList = new List<TaskData>();
+            foreach (var item in aiOrderVisit.AiOrderVisitDetails)
+            {
+                var taskData = new TaskData();
+                taskData.CalledNumber = item.Order.FromPhone;
+                taskData.VariableList = new List<Variable>();
+               
+                if (item.Order.FromGender!= EGender.Unknown)
+                {
+                    if (!string.IsNullOrEmpty(item.Order.FromName))
+                    {
+                        taskData.VariableList.Add(new Variable() { Code = "姓名", Value = item.Order.FromName });
+                    }
+                    taskData.VariableList.Add(new Variable() { Code = "gender", Value = item.Order.FromGender == EGender.Female ? "女士" : "先生" });
+                }
+               
+                taskData.VariableList.Add(new Variable() { Code = "反馈时间", Value = item.Order.CreationTime.ToString("yyyy年MM月dd日hh点mm分") });
+                taskData.VariableList.Add(new Variable() { Code = "反馈问题", Value = item.Order.Title });
+            }
+            requestData.TaskDataList = taskDataList;
+            var response = await ExecuteAsync<AiVisitServiceRequest, AiVisitServiceResponse>(_baseUrl + "edas/batchTask",Method.Post, requestData,cancellationToken);
+
+            //拼对象 TODO
+            aiOrderVisit.BatchUid = response.BatchUid;
+            for (int i = 0; i < aiOrderVisit.AiOrderVisitDetails.Count; i++)
+            {
+                var taskInfo = response.TaskInfoList.FirstOrDefault(x => x.CalledNumber == aiOrderVisit.AiOrderVisitDetails[i].OuterNo);
+                if (taskInfo!=null)
+                {
+                    aiOrderVisit.AiOrderVisitDetails[i].AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.InProgress;
+                    aiOrderVisit.AiOrderVisitDetails[i].TaskUid = taskInfo.TaskUid;
+                }
+                else
+                {
+                    aiOrderVisit.AiOrderVisitDetails[i].AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Cancel;
+                }
+            }
+
+            return aiOrderVisit;
+        }
+
+
+        public async Task<TResponse> ExecuteAsync<TRequest,TResponse>(string path, Method httpMethod, TRequest request,
+            CancellationToken cancellationToken)
+            where TRequest : class
+        {
+            var req = new RestRequest(path, httpMethod);
+            req.AddHeader("content-type", "application/json");
+            req.AddHeader("token", "");
+            req.AddHeader("version", _serviceversion);
+            req.AddHeader("appkey", _appkey);
+            if (httpMethod is Method.Get)
+            {
+                req.AddObject(request);
+            }
+            else
+            {
+                req.AddJsonBody(request);
+            }
+
+            try
+            {
+                var response = await _client.ExecuteAsync<TResponse>(req, cancellationToken);
+                return response.Data;
+            }
+            catch (Exception e)
+            {
+                throw new HttpRequestException($"智能质检平台错误,Error: {e.Message}");
+            }
+        }
+    }
+}

+ 63 - 0
src/Hotline.Ai.Jths/OrderVisits/AiVisitServiceRequest.cs

@@ -0,0 +1,63 @@
+
+namespace Hotline.Ai.Jths.OrderVisits
+{
+    public class AiVisitServiceRequest
+    {
+        /// <summary>
+        /// 批次名称
+        /// </summary>
+        public string BatchName { get; set; }
+        /// <summary>
+        /// 批次状态 1:启动 2:暂停
+        /// </summary>
+        public int BatchStatus { get; set; }
+        /// <summary>
+        /// 场景ID
+        /// </summary>
+        public string SceneUid { get; set; }
+        /// <summary>
+        /// 任务数据
+        /// </summary>
+        public List<TaskData> TaskDataList { get; set; }
+        /// <summary>
+        /// 优先级
+        /// </summary>
+        public int Priority { get; set; }
+        /// <summary>
+        /// 外呼开始时间
+        /// </summary>
+        public DateTime StartDate { get; set; }
+        /// <summary>
+        /// 外呼结束时间
+        /// </summary>
+        public DateTime EndDate { get; set; }
+
+        /// <summary>
+        /// 节日禁呼 0:否 1:是
+        /// </summary>
+        public int FestivalBan { get; set; }
+        /// <summary>
+        /// 外呼规则类型 1:自定义 2::使用现有规则
+        /// </summary>
+        public int RuleType { get; set; }
+
+        /// <summary>
+        /// 现有外呼规则ID
+        /// </summary>
+        public string RuleUid { get; set; }
+    }
+
+    public class TaskData
+    {
+        public string CalledNumber { get; set; }
+
+        public List<Variable> VariableList { get; set; }
+    }
+
+    public class Variable
+    {
+        public string Code { get; set; }
+
+        public string Value { get; set; }
+    }
+}

+ 21 - 0
src/Hotline.Ai.Jths/OrderVisits/AiVisitServiceResponse.cs

@@ -0,0 +1,21 @@
+
+namespace Hotline.Ai.Jths.OrderVisits
+{
+    public class AiVisitServiceResponse
+    {
+
+
+
+        public string BatchUid { get; set; }
+
+        public List<TaskInfoList> TaskInfoList { get; set; }
+    }
+
+
+    public class TaskInfoList
+    {
+        public string TaskUid { get; set; }
+
+        public string CalledNumber { get; set; }
+    }
+}

+ 118 - 1
src/Hotline.Api/Controllers/AiController.cs

@@ -1,19 +1,34 @@
 
+using Consul;
 using Hotline.Caching.Interfaces;
+using Hotline.Orders;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Ai;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http.Features;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
 using Newtonsoft.Json;
 using XF.Domain.Constants;
+using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers
 {
     public class AiController: BaseController
     {
         private readonly ISystemSettingCacheManager _systemSettingCacheManager;
-        public AiController(ISystemSettingCacheManager systemSettingCacheManager)
+        private readonly IRepository<AiOrderVisit> _aiOrderVisitRepository;
+        private readonly IRepository<AiOrderVisitDetail> _aiOrderVisitDetailRepository;
+        private readonly IRepository<OrderVisit> _orderVisitRepository;
+        private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+
+        public AiController(ISystemSettingCacheManager systemSettingCacheManager,IRepository<AiOrderVisit> aiOrderVisitRepository,IRepository<AiOrderVisitDetail>  aiOrderVisitDetailRepository,IRepository<OrderVisit> orderVisitRepository,IRepository<OrderVisitDetail> orderVisitDetailRepository)
         {
            _systemSettingCacheManager = systemSettingCacheManager;
+            _aiOrderVisitRepository = aiOrderVisitRepository;
+            _aiOrderVisitDetailRepository = aiOrderVisitDetailRepository;
+            _orderVisitRepository = orderVisitRepository;
+            _orderVisitDetailRepository = orderVisitDetailRepository;
         }
 
 
@@ -50,6 +65,108 @@ namespace Hotline.Api.Controllers
             return IsOk;
         }
 
+        /// <summary>
+        /// 智能回访外呼结果回传
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("visit/aivisit-back")]
+        public async Task AiVisitBack([FromBody]AiVisitBackDto dto)
+        {
+            var aiOrderVisit = await _aiOrderVisitRepository.Queryable()
+                .Includes(x => x.AiOrderVisitDetails,s=>s.OrderVisit)
+                .FirstAsync(x => x.BatchUid == dto.BatchUid);
+            if (aiOrderVisit!=null)
+            {
+                //验证记录中是否存在有结果的任务
+                if (dto.TaskStatus == 6)//执行完
+                {
+                    var aiOrderVisitDetail = aiOrderVisit.AiOrderVisitDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid);
+                    if (aiOrderVisitDetail != null)
+                    {
+                        var callRecord = dto.CallRecordList.OrderByDescending(x => x.CallNo).FirstOrDefault(x => x.ReturnVisit == 1);
+                        if (callRecord != null) //有结果的任务
+                        {
+                            aiOrderVisitDetail.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Ended; //更新AI子表
+                            aiOrderVisit.VisitedCount++;
+                            //处理结果
+                            var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id);
+                            //先处理子表
+                            //先处理坐席(因没有坐席回访,所以默认满意)
+                            var seatDetail = visitDetail.First(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Seat);
+                            if (seatDetail != null)
+                            {
+                                seatDetail.VoiceEvaluate = Share.Enums.Order.EVoiceEvaluate.Satisfied;
+                                seatDetail.SeatEvaluate = Share.Enums.Order.ESeatEvaluate.Satisfied;
+                            }
+                            await _orderVisitDetailRepository.UpdateAsync(seatDetail, HttpContext.RequestAborted);
+                            //处理部门
+                            var orgDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Org).ToList();
+                            //过滤结果
+                            var orgProcessingResults = new Kv();
+                            var orgHandledAttitude = new Kv();
+                            foreach (var item in callRecord.QuestionnaireResult)
+                            {
+                                //服务过程满意度
+                                if (item.QuestionId == "2" )
+                                {
+                                    if (item.QuestionResult == "满意")
+                                    {
+                                        orgHandledAttitude = new Kv() { Key="4", Value="满意" };
+                                    }
+                                    else
+                                    {
+                                        orgHandledAttitude = new Kv() { Key = "2", Value = "不满意" };
+                                    }
+                                }
+                                //办件结果满意度
+                                if (item.QuestionId == "3")
+                                {
+                                    if (item.QuestionResult == "满意")
+                                    {
+                                        orgProcessingResults = new Kv() { Key = "4", Value = "满意" };
+                                    }
+                                    else
+                                    {
+                                        orgProcessingResults = new Kv() { Key = "4", Value = "不满意" };
+
+                                    }
+                                }
+                            }
+                            //处理结果
+                            orgDetail.ForEach(x =>
+                            {
+                                x.OrgHandledAttitude = orgHandledAttitude;
+                                x.OrgProcessingResults = orgProcessingResults;
+                                if (orgProcessingResults.Value == "不满意")
+                                {
+                                    x.OrgNoSatisfiedReason = new List<Kv>() { new Kv() { Key = "7", Value = "未回复" } };
+                                }
+                            });
+                            await _orderVisitDetailRepository.UpdateRangeAsync(orgDetail, HttpContext.RequestAborted);
+                            //处理主表
+                            aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.Visited;
+                            aiOrderVisitDetail.OrderVisit.AiVisitCount++;
+                            aiOrderVisitDetail.OrderVisit.VisitTime = DateTime.Now;
+                            aiOrderVisitDetail.OrderVisit.AiVisitTime();
+                            await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted);
+                        }
+                        else
+                        {
+                            aiOrderVisitDetail.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Cancel; //更新AI子表
+                            aiOrderVisit.VisitedFailCount++;
+                            //处理回访主表
+                            aiOrderVisitDetail.OrderVisit.AiVisitTime();
+                            await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted);
+                        }
+                        await _aiOrderVisitDetailRepository.UpdateAsync(aiOrderVisitDetail, HttpContext.RequestAborted);
+                        await _aiOrderVisitRepository.UpdateAsync(aiOrderVisit, HttpContext.RequestAborted);
+                    }
+                    
+                }
+            }
+        }
 
     }
 }

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

@@ -261,10 +261,15 @@ public class OrderController : BaseController
         orderVisit.VisitState = EVisitState.WaitForVisit;
         orderVisit.PublishTime = DateTime.Now;
         orderVisit.IsCanHandle = true;
+
         if (order is { ProcessType: EProcessType.Zhiban, CounterSignType: null })
         {
             orderVisit.VisitState = EVisitState.Visited;
         }
+        if(order.CounterSignType != ECounterSignType.Center)
+        {
+            orderVisit.IsCanAiVisit = true;
+        }
 
         string visitId = await _orderVisitRepository.AddAsync(orderVisit);
 

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

@@ -45,7 +45,7 @@ internal static class StartupExtensions
         services.Configure<IdentityConfiguration>(d => configuration.GetSection(nameof(IdentityConfiguration)).Bind(d));
         services.Configure<CallCenterConfiguration>(d => configuration.GetSection(nameof(CallCenterConfiguration)).Bind(d));
         services.Configure<ChannelConfiguration>(d => configuration.GetSection(nameof(ChannelConfiguration)).Bind(d));
-
+        
         // Add services to the container.
         services
             .BatchInjectServices()
@@ -103,6 +103,9 @@ internal static class StartupExtensions
         //宜宾企业服务
         services.AddYbEnterpriseSdk("", "", "", "");
 
+        var aiVisitConfig = configuration.GetRequiredSection("AiVisit").Get<AiVisitConfig>();
+        services.AddAiVisitService(aiVisitConfig.Url, aiVisitConfig.Appkey, aiVisitConfig.ServiceVersion, aiVisitConfig.SceneUid, aiVisitConfig.RuleUid);
+
         //sqlsugar
         services.AddSqlSugar(configuration);
         //services.AddWexDb(configuration);

+ 8 - 0
src/Hotline.Api/config/appsettings.Development.json

@@ -201,6 +201,14 @@
     //智能质检
     "AiQuality": {
       "Url": ""
+    },
+    //智能回访
+    "AiVisit": {
+      "Url": "",
+      "Appkey": "fwkj",
+      "ServiceVersion": "V1.0.0", //接口版本号
+      "SceneUid": "", //场景ID
+      "RuleUid": ""//现有规则ID
     }
   }
 

+ 8 - 0
src/Hotline.Api/config/appsettings.json

@@ -101,5 +101,13 @@
   //智能质检
   "AiQuality": {
     "Url": ""
+  },
+  //智能回访
+  "AiVisit": {
+    "Url": "",
+    "Appkey": "fwkj",
+    "ServiceVersion": "V1.0.0", //接口版本号
+    "SceneUid": "", //场景ID
+    "RuleUid": "" //现有规则ID
   }
 }

+ 13 - 0
src/Hotline.Application/Visit/IOrderVisitApplication.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Visit
+{
+    public interface IOrderVisitApplication
+    {
+        Task AddAiVisit(string aivisitId, CancellationToken cancellationToken);
+    }
+}

+ 37 - 0
src/Hotline.Application/Visit/OrderVisitApplication.cs

@@ -0,0 +1,37 @@
+using Hotline.Ai.Visit;
+using Hotline.Orders;
+using StackExchange.Redis;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Visit
+{
+    public class OrderVisitApplication : IOrderVisitApplication, IScopeDependency
+    {
+        private readonly IRepository<AiOrderVisit> _aiOrderVisitRepository;
+        private readonly IAiVisitService _aiVisitService;
+        public OrderVisitApplication(IRepository<AiOrderVisit> aiOrderVisitRepository,IAiVisitService aiVisitService)
+        {
+            _aiOrderVisitRepository = aiOrderVisitRepository;
+            _aiVisitService = aiVisitService;
+        }
+
+
+        public async Task AddAiVisit(string aivisitId, CancellationToken cancellationToken)
+        {
+            var aiOrderVisit =await _aiOrderVisitRepository.Queryable()
+                .Includes(x => x.AiOrderVisitDetails, s => s.Order)
+                .Includes(x => x.AiOrderVisitDetails, s => s.OrderVisit)
+                .FirstAsync(x => x.Id == aivisitId);
+
+            aiOrderVisit = await _aiVisitService.CreateAiOrderVisitTask(aiOrderVisit, cancellationToken);
+
+            await _aiOrderVisitRepository.UpdateNav(aiOrderVisit).Include(d => d.AiOrderVisitDetails).ExecuteCommandAsync();
+        }
+    }
+}

+ 57 - 0
src/Hotline.Share/Dtos/Ai/AiDto.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Ai
+{
+    public class AiVisitBackDto
+    {
+        public string BatchUid { get; set; }
+
+        public string BatchName { get; set; }
+
+        public string SceneUid { get; set; }
+
+        public string SenceName { get; set; }
+
+        public string TaskUid { get; set; }
+
+        public string CalledNumber { get; set; }
+        /// <summary>
+        /// 任务状态( 1 :待执行、2 :暂停中、3 :执行中、4 :失效、5 :呼叫失败 、6:已结束(完成或取消))
+        /// </summary>
+        public int TaskStatus { get; set; }
+
+        public string CallTimes { get; set; }
+
+        public List<CallRecordDto> CallRecordList { get; set; }
+    }
+
+    public class CallRecordDto
+    {
+        public int CallNo { get; set; }
+
+        /// <summary>
+        /// 呼叫状态(1:呼叫中、2:完成、3:呼叫失败)
+        /// </summary>
+        public int CallStatus { get; set; }
+
+        /// <summary>
+        /// 回访结果 (1成功、2:不涉及、3:失败)
+        /// </summary>
+        public int ReturnVisit { get; set; }
+
+        public List<QuestionnaireResult> QuestionnaireResult { get; set; }
+    }
+
+    public class QuestionnaireResult
+    {
+        public string QuestionId { get; set; }
+
+        public string QuestionChoice { get; set; }
+
+        public string QuestionResult { get; set; }
+    }
+}

+ 20 - 0
src/Hotline.Share/Enums/Ai/EAiOrderVisitState.cs

@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+
+namespace Hotline.Share.Enums.Ai
+{
+    public enum EAiOrderVisitState
+    {
+        [Description("未开始")]
+        NoStarted = 1,
+
+        [Description("进行中")]
+        InProgress = 2,
+
+        [Description("已结束")]
+        Ended = 3,
+
+        [Description("取消")]
+        Cancel = 4,
+    }
+}

+ 18 - 0
src/Hotline.Share/Enums/Ai/EAiOrderVisitTaskState.cs

@@ -0,0 +1,18 @@
+
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Ai
+{
+    public enum EAiOrderVisitTaskState
+    {
+        [Description("未开始")]
+        NoStarted = 1,
+
+        [Description("进行中")]
+        InProgress = 2,
+
+        [Description("已结束")]
+        Ended = 3,
+    }
+
+}

+ 10 - 0
src/Hotline/Ai/Visit/IAiVisitService.cs

@@ -0,0 +1,10 @@
+
+using Hotline.Orders;
+
+namespace Hotline.Ai.Visit
+{
+    public interface IAiVisitService
+    {
+        Task<AiOrderVisit> CreateAiOrderVisitTask(AiOrderVisit aiOrderVisit, CancellationToken cancellationToken);
+    }
+}

+ 60 - 0
src/Hotline/Orders/AiOrderVisit.cs

@@ -0,0 +1,60 @@
+using Hotline.Share.Enums.Ai;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Orders
+{
+    [Description("智能回访")]
+    public class AiOrderVisit : CreationEntity
+    {
+        public string Name { get; set; }
+
+        public DateTime BeginTime { get; set; }
+
+        public DateTime EndTime { get; set; }
+
+        public EAiOrderVisitTaskState TaskState {get;set;}
+        /// <summary>
+        /// 批次上报结果ID
+        /// </summary>
+        public string? BatchUid { get; set; }
+
+
+        /// <summary>
+        /// 节日禁呼 0:否 1:是
+        /// </summary>
+        public int FestivalBan { get; set; }
+
+        /// <summary>
+        /// 外呼规则类型 1:自定义 2::使用现有规则
+        /// </summary>
+        public int RuleType { get; set; }
+
+        /// <summary>
+        /// 现有外呼规则ID
+        /// </summary>
+        public string RuleUid { get; set; }
+
+        /// <summary>
+        /// 子表
+        /// </summary>
+        [Navigate(NavigateType.OneToMany, nameof(AiOrderVisitDetail.AiOrderVisitId))]
+        public List<AiOrderVisitDetail> AiOrderVisitDetails { get; set; }
+
+        /// <summary>
+        /// 应回访数量
+        /// </summary>
+        public int HasVisitCount { get; set; }
+
+        /// <summary>
+        /// 已回访成功数量
+        /// </summary>
+        public int VisitedCount { get; set; }
+
+        /// <summary>
+        /// 已回访失败数量
+        /// </summary>
+        public int VisitedFailCount { get; set; }
+    }
+}

+ 52 - 0
src/Hotline/Orders/AiOrderVisitDetail.cs

@@ -0,0 +1,52 @@
+using Hotline.Share.Enums.Ai;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Orders
+{
+    public class AiOrderVisitDetail : CreationEntity
+    {
+        /// <summary>
+        /// 主表ID
+        /// </summary>
+        public string AiOrderVisitId { get; set; }
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string OrderId { get; set; }
+
+        /// <summary>
+        /// 工单
+        /// </summary>
+        [Navigate(NavigateType.OneToOne, nameof(OrderId))]
+        public Order Order { get; set; }
+        /// <summary>
+        /// 工单回访主表ID
+        /// </summary>
+        public string OrderVisitId { get; set; }
+        /// <summary>
+        /// 工单
+        /// </summary>
+        [Navigate(NavigateType.OneToOne, nameof(OrderVisitId))]
+        public OrderVisit OrderVisit { get; set; }
+        /// <summary>
+        /// 外呼电话
+        /// </summary>
+        public string OuterNo { get; set; }
+
+        public EAiOrderVisitState AiOrderVisitState { get;set;}
+
+        /// <summary>
+        /// 批此上传成功后任务ID
+        /// </summary>
+        public string? TaskUid { get; set; }
+       
+
+
+    }
+}

+ 30 - 0
src/Hotline/Orders/OrderVisit.cs

@@ -94,9 +94,39 @@ public class OrderVisit : CreationEntity
     /// </summary>
     public bool IsCanHandle { get; set; }
 
+    /// <summary>
+    /// 是否可以AI回访
+    /// </summary>
+    public bool? IsCanAiVisit { get; set; }
+
     /// <summary>
     /// 渠道为电话时,此字段存在
     /// </summary>
     public string? CallId { get; set; }
 
+    /// <summary>
+    /// 第一次电话回访拨打时间
+    /// </summary>
+    public DateTime? FirstVisitTime { get; set; }
+
+    /// <summary>
+    /// 最后一次电话回访拨打时间
+    /// </summary>
+    public DateTime? LastVisitTime { get; set; }
+
+    /// <summary>
+    /// 智能回访次数
+    /// </summary>
+    [SugarColumn( DefaultValue = "0" )]
+    public int AiVisitCount { get; set; }
+
+    public void AiVisitTime()
+    {
+        LastVisitTime = DateTime.Now;
+        if (FirstVisitTime is null)
+        {
+            FirstVisitTime = LastVisitTime;
+        }
+    }
+
 }