Procházet zdrojové kódy

新增网格员回复

qinchaoyue před 4 měsíci
rodič
revize
1bb2c968ec
23 změnil soubory, kde provedl 694 přidání a 130 odebrání
  1. 34 1
      src/Hotline.Api/Controllers/Snapshot/SnapshotController.cs
  2. 1 0
      src/Hotline.Api/Hotline.Api.csproj
  3. 3 0
      src/Hotline.Api/StartupExtensions.cs
  4. 21 4
      src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs
  5. 85 40
      src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs
  6. 3 1
      src/Hotline.Application.Tests/Mock/OrderServiceMock.cs
  7. 0 1
      src/Hotline.Application.Tests/Startup.cs
  8. 13 0
      src/Hotline.Application/Mappers/SnapshotMapperConfigs.cs
  9. 4 2
      src/Hotline.Application/Snapshot/DefaultSnapshotApplication.cs
  10. 30 0
      src/Hotline.Application/Snapshot/ISnapshotApplication.cs
  11. 75 0
      src/Hotline.Application/Snapshot/Notifications/SnapshotHandler.cs
  12. 146 16
      src/Hotline.Application/Snapshot/SnapshotApplicationBase.cs
  13. 4 2
      src/Hotline.Application/Snapshot/ZiGongSnapshotApplication.cs
  14. 5 0
      src/Hotline.Repository.SqlSugar/Snapshot/OrderSnapshotRepository.cs
  15. 5 0
      src/Hotline.Repository.SqlSugar/Snapshot/ThirdAccountRepository.cs
  16. 117 0
      src/Hotline.Share/Dtos/Snapshot/GuiderSystemDto.cs
  17. 52 0
      src/Hotline.Share/Enums/Snapshot/EGuiderSystemReplyType.cs
  18. 14 0
      src/Hotline.Share/Mq/EventNames.Snapshot.cs
  19. 6 0
      src/Hotline/Snapshot/Interfaces/IOrderSnapshotRepository.cs
  20. 1 0
      src/Hotline/Snapshot/Interfaces/IThirdAccountRepository.cs
  21. 15 54
      src/Hotline/Snapshot/Notifications/PostGuiderSystemNotification.cs
  22. 38 7
      src/Hotline/Snapshot/OrderSnapshot.cs
  23. 22 2
      src/TianQue.Sdk/TiqnQueService.cs

+ 34 - 1
src/Hotline.Api/Controllers/Snapshot/SnapshotController.cs

@@ -62,7 +62,6 @@ public class SnapshotController : BaseController
     public async Task<HomePageOutDto> GetHomePageAsync()
         => await _snapshotApplication.GetHomePageAsync();
 
-
     /// <summary>
     /// 行业界面基础信息
     /// </summary>
@@ -93,7 +92,10 @@ public class SnapshotController : BaseController
         order.InitId();
         await _orderDomainService.AddAsync(order);
         if (dto.Files.NotNullOrEmpty())
+        {
             order.FileJson = await _fileRepository.AddFileAsync(dto.Files, order.Id, HttpContext.RequestAborted);
+            await _orderRepository.UpdateAsync(order);
+        }
         var orderSnapshot = dto.Adapt<OrderSnapshot>();
         orderSnapshot.Id = order.Id;
         orderSnapshot.IndustryId = dto.IndustryId;
@@ -261,6 +263,7 @@ public class SnapshotController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpPost("report")]
+    [LogFilter("志愿者上报")]
     public async Task<AddVolunteerReportOutDto> AddVolunteerReportAsync([FromBody] AddVolunteerReportInDto dto)
         => await _snapshotApplication.AddVolunteerReportAsync(dto, HttpContext.RequestAborted);
 
@@ -285,4 +288,34 @@ public class SnapshotController : BaseController
     [HttpPut("third/invitationcode")]
     public async Task SaveInvitationCodeAsync([FromBody] SaveInvitationCodeInDto dto)
         => await _snapshotApplication.SaveInvitationCodeAsync(dto);
+
+    /// <summary>
+    /// 网格员系统补推接口;
+    /// 根据工单No推送到网格员系统, 多个使用,号分隔
+    /// </summary>
+    /// <param name="nos">工单No集合,多个使用 ',' 号分隔</param>
+    /// <returns></returns>
+    [HttpPut("guider/nos")]
+    public async Task PostOrderGuiderSystemAsync([FromBody] string nos)
+    {
+        foreach (var no in nos.Split(','))
+        {
+            var orderId = await _orderRepository.Queryable()
+                .Where(m => m.No == no)
+                .Select(m => m.Id)
+                .FirstAsync() ?? throw UserFriendlyException.SameMessage("工单不存在");
+            await _snapshotApplication.PostOrderGuiderSystemAsync(orderId, HttpContext.RequestAborted);
+        }
+    }
+
+    /// <summary>
+    /// 接收网格员系统回调
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("guider/reply")]
+    [LogFilter("网格员系统回调")]
+    public async Task SaveGuiderSystemReplayAsync([FromBody] GuiderSystemInDto dto)
+    {
+        await _snapshotApplication.SaveGuiderSystemReplyAsync(dto, HttpContext.RequestAborted);
+    }
 }

+ 1 - 0
src/Hotline.Api/Hotline.Api.csproj

@@ -28,6 +28,7 @@
     <ProjectReference Include="..\Hotline.Application\Hotline.Application.csproj" />
     <ProjectReference Include="..\Hotline.Logger\Hotline.Logger.csproj" />
     <ProjectReference Include="..\Hotline.WeChat\Hotline.WeChat.csproj" />
+    <ProjectReference Include="..\TianQue.Sdk\TianQue.Sdk.csproj" />
   </ItemGroup>
 
   <ItemGroup>

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

@@ -40,6 +40,8 @@ using Microsoft.AspNetCore.ResponseCompression;
 using Senparc.Weixin.RegisterServices;
 using Senparc.Weixin.AspNet;
 using Hotline.WeChat;
+using Hotline.Snapshot.Interfaces;
+using TianQue.Sdk;
 
 
 namespace Hotline.Api;
@@ -214,6 +216,7 @@ internal static class StartupExtensions
         services.AddScoped<Users.IThirdIdentiyService, WeChatService>();
 
         services.AddSenparcWeixin(configuration);
+        services.AddScoped<IGuiderSystemService, TiqnQueService>();
 
         //services.AddScoped<LogFilterAttribute>();
         //ServiceLocator.Instance = services.BuildServiceProvider();

+ 21 - 4
src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs

@@ -1,7 +1,9 @@
 using System.Threading;
 using System.Threading.Tasks;
+using DotNetCore.CAP;
 using Hotline.Application.Snapshot;
 using Hotline.Caching.Interfaces;
+using Hotline.EventBus;
 using Hotline.File;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
@@ -30,7 +32,7 @@ namespace Hotline.Application.Tests.Snapshot
         private readonly Mock<IRepository<RedPackRecord>> _redPackRecordRepositoryMock;
         private readonly Mock<IRepository<Order>> _orderRepositoryMock;
         private readonly Mock<IThirdAccountRepository> _thirdAccountRepositoryMock;
-        private readonly Mock<IRepository<OrderSnapshot>> _orderSnapshotRepositoryMock;
+        private readonly Mock<IOrderSnapshotRepository> _orderSnapshotRepositoryMock;
         private readonly Mock<ISystemSettingCacheManager> _systemSettingCacheManagerMock;
         private readonly Mock<ISystemAreaDomainService> _systemAreaDomainServiceMock;
         private readonly Mock<IFileRepository> _fileRepositoryMock;
@@ -41,6 +43,11 @@ namespace Hotline.Application.Tests.Snapshot
         private readonly Mock<IRepository<SystemArea>> _systemAreaRepositoryMock;
         private readonly Mock<IVolunteerRepository> _volunteerRepositoryMock;
         private readonly Mock<IVolunteerReportRepository> _volunteerReportRepositoryMock;
+        private readonly Mock<ISystemLogRepository> _systemLogMock;
+        private readonly Mock<IGuiderSystemService> _guiderSystemServiceMock;
+        private readonly Mock<ICapPublisher> _capPublisherMock;
+        private readonly Mock<Publisher> _publisherMock;
+        private readonly Mock<IGuiderInfoRepository> _guiderInfoRepositoryMock;
 
         private readonly DefaultSnapshotApplication _snapshotApplication;
 
@@ -53,7 +60,7 @@ namespace Hotline.Application.Tests.Snapshot
             _redPackRecordRepositoryMock = new Mock<IRepository<RedPackRecord>>();
             _orderRepositoryMock = new Mock<IRepository<Order>>();
             _thirdAccountRepositoryMock = new Mock<IThirdAccountRepository>();
-            _orderSnapshotRepositoryMock = new Mock<IRepository<OrderSnapshot>>();
+            _orderSnapshotRepositoryMock = new Mock<IOrderSnapshotRepository>();
             _systemSettingCacheManagerMock = new Mock<ISystemSettingCacheManager>();
             _systemAreaDomainServiceMock = new Mock<ISystemAreaDomainService>();
             _fileRepositoryMock = new Mock<IFileRepository>();
@@ -64,8 +71,13 @@ namespace Hotline.Application.Tests.Snapshot
             _systemAreaRepositoryMock = new Mock<IRepository<SystemArea>>();
             _volunteerRepositoryMock = new Mock<IVolunteerRepository>();
             _volunteerReportRepositoryMock = new Mock<IVolunteerReportRepository>();
+            _systemLogMock = new Mock<ISystemLogRepository>();
+            _guiderSystemServiceMock = new Mock<IGuiderSystemService>();
+            _capPublisherMock = new Mock<ICapPublisher>();
+            _publisherMock = new Mock<Publisher>();
+            _guiderInfoRepositoryMock = new Mock<IGuiderInfoRepository>();
 
-            _snapshotApplication = new DefaultSnapshotApplication(
+        _snapshotApplication = new DefaultSnapshotApplication(
                 _thirdLoginServiceMock.Object,
                 _industryRepositoryMock.Object,
                 _bulletinRepositoryMock.Object,
@@ -83,7 +95,12 @@ namespace Hotline.Application.Tests.Snapshot
                 _practitionerRepositoryMock.Object,
                 _systemAreaRepositoryMock.Object,
                 _volunteerRepositoryMock.Object,
-                _volunteerReportRepositoryMock.Object
+                _volunteerReportRepositoryMock.Object,
+                _systemLogMock.Object,
+                _guiderSystemServiceMock.Object,
+                _capPublisherMock.Object,
+                _publisherMock.Object,
+                _guiderInfoRepositoryMock.Object
             );
         }
 

+ 85 - 40
src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs

@@ -42,9 +42,8 @@ public class SnapshotApplicationTest : TestBase
     private readonly ISessionContext _sessionContext;
     private readonly IGuiderSystemService _guiderSystemService;
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
-    private readonly SnapshotPushNotificationHandler _snapshotPushNotificationHandler;
 
-    public SnapshotApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, ISnapshotApplication snapshotApplication, IIdentityAppService identityAppService, IRepository<RedPackRecord> redPackRecordRepository, IIndustryApplication industryApplication, IIndustryRepository industryRepository, IFileRepository fileRepository, OrderServiceMock orderServiceMock, IOrderRepository orderRepository, IOrderSnapshotRepository orderSnapshotRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, ISessionContext sessionContext, IGuiderSystemService guiderSystemService, ISystemSettingCacheManager systemSettingCacheManager, SnapshotPushNotificationHandler snapshotPushNotificationHandler) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
+    public SnapshotApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, ISnapshotApplication snapshotApplication, IIdentityAppService identityAppService, IRepository<RedPackRecord> redPackRecordRepository, IIndustryApplication industryApplication, IIndustryRepository industryRepository, IFileRepository fileRepository, OrderServiceMock orderServiceMock, IOrderRepository orderRepository, IOrderSnapshotRepository orderSnapshotRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, ISessionContext sessionContext, IGuiderSystemService guiderSystemService, ISystemSettingCacheManager systemSettingCacheManager) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _snapshotApplication = snapshotApplication;
         _identityAppService = identityAppService;
@@ -59,7 +58,6 @@ public class SnapshotApplicationTest : TestBase
         _sessionContext = sessionContext;
         _guiderSystemService = guiderSystemService;
         _systemSettingCacheManager = systemSettingCacheManager;
-        _snapshotPushNotificationHandler = snapshotPushNotificationHandler;
     }
 
     [Fact]
@@ -150,11 +148,6 @@ public class SnapshotApplicationTest : TestBase
         orderPublishDetail.Workflow.Any(m => m.Name.IsNullOrEmpty()).ShouldBeFalse();
     }
 
-    [Fact]
-    public async Task GetOrderPublishDetail_Test()
-    { 
-    }
-
     [Fact]
     public async Task GetBulletins_Test()
     {
@@ -171,6 +164,10 @@ public class SnapshotApplicationTest : TestBase
         items.Any(m => m.Id.IsNullOrEmpty()).ShouldBe(false, "Id错误");
     }
 
+    /// <summary>
+    /// 获取小程序个人中心
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetSnapshotUserInfo_Test()
     {
@@ -181,6 +178,10 @@ public class SnapshotApplicationTest : TestBase
         result.PhoneNumberMask.Contains("***").ShouldBeTrue();
     }
 
+    /// <summary>
+    /// 根据OpenId刷新token
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task RefreshTokenAsync()
     { 
@@ -191,6 +192,10 @@ public class SnapshotApplicationTest : TestBase
         newToken.PhoneNumber.ShouldNotBeNullOrEmpty();
     }
 
+    /// <summary>
+    /// 请求第三方token
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetThirdToken_Test()
     {
@@ -225,6 +230,10 @@ public class SnapshotApplicationTest : TestBase
         page.Count.ShouldNotBe(0, $"状态:{status.GetDescription()} 数据为空");
     }
 
+    /// <summary>
+    /// 获取工单详情
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetSnapshotOrderDetail_Test()
     {
@@ -237,6 +246,12 @@ public class SnapshotApplicationTest : TestBase
         detail.Content.ShouldNotBeNullOrEmpty();
     }
 
+    /// <summary>
+    /// 红包列表记录
+    /// </summary>
+    /// <param name="count"></param>
+    /// <param name="exp"></param>
+    /// <returns></returns>
     [Theory]
     [InlineData(2, 2)]
     [InlineData(12, 12)]
@@ -247,6 +262,10 @@ public class SnapshotApplicationTest : TestBase
         items.Count.ShouldBe(exp, $"应该:{exp}, 实际 {items.Count}");
     }
 
+    /// <summary>
+    /// 红包总额
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetRedPackReceivedTotal_Test()
     {
@@ -255,6 +274,10 @@ public class SnapshotApplicationTest : TestBase
         amount.ShouldNotBe("0");
     }
 
+    /// <summary>
+    /// 添加志愿者上报信息
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task AddVolunteerReport_Test()
     {
@@ -269,18 +292,10 @@ public class SnapshotApplicationTest : TestBase
         result.Id.ShouldNotBeNull();
     }
 
-    [Fact]
-    public async Task SnapshotPushNotificationHandler_Test()
-    {
-        var order = _orderServiceMock.CreateSnapshotOrder().GetCreateResult();
-        await _snapshotPushNotificationHandler.Handle(new PostGuiderSystemNotification { OrderId = order.Id}, CancellationToken.None);
-        var orderSnapshot = await _orderSnapshotRepository.GetAsync(order.Id);
-        orderSnapshot.ShouldNotBeNull();
-        orderSnapshot.GuiderAccLog.ShouldNotBeNullOrEmpty();
-        orderSnapshot.GuiderSystemId.ShouldNotBeNullOrEmpty();
-        orderSnapshot.DeadLine.ShouldNotBeNull();
-    }
-
+    /// <summary>
+    /// 保存用户邀请码
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task SaveInvitationCode_Test()
     {
@@ -290,20 +305,11 @@ public class SnapshotApplicationTest : TestBase
         third.InvitationCode.ShouldBe(code);
     }
 
-    [Fact]
-    public async Task PostOrder_Test()
-    {
-        var orderSnapshot = await _orderSnapshotRepository.Queryable()
-            .OrderByDescending(m => m.CreationTime).FirstAsync();
-        var order = await _orderRepository.GetAsync("08dd18e0-1c9e-4aa5-8dc6-f639e8d1b3ea");
-        var tiqnque = _systemSettingCacheManager.TianQueAppKeySecret.Split('|');
-        var token = new ThirdTokenDto { AppId = tiqnque[0], Secret = tiqnque[1] };
-        order.No = DateTime.Now.ToString("yyyyMMddhhmmssyyyy");
-        orderSnapshot.DeadLine = DateTime.Now.AddDays(_systemSettingCacheManager.OvertimeBack);
-        var result = await _guiderSystemService.PostOrder(order, orderSnapshot, token);
-        result.ShouldNotBeNull();
-    }
-
+    /// <summary>
+    /// 获取志愿者集合
+    /// 获取志愿者详情
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetPractitionerItems_Test()
     {
@@ -320,6 +326,11 @@ public class SnapshotApplicationTest : TestBase
         item.PhoneNumber.ShouldNotBeNullOrEmpty();
     }
 
+    /// <summary>
+    /// 统计红包数据
+    /// </summary>
+    /// <param name="status"></param>
+    /// <returns></returns>
     [Theory]
     [InlineData(ERedPackPickupStatus.Unreceived)]
     [InlineData(ERedPackPickupStatus.Received)]
@@ -351,12 +362,6 @@ public class SnapshotApplicationTest : TestBase
         detail.Content.ShouldNotBeNullOrEmpty();
     }
 
-    [Fact]
-    public async Task InitRedPackDataAsync()
-    {
-       
-    }
-
     /// <summary>
     /// 测试行业
     /// 测试添加数据是否和获取的数据一致
@@ -415,4 +420,44 @@ public class SnapshotApplicationTest : TestBase
             await _fileRepository.Removeable().Where(m => m.Id == pageDto.Files.First().Id).ExecuteCommandAsync();
         }
     }
+
+    /// <summary>
+    /// 上报线索
+    /// 推送网格员
+    /// 网格员回复
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task Snapshot_Test()
+    { 
+        var order = _orderServiceMock.CreateSnapshotOrder()
+            .GetCreateResult();
+        await _snapshotApplication.PostOrderGuiderSystemAsync(order.Id, CancellationToken.None);
+        var orderSnapshot = await _orderSnapshotRepository.GetAsync(order.Id);
+        var replyDto = new GuiderSystemInDto 
+        {
+            ReplyCode = order.No,
+            AppealNumber = orderSnapshot.NetworkENumber,
+            ReplyDate = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"),
+            ReplyUserName = "18181552753@zgsg",
+            ReplyBMName = "瓦市村村民委员会",
+            ReplyResultType = "2",
+            ReplyISTrue = "1",
+            IsRepeat = "0",
+            IsHiddenDanger = "1",
+            MemberName = "许利洪",
+            MemberMobile = "18181552753",
+            ReplyContent = "到现场查实,存在安全隐患",
+            OrgId = "4828",
+            OrgName = "瓦市村村民委员会",
+            OrgFullName = "四川省/自贡市/沿滩区/永安镇/瓦市村村民委员会",
+            DepartmentNo = "510311106206",
+            ParentOrgId = "4821",
+            ReplyFileList = new List<string>
+            {
+                "http://10.0.188.11:1234/tqOssManager/getObjectByUri/sichuan/scgrid/jpg/2024/12/5/095020318625.jpg"
+            }
+        };
+        await _snapshotApplication.SaveGuiderSystemReplyAsync(replyDto, CancellationToken.None);
+    }
 }

+ 3 - 1
src/Hotline.Application.Tests/Mock/OrderServiceMock.cs

@@ -54,7 +54,9 @@ public class OrderServiceMock
     public OrderServiceMock CreateSnapshotOrder()
     {
         var homePage = _snapshotController.GetHomePageAsync().GetAwaiter().GetResult();
-        var industry = homePage.Industrys.Where(m => m.IndustryType == EIndustryType.Clue).FirstOrDefault();
+        var industry = homePage.Industrys
+            .Where(m => m.IndustryType == EIndustryType.Clue)
+            .FirstOrDefault();
         var pageBase = _snapshotController.GetIndustryBaseAsync(industry.Id).GetAwaiter().GetResult();
 
         var inDto = _fixture.Create<AddSnapshotOrderInDto>();

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

@@ -185,7 +185,6 @@ public class Startup
             services.AddScoped<XingTangCallsSyncJob>();
             services.AddXingTangDb(callCenterConfiguration.XingTang);
             services.AddScoped<IGuiderSystemService, TiqnQueService>();
-            services.AddScoped<SnapshotPushNotificationHandler>();
             //ServiceLocator.Instance = services.BuildServiceProvider();
         }
 

+ 13 - 0
src/Hotline.Application/Mappers/SnapshotMapperConfigs.cs

@@ -38,5 +38,18 @@ public class SnapshotMapperConfigs : IRegister
 
         config.ForType<AddPractitionerInDto, Practitioner>()
             .Ignore(m => m.Gender);
+
+        config.ForType<GuiderSystemInDto, OrderSnapshot>()
+            .Map(m => m.ReplyDate, n => n.ReplyDate)
+            .Map(m => m.ReplyUserName, n => n.ReplyUserName)
+            .Map(m => m.ReplyBMName, n => n.ReplyBMName)
+            .Map(m => m.ReplyResultType, n => n.ReplyResultType)
+            .Map(m => m.IsTruth, n => n.ReplyISTrue)
+            .Map(m => m.IsDeal, n => n.ReplyResultType == "2")
+            .Map(m => m.IsRepetition, n => n.IsRepeat == "1")
+            .Map(m => m.IsDanger, n => n.IsHiddenDanger == "1")
+            .Map(m => m.MemberName, n => n.MemberName)
+            .Map(m => m.MemberMobile, n => n.MemberMobile)
+            .Map(m => m.NetworkRemark, n => n.ReplyContent);
     }
 }

+ 4 - 2
src/Hotline.Application/Snapshot/DefaultSnapshotApplication.cs

@@ -1,5 +1,7 @@
-using Hotline.Caching.Interfaces;
+using DotNetCore.CAP;
+using Hotline.Caching.Interfaces;
 using Hotline.DI;
+using Hotline.EventBus;
 using Hotline.File;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
@@ -23,7 +25,7 @@ namespace Hotline.Application.Snapshot;
 public class DefaultSnapshotApplication : SnapshotApplicationBase
     , ISnapshotApplication, IScopeDependency
 {
-    public DefaultSnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository,  IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager, snapshotOrderPublishRepository, workflowTraceRepository, practitionerRepository,  systemAreaRepository, volunteerRepository, volunteerReportRepository)
+    public DefaultSnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IOrderSnapshotRepository orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository,  IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository, ISystemLogRepository systemLog, IGuiderSystemService guiderSystemService, ICapPublisher capPublisher, Publisher publisher, IGuiderInfoRepository guiderInfoRepository) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager, snapshotOrderPublishRepository, workflowTraceRepository, practitionerRepository,  systemAreaRepository, volunteerRepository, volunteerReportRepository, systemLog, guiderSystemService, capPublisher, publisher, guiderInfoRepository)
     {
     }
 }

+ 30 - 0
src/Hotline.Application/Snapshot/ISnapshotApplication.cs

@@ -166,4 +166,34 @@ public interface ISnapshotApplication
     /// 获取小程序首页弹窗
     /// </summary>
     Task<BulletinOutDto> GetBulletionPopupAsync(CancellationToken requestAborted);
+
+    /// <summary>
+    /// 推送工单到网格员系统
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    Task PostOrderGuiderSystemAsync(string orderId, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 延迟检查网格员是否回复工单
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task GuiderSystemReplyDelayAsync(string orderId, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 网格员系统回复
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task SaveGuiderSystemReplyAsync(GuiderSystemInDto dto, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 根据网格员系统回复的内容同步网格员信息
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task SyncGuiderInfoAsync(string orderId, CancellationToken cancellationToken);
 }

+ 75 - 0
src/Hotline.Application/Snapshot/Notifications/SnapshotHandler.cs

@@ -0,0 +1,75 @@
+using DotNetCore.CAP;
+using Hotline.Caching.Interfaces;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Mq;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Snapshot.Notifications;
+using MediatR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Snapshot.Notifications;
+/// <summary>
+/// 推送网格员系统
+/// </summary>
+public class SnapshotPushNotificationHandler : INotificationHandler<PostGuiderSystemNotification>
+{
+    private readonly ISnapshotApplication _snapshotApplication;
+
+    public SnapshotPushNotificationHandler(ISnapshotApplication snapshotApplication)
+    {
+        _snapshotApplication = snapshotApplication;
+    }
+
+    public async Task Handle(PostGuiderSystemNotification notification, CancellationToken cancellationToken)
+    {
+        await _snapshotApplication.PostOrderGuiderSystemAsync(notification.OrderId, cancellationToken);
+    }
+}
+
+/// <summary>
+/// 推送成功后发送的延迟消息;
+/// 延迟检查网格员是否回复了工单;
+/// </summary>
+/// <param name="orderId"></param>
+public class PostGuiderSystemDelayedNotificationHandler : ICapSubscribe, IScopeDependency
+{
+    private readonly ISnapshotApplication _snapshotApplication;
+
+    public PostGuiderSystemDelayedNotificationHandler(ISnapshotApplication snapshotApplication)
+    {
+        _snapshotApplication = snapshotApplication;
+    }
+
+    [CapSubscribe(EventNames.GuiderSystemReplyDelay)]
+    public async Task Handle(PostGuiderSystemDelayed message, CancellationToken cancellationToken)
+    {
+        await _snapshotApplication.GuiderSystemReplyDelayAsync(message.OrderId, cancellationToken);
+    }
+}
+
+/// <summary>
+/// 网格员办结事件处理
+/// 把网格员信息回填到系统中, 和小程序账号绑定
+/// </summary>
+public class GuiderSystemFieldNotificationHandler : INotificationHandler<GuiderSystemFieldNotification>
+{
+    private readonly ISnapshotApplication _snapshotApplication1;
+
+    public GuiderSystemFieldNotificationHandler(ISnapshotApplication snapshotApplication1)
+    {
+        _snapshotApplication1 = snapshotApplication1;
+    }
+
+    public async Task Handle(GuiderSystemFieldNotification notification, CancellationToken cancellationToken)
+    {
+        await _snapshotApplication1.SyncGuiderInfoAsync(notification.OrderId, cancellationToken);
+    }
+}

+ 146 - 16
src/Hotline.Application/Snapshot/SnapshotApplicationBase.cs

@@ -29,6 +29,11 @@ using Microsoft.AspNetCore.Http;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Mq;
+using Hotline.Snapshot.Notifications;
+using Hotline.EventBus;
+using Hotline.Quality.Notifications;
 
 namespace Hotline.Application.Snapshot;
 
@@ -48,8 +53,8 @@ public abstract class SnapshotApplicationBase
     private readonly IThirdIdentiyService _thirdLoginService;
     private readonly ISessionContext _sessionContext;
     private readonly IRepository<RedPackRecord> _redPackRecordRepository;
-    private readonly IRepository<OrderSnapshot> _orderSnapshotRepository;
-    private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly ISystemSettingCacheManager _sysSetting;
     private readonly ISystemAreaDomainService _systemAreaDomainService;
     private readonly IRepository<SystemArea> _systemAreaRepository;
     private readonly IFileRepository _fileRepository;
@@ -59,8 +64,13 @@ public abstract class SnapshotApplicationBase
     private readonly IPractitionerRepository _practitionerRepository;
     private readonly IVolunteerRepository _volunteerRepository;
     private readonly IVolunteerReportRepository _volunteerReportRepository;
+    private readonly ISystemLogRepository _systemLog;
+    private readonly IGuiderSystemService _guiderSystemService;
+    private readonly ICapPublisher _capPublisher;
+    private readonly Publisher _publisher;
+    private readonly IGuiderInfoRepository _guiderInfoRepository;
 
-    public SnapshotApplicationBase(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository, IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository)
+    public SnapshotApplicationBase(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IOrderSnapshotRepository orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository, IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository, ISystemLogRepository systemLog, IGuiderSystemService guiderSystemService, ICapPublisher capPublisher, Publisher publisher, IGuiderInfoRepository guiderInfoRepository)
     {
         _thirdLoginService = thirdLoginService;
         _industryRepository = industryRepository;
@@ -70,7 +80,7 @@ public abstract class SnapshotApplicationBase
         _orderRepository = orderRepository;
         _thirdAccountRepository = thirdAccountRepository;
         _orderSnapshotRepository = orderSnapshotRepository;
-        _systemSettingCacheManager = systemSettingCacheManager;
+        _sysSetting = systemSettingCacheManager;
         _systemAreaDomainService = systemAreaDomainService;
         _fileRepository = fileRepository;
         _systemDicDataCacheManager = systemDicDataCacheManager;
@@ -80,6 +90,11 @@ public abstract class SnapshotApplicationBase
         _systemAreaRepository = systemAreaRepository;
         _volunteerRepository = volunteerRepository;
         _volunteerReportRepository = volunteerReportRepository;
+        _systemLog = systemLog;
+        _guiderSystemService = guiderSystemService;
+        _capPublisher = capPublisher;
+        _publisher = publisher;
+        _guiderInfoRepository = guiderInfoRepository;
     }
 
     /// <summary>
@@ -88,8 +103,8 @@ public abstract class SnapshotApplicationBase
     /// <returns></returns>
     public async Task<HomePageOutDto> GetHomePageAsync()
     {
-        var fileServiceUrl = _systemSettingCacheManager.FileServerUrl;
-        var fileDownloadApi = fileServiceUrl + _systemSettingCacheManager.FileDownloadApi;
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
         var items = await _industryRepository.Queryable()
             .Where(m => m.IsEnable)
             .OrderBy(m => m.DisplayOrder)
@@ -108,7 +123,7 @@ public abstract class SnapshotApplicationBase
 
         return new HomePageOutDto
         {
-            Banners = _systemSettingCacheManager.AppBanner.Split('|').Select(m => fileDownloadApi + m).ToList(),
+            Banners = _sysSetting.AppBanner.Split('|').Select(m => fileDownloadApi + m).ToList(),
             Industrys = items
         };
     }
@@ -117,7 +132,7 @@ public abstract class SnapshotApplicationBase
     /// 获取小程序首页弹窗
     /// </summary>
     public async Task<BulletinOutDto> GetBulletionPopupAsync(CancellationToken requestAborted)
-    { 
+    {
         var item = await _bulletinRepository.Queryable()
             .Where(m => m.BulletinState == EBulletinState.ReviewPass)
             .Where(m => m.BulletinTime >= DateTime.Now && m.IsPopup == true)
@@ -133,8 +148,8 @@ public abstract class SnapshotApplicationBase
     /// <returns></returns>
     public async Task<IList<IndustryOutDto>> GetIndustresAsync()
     {
-        var fileServiceUrl = _systemSettingCacheManager.FileServerUrl;
-        var fileDownloadApi = fileServiceUrl + _systemSettingCacheManager.FileDownloadApi;
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
         var items = await _industryRepository.Queryable()
             .Where(m => m.IsEnable)
             .OrderBy(m => m.DisplayOrder)
@@ -150,8 +165,8 @@ public abstract class SnapshotApplicationBase
     /// <returns></returns>
     public async Task<IndustryBaseOutDto> GetIndustryBaseAsync(string id, CancellationToken requestAborted)
     {
-        var fileServiceUrl = _systemSettingCacheManager.FileServerUrl;
-        var fileDownloadApi = fileServiceUrl + _systemSettingCacheManager.FileDownloadApi;
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
         var indurstry = await _industryRepository.GetAsync(id, requestAborted)
             ?? throw UserFriendlyException.SameMessage("行业不存在:" + id);
 
@@ -226,7 +241,7 @@ public abstract class SnapshotApplicationBase
             throw UserFriendlyException.SameMessage("工单不存在");
 
         var outDto = publish.Adapt<OrderPublishDetailOutDto>();
-        var fileServiceUrl = _systemSettingCacheManager.FileServerUrl;
+        var fileServiceUrl = _sysSetting.FileServerUrl;
         if (outDto.FileJson != null)
         {
             foreach (var item in outDto.FileJson)
@@ -332,7 +347,7 @@ public abstract class SnapshotApplicationBase
             throw UserFriendlyException.SameMessage("工单不存在");
 
         var outDto = order.Adapt<OrderPublishDetailOutDto>();
-        var fileServiceUrl = _systemSettingCacheManager.FileServerUrl;
+        var fileServiceUrl = _sysSetting.FileServerUrl;
         if (outDto.FileJson != null)
         {
             foreach (var item in outDto.FileJson)
@@ -487,6 +502,119 @@ public abstract class SnapshotApplicationBase
         await _thirdAccountRepository.UpdateAsync(third);
     }
 
+    /// <summary>
+    /// 推送工单到网格员系统
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    public async Task PostOrderGuiderSystemAsync(string orderId, CancellationToken cancellationToken)
+    {
+        string LogName = "推送网格员系统";
+        var order = await _orderRepository.GetAsync(orderId, cancellationToken);
+        var orderSnapshot = await _orderSnapshotRepository.GetAsync(orderId, cancellationToken);
+        if (order is null || orderSnapshot is null)
+        {
+            var msg = order is null ? "order" : "orderSnapshot";
+            _systemLog.Add(LogName, $"OrderId: {orderId}", $"根据Id查询{msg}为null");
+            return;
+        }
+        orderSnapshot.DeadLine = DateTime.Now.AddHours(_sysSetting.OvertimeBack);
+        var keySecret = _sysSetting.TianQueAppKeySecret.Split('|');
+
+        var token = new ThirdTokenDto
+        {
+            AppId = keySecret[0],
+            Secret = keySecret[1]
+        };
+        if (order.FileJson.NotNullOrEmpty())
+        {
+            var url = _sysSetting.FileServerUrl;
+            order.FileJson.ForEach(m => { m.Path = url + m.Path; });
+        }
+        var result = await _guiderSystemService.PostOrder(order, orderSnapshot, token);
+        orderSnapshot.GuiderAccLog = result.Result.ToJson();
+        if (result.Code != 0)
+        {
+            _systemLog.Add(LogName, $"OrderNo: {order.No}", $"推送失败: {result.ToJson()}");
+            orderSnapshot.GuiderAccLog = result.ToJson();
+        }
+        orderSnapshot.NetworkENumber = result.Result.GuiderSystemId;
+        await _orderSnapshotRepository.UpdateAsync(orderSnapshot, cancellationToken);
+        _systemLog.Add(LogName, $"OrderNo: {order.No}", status: 1);
+        if (result.Code == 0)
+        {
+            await _capPublisher.PublishAsync(EventNames.GuiderSystemReplyDelay, new PostGuiderSystemDelayed(order.Id));
+        }
+    }
+
+    /// <summary>
+    /// 延迟检查网格员是否回复工单
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task GuiderSystemReplyDelayAsync(string orderId, CancellationToken cancellationToken)
+    {
+        var orderSnapshot = await _orderSnapshotRepository.GetAsync(orderId)
+            ?? throw new UserFriendlyException($"orderId:{orderId} order_snapshot 不存在该数据");
+        if (orderSnapshot.IsDeal != null && orderSnapshot.IsDeal == true)
+            return;
+        // 网格员未回复, 推送事件
+        await _publisher.PublishAsync(new GuiderSystemTimeOutBackNotification(orderId), cancellationToken);
+    }
+
+    /// <summary>
+    /// 网格员系统回复
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task SaveGuiderSystemReplyAsync(GuiderSystemInDto dto, CancellationToken token)
+    {
+        var orderSnapshot = await _orderSnapshotRepository.GetByGuiderSystemIdAsync(dto.AppealNumber)
+            ?? throw UserFriendlyException.SameMessage("工单不存在");
+        dto.Adapt(orderSnapshot);
+        await _orderSnapshotRepository.UpdateAsync(orderSnapshot);
+        // 网格员办结
+        if (orderSnapshot.ReplyResultType == EGuiderSystemReplyType.Field)
+        {
+            await _publisher.PublishAsync(new GuiderSystemFieldNotification(orderSnapshot.Id), token);
+        }
+        // 网格员超时未回复退回
+        if (orderSnapshot.ReplyResultType == EGuiderSystemReplyType.Returned)
+        {
+            await _publisher.PublishAsync(new GuiderSystemTimeOutBackNotification(orderSnapshot.Id), token);
+        }
+    }
+
+    /// <summary>
+    /// 根据网格员系统回复的内容同步网格员信息
+    /// </summary>
+    /// <param name="orderId"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task SyncGuiderInfoAsync(string orderId, CancellationToken cancellationToken)
+    {
+        var guiderInfo = await _orderSnapshotRepository.Queryable()
+            .Where(m => m.Id == orderId)
+            .Select(m => new { m.MemberName, m.MemberMobile })
+            .FirstAsync(cancellationToken);
+        var guider = await _guiderInfoRepository.GetByPhoneNumberAsync(guiderInfo.MemberMobile);
+        if (guider != null) return;
+
+        var entity = new GuiderInfo
+        {
+            Name = guiderInfo.MemberName,
+            PhoneNumber = guiderInfo.MemberMobile
+        };
+        entity.Id = await _guiderInfoRepository.AddAsync(entity, cancellationToken);
+        var third = await _thirdAccountRepository.GetByPhoneNumberAsync(guiderInfo.MemberMobile);
+
+        if (third == null) return;
+        third.UserId = entity.Id;
+        third.CitizenType = EReadPackUserType.Guider;
+        await _thirdAccountRepository.UpdateAsync(third, cancellationToken);
+    }
+
     #region 从业人员
 
     /// <summary>
@@ -552,7 +680,7 @@ public abstract class SnapshotApplicationBase
     /// <param name="cancellationToken"></param>
     /// <returns></returns>
     public async Task<PractitionerDetailOutDto> GetPractitionerDetailAsync(string id, CancellationToken cancellationToken)
-    { 
+    {
         var item = await _practitionerRepository.GetAsync(id, cancellationToken)
             ?? throw UserFriendlyException.SameMessage("从业人员不存在");
         return item.Adapt<PractitionerDetailOutDto>();
@@ -584,8 +712,10 @@ public abstract class SnapshotApplicationBase
         entity.VolunteerPhone = volunteer.PhoneNumber;
 
         entity.Id = await _volunteerReportRepository.AddAsync(entity, requestAborted);
-        await _fileRepository.AddFileAsync(dto.Files, entity.Id , requestAborted);
+        await _fileRepository.AddFileAsync(dto.Files, entity.Id, requestAborted);
         return entity.Adapt<AddVolunteerReportOutDto>();
     }
     #endregion
+
+
 }

+ 4 - 2
src/Hotline.Application/Snapshot/ZiGongSnapshotApplication.cs

@@ -1,5 +1,7 @@
-using Hotline.Caching.Interfaces;
+using DotNetCore.CAP;
+using Hotline.Caching.Interfaces;
 using Hotline.DI;
+using Hotline.EventBus;
 using Hotline.File;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
@@ -22,7 +24,7 @@ namespace Hotline.Application.Snapshot;
 [Injection(AppScopes = EAppScope.ZiGong)]
 public class ZiGongSnapshotApplication : SnapshotApplicationBase, ISnapshotApplication, IScopeDependency
 {
-    public ZiGongSnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository, IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager, snapshotOrderPublishRepository, workflowTraceRepository, practitionerRepository, systemAreaRepository, volunteerRepository, volunteerReportRepository)
+    public ZiGongSnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IOrderSnapshotRepository orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, IRepository<WorkflowTrace> workflowTraceRepository, IPractitionerRepository practitionerRepository, IRepository<SystemArea> systemAreaRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository, ISystemLogRepository systemLog, IGuiderSystemService guiderSystemService, ICapPublisher capPublisher, Publisher publisher, IGuiderInfoRepository guiderInfoRepository) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager, snapshotOrderPublishRepository, workflowTraceRepository, practitionerRepository, systemAreaRepository, volunteerRepository, volunteerReportRepository, systemLog, guiderSystemService, capPublisher, publisher, guiderInfoRepository)
     {
     }
 }

+ 5 - 0
src/Hotline.Repository.SqlSugar/Snapshot/OrderSnapshotRepository.cs

@@ -15,4 +15,9 @@ public class OrderSnapshotRepository : BaseRepository<OrderSnapshot>, IOrderSnap
     public OrderSnapshotRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
     {
     }
+
+    public async Task<OrderSnapshot> GetByGuiderSystemIdAsync(string appealNumber)
+    {
+        return await Queryable().Where(m => m.NetworkENumber == appealNumber).FirstAsync();
+    }
 }

+ 5 - 0
src/Hotline.Repository.SqlSugar/Snapshot/ThirdAccountRepository.cs

@@ -16,4 +16,9 @@ public class ThirdAccountRepository : BaseRepository<ThirdAccount>,  IThirdAccou
         => await Queryable()
         .Where(p => p.OpenId == openId)
         .FirstAsync();
+
+    public async Task<ThirdAccount> GetByPhoneNumberAsync(string phoneNumber)
+        => await Queryable()
+        .Where(m => m.PhoneNumber == phoneNumber)
+        .FirstAsync();
 }

+ 117 - 0
src/Hotline.Share/Dtos/Snapshot/GuiderSystemDto.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Snapshot;
+
+/// <summary>
+/// 网格员系统回复
+/// </summary>
+public class GuiderSystemInDto
+{
+    /// <summary>
+    /// 密钥
+    /// </summary>
+    public string VailCode { get; set; } = "";
+
+    /// <summary>
+    /// 唯一标识/工单编号
+    /// </summary>
+    public string ReplyCode { get; set; } = "";
+
+    /// <summary>
+    /// 办理时间
+    /// </summary>
+    public string ReplyDate { get; set; } = "";
+
+    /// <summary>
+    /// 办理人
+    /// </summary>
+    public string ReplyUserName { get; set; } = "";
+
+    /// <summary>
+    /// 办理部门
+    /// </summary>
+    public string ReplyBMName { get; set; } = "";
+
+    /// <summary>
+    /// 办理状态 1,7:流转 2:办结 3:退回 4:网格员签收 5:消息推送 6: 超时自动退单
+    /// </summary>
+    public string ReplyResultType { get; set; } = "";
+
+    /// <summary>
+    /// 是否属实 1:是 0:否
+    /// </summary>
+    public string ReplyISTrue { get; set; } = "";
+
+    /// <summary>
+    /// 是否重复 1:是 0:否
+    /// </summary>
+    public string IsRepeat { get; set; } = "0";
+
+    /// <summary>
+    /// 重复工单号
+    /// </summary>
+    public string RepeatReplyCode { get; set; } = "";
+
+    /// <summary>
+    /// 是否隐患 1:是 0:否
+    /// </summary>
+    public string IsHiddenDanger { get; set; } = "0";
+
+    /// <summary>
+    /// 是否重大隐患 - 1 是、0否
+    /// </summary>
+    public string IsMajorHidden { get; set; } = "0";
+
+    /// <summary>
+    /// 网格员姓名
+    /// </summary>
+    public string MemberName { get; set; } = "";
+
+    /// <summary>
+    /// 网格员电话
+    /// </summary>
+    public string MemberMobile { get; set; } = "";
+
+    /// <summary>
+    /// 网格化诉求编号
+    /// </summary>
+    public string AppealNumber { get; set; } = "";
+
+    /// <summary>
+    /// 办理内容
+    /// </summary>
+    public string ReplyContent { get; set; } = "";
+
+    /// <summary>
+    /// 区域id
+    /// </summary>
+    public string OrgId { get; set; } = "";
+
+    /// <summary>
+    /// 区域名称
+    /// </summary>
+    public string OrgName { get; set; } = "";
+    /// <summary>
+    /// 区域全称
+    /// </summary>
+    public string OrgFullName { get; set; } = "";
+
+    /// <summary>
+    /// 区域全称
+    /// </summary>
+    public string DepartmentNo { get; set; } = "";
+
+    /// <summary>
+    /// 区域全称
+    /// </summary>
+    public string ParentOrgId { get; set; } = "";
+
+    /// <summary>
+    /// 附件
+    /// </summary>
+    public List<String> ReplyFileList { get; set; }
+}

+ 52 - 0
src/Hotline.Share/Enums/Snapshot/EGuiderSystemReplyType.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Enums.Snapshot;
+public enum EGuiderSystemReplyType
+{
+    /// <summary>
+    /// 未知
+    /// </summary>
+    [Description("未知")]
+    Unknow = 1,
+
+    /// <summary>
+    /// 办结
+    /// </summary>
+    [Description("办结")]
+    Field = 2,
+
+    /// <summary>
+    /// 退回
+    /// </summary>
+    [Description("退回")]
+    Returned = 3,
+
+    /// <summary>
+    /// 签收
+    /// </summary>
+    [Description("签收")]
+    Sign = 4,
+
+    /// <summary>
+    /// 消息推送
+    /// </summary>
+    [Description("消息推送")]
+    SendMessage = 5,
+
+    /// <summary>
+    /// 超时自动退单
+    /// </summary>
+    [Description("超时自动退单")]
+    TimeoutBack = 6,
+
+    /// <summary>
+    /// 流转
+    /// </summary>
+    [Description("流转")]
+    Circulating = 7
+}

+ 14 - 0
src/Hotline.Share/Mq/EventNames.Snapshot.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Mq;
+public partial class  EventNames
+{
+    /// <summary>
+    /// 延迟检查网格员系统是否回复
+    /// </summary>
+    public const string GuiderSystemReplyDelay = "snapshot.guider.reply.delay";
+}

+ 6 - 0
src/Hotline/Snapshot/Interfaces/IOrderSnapshotRepository.cs

@@ -8,4 +8,10 @@ using XF.Domain.Repository;
 namespace Hotline.Snapshot.Interfaces;
 public interface IOrderSnapshotRepository : IRepository<OrderSnapshot>
 {
+    /// <summary>
+    /// 根据网格员系统ID随手拍工单
+    /// </summary>
+    /// <param name="appealNumber"></param>
+    /// <returns></returns>
+    Task<OrderSnapshot> GetByGuiderSystemIdAsync(string appealNumber);
 }

+ 1 - 0
src/Hotline/Snapshot/Interfaces/IThirdAccountRepository.cs

@@ -4,4 +4,5 @@ namespace Hotline.Snapshot.Interfaces;
 public interface IThirdAccountRepository : IRepository<ThirdAccount>
 {
     Task<ThirdAccount> GetByOpenIdAsync(string openId);
+    Task<ThirdAccount> GetByPhoneNumberAsync(string memberMobile);
 }

+ 15 - 54
src/Hotline/Snapshot/Notifications/PostGuiderSystemNotification.cs

@@ -1,4 +1,5 @@
 using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
 using Hotline.Orders;
 using Hotline.Settings;
 using Hotline.Share.Dtos.Snapshot;
@@ -16,62 +17,22 @@ namespace Hotline.Snapshot.Notifications;
 /// <summary>
 /// 推送网格员系统通知
 /// </summary>
-public class PostGuiderSystemNotification : INotification
-{
-    /// <summary>
-    /// Id
-    /// </summary>
-    public string OrderId { get; set; }
-}
+public record PostGuiderSystemNotification(string OrderId) : INotification;
 
 /// <summary>
-/// 推送网格员系统
+/// 推送成功后发送的延迟消息
 /// </summary>
-public class SnapshotPushNotificationHandler : INotificationHandler<PostGuiderSystemNotification>
-{
-    private readonly string LogName = "推送网格员系统";
-    private readonly IGuiderSystemService _guiderSystemService;
-    private readonly IOrderRepository _orderRepository;
-    private readonly ISystemLogRepository _systemLog;
-    private readonly ISystemSettingCacheManager _sysSetting;
-    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+/// <param name="orderId"></param>
+public record PostGuiderSystemDelayed(string OrderId);
 
-    public SnapshotPushNotificationHandler(IGuiderSystemService guiderSystemService, IOrderRepository orderRepository, ISystemLogRepository systemLog, ISystemSettingCacheManager systemSettingCacheManager, IOrderSnapshotRepository orderSnapshotRepository)
-    {
-        _guiderSystemService = guiderSystemService;
-        _orderRepository = orderRepository;
-        _systemLog = systemLog;
-        _sysSetting = systemSettingCacheManager;
-        _orderSnapshotRepository = orderSnapshotRepository;
-    }
-
-    public async Task Handle(PostGuiderSystemNotification notification, CancellationToken cancellationToken)
-    {
-        var order = await _orderRepository.GetAsync(notification.OrderId);
-        var orderSnapshot = await _orderSnapshotRepository.GetAsync(notification.OrderId);
-        if (order is null || orderSnapshot is null)
-        {
-            var msg = order is null ? "order" : "orderSnapshot";
-            _systemLog.Add(LogName, $"OrderId: {notification.OrderId}", $"根据Id查询{msg}为null");
-            return;
-        }
-        orderSnapshot.DeadLine = DateTime.Now.AddHours(_sysSetting.OvertimeBack);
-        var keySecret = _sysSetting.TianQueAppKeySecret.Split('|');
+/// <summary>
+/// 网格员系统超时未回复 或者网格员系统超时退回
+/// </summary>
+/// <param name="orderId"></param>
+public record GuiderSystemTimeOutBackNotification(string OrderId) : INotification;
 
-        var token = new ThirdTokenDto
-        {
-            AppId = keySecret[0],
-            Secret = keySecret[1]
-        };
-        var result = await _guiderSystemService.PostOrder(order, orderSnapshot, token);
-        orderSnapshot.GuiderAccLog = result.Result.ToJson();
-        if (result.Code != 0)
-        {
-            _systemLog.Add(LogName, $"OrderNo: {order.No}", $"推送失败: {result.ToJson()}");
-            orderSnapshot.GuiderAccLog = result.ToJson();
-        }
-        orderSnapshot.GuiderSystemId = result.Result.GuiderSystemId;
-        await _orderSnapshotRepository.UpdateAsync(orderSnapshot);
-        _systemLog.Add(LogName, $"OrderNo: {order.No}", status: 1);
-    }
-}
+/// <summary>
+/// 网格员系统办结
+/// </summary>
+/// <param name="OrderId"></param>
+public record GuiderSystemFieldNotification(string OrderId) : INotification;

+ 38 - 7
src/Hotline/Snapshot/OrderSnapshot.cs

@@ -1,4 +1,5 @@
 using Hotline.Orders;
+using Hotline.Share.Enums.Snapshot;
 using SqlSugar;
 using System.ComponentModel;
 using XF.Domain.Repository;
@@ -76,12 +77,6 @@ public class OrderSnapshot : CreationSoftDeleteEntity
 
     #region 网格员回复
 
-    /// <summary>
-    /// 网格员系统Id
-    /// </summary>
-    [SugarColumn(ColumnDescription = "网格员系统Id")]
-    public string? GuiderSystemId { get;  set; }
-
     /// <summary>
     /// 网格员系统交互日志
     /// </summary>
@@ -127,7 +122,43 @@ public class OrderSnapshot : CreationSoftDeleteEntity
     /// <summary>
     /// 网格员回复内容
     /// </summary>
-    [SugarColumn(ColumnDescription = "网格员回复内容")]
+    [SugarColumn(ColumnDescription = "网格员回复内容", Length = 2048)]
     public string? NetworkRemark { get; set; }
+
+    /// <summary>
+    /// 网格员办理时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网格员办理时间")]
+    public DateTime? ReplyDate { get; set; }
+
+    /// <summary>
+    /// 网格员姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网格员姓名")]
+    public string MemberName { get; set; }
+
+    /// <summary>
+    /// 网格员电话
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网格员电话")]
+    public string MemberMobile { get; set; }
+
+    /// <summary>
+    /// 办理人账号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "办理人账号")]
+    public string? ReplyUserName { get; set; }
+
+    /// <summary>
+    /// 办理部门
+    /// </summary>
+    [SugarColumn(ColumnDescription = "办理部门")]
+    public string? ReplyBMName { get; set; }
+
+    /// <summary>
+    /// 办理状态 1,7:流转 2:办结 3:退回 4:网格员签收 5:消息推送 6: 超时自动退单
+    /// </summary>
+    [SugarColumn(ColumnDescription ="网格员办理状态")]
+    public EGuiderSystemReplyType? ReplyResultType { get; set; }
     #endregion
 }

+ 22 - 2
src/TianQue.Sdk/TiqnQueService.cs

@@ -14,6 +14,7 @@ using System.Net.Http.Headers;
 using System.Text;
 using TianQue.Sdk.Models;
 using XF.Domain.Dependency;
+using FileInfo = TianQue.Sdk.Models.FileInfo;
 
 namespace TianQue.Sdk;
 
@@ -28,6 +29,13 @@ public class TiqnQueService : IGuiderSystemService, IScopeDependency
         _sysSetting = sysSetting;
     }
 
+    /// <summary>
+    /// 推送工单到网格员系统
+    /// </summary>
+    /// <param name="order"></param>
+    /// <param name="orderSnapshot"></param>
+    /// <param name="tokenDto"></param>
+    /// <returns></returns>
     public async Task<ApiResponse<GuiderSystemOutDto>> PostOrder(Order order, OrderSnapshot orderSnapshot, ThirdTokenDto tokenDto)
     {
         TQHttpClient httpClient;
@@ -43,8 +51,8 @@ public class TiqnQueService : IGuiderSystemService, IScopeDependency
         acceptInfo.DetailAddress = order.FullAddress!; // 详细地址
         acceptInfo.Topic = order.Title!; // 线索主题
         acceptInfo.DetailContent = order.Content!; // 详细内容
-        acceptInfo.Lon = order.Longitude.ToString(); // 经度
-        acceptInfo.Lat = order.Latitude.ToString(); // 纬度
+        acceptInfo.Lon = order.Longitude.ToString()!; // 经度
+        acceptInfo.Lat = order.Latitude.ToString()!; // 纬度
         acceptInfo.RootCategoryInfo = ""; // order.HotspotName!; // 热点分类(大类)
         acceptInfo.CategoryInfo = "";// order.HotspotSpliceName!; // 热点分类(小类)
         acceptInfo.DeadLine = orderSnapshot.DeadLine!.Value; // 截止时间
@@ -62,6 +70,18 @@ public class TiqnQueService : IGuiderSystemService, IScopeDependency
                 ReflectUserName = order.FromName!, // 姓名
             } 
         };
+        if (order.FileJson != null)
+        {
+            acceptInfo.Files = new List<FileInfo>();
+            foreach (var item in order.FileJson)
+            {
+                acceptInfo.Files.Add(new FileInfo
+                {
+                    FileContent = item.Path,
+                    FileName = item.FileName + "." + item.FileType
+                });
+            }
+        }
         acceptInfo.ValidateObject();
         var result = await httpClient.PostAsync<ApiReponse<AcceptInfoSuccessDto>>(new Uri(_sysSetting.TianQuanPostAcceptInfoApi), acceptInfo);
         return new ApiResponse<GuiderSystemOutDto>