Kaynağa Gözat

完成发送回访短信

qinchaoyue 7 ay önce
ebeveyn
işleme
b302172fe9

+ 2 - 27
src/Hotline.Api/Controllers/OrderController.cs

@@ -812,33 +812,8 @@ public class OrderController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpPost("visit/sms")]
-    public async Task<bool> VisitPushSMSAsync([FromBody] List<string> visitIds)
-    {
-        var phoneNumbers = await _orderVisitRepository.Queryable()
-            .Includes(d => d.Order)
-            .Where(d => visitIds.Contains(d.Id) && d.VisitState == EVisitState.WaitForVisit)
-            .Select(d => new { d.Id, d.Order.Contact, d.Order.Password, d.No, d.OrderId, d.Order.Title, d.Order.FromName })
-            .ToListAsync(HttpContext.RequestAborted);
-
-        foreach (var item in phoneNumbers)
-        {
-            var messageDto = new Share.Dtos.Push.MessageDto
-            {
-                PushBusiness = EPushBusiness.OrderAccept,
-                ExternalId = item.Id,
-                OrderId = item.OrderId,
-                PushPlatform = EPushPlatform.Sms,
-                //Content =
-                //  $"温馨提示:您的来电已受理(流水号:{order.No};提取码:{order.Password},可通过网站(http://hotline.12345lm.cn)进行查询,谢谢。【宜宾12345热线平台】)",
-                Remark = item.Title,
-                Name = item.FromName,
-                TemplateCode = "1005",
-                Params = new List<string>() { item.No, item.Password },
-                TelNumber = item.Contact,
-            };
-            await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
-        }
-    }
+    public async Task VisitPushSMSAsync([FromBody] VisitSmsInDto dto)
+        => await _orderApplication.VisitPushSMSAsync(dto, HttpContext.RequestAborted);
 
     /// <summary>
     /// 回访查询基础数据

+ 40 - 0
src/Hotline.Application.Tests/Application/OrderApplicationTest.cs

@@ -0,0 +1,40 @@
+using Hotline.Application.Orders;
+using Hotline.Orders;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.Order;
+using Shouldly;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Tests.Application;
+public class OrderApplicationTest
+{
+    private readonly IOrderApplication _orderApplication;
+    private readonly IRepository<OrderVisit> _orderVisitRepository;
+
+    public OrderApplicationTest(IOrderApplication orderApplication, IRepository<OrderVisit> orderVisitRepository)
+    {
+        _orderApplication = orderApplication;
+        _orderVisitRepository = orderVisitRepository;
+    }
+
+    [Fact]
+    public async Task VisitPushSMS_Test()
+    {
+        var orderVisit = await _orderVisitRepository.Queryable()
+            .Where(m => m.VisitState == EVisitState.WaitForVisit)
+            .OrderByDescending(m => m.CreationTime)
+            .FirstAsync();
+        var dto = new VisitSmsInDto
+        {
+            Ids = new List<string> { orderVisit.Id }
+        };
+        await _orderApplication.VisitPushSMSAsync(dto, new CancellationToken());
+        (await _orderVisitRepository.Queryable().Where(m => m.Id == orderVisit.Id).FirstAsync())
+            .VisitState.ShouldBe(EVisitState.SMSVisiting);
+    }
+}

+ 0 - 159
src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs

@@ -1,159 +0,0 @@
-//using Hotline.Application.Identity;
-//using Hotline.Application.Snapshot;
-//using Hotline.Share.Dtos.Article;
-//using Hotline.Share.Dtos.Snapshot;
-//using Hotline.Share.Enums;
-//using Hotline.Share.Enums.Snapshot;
-//using Hotline.Share.Tools;
-//using Hotline.Snapshot;
-//using Shouldly;
-//using XF.Domain.Repository;
-//using XF.Utility.EnumExtensions;
-
-//namespace Hotline.Application.Tests.Application;
-//public class SnapshotApplicationTest
-//{
-//    private readonly ISnapshotApplication _snapshotApplication;
-//    private readonly IIdentityAppService _identityAppService;
-//    private readonly IRepository<RedPackRecord> _redPackRecordRepository;
-
-//    public SnapshotApplicationTest(ISnapshotApplication snapshotApplication, IIdentityAppService identityAppService, IRepository<RedPackRecord> redPackRecordRepository)
-//    {
-//        _snapshotApplication = snapshotApplication;
-//        _identityAppService = identityAppService;
-//        _redPackRecordRepository = redPackRecordRepository;
-//    }
-
-//    [Fact]
-//    public async Task GetHomePage_Test()
-//    {
-//        var result = await _snapshotApplication.GetHomePageAsync();
-//        result.Any().ShouldBe(true, "首页数据为空");
-//        result.First().DisplayOrder.ShouldBe(1, "排序异常");
-//    }
-
-//    [Fact]
-//    public async Task GetBulletins_Test()
-//    {
-//        var homePage = await _snapshotApplication.GetHomePageAsync();
-//        var inDto = new BulletinInDto
-//        {
-//            IndustryId = homePage.First(m => m.Name == "文化旅游").Id,
-//        };
-//        var items = await _snapshotApplication.GetBulletinsAsync(inDto);
-//        items.ShouldNotBeNull();
-//        items.Any().ShouldBe(true, "公告数据为空");
-//        items.Any(m => m.Title.IsNullOrEmpty()).ShouldBe(false, "标题错误");
-//        items.Any(m => m.Content.IsNullOrEmpty()).ShouldBe(false, "内容错误");
-//        items.Any(m => m.Id.IsNullOrEmpty()).ShouldBe(false, "Id错误");
-//    }
-
-//    [Fact]
-//    public async Task GetSnapshotUserInfo_Test()
-//    {
-//        var result = await _snapshotApplication.GetSnapshotUserInfoAsync();
-//        result.ShouldNotBeNull();
-//        result.PhoneNumber.ShouldNotBeNullOrEmpty();
-//    }
-
-//    [Fact]
-//    public async Task GetThirdToken_Test()
-//    {
-//        var result = await _identityAppService.GetThredTokenAsync(new ThirdTokenInDto { Code = "0e3dql000zdwLS1ZRn000Z3SFR3dql00" });
-//        result.Code.ShouldBe(0);
-//        result.Result.Token.ShouldNotBeNull();
-//        result.Result.UserType.ShouldBe(EReadPackUserType.Citizen);
-//    }
-
-//    [Theory]
-//    [InlineData("")]
-//    [InlineData("测")]
-//    public async Task SnapshotOrder_Test(string key)
-//    {
-//        var dto = new OrderInDto();
-//        dto.KeyWords = key;
-//        var page = await _snapshotApplication.GetSnapshotOrdersAsync(dto);
-//        page.Total.ShouldNotBe(0);
-//        page.Items.FirstOrDefault()?.IndustryName.ShouldNotBeNullOrEmpty();
-//        page.Items.FirstOrDefault()?.OrderNo.ShouldNotBeNullOrEmpty();
-//        page.Items.FirstOrDefault()?.StatusText.ShouldNotBeNullOrEmpty();
-//        page.Items.FirstOrDefault()?.Area.ShouldNotBeNullOrEmpty();
-//    }
-
-//    [Theory]
-//    [InlineData(EOrderQueryStatus.All, 3)]
-//    [InlineData(EOrderQueryStatus.Reply, 2)]
-//    [InlineData(EOrderQueryStatus.NoReply, 1)]
-//    [InlineData(EOrderQueryStatus.Appraise, 1)]
-//    public async Task SnapshotOrderStatus_Test(EOrderQueryStatus status, int count)
-//    {
-//        var dto = new OrderInDto { Status = status };
-//        var page = await _snapshotApplication.GetSnapshotOrdersAsync(dto);
-//        page.Total.ShouldNotBe(0, $"状态:{status.GetDescription()} 数据为空");
-//        page.Total.ShouldBe(count, $"状态:{status.GetDescription()} 数据条数错误");
-//        page.Items.FirstOrDefault()?.IndustryName.ShouldNotBeNullOrEmpty();
-//        page.Items.FirstOrDefault()?.OrderNo.ShouldNotBeNullOrEmpty();
-//        page.Items.FirstOrDefault()?.StatusText.ShouldNotBeNullOrEmpty();
-//        page.Items.FirstOrDefault()?.Area.ShouldNotBeNullOrEmpty();
-
-//        dto.PageIndex = 2;
-//        page = await _snapshotApplication.GetSnapshotOrdersAsync(dto);
-//        page.Items.Count.ShouldBe(0);
-//    }
-
-//    [Fact]
-//    public async Task GetSnapshotOrderDetail_Test()
-//    {
-//        var page = await _snapshotApplication.GetSnapshotOrdersAsync(new OrderInDto());
-//        var id = page.Items.First().Id;
-//        var detail = await _snapshotApplication.GetSnapshotOrderDetailAsync(id);
-//        detail.Id.ShouldBe(id);
-//        detail.IndustryName.ShouldNotBeNull();
-//    }
-
-//    [Theory]
-//    [InlineData(2, 2)]
-//    [InlineData(12, 12)]
-//    public async Task GetRedPackDateAsync(int count, int exp)
-//    {
-//        var items = await _snapshotApplication.GetRedPackDateAsync(new RedPackDateInDto { Count = count});
-//        items.Count.ShouldNotBe(0, "0数据");
-//        items.Count.ShouldBe(exp, $"应该:{exp}, 实际 {items.Count}");
-//    }
-
-//    [Theory]
-//    [InlineData(ERedPackPickupStatus.Unreceived)]
-//    [InlineData(ERedPackPickupStatus.Received)]
-//    public async Task GetRedPacksAsync(ERedPackPickupStatus status)
-//    {
-//        var page = await _snapshotApplication.GetRedPacksAsync(new RedPacksInDto { Status = status});
-//        page.Total.ShouldNotBe(0, "数据不应该为空");
-//    }
-
-//    [Fact]
-//    public async Task GetBulletinsDetail_Test()
-//    {
-//        var detail = await _snapshotApplication.GetBulletinsDetailAsync("08dc788f-20f4-4bf1-83d3-b5a8a4f395b0");
-//        detail.Id.ShouldNotBeNullOrEmpty();
-//        detail.Title.ShouldNotBeNullOrEmpty();
-//        detail.Content.ShouldNotBeNullOrEmpty();
-//    }
-
-//    [Fact]
-//    public async Task InitRedPackDataAsync()
-//    {
-//        for (int i = 0; i < 12; i++)
-//        {
-//            var now = DateTime.Now;
-//            var entity = new RedPackRecord
-//            {
-//                OrderId = "111111111",
-//                Amount = 10 * 1000,
-//                CreationTime = new DateTime(2024, i + 1, 02, now.Hour, now.Minute, now.Second),
-//                WXOpenId = "测试生成的OpenId",
-//                PickupStatus = ERedPackPickupStatus.Received,
-//            };
-//            await _redPackRecordRepository.AddAsync(entity);
-//        }
-//    }
-//}

+ 1 - 0
src/Hotline.Application.Tests/Hotline.Application.Tests.csproj

@@ -36,6 +36,7 @@
     <ProjectReference Include="..\Hotline.Api\Hotline.Api.csproj" />
     <ProjectReference Include="..\Hotline.Application\Hotline.Application.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
+    <ProjectReference Include="..\Tr.Sdk\Tr.Sdk.csproj" />
     <ProjectReference Include="..\XF.Domain.Repository\XF.Domain.Repository.csproj" />
     <ProjectReference Include="..\XF.Domain\XF.Domain.csproj" />
   </ItemGroup>

+ 44 - 0
src/Hotline.Application.Tests/Mock/MediatorMock.cs

@@ -0,0 +1,44 @@
+using MediatR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Tests.Mock;
+public class MediatorMock : IMediator
+{
+    public IAsyncEnumerable<TResponse> CreateStream<TResponse>(IStreamRequest<TResponse> request, CancellationToken cancellationToken = default)
+    {
+        throw new NotImplementedException();
+    }
+
+    public IAsyncEnumerable<object?> CreateStream(object request, CancellationToken cancellationToken = default)
+    {
+        throw new NotImplementedException();
+    }
+
+    public Task Publish(object notification, CancellationToken cancellationToken = default)
+    {
+        throw new NotImplementedException();
+    }
+
+    public async Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification
+    {
+    }
+
+    public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
+    {
+        throw new NotImplementedException();
+    }
+
+    public Task Send<TRequest>(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest
+    {
+        throw new NotImplementedException();
+    }
+
+    public Task<object?> Send(object request, CancellationToken cancellationToken = default)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 18 - 0
src/Hotline.Application.Tests/Startup.cs

@@ -36,6 +36,13 @@ using XF.Domain.Options;
 using Hotline.Settings.TimeLimitDomain;
 using Hotline.Settings.TimeLimitDomain.ExpireTimeSupplier;
 using Microsoft.AspNetCore.WebSockets;
+using MediatR;
+using Hotline.Application.Tests.Mock;
+using Hotline.Repository.SqlSugar.Ts;
+using Hotline.Application.CallCenter;
+using Hotline.Application.CallCenter.Calls;
+using Hotline.CallCenter.Configs;
+using Tr.Sdk;
 
 namespace Hotline.Application.Tests;
 public class Startup
@@ -75,6 +82,10 @@ public class Startup
             var appConfigurationSection = configuration.GetRequiredSection(nameof(AppConfiguration));
             var appConfiguration = appConfigurationSection.Get<AppConfiguration>();
             if (appConfiguration is null) throw new ArgumentNullException(nameof(appConfiguration));
+
+            var callCenterConfigurationSection = configuration.GetRequiredSection(nameof(CallCenterConfiguration));
+            var callCenterConfiguration = callCenterConfigurationSection.Get<CallCenterConfiguration>();
+
             services.Configure<AppConfiguration>(d => appConfigurationSection.Bind(d));
             services.Configure<IdentityConfiguration>(d => configuration.GetSection(nameof(IdentityConfiguration)).Bind(d));
 
@@ -101,6 +112,12 @@ public class Startup
             });
             services.AddScoped(typeof(IPasswordHasher<>), typeof(PasswordHasher<>));
             services.AddScoped(typeof(ITypedCache<>), typeof(DefaultTypedCache<>));
+            services.AddScoped(typeof(IRepositoryTextSearch<>), typeof(BaseRepositoryTextSearch<>));
+            services.AddScoped<ICallApplication, TianRunCallApplication>();
+            services.AddScoped<ITrApplication, TrApplication>();
+            services.AddTrSdk(callCenterConfiguration.TianRun.Address,
+                        callCenterConfiguration.TianRun.Username,
+                        callCenterConfiguration.TianRun.Password);
             services.RegisterMediatR(appConfiguration);
 
             // services.AddSenparcWeixinServices(configuration);
@@ -124,6 +141,7 @@ public class Startup
             services.AddScoped<ExpireTimeFactory>();
             services.AddScoped<YiBinExpireTimeLimit>();
             services.AddScoped<ZiGongExpireTimeLimit>();
+            services.AddScoped<IMediator, MediatorMock>();
 
             ServiceLocator.Instance = services.BuildServiceProvider();
         }

+ 19 - 11
src/Hotline.Application/Orders/IOrderApplication.cs

@@ -17,6 +17,7 @@ using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Requests;
 using MediatR;
+using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Entities;
@@ -74,6 +75,13 @@ namespace Hotline.Application.Orders
         /// <returns></returns>
         Task OrderVisitWeb(OrderVisitWebDto dto, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 发送回访短信
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task VisitPushSMSAsync(VisitSmsInDto dto, CancellationToken cancellationToken);
+
         /// <summary>
         /// 回访来源统计
         /// </summary>
@@ -218,20 +226,20 @@ namespace Hotline.Application.Orders
         /// <returns></returns>
         ISugarQueryable<OrderBiCentreDataListVo> CentreDataList(ReportPagedRequest dto);
 
-		/// <summary>
-		/// 热点受理类型统计
+        /// <summary>
+        /// 热点受理类型统计
         /// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		Task<(List<SystemDicData> acceptTypes, object items)> HotspotAndAcceptTypeStatistics(HotspotAndAcceptTypeStatisticsReq dto);
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<(List<SystemDicData> acceptTypes, object items)> HotspotAndAcceptTypeStatistics(HotspotAndAcceptTypeStatisticsReq dto);
 
 
-		/// <summary>
-		/// 热点受理类型统计--导出
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		Task<DataTable> HotspotAndAcceptTypeStatisticsExport(HotspotAndAcceptTypeStatisticsReq dto);
+        /// <summary>
+        /// 热点受理类型统计--导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        Task<DataTable> HotspotAndAcceptTypeStatisticsExport(HotspotAndAcceptTypeStatisticsReq dto);
 
         /// <summary>
         /// 热点受理类型明细

+ 45 - 1
src/Hotline.Application/Orders/OrderApplication.cs

@@ -54,12 +54,15 @@ using XF.Domain.Entities;
 using Hotline.Settings.TimeLimitDomain;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.SeedData;
+using Hotline.Share.Enums.Push;
+using Hotline.Push.Notifies;
 
 namespace Hotline.Application.Orders;
 
 public class OrderApplication : IOrderApplication, IScopeDependency
 {
 
+    private readonly IMediator _mediator;
     private readonly IOrderDomainService _orderDomainService;
     private readonly IWorkflowDomainService _workflowDomainService;
     private readonly IOrderRepository _orderRepository;
@@ -114,7 +117,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<OrderPublish> orderPublishRepository,
         IRepository<OrderScreen> orderScreenRepository,
         IRepository<OrderSendBackAudit> orderSendBackAuditRepository,
-        ICalcExpireTime expireTime)
+        ICalcExpireTime expireTime,
+        IMediator mediator)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -142,6 +146,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _orderPublishRepository = orderPublishRepository;
         _orderSendBackAuditRepository = orderSendBackAuditRepository;
         _expireTime = expireTime;
+        _mediator = mediator;
     }
 
     /// <summary>
@@ -634,6 +639,45 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
     }
 
+
+    /// <summary>
+    /// 发送回访短信
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    /// <exception cref="NotImplementedException"></exception>
+    public async Task VisitPushSMSAsync(VisitSmsInDto dto, CancellationToken cancellationToken)
+    {
+        var orderVisitList = await _orderVisitRepository.Queryable()
+            .Includes(d => d.Order)
+            .Where(d => dto.Ids.Contains(d.Id) && d.VisitState == EVisitState.WaitForVisit)
+            .Select(d => new { d.Id, d.Order.SourceChannelCode,  d.Order.Contact, d.Order.Password, d.No, d.OrderId, d.Order.Title, d.Order.FromName })
+            .ToListAsync(cancellationToken);
+
+        foreach (var item in orderVisitList)
+        {
+            var code = "1013";
+            if (item.SourceChannelCode == "ZGSSP") code = "1012";
+            var messageDto = new Share.Dtos.Push.MessageDto
+            {
+                PushBusiness = EPushBusiness.VisitSms,
+                ExternalId = item.Id,
+                OrderId = item.OrderId,
+                PushPlatform = EPushPlatform.Sms,
+                Remark = item.Title,
+                Name = item.FromName,
+                TemplateCode = code,
+                Params = new List<string>() { item.No, item.Password },
+                TelNumber = item.Contact,
+            };
+            await _mediator.Publish(new PushMessageNotify(messageDto), cancellationToken);
+            await _orderVisitRepository.Updateable()
+                .Where(m => m.Id == item.Id)
+                .SetColumns(m => m.VisitState == EVisitState.SMSVisiting)
+                .ExecuteCommandAsync();
+        }
+    }
+
     public ISugarQueryable<Order> QueryOrders(QueryOrderDto dto)
     {
         var isCenter = _sessionContext.OrgIsCenter;

+ 6 - 0
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -242,6 +242,12 @@ namespace Hotline.Share.Dtos.Order
         public string UserId { get; set; }
     }
 
+    public class VisitSmsInDto
+    { 
+        [Required]
+        public List<string> Ids { get; set; }
+    }
+
     public record VisitDto
     {
         /// <summary>

+ 6 - 0
src/Hotline.Share/Enums/Push/EPushBusiness.cs

@@ -54,4 +54,10 @@ public enum EPushBusiness
     /// </summary>
     [Description("批量短信")]
     BatchSms = 7,
+
+    /// <summary>
+    /// 回访短信
+    /// </summary>
+    [Description("回访短信")]
+    VisitSms = 8,
 }