Forráskód Böngészése

Merge branch 'test' into lib/test

libin 3 hónapja
szülő
commit
31ae11206a
100 módosított fájl, 6431 hozzáadás és 493 törlés
  1. 7 0
      Hotline.sln
  2. 71 2
      src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs
  3. 0 1
      src/Hotline.Api/Controllers/CallController.cs
  4. 1 0
      src/Hotline.Api/Controllers/HomeController.cs
  5. 12 1
      src/Hotline.Api/Controllers/IdentityController.cs
  6. 64 19
      src/Hotline.Api/Controllers/JudicialManagementOrdersController.cs
  7. 285 21
      src/Hotline.Api/Controllers/OrderController.cs
  8. 75 0
      src/Hotline.Api/Controllers/OrderModuleControllers/OrderCarbonCopyController.cs
  9. 1 1
      src/Hotline.Api/Controllers/OrderModuleControllers/OrderComplementController.cs
  10. 281 2
      src/Hotline.Api/Controllers/Snapshot/IndustryController.cs
  11. 193 0
      src/Hotline.Api/Controllers/Snapshot/RedPackController.cs
  12. 250 0
      src/Hotline.Api/Controllers/Snapshot/SnapshotBulletinController.cs
  13. 226 15
      src/Hotline.Api/Controllers/Snapshot/SnapshotController.cs
  14. 206 0
      src/Hotline.Api/Controllers/Snapshot/SnapshotOrderController.cs
  15. 4 0
      src/Hotline.Api/Controllers/SysController.cs
  16. 47 2
      src/Hotline.Api/Controllers/TestController.cs
  17. 1 4
      src/Hotline.Api/Hotline.Api.csproj
  18. 1 0
      src/Hotline.Api/Realtimes/RealtimeMethods.cs
  19. 8 0
      src/Hotline.Api/Realtimes/RealtimeService.cs
  20. 18 8
      src/Hotline.Api/StartupExtensions.cs
  21. 3 3
      src/Hotline.Api/config/appsettings.Development.json
  22. 0 2
      src/Hotline.Application.Contracts/Validators/Snapshot/IndustryValidator.cs
  23. 41 1
      src/Hotline.Application.Tests/Application/DefaultCallApplicationTest.cs
  24. 100 0
      src/Hotline.Application.Tests/Application/IndustryApplicationTest.cs
  25. 2 1
      src/Hotline.Application.Tests/Application/KnowApplicationTest.cs
  26. 72 0
      src/Hotline.Application.Tests/Application/OrderSnapshotApplicationTest.cs
  27. 64 0
      src/Hotline.Application.Tests/Application/RedPackApplicationTest.cs
  28. 51 6
      src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs
  29. 343 51
      src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs
  30. 13 6
      src/Hotline.Application.Tests/Application/SystemSettingCacheManagerTest.cs
  31. 9 10
      src/Hotline.Application.Tests/Controller/DefaultSessionContext.cs
  32. 37 0
      src/Hotline.Application.Tests/Controller/IndustryControllerTest.cs
  33. 2 1
      src/Hotline.Application.Tests/Controller/KnowledgeControllerTest.cs
  34. 61 3
      src/Hotline.Application.Tests/Controller/OrderControllerTest.cs
  35. 54 3
      src/Hotline.Application.Tests/Controller/SnapshotControllerTest.cs
  36. 2 1
      src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs
  37. 1 1
      src/Hotline.Application.Tests/Hotline.Application.Tests.csproj
  38. 3 0
      src/Hotline.Application.Tests/Infrastructure/TestSettingConstants.cs
  39. 50 0
      src/Hotline.Application.Tests/Infrastructure/TianQueTest.cs
  40. 24 0
      src/Hotline.Application.Tests/Infrastructure/WeiXinTest.cs
  41. 19 0
      src/Hotline.Application.Tests/Mock/Interfaces/IOrderServiceStartWorkflow.cs
  42. 97 19
      src/Hotline.Application.Tests/Mock/OrderServiceMock.cs
  43. 145 0
      src/Hotline.Application.Tests/Mock/OrderServiceStartWorkflow.cs
  44. 6 2
      src/Hotline.Application.Tests/Mock/ThirdTestService.cs
  45. 0 10
      src/Hotline.Application.Tests/Repository/FWMQRepositoryTest.cs
  46. 19 6
      src/Hotline.Application.Tests/Startup.cs
  47. 39 18
      src/Hotline.Application.Tests/TestBase.cs
  48. 19 4
      src/Hotline.Application/Bigscreen/DataScreenRefreshService.cs
  49. 17 0
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  50. 6 6
      src/Hotline.Application/CallCenter/ICallApplication.cs
  51. 10 0
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  52. 32 13
      src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs
  53. 0 1
      src/Hotline.Application/Hotline.Application.csproj
  54. 7 0
      src/Hotline.Application/Identity/IIdentityAppService.cs
  55. 49 5
      src/Hotline.Application/Identity/IdentityAppService.cs
  56. 13 10
      src/Hotline.Application/JudicialManagement/EnforcementApplication.cs
  57. 1 0
      src/Hotline.Application/Knowledge/KnowApplication.cs
  58. 7 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  59. 0 2
      src/Hotline.Application/Mappers/OrderMapperConfigs.cs
  60. 68 1
      src/Hotline.Application/Mappers/SnapshotMapperConfigs.cs
  61. 1 1
      src/Hotline.Application/Orders/Handles/OrderHandler/AddOrderPushMessageNotifyHandler.cs
  62. 1 1
      src/Hotline.Application/Orders/Handles/OrderHandler/GetOrderDetailNotifyHandler.cs
  63. 1 1
      src/Hotline.Application/Orders/Handles/OrderHandler/OrderRelateCallHandler.cs
  64. 1 1
      src/Hotline.Application/Orders/Handles/OrderHandler/OrderVisitSmsHandler.cs
  65. 3 3
      src/Hotline.Application/Orders/Handles/OrderHandler/TranspondCityNotifyHandler.cs
  66. 1 1
      src/Hotline.Application/Orders/Handles/OrderHandler/UpdateOrderPushMessageNotifyHandler.cs
  67. 9 9
      src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenEndWorkflowHandler.cs
  68. 101 0
      src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs
  69. 37 0
      src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenStartWorkflowHandler.cs
  70. 53 0
      src/Hotline.Application/Orders/Handles/SnapshotHandler/GuiderSystemTimeoutHandler.cs
  71. 7 0
      src/Hotline.Application/Orders/IOrderApplication.cs
  72. 16 0
      src/Hotline.Application/Orders/IOrderCarbonCopyApplication.cs
  73. 72 5
      src/Hotline.Application/Orders/OrderApplication.cs
  74. 41 0
      src/Hotline.Application/Orders/OrderCarbonCopyApplication.cs
  75. 5 1
      src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs
  76. 0 37
      src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenStartWorkflowHandler.cs
  77. 64 65
      src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs
  78. 7 2
      src/Hotline.Application/Snapshot/DefaultSnapshotApplication.cs
  79. 164 0
      src/Hotline.Application/Snapshot/IIndustryApplication.cs
  80. 113 0
      src/Hotline.Application/Snapshot/IOrderSnapshotApplication.cs
  81. 87 0
      src/Hotline.Application/Snapshot/IRedPackApplication.cs
  82. 155 6
      src/Hotline.Application/Snapshot/ISnapshotApplication.cs
  83. 17 0
      src/Hotline.Application/Snapshot/ISnapshotBulletinApplication.cs
  84. 350 3
      src/Hotline.Application/Snapshot/IndustryApplication.cs
  85. 109 0
      src/Hotline.Application/Snapshot/Notifications/SnapshotHandler.cs
  86. 425 0
      src/Hotline.Application/Snapshot/OrderSnapshotApplication.cs
  87. 448 0
      src/Hotline.Application/Snapshot/RedPackApplication.cs
  88. 636 59
      src/Hotline.Application/Snapshot/SnapshotApplicationBase.cs
  89. 68 0
      src/Hotline.Application/Snapshot/SnapshotBulletinApplication.cs
  90. 7 2
      src/Hotline.Application/Snapshot/ZiGongSnapshotApplication.cs
  91. 40 30
      src/Hotline.Application/StatisticalReport/OrderReportApplication.cs
  92. 4 0
      src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  93. 24 3
      src/Hotline.Repository.SqlSugar/File/FileRepository.cs
  94. 2 1
      src/Hotline.Repository.SqlSugar/Orders/OrderVisitDetailRepository.cs
  95. 18 0
      src/Hotline.Repository.SqlSugar/Snapshot/CommunityInfoRepository.cs
  96. 23 0
      src/Hotline.Repository.SqlSugar/Snapshot/GuiderInfoRepository.cs
  97. 19 0
      src/Hotline.Repository.SqlSugar/Snapshot/IndustryCaseRepository.cs
  98. 18 0
      src/Hotline.Repository.SqlSugar/Snapshot/IndustryLogRepository.cs
  99. 1 0
      src/Hotline.Repository.SqlSugar/Snapshot/IndustryRepository.cs
  100. 46 0
      src/Hotline.Repository.SqlSugar/Snapshot/OrderSnapshotRepository.cs

+ 7 - 0
Hotline.sln

@@ -59,6 +59,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Logger", "src\Hotli
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.WeChat", "src\Hotline.WeChat\Hotline.WeChat.csproj", "{75215667-65AF-4B7B-85E7-3140239B30CC}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TianQue.Sdk", "src\TianQue.Sdk\TianQue.Sdk.csproj", "{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -153,6 +155,10 @@ Global
 		{75215667-65AF-4B7B-85E7-3140239B30CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{75215667-65AF-4B7B-85E7-3140239B30CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{75215667-65AF-4B7B-85E7-3140239B30CC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -183,6 +189,7 @@ Global
 		{9F99C272-5BC2-452C-9D97-BC756AF04669} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{75215667-65AF-4B7B-85E7-3140239B30CC} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{6CF27647-D0E0-4D17-80FB-3EE57864A2B4} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

+ 71 - 2
src/Hotline.Api/Controllers/Bigscreen/DataScreenController.cs

@@ -1,9 +1,11 @@
-using Hotline.Configurations;
+using DocumentFormat.OpenXml.Drawing;
+using Hotline.Configurations;
 using Hotline.KnowledgeBase;
 using Hotline.Orders;
 using Hotline.Repository.SqlSugar.Orders;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Bigscreen;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.KnowledgeBase;
@@ -28,11 +30,13 @@ namespace Hotline.Api.Controllers.Bigscreen
         private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
         private readonly IRepository<SystemArea> _systemAreaRepository;
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        private readonly IRepository<OrderSecondaryHandling> _orderSecondaryHandlingRepository;
 
         public DataScreenController(IOrderRepository orderRepository, IRepository<OrderVisit> orderVisitRepository,
             IRepository<OrderDelay> orderDelayRepository, IRepository<Knowledge> knowledgeRepository, IRepository<KnowledgePv> knowledgePvRepository,
             IMapper mapper, IRepository<OrderVisitDetail> orderVisitDetailRepository, IRepository<SystemArea> systemAreaRepository,
-            IOptionsSnapshot<AppConfiguration> appOptions)
+            IOptionsSnapshot<AppConfiguration> appOptions,
+            IRepository<OrderSecondaryHandling> orderSecondaryHandlingRepository)
         {
             _orderRepository = orderRepository;
             _orderVisitRepository = orderVisitRepository;
@@ -43,6 +47,7 @@ namespace Hotline.Api.Controllers.Bigscreen
             _orderVisitDetailRepository = orderVisitDetailRepository;
             _systemAreaRepository = systemAreaRepository;
             _appOptions = appOptions;
+            _orderSecondaryHandlingRepository = orderSecondaryHandlingRepository;
         }
 
         /// <summary>
@@ -590,5 +595,69 @@ namespace Hotline.Api.Controllers.Bigscreen
                 return list;
             }
         }
+
+        /// <summary>
+        /// 二次办理统计
+        /// </summary>
+        /// <param name="StartTime"></param>
+        /// <param name="EndTime"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-secondary-statistics")]
+        public async Task<SecondaryProcessingOrderStatisticsDto> OrderSecondaryStatistics(DateTime StartTime, DateTime EndTime)
+        {
+            EndTime = EndTime.AddDays(1).AddSeconds(-1);
+            var data = new SecondaryProcessingOrderStatisticsDto
+            {
+                OrderCount = await _orderSecondaryHandlingRepository.Queryable()
+                 .Where(x => x.AuditTime >= StartTime && x.AuditTime <= EndTime && x.State != ESecondaryHandlingState.NotApply
+                 && x.State != ESecondaryHandlingState.Apply && x.State != ESecondaryHandlingState.Refuse).CountAsync(),
+
+                OrderOverdueCount = await _orderSecondaryHandlingRepository.Queryable()
+                .Includes(x => x.Order)
+                 .Where(x => x.Order.ExpiredStatus == EExpiredStatus.Expired && x.AuditTime >= StartTime && x.AuditTime <= EndTime
+                 && x.State != ESecondaryHandlingState.NotApply
+                 && x.State != ESecondaryHandlingState.Apply && x.State != ESecondaryHandlingState.Refuse)
+                 .CountAsync(),
+
+                OrderSoonOverdueCount = await _orderSecondaryHandlingRepository.Queryable()
+                .Includes(x => x.Order)
+                 .Where(x => x.Order.ExpiredStatus == EExpiredStatus.GoingToExpired && x.AuditTime >= StartTime && x.AuditTime <= EndTime
+                 && x.State != ESecondaryHandlingState.NotApply
+                 && x.State != ESecondaryHandlingState.Apply && x.State != ESecondaryHandlingState.Refuse)
+                 .CountAsync()
+            };
+            var da = await _orderSecondaryHandlingRepository.Queryable()
+                .LeftJoin<OrderVisit>((os, ov) => os.VisitId == ov.Id)
+                    .LeftJoin<OrderVisitDetail>((os, ov, od) => os.VisitDetailId == od.Id)
+                    .Where((os, ov, od) => ov.VisitTime >= StartTime && ov.VisitTime <= EndTime && os.State != ESecondaryHandlingState.NotApply
+                    && os.State != ESecondaryHandlingState.Apply && os.State != ESecondaryHandlingState.Refuse)
+                    .Select((os, ov, od) => new SecondarySatisfactionDto()
+                    {
+                        Count = SqlFunc.AggregateCount(os.Id),
+                        NoSatisfiedCount = SqlFunc.AggregateSum(SqlFunc.IIF(SqlFunc.JsonField(od.OrgProcessingResults, "Key") == "1"
+                        || SqlFunc.JsonField(od.OrgProcessingResults, "Key") == "2", 1, 0)),//不满意数
+                    }).FirstAsync();
+            if (da != null)
+                data.SatisfactionRate = da.SatisfiedRate;
+            return data;
+        }
+
+        /// <summary>
+        /// 二次办理中工单概览
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("order-secondary-handling-query")]
+        public async Task<List<OrderSecondaryHandlingDto>> OrderSecondaryHandlingDetailQuery()
+        {
+            var quer = await _orderSecondaryHandlingRepository.Queryable()
+                 .Includes(x=>x.Order)
+                  .Where(x => x.CreationTime.Date == DateTime.Now.Date)
+                  .OrderByDescending(x => x.CreationTime)
+                  .Take(50)
+                 .ToListAsync();
+            return _mapper.Map<List<OrderSecondaryHandlingDto>>(quer);
+        }
     }
 }

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

@@ -112,7 +112,6 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="callId"></param>
         /// <returns></returns>
-        /// <exception cref="NotImplementedException"></exception>
         [HttpGet("{callId}")]
         public Task<List<CallNative>> GetCall(string callId)
         {

+ 1 - 0
src/Hotline.Api/Controllers/HomeController.cs

@@ -193,6 +193,7 @@ public class HomeController : BaseController
             NationalPlatformWordLimit = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.NationalPlatformWordLimit).SettingValue[0]),
             HandleOpinionWordLimit= int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.HandleOpinionWordLimit).SettingValue[0]),
             CallInOpenType = int.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.CallInOpenType).SettingValue[0]),
+            Snapshot = _systemSettingCacheManager.Snapshot
         };
         return rsp;
     }

+ 12 - 1
src/Hotline.Api/Controllers/IdentityController.cs

@@ -101,6 +101,16 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
     public async Task<TokenOutDto> GetThirdTokenAsync([FromBody] ThirdTokenInDto dto)
         => await _identityAppService.GetThredTokenAsync(dto);
 
+    /// <summary>
+    /// 根据OpenId刷新令牌
+    /// </summary>
+    /// <param name="openId"></param>
+    /// <returns></returns>
+    [HttpGet("third/refresh")]
+    [AllowAnonymous]
+    public async Task<TokenOutDto> RefreshTokenAsync(string openId)
+        => await _identityAppService.RefreshTokenAsync(openId);
+
     [AllowAnonymous]
     [ApiExplorerSettings(IgnoreApi = true)]
     [HttpPost("token")]
@@ -170,7 +180,8 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
             MenuLogoImageMini = menuLogoImageMini,
             IsLoginMessageCode = IsLoginMessageCode,
             AppScope = _appOptions.Value.AppScope,
-            CallCenterType = _appOptions.Value.GetDefaultAppScopeConfiguration().CallCenterType
+            CallCenterType = _appOptions.Value.GetDefaultAppScopeConfiguration().CallCenterType,
+            Snapshot = _systemSettingCacheManager.Snapshot
         };
     }
 

+ 64 - 19
src/Hotline.Api/Controllers/JudicialManagementOrdersController.cs

@@ -1,4 +1,5 @@
-using Hotline.Api.Filter;
+using DocumentFormat.OpenXml.Office2010.Excel;
+using Hotline.Api.Filter;
 using Hotline.Application.JudicialManagement;
 using Hotline.Caching.Interfaces;
 using Hotline.File;
@@ -37,6 +38,7 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<EnforcementOrdersHandler> _enforcementOrdersHandlerRepository;
         private readonly IEnforcementApplication _enforcementApplication;
         private readonly IRepository<Order> _orderRepository;
+        private readonly IRepository<LawEnforcementAgencies> _lawEnforcementAgenciesRepository;
 
         /// <summary>
         /// 
@@ -63,11 +65,12 @@ namespace Hotline.Api.Controllers
          ISessionContext sessionContext,
          IRepository<SystemArea> systemAreaRepository,
          IRepository<JudicialManagementOrders> judicialManagementOrdersRepository,
-          IFileRepository fileRepository,
-          IJudicialManagementOrdersService judicialManagementOrdersService,
-           IRepository<EnforcementOrdersHandler> enforcementOrdersHandlerRepository,
-           IEnforcementApplication enforcementApplication,
-            IRepository<Order> orderRepository
+         IFileRepository fileRepository,
+         IJudicialManagementOrdersService judicialManagementOrdersService,
+         IRepository<EnforcementOrdersHandler> enforcementOrdersHandlerRepository,
+         IEnforcementApplication enforcementApplication,
+         IRepository<Order> orderRepository,
+         IRepository<LawEnforcementAgencies> lawEnforcementAgenciesRepository
          )
         {
             _judicialComplaintsEventTypeRepository = judicialComplaintsEventTypeRepository;
@@ -83,6 +86,7 @@ namespace Hotline.Api.Controllers
             _enforcementOrdersHandlerRepository = enforcementOrdersHandlerRepository;
             _enforcementApplication = enforcementApplication;
             _orderRepository = orderRepository;
+            _lawEnforcementAgenciesRepository = lawEnforcementAgenciesRepository;
         }
 
         /// <summary>
@@ -100,13 +104,14 @@ namespace Hotline.Api.Controllers
             if (dto.Files.Any())
                 order.FileJson = await _fileRepository.AddFileAsync(dto.Files, order.Id, "", HttpContext.RequestAborted);
             await _judicialManagementOrdersService.AddAsync(order, true, HttpContext.RequestAborted);
+
             //处理执法部门
-            if (dto.EnforcementOrdersHandler != null && dto.EnforcementOrdersHandler.Any())
+            if (dto.LawEnforcementAgencies != null && dto.LawEnforcementAgencies.Any())
             {
-                List<EnforcementOrdersHandler> enforcementOrdersHandlers = new();
-                foreach (var item in dto.EnforcementOrdersHandler)
+                List<LawEnforcementAgencies> lawEnforcementAgencies = new();
+                foreach (var item in dto.LawEnforcementAgencies)
                 {
-                    enforcementOrdersHandlers.Add(new EnforcementOrdersHandler
+                    lawEnforcementAgencies.Add(new LawEnforcementAgencies
                     {
                         OrderId = order.Id,
                         OrderNo = order.No,
@@ -115,8 +120,8 @@ namespace Hotline.Api.Controllers
                         OrgName = item.Key
                     });
                 }
-                if (enforcementOrdersHandlers != null && enforcementOrdersHandlers.Any())
-                    await _enforcementOrdersHandlerRepository.AddRangeAsync(enforcementOrdersHandlers, HttpContext.RequestAborted);
+                if (lawEnforcementAgencies != null && lawEnforcementAgencies.Any())
+                    await _lawEnforcementAgenciesRepository.AddRangeAsync(lawEnforcementAgencies, HttpContext.RequestAborted);
             }
             return order.Id;
         }
@@ -143,12 +148,12 @@ namespace Hotline.Api.Controllers
             await _judicialManagementOrdersRepository.UpdateAsync(order, HttpContext.RequestAborted);
 
             //处理执法部门
-            if (dto.EnforcementOrdersHandler != null && dto.EnforcementOrdersHandler.Any())
+            if (dto.LawEnforcementAgencies != null && dto.LawEnforcementAgencies.Any())
             {
-                List<EnforcementOrdersHandler> enforcementOrdersHandlers = new();
-                foreach (var item in dto.EnforcementOrdersHandler)
+                List<LawEnforcementAgencies> lawEnforcementAgencies = new();
+                foreach (var item in dto.LawEnforcementAgencies)
                 {
-                    enforcementOrdersHandlers.Add(new EnforcementOrdersHandler
+                    lawEnforcementAgencies.Add(new LawEnforcementAgencies
                     {
                         OrderId = order.Id,
                         OrderNo = order.No,
@@ -156,10 +161,10 @@ namespace Hotline.Api.Controllers
                         OrgCode = item.Value,
                         OrgName = item.Key
                     });
-                    if (enforcementOrdersHandlers != null && enforcementOrdersHandlers.Any())
+                    if (lawEnforcementAgencies != null && lawEnforcementAgencies.Any())
                     {
-                        await _enforcementOrdersHandlerRepository.RemoveAsync(p => p.OrderId == order.Id, false, HttpContext.RequestAborted);
-                        await _enforcementOrdersHandlerRepository.AddRangeAsync(enforcementOrdersHandlers, HttpContext.RequestAborted);
+                        await _lawEnforcementAgenciesRepository.RemoveAsync(p => p.OrderId == order.Id, false, HttpContext.RequestAborted);
+                        await _lawEnforcementAgenciesRepository.AddRangeAsync(lawEnforcementAgencies, HttpContext.RequestAborted);
                     }
 
                 }
@@ -185,6 +190,46 @@ namespace Hotline.Api.Controllers
             await _judicialManagementOrdersRepository.UpdateAsync(order, HttpContext.RequestAborted);
         }
 
+        /// <summary>
+        /// 关联执法部门
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPut("associated_law_enforcement_gencies")]
+        [LogFilter("关联执法部门")]
+        public async Task AssociatedLawEnforcementAgencies([FromBody] AssociatedLawEnforcementAgenciesDto dto)
+        {
+            if (dto.LawEnforcementAgencies == null || dto.LawEnforcementAgencies.Count == 0)
+                throw UserFriendlyException.SameMessage("请选择执法部门");
+
+            var order = await _judicialManagementOrdersRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+            if (order == null)
+                throw UserFriendlyException.SameMessage("工单查询失败");
+            //处理执法部门
+            if (dto.LawEnforcementAgencies != null && dto.LawEnforcementAgencies.Any())
+            {
+                List<LawEnforcementAgencies> lawEnforcementAgencies = new();
+                foreach (var item in dto.LawEnforcementAgencies)
+                {
+                    lawEnforcementAgencies.Add(new LawEnforcementAgencies
+                    {
+                        OrderId = order.Id,
+                        OrderNo = order.No,
+                        OrderSoure = EOrderSoure.Enforcement,
+                        OrgCode = item.Value,
+                        OrgName = item.Key
+                    });
+                }
+                if (lawEnforcementAgencies != null && lawEnforcementAgencies.Any())
+                {
+                    await _lawEnforcementAgenciesRepository.AddRangeAsync(lawEnforcementAgencies, HttpContext.RequestAborted);
+                    order.LawEnforcementAgencies = dto.LawEnforcementAgencies;
+                    await _judicialManagementOrdersRepository.UpdateAsync(order, HttpContext.RequestAborted);
+                }
+            }
+
+        }
+
         /// <summary>
         /// 查询工单详情
         /// </summary>

+ 285 - 21
src/Hotline.Api/Controllers/OrderController.cs

@@ -68,6 +68,21 @@ using XF.Domain.Entities;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
+using Hotline.Application.Contracts.Validators.FlowEngine;
+using Hotline.Authentications;
+using Hotline.Share.Dtos.CallCenter;
+using Hotline.Share.Mq;
+using Hotline.CallCenter.Calls;
+using Hotline.FlowEngine.Notifications;
+using Hotline.Share.Dtos.Order.Detail;
+using Hotline.Share.Dtos.File;
+using Hotline.Share.Dtos.Org;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Snapshot.Notifications;
+using Hotline.Snapshot;
+using Hotline.Application.Snapshot;
+using Hotline.Share.Dtos.Snapshot;
+using OrderDto = Hotline.Share.Dtos.Order.OrderDto;
 
 namespace Hotline.Api.Controllers;
 
@@ -122,7 +137,6 @@ public class OrderController : BaseController
     private readonly IOrderApplication _orderApplication;
     private readonly IPushDomainService _pushDomainService;
     private readonly ILogger<OrderController> _logger;
-    private readonly ITypedCache<YbEnterpriseToken> _cacheResponse;
     private readonly IRepository<OrderSendBackAudit> _orderSendBackAuditRepository;
     private readonly IRepository<User> _userRepository;
     private readonly IExportApplication _exportApplication;
@@ -143,6 +157,10 @@ public class OrderController : BaseController
     private readonly IRepository<OrderRevoke> _orderRevokeRepository;
     private readonly IOrderTerminateRepository _orderTerminateRepository;
     private readonly ISystemLogApplication _systemLogApplication;
+    private readonly IRepository<OrderCarboncopy> _orderCarboncopy;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly IOrderSnapshotApplication _orderSnapshotApplication;
+    private readonly IIndustryRepository _industryRepository;
 
     public OrderController(
         IOrderDomainService orderDomainService,
@@ -208,8 +226,12 @@ public class OrderController : BaseController
         IRepository<OrderRevoke> orderRevokeRepository,
         BaseDataApplication baseDataApplication,
         IOrderTerminateRepository orderTerminateRepository,
+        IRepository<OrderCarboncopy> orderCarboncopy,
         ITypedCache<string> typeCache,
-        ISystemLogApplication systemLogApplication)
+        ISystemLogApplication systemLogApplication,
+        IOrderSnapshotRepository orderSnapshotRepository,
+        IIndustryRepository industryRepository,
+        IOrderSnapshotApplication orderSnapshotApplication)
     {
         _orderDomainService = orderDomainService;
         _orderRepository = orderRepository;
@@ -253,7 +275,6 @@ public class OrderController : BaseController
         _logger = logger;
         _orderApplication = orderApplication;
         _pushDomainService = pushDomainService;
-        _cacheResponse = cacheResponse;
         _orderSendBackAuditRepository = orderSendBackAuditRepository;
         _userRepository = userRepository;
         _exportApplication = exportApplication;
@@ -273,10 +294,14 @@ public class OrderController : BaseController
         _callNativeRepository = callNativeRepository;
         _baseDataApplication = baseDataApplication;
         _orderTerminateRepository = orderTerminateRepository;
+        _orderCarboncopy = orderCarboncopy;
         _orderRevokeRepository = orderRevokeRepository;
         _typeCache = typeCache;
         _baseDataApplication = baseDataApplication;
         _systemLogApplication = systemLogApplication;
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _industryRepository = industryRepository;
+        _orderSnapshotApplication = orderSnapshotApplication;
     }
 
     #endregion
@@ -563,6 +588,8 @@ public class OrderController : BaseController
         }
 
         string visitId = await _orderVisitRepository.AddAsync(orderVisit);
+        await _orderSnapshotApplication.UpdateLabelAsync(order.Id, dto.SnapshotLabels);
+
 
         //新增回访信息
         var visitedDetail = new List<OrderVisitDetail>();
@@ -724,6 +751,12 @@ public class OrderController : BaseController
         res.idNames = order.CounterSignType == null
             ? null
             : idNames.Where(d => d.Key != idName.Key).ToList();
+
+        if (_systemSettingCacheManager.Snapshot)
+        {
+            res.IsSnapshot = await _orderSnapshotRepository.AnyAsync(m => m.Id == order.Id);
+            res.SnapshotLabel = _sysDicDataCacheManager.SnapshotOrderLabel;
+        }
         return res;
     }
 
@@ -857,6 +890,15 @@ public class OrderController : BaseController
             }
         }
 
+        if (_systemSettingCacheManager.Snapshot)
+        {
+            await _orderSnapshotRepository.GetAsync(pubentity.OrderId)
+                .Then(m =>
+                {
+                    pubentity.Labels = m.Labels;
+                });
+        }
+
         return pubentity;
     }
 
@@ -3210,12 +3252,31 @@ public class OrderController : BaseController
     [HttpGet("history_all")]
     public async Task<PagedDto<OrderDto>> QueryAll([FromQuery] QueryOrderHistoryDto dto)
     {
-        var (total, items) = await _orderRepository.Queryable()
+        var query = _orderRepository.Queryable()
             .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.Contact == dto.PhoneNo)
             .WhereIF(!string.IsNullOrEmpty(dto.OrderId), d => d.Id != dto.OrderId)
-            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!))
+            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!));
+
+        //随手拍
+        bool.TryParse(
+            _systemSettingCacheManager.GetSetting(SettingConstants.Snapshot)?.SettingValue[0],
+            out bool isSnapshotEnable);
+        if (isSnapshotEnable && !string.IsNullOrEmpty(dto.IndustryId))
+        {
+            query.Where(d => d.OrderSnapshot.IndustryId == dto.IndustryId);
+        }
+
+        //var (total, items) = await _orderRepository.Queryable()
+        //    .WhereIF(!string.IsNullOrEmpty(dto.PhoneNo), d => d.Contact == dto.PhoneNo)
+        //    .WhereIF(!string.IsNullOrEmpty(dto.OrderId), d => d.Id != dto.OrderId)
+        //    .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Title.Contains(dto.Keyword!) || d.No.Contains(dto.Keyword!))
+        //    .OrderByDescending(d => d.CreationTime)
+        //    .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
+        var (total, items) = await query
             .OrderByDescending(d => d.CreationTime)
             .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
+
         return new PagedDto<OrderDto>(total, _mapper.Map<IReadOnlyList<OrderDto>>(items));
     }
 
@@ -3516,6 +3577,15 @@ public class OrderController : BaseController
             orderTerminateList.Any(x => x.Status == ETerminateStatus.Refuse) ? "不同意" :
             orderTerminateList.Any(x => x.Status == ETerminateStatus.Approval || x.Status == ETerminateStatus.SendBack) ? "审批中" : null;
 
+        if (_systemSettingCacheManager.Snapshot)
+        {
+            var snapshot = await _orderSnapshotRepository.Queryable()
+                .Where(m => m.Id == order.Id)
+                .Select(m => new { m.IndustryId, m.IndustryName })
+                .FirstAsync(HttpContext.RequestAborted);
+            dto.IndustryName = snapshot.IndustryName;
+            dto.IndustryId = snapshot.IndustryId;
+        }
         dto.IsReTransact = await _orderSpecialRepository.Queryable()
             .Where(x => x.OrderId == dto.Id && x.SpecialType == ESpecialType.ReTransact).AnyAsync();
 
@@ -3574,6 +3644,17 @@ public class OrderController : BaseController
 
         await _orderDomainService.AddAsync(order, true, HttpContext.RequestAborted);
 
+        if (_systemSettingCacheManager.Snapshot && dto.IndustryId.NotNullOrEmpty() && dto.IndustryName.NotNullOrEmpty())
+        {
+            var snapshot = new OrderSnapshot
+            {
+                Id = order.Id,
+                IndustryId = dto.IndustryId,
+                IndustryName = dto.IndustryName
+            };
+            await _orderSnapshotRepository.AddAsync(snapshot, HttpContext.RequestAborted);
+        }
+
         //订阅此事件的内部处理工单数据只能更新各自业务的字段,不能全部更新
         //新增工单其他处理事件  (受理短信)
         if (!string.IsNullOrEmpty(order.Contact) && order.Contact.Length == 11)
@@ -3951,6 +4032,20 @@ public class OrderController : BaseController
             outDto.Opinion = await _typeCache.GetAsync($"tmp_opinion_{orderId}{_sessionContext.UserId}", HttpContext.RequestAborted);
         }
 
+        //随手拍
+        bool.TryParse(_systemSettingCacheManager.GetSetting(SettingConstants.Snapshot)?.SettingValue[0],
+            out bool isSnapshotEnable);
+        if (isSnapshotEnable)
+        {
+            var orderSnapshot = await _orderSnapshotRepository.GetAsync(orderId, HttpContext.RequestAborted);
+            if (orderSnapshot is null)
+                throw new UserFriendlyException($"无效编号, id: {orderId}");
+            if (string.CompareOrdinal(orderSnapshot.IndustryName, "安全隐患") == 0)
+            {
+                outDto.Steps = outDto.Steps.Where(d => d.BusinessType != EBusinessType.Send).ToList();
+            }
+        }
+
         return outDto;
     }
 
@@ -3967,7 +4062,13 @@ public class OrderController : BaseController
                 $"非法参数, {string.Join(',', validResult.Errors.Select(d => d.ErrorMessage))}");
         var order = await _orderApplication.SaveOrderWorkflowInfo(dto, HttpContext.RequestAborted);
 
-        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.Workflow.WorkflowId, withSteps: true, withTraces: true,
+        // 随手拍业务处理
+        if (_systemSettingCacheManager.Snapshot)
+        {
+            await _orderSnapshotApplication.SaveOrderWorkflowInfo(dto);
+        }
+
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.Workflow.WorkflowId, withDefine: true, withSteps: true, withTraces: true,
             cancellationToken: HttpContext.RequestAborted);
 
         //await _workflowApplication.NextAsync(dto.WorkflowDto, order.ExpiredTime, HttpContext.RequestAborted);
@@ -3985,6 +4086,64 @@ public class OrderController : BaseController
         }
     }
 
+    /// <summary>
+    /// 工单批量标注是否安全生产
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("sign/bath")]
+    public async Task<string> OrderSignBathAsync([FromBody] OrderSignBathInDto dto)
+    {
+        var stringBuilder = new StringBuilder();
+        foreach (var orderId in dto.OrderIds)
+        {
+            var order = await _orderRepository.GetAsync(orderId, HttpContext.RequestAborted);
+            if (order is null)
+            {
+                stringBuilder.Append($"工单:{orderId} 不存在");
+                continue;
+            }
+
+            var snapshot = await _orderSnapshotRepository.UpdateSafetyAsync(orderId, dto.IsSafetyDepartment, dto.Remark);
+            if (snapshot is null)
+            {
+                stringBuilder.Append($"随手拍: {orderId} 不存在");
+            }
+            var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withDefine: true, withSteps: true, withTraces: true,
+    cancellationToken: HttpContext.RequestAborted);
+
+            var nextSteps = await _workflowApplication.GetNextStepsAsync(order.WorkflowId, HttpContext.RequestAborted);
+            var stepInfo = nextSteps.Steps.FirstOrDefault(m => m.BusinessType == EBusinessType.Send);
+            if (stepInfo == null)
+            {
+                stringBuilder.Append($"下一步节点: [派单组] 未找到");
+            }
+            var data = new OrderHandleFlowDto
+            {
+                OrderId = orderId,
+                IsSafetyDepartment = dto.IsSafetyDepartment
+            };
+            var workflowDto = new NextWorkflowDto
+            {
+                WorkflowId = order.WorkflowId,
+                StepId = nextSteps.StepId,
+                NextStepCode = stepInfo.Key,
+                NextStepName = stepInfo.Value,
+                Opinion = dto.Remark,
+                BackToCountersignEnd = false,
+                IsSms = false,
+                IsForwarded = false,
+                HandlerType = EHandlerType.OrgLevel,
+                BusinessType = EBusinessType.Send,
+                FlowDirection = EFlowDirection.CenterToCenter,
+            };
+            var startStep = workflow.Steps.First(d => d.Id == nextSteps.StepId);
+            await HandleOrderAsync(order, workflow, startStep, data, workflowDto, HttpContext.RequestAborted);
+            stringBuilder.AppendLine($"{orderId} 标注完成;");
+        }
+        return stringBuilder.ToString();
+    }
+
     private async Task HandleOrderAsync(Order order, Workflow workflow, WorkflowStep startStep, OrderHandleFlowDto orderHandleFlowDto,
         BasicWorkflowDto workflowDto, CancellationToken cancellationToken)
     {
@@ -4035,14 +4194,6 @@ public class OrderController : BaseController
                     }).Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
                 }
 
-                //if (workflowDto.BusinessType == EBusinessType.Seat)
-                //{
-                // await _orderRepository.Updateable().SetColumns(o => new Order()
-                // {
-                //  Status = EOrderStatus.WaitForAccept
-                // }).Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
-                //}
-
                 await _workflowDomainService.NextAsync(_sessionContext, nextDto, order.ExpiredTime, isAutoFillSummaryOpinion, cancellationToken);
                 break;
             case EOrderAssignMode.CrossLevel:
@@ -4052,7 +4203,7 @@ public class OrderController : BaseController
                 orderHandleFlowDto.CrossSteps = orderHandleFlowDto.CrossSteps.OrderBy(d => d.Sort).ToList();
                 var stepCount = orderHandleFlowDto.CrossSteps.Count;
                 var unhandleSteps = new List<WorkflowStep> { startStep };
-                for (int i = 0; i < stepCount; i++)
+                for (int i = 0;i < stepCount;i++)
                 {
                     var crossStep = orderHandleFlowDto.CrossSteps[i];
                     var tempSteps = new List<WorkflowStep>();
@@ -4083,12 +4234,98 @@ public class OrderController : BaseController
 
                 break;
             case EOrderAssignMode.MainAndSecondary:
+                //主协办暂只支持指派给部门办理,且主办部门有且只有一个一级部门
+                nextDto = _mapper.Map<NextWorkflowDto>(workflowDto);
+                nextDto.WorkflowId = startStep.WorkflowId;
+                nextDto.StepId = startStep.Id;
+                nextDto.HandlerType = EHandlerType.OrgLevel;
+                nextDto.FlowDirection = EFlowDirection.CenterToOrg;
+                var secondaryOrgs = orderHandleFlowDto.SecondaryOrgs.DistinctBy(d => d.Id).ToList();
+                var nextHandleOrgs = secondaryOrgs
+                    .Where(d => d.Level == 1 && d.Id.StartsWith(OrgSeedData.CenterId))
+                    .ToList();
+                if (nextHandleOrgs.Any())
+                    nextDto.NextHandlers.AddRange(nextHandleOrgs.Select(d => new FlowStepHandler
+                    {
+                        Key = d.Id,
+                        Value = d.Name,
+                        OrgId = d.Id,
+                        OrgName = d.Name
+                    }));
+                nextDto.IsStartCountersign = nextDto.NextHandlers.Count > 1;
+
+                await HandleNextInMainAndSecondaryAsync(_sessionContext, workflow.WorkflowDefinition,
+                    secondaryOrgs, nextDto, order.ExpiredTime, isAutoFillSummaryOpinion,
+                    cancellationToken);
+
+                //抄送
+                var ccs = orderHandleFlowDto.Copys
+                     .Where(d => !string.IsNullOrEmpty(d.OrgId)
+                                 || !string.IsNullOrEmpty(d.RoleId)
+                                 || !string.IsNullOrEmpty(d.UserId))
+                     .Select(d => _mapper.Map<OrderCarboncopy>(d))
+                     .Distinct()
+                     .ToList();
+                ccs.ForEach(d => d.OrderId = order.Id);
+                await _orderCarboncopy.AddRangeAsync(ccs, HttpContext.RequestAborted);
+
                 break;
             default:
                 throw new ArgumentOutOfRangeException();
         }
     }
 
+    private async Task HandleNextInMainAndSecondaryAsync(ISessionContext current, WorkflowDefinition definition, List<OrgDto> orgs,
+      NextWorkflowDto? flowDto, DateTime? expiredTime, bool isAutoFillSummaryOpinion, CancellationToken cancellation)
+    {
+        if (flowDto is null || !flowDto.NextHandlers.Any()) return;
+        var currentSteps = await _workflowDomainService.NextAsync(current, flowDto, expiredTime,
+            isAutoFillSummaryOpinion, cancellation);
+
+        foreach (var currentStep in currentSteps)
+        {
+            var currentStepHandlerOrgId = currentStep?.HandlerOrgId;
+            if (string.IsNullOrEmpty(currentStepHandlerOrgId))
+                throw new UserFriendlyException($"数据异常, 待办部门id为空, stepId: {currentStep.Id}");
+            var nextStepHandlerOrgLevel = currentStepHandlerOrgId.CalcOrgLevel() + 1;
+            var nextHandlers = orgs.Where(d => d.Level == nextStepHandlerOrgLevel && d.Id.StartsWith(currentStepHandlerOrgId))
+                .Select(d => new FlowStepHandler
+                {
+                    Key = d.Id,
+                    Value = d.Name,
+                    OrgId = d.Id,
+                    OrgName = d.Name
+                })
+                .ToList();
+            if (nextHandlers.Any())
+            {
+                var nextStepDefine = definition.FindStepDefines(currentStep.NextSteps.Select(d => d.Code))
+                    .FirstOrDefault(d =>
+                        d.HandlerType == EHandlerType.OrgLevel &&
+                        d.HandlerTypeItems.Any(x => x.Key == nextStepHandlerOrgLevel.ToString()));
+                if (nextStepDefine == null)
+                    throw new UserFriendlyException($"流程模板未配置该部门等级, defineId: {definition.Id}, level: {nextStepHandlerOrgLevel}");
+
+                var nextDto = new NextWorkflowDto
+                {
+                    WorkflowId = flowDto.WorkflowId,
+                    StepId = currentStep.Id,
+                    NextStepCode = nextStepDefine.Code,
+                    NextStepName = nextStepDefine.Name,
+                    FlowDirection = EFlowDirection.OrgToOrg,
+                    HandlerType = nextStepDefine.HandlerType,
+                    StepType = nextStepDefine.StepType,
+                    IsSms = false,
+                    NextHandlers = nextHandlers,
+                    IsStartCountersign = nextHandlers.Count > 1,
+                    BusinessType = nextStepDefine.BusinessType,
+                };
+
+                await HandleNextInMainAndSecondaryAsync(current, definition, orgs, nextDto, expiredTime, isAutoFillSummaryOpinion, cancellation);
+            }
+        }
+    }
+
     private async Task AverageSendOrderAsync(NextWorkflowDto nextDto, CancellationToken cancellationToken)
     {
         // 平均派单
@@ -4147,6 +4384,7 @@ public class OrderController : BaseController
             .ToList().Adapt<List<SystemDicDataOutDto>>();
 
         rsp.CounterSignType = order.CounterSignType;
+        await _orderSnapshotApplication.GetNextStepsDatabaseAsync(rsp, orderId);
         return rsp;
     }
 
@@ -4276,8 +4514,12 @@ public class OrderController : BaseController
         ////todo 自贡add 4
         //FocusOnEvents.Add(new Kv { Key = "4", Value = "24小时办结" });
 
+        var industryItems = await _industryRepository.Queryable()
+            .Select(d => new { d.Id, d.Name, })
+            .ToListAsync();
         var rsp = new
         {
+            Industry = industryItems,
             TranspondCity = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.TranspondCity),
             ChannelOptions = _sysDicDataCacheManager.GetSysDicDataCache(TimeLimitBaseDataConsts.SourceChannel),
             AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
@@ -4411,6 +4653,19 @@ public class OrderController : BaseController
         return _mapper.Map<IReadOnlyList<OrderFlowTraceDto>>(traces);
     }
 
+    /// <summary>
+    /// 流转到坐席的次数
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("count-of-flow-to-seat/{orderId}")]
+    public async Task<int> FlowToSeatCountAsync(string orderId)
+    {
+        var order = await _orderRepository.Queryable()
+            .Includes(d => d.WorkflowTraces)
+            .FirstAsync(d => d.Id == orderId, HttpContext.RequestAborted);
+        return order.WorkflowTraces.Count(d => d.IsOrigin && d.BusinessType == EBusinessType.Seat);
+    }
+
     #endregion
 
     #region 工单待办
@@ -4587,14 +4842,23 @@ public class OrderController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpGet("waited/center/base")]
-    public async Task<object> WaitedForCenterBaseData()
+    public async Task<Dictionary<string, object>> WaitedForCenterBaseData()
     {
-        var rsp = new
+        var rsp = new Dictionary<string, object>
         {
-            OrderStatus = EnumExts.GetDescriptions<EOrderStatus>(),
-            ExpiredStatus = EnumExts.GetDescriptions<EExpiredStatus>(),
-            StepNames = new string[] { "话务部", "派单组", "班长审批" }
+            {"orderStatus" ,  EnumExts.GetDescriptions<EOrderStatus>() },
+            {"expiredStatus",  EnumExts.GetDescriptions<EExpiredStatus>() },
+            { "stepNames" , new string[] { "话务部", "派单组", "班长审批" } },
         };
+
+        if (_systemSettingCacheManager.Snapshot)
+        {
+            var industry = await _industryRepository.Queryable()
+           .Select(d => new { d.Id, d.Name, })
+           .ToListAsync();
+            rsp.Add("industry", industry);
+        }
+
         return rsp;
     }
 
@@ -4733,7 +4997,7 @@ public class OrderController : BaseController
                 dto.Handler = handler;
             }
         }
-
+         
         if (oneSendBack || twoSendBack)
         {
             var sendBack = await _orderSendBackAuditRepository.Queryable()

+ 75 - 0
src/Hotline.Api/Controllers/OrderModuleControllers/OrderCarbonCopyController.cs

@@ -0,0 +1,75 @@
+using Hotline.Application.Orders;
+using Hotline.Caching.Services;
+using Hotline.FlowEngine.WorkflowModules;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Settings.TimeLimits;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Order.CarbonCopy;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Requests;
+using MapsterMapper;
+using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using XF.Domain.Repository;
+using XF.Utility.EnumExtensions;
+using Hotline.Caching.Interfaces;
+using Mapster;
+
+namespace Hotline.Api.Controllers.OrderModuleControllers
+{
+    /// <summary>
+    /// 工单抄送
+    /// </summary>
+    public class OrderCarbonCopyController : BaseController
+    {
+        private readonly IOrderCarbonCopyApplication _orderCarbonCopyApplication;
+        private readonly ISystemDicDataCacheManager _sysDicDataCacheManager;
+        private readonly IMapper _mapper;
+
+        public OrderCarbonCopyController(
+            IOrderCarbonCopyApplication orderCarbonCopyApplication,
+            ISystemDicDataCacheManager sysDicDataCacheManager,
+            IMapper mapper
+        )
+        {
+            _orderCarbonCopyApplication = orderCarbonCopyApplication;
+            _sysDicDataCacheManager = sysDicDataCacheManager;
+            _mapper = mapper;
+        }
+
+        [HttpGet]
+        public async Task<IReadOnlyList<OrderDto>> Query([FromQuery] QueryCarbonCopyRequest dto)
+        {
+            var orderccs = await _orderCarbonCopyApplication
+                .Query(dto)
+                .ToPageListWithoutTotalAsync(dto, HttpContext.RequestAborted);
+
+            var orderdtos = orderccs.Select(d => d.Order.Adapt<OrderDto>()).ToList();
+            return orderdtos;
+        }
+
+        [HttpGet("count")]
+        public async Task<int> Count([FromQuery] QueryCarbonCopyRequest dto)
+        {
+            return await _orderCarbonCopyApplication
+                .Query(dto)
+                .CountAsync(HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 列表页面基础数据
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("base-data")]
+        public async Task<object> BaseData()
+        {
+            var rsp = new
+            {
+                AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
+            };
+            return rsp;
+        }
+    }
+}

+ 1 - 1
src/Hotline.Api/Controllers/OrderControlls/OrderComplementController.cs → src/Hotline.Api/Controllers/OrderModuleControllers/OrderComplementController.cs

@@ -2,7 +2,7 @@
 using Hotline.Share.Dtos.Order;
 using Microsoft.AspNetCore.Mvc;
 
-namespace Hotline.Api.Controllers.OrderControlls;
+namespace Hotline.Api.Controllers.OrderModuleControllers;
 
 public class OrderComplementController : BaseController
 {

+ 281 - 2
src/Hotline.Api/Controllers/Snapshot/IndustryController.cs

@@ -1,7 +1,18 @@
-using Hotline.Application.Snapshot;
+using Amazon.Runtime.Internal.Transform;
+using Hotline.Application.Snapshot;
+using Hotline.Caching.Interfaces;
+using Hotline.Configurations;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Settings;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using SqlSugar;
 
 namespace Hotline.Api.Controllers.Snapshot;
 
@@ -11,14 +22,36 @@ namespace Hotline.Api.Controllers.Snapshot;
 public class IndustryController : BaseController
 {
     private readonly IIndustryRepository _industryRepository;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
     private readonly IIndustryApplication _industryApplication;
+    private readonly ISystemAreaDomainService _systemAreaDomainService;
+    private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+    private readonly ISystemOrganizeRepository _systemOrganizeRepository;
 
-    public IndustryController(IIndustryRepository industryRepository, IIndustryApplication industryApplication)
+    public IndustryController(IIndustryRepository industryRepository, IIndustryApplication industryApplication, ISystemDicDataCacheManager systemDicDataCacheManager, ISystemAreaDomainService systemAreaDomainService, IOptionsSnapshot<AppConfiguration> appOptions, ISystemOrganizeRepository systemOrganizeRepository)
     {
         _industryRepository = industryRepository;
         _industryApplication = industryApplication;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _systemAreaDomainService = systemAreaDomainService;
+        _appOptions = appOptions;
+        _systemOrganizeRepository = systemOrganizeRepository;
     }
 
+    /// <summary>
+    /// 添加行业页面基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("basedata")]
+    public async Task<Dictionary<string, object>> GetBaseData()
+    {
+        return new Dictionary<string, object>
+        {
+            { "department", await _systemOrganizeRepository.GetOrgEnabled() },
+            { "acceptType", _systemDicDataCacheManager.AcceptType},
+            { "bulletinType", _systemDicDataCacheManager.SnapshotBulletinType}
+        };
+    }
     /// <summary>
     /// 新增行业
     /// </summary>
@@ -26,4 +59,250 @@ public class IndustryController : BaseController
     [HttpPost]
     public async Task<string> AddIndustry([FromBody] AddIndustryDto dto)
         => await _industryApplication.AddIndustryAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 行业详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("{id}")]
+    public async Task<IndustryDetailOutDto> GetIndustryDetailAsync(string id)
+        => await _industryApplication.GetIndustryDetailAsync(id);
+
+    /// <summary>
+    /// 获取行业集合
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("industry")]
+    public async Task<PagedDto<IndustryItemsOutDto>> GetIndustryAsync([FromQuery] IndustryListInDto dto)
+        => (await _industryApplication.GetIndustres(dto).ToPagedListAsync(dto, HttpContext.RequestAborted)).ToPaged();
+
+    /// <summary>
+    /// 修改行业
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut]
+    public async Task UpdateIndustry([FromBody] UpdateIndustryInDto dto)
+        => await _industryApplication.UpdateIndustryAsync(dto, HttpContext.RequestAborted);
+
+    #region 行业线索
+    /// <summary>
+    /// 行业线索集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("case")]
+    public async Task<PagedDto<IndustryCaseItemOutDto>> GetIndustryCaseItemAsync([FromQuery] IndustryCaseItemInDto dto)
+        => (await _industryApplication.GetIndustryCaseItems(dto).ToPagedListAsync(dto, HttpContext.RequestAborted)).ToPaged();
+
+    /// <summary>
+    /// 添加行业线索
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("case")]
+    public async Task AddIndustryCaseAsync([FromBody] AddIndustryCaseDto dto)
+        => await _industryApplication.AddIndustryCaseAsync(dto);
+
+    /// <summary>
+    /// 更新行业线索
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("case")]
+    public async Task UpdateIndustryCaseAsync([FromBody] UpdateIndustryCaseDto dto)
+        => await _industryApplication.UpdateIndustryCaseAsync(dto);
+
+    /// <summary>
+    /// 获取行业线索详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("case/{id}")]
+    public async Task<IndustryCase> GetIndustryCaseDetailAsync(string id)
+        => await _industryApplication.GetIndustryCaseAsync(id);
+
+    /// <summary>
+    /// 页面基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("case/database")]
+    public async Task<Dictionary<string, object>> GetIndustryCaseDataBaseAsync()
+    {
+        var items = await _industryRepository.Queryable()
+            .Select(m => new { m.Id, m.Name })
+            .ToListAsync();
+        return new Dictionary<string, object>
+        {
+            { "industry", items }
+        };
+    }
+    #endregion
+
+    #region 行业短信模板
+    /// <summary>
+    /// 行业短信模板集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("sms_template")]
+    public async Task<PagedDto<SnapshotSMSTemplateItemsOutDto>> GetSmsTemplateItemsAsync([FromQuery] SnapshotSMSTemplateItemsInDto dto)
+        => (await _industryApplication.GetSMSTemplates(dto).ToPagedListAsync(dto, HttpContext.RequestAborted)).ToPaged();
+
+    /// <summary>
+    /// 添加行业短信模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("sms_template")]
+    public async Task AddSmsTemplateAsync([FromBody] AddSnapshotSMSTemplateInDto dto)
+        => await _industryApplication.AddSMSTemplateAsync(dto);
+
+    /// <summary>
+    /// 修改行业短信模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("sms_template")]
+    public async Task UpdateSmsTemplateAsync([FromBody] UpdateSnapshotSMSTemplateInDto dto)
+        => await _industryApplication.UpdateSMSTemplateAsync(dto);
+
+    /// <summary>
+    /// 短信详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("sms_template/{id}")]
+    public async Task<SnapshotSMSTemplateItemsOutDto> GetSMSTemplateDetailAsync(string id)
+        => await _industryApplication.GetSMSTemplateDetailAsync(id);
+    #endregion
+
+    #region 区域从业人员
+    /// <summary>
+    /// 区域从业人员集合
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("practitioner")]
+    public async Task<PagedDto<PractitionerItemsOutDto>> GetPractitionerItemsAsync([FromQuery] PractitionerItemsInDto dto)
+        => (await _industryApplication.GetPractitionerItemsAsync(dto).ToPagedListAsync(dto, HttpContext.RequestAborted)).ToPaged();
+
+    /// <summary>
+    /// 添加区域从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("practitioner")]
+    public async Task<string> AddPractitionerAsync([FromBody] AddPractitionerInDto dto)
+        => await _industryApplication.AddPractitionerAsync(dto);
+
+    /// <summary>
+    /// 删除区域从业人员
+    /// </summary>
+    /// <param name="ids"></param>
+    /// <returns></returns>
+    [HttpDelete("practitioner")]
+    public async Task DeletePractitionerAsync([FromBody] IList<string> ids)
+        => await _industryApplication.DeletePractitionerAsync(ids);
+
+    /// <summary>
+    /// 修改区域从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("practitioner")]
+    public async Task UpdatePractitionerAsync(UpdatePractitionerInDto dto)
+        => await _industryApplication.UpdatePractitionerAsync(dto);
+
+    /// <summary>
+    /// 从业人员详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("practitioner/{id}")]
+    public async Task<PractitionerItemsOutDto> GetPractitionerAsync(string id)
+        => await _industryApplication.GetPractitionerAsync(id);
+
+    /// <summary>
+    /// 添加从业人员基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("practitioner/basedata")]
+    public async Task<Dictionary<string, object>> GetPractitionerDataBaseAsync()
+    {
+        var parentId = "";
+        if (_appOptions.Value.IsZiGong) parentId = _appOptions.Value.ZiGong.AreaCode;
+        return new Dictionary<string, object>
+        {
+            { "area", await _systemAreaDomainService.GetAreaTree(parentId: parentId)}
+        };
+    }
+    #endregion
+
+    #region 志愿者
+    /// <summary>
+    /// 志愿者集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("volunteer")]
+    public async Task<PagedDto<VolunteerItemsOutDto>> GetVolunteerItemsAsync([FromQuery] VolunteerItemsInDto dto)
+        => (await _industryApplication.GetVolunteerItemsAsync(dto).ToPagedListAsync(dto, HttpContext.RequestAborted)).ToPaged();
+
+    /// <summary>
+    /// 添加志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("volunteer")]
+    public async Task<string> AddVolunteerAsync([FromBody] AddVolunteerInDto dto)
+        => await _industryApplication.AddVolunteerAsync(dto);
+
+    /// <summary>
+    /// 批量删除志愿者
+    /// </summary>
+    /// <param name="ids"></param>
+    /// <returns></returns>
+    [HttpDelete("volunteer")]
+    public async Task DeleteVolunteerAsync([FromBody] IList<string> ids)
+        => await _industryApplication.DeleteVolunteerAsync(ids);
+
+    /// <summary>
+    /// 志愿者详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("volunteer/{id}")]
+    public async Task<Volunteer> GetVolunteerAsync(string id)
+        => await _industryApplication.GetVolunteerAsync(id);
+
+    /// <summary>
+    /// 修改志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("volunteer")]
+    public async Task UpdateVolunteerAsync([FromBody] UpdateVolunteerInDto dto)
+        => await _industryApplication.UpdateVolunteerAsync(dto);
+
+    /// <summary>
+    /// 志愿者上报集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("volunteer/report")]
+    public async Task<PagedDto<VolunteerReportItemsOutDto>> GetVolunteerReportItemsAsync([FromQuery] VolunteerReportItemsInDto dto)
+        => (await _industryApplication.GetVolunteerReportItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+    #endregion
+
+    #region 修改行业记录
+    /// <summary>
+    /// 行业修改记录
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("order/industry/log")]
+    public async Task<PagedDto<IndustryLogItemsOutDto>> GetIndustryLogItemsAsync([FromQuery] IndustryLogItemsInDto dto)
+        => (await _industryApplication.GetIndustryLogItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+    #endregion
 }

+ 193 - 0
src/Hotline.Api/Controllers/Snapshot/RedPackController.cs

@@ -0,0 +1,193 @@
+using Hotline.Application.Snapshot;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Snapshot;
+using Microsoft.AspNetCore.Mvc;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Caching.Interfaces;
+using XF.Utility.EnumExtensions;
+using Hotline.Share.Enums.Snapshot;
+using Quartz.Impl.Triggers;
+
+namespace Hotline.Api.Controllers.Snapshot;
+
+public class RedPackController : BaseController
+{
+    private readonly IRedPackApplication _redPackApplication;
+    private readonly IIndustryRepository _industryRepository;
+    private readonly ISystemDicDataCacheManager _sysDic;
+    private readonly ISnapshotSMSTemplateRepository _snapshotSMSTemplateRepository;
+
+    public RedPackController(IRedPackApplication redPackApplication, IIndustryRepository industryRepository, ISystemDicDataCacheManager sysDic, ISnapshotSMSTemplateRepository snapshotSMSTemplateRepository)
+    {
+        _redPackApplication = redPackApplication;
+        _industryRepository = industryRepository;
+        _sysDic = sysDic;
+        _snapshotSMSTemplateRepository = snapshotSMSTemplateRepository;
+    }
+
+    #region 红包审核
+    /// <summary>
+    /// 获取市民红包审批列表
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("audit")]
+    public async Task<PagedDto<SnapshotOrderAuditItemsOutDto>> GetRedPackAuditItemsAsync([FromQuery] SnapshotOrderAuditItemsInDto dto)
+        => (await _redPackApplication.GetRedPackAuditItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 获取网格员红包审批集合(政法委或应急管理局:根据账号登录返回数据)
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("audit/guider")]
+    public async Task<PagedDto<SnapshotOrderGuiderAuditItemsOutDto>> GetRedPackGuiderAuditItemsAsync([FromQuery] SnapshotOrderGuiderAuditItemsInDto dto)
+        => (await _redPackApplication.GetRedPackGuiderAuditItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 获取网格员审核详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    [HttpGet("audit/guider/{id}")]
+    public async Task<SnapshotOrderAuditDetailOutDto> GetRedPackGuiderAuditDetailAsync(string id)
+        => await _redPackApplication.GetRedPackGuiderAuditDetailAsync(id);
+
+    /// <summary>
+    /// 网格员红包审核通过或拒绝
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("audit/guider")]
+    public async Task UpdateRedPackGuiderAuditAsync([FromBody] UpdateRedPackGuiderAuditInDto dto)
+        => await _redPackApplication.AuditRedPackGuiderAuditAsync(dto);
+
+    /// <summary>
+    /// 获取审核详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    [HttpGet("audit/{id}")]
+    public async Task<SnapshotOrderAuditDetailOutDto> GetRedPackAuditDetailAsync(string id)
+        => await _redPackApplication.GetRedPackAuditDetailAsync(id);
+
+    /// <summary>
+    /// 获取特提(退回)参数
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("audit/back/{id}")]
+    public async Task<GetAuditBackBaseDataOutDto> GetAuditBackBaseDataAsync(string id)
+        => await _redPackApplication.GetAuditBackBaseDataAsync(id);
+
+    /// <summary>
+    /// 添加备注
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("audit/remark")]
+    public async Task UpdateRedPackAuditRemarkAsync([FromBody] UpdateRedPackAuditRemarkInDto dto)
+        => await _redPackApplication.UpdateRedPackAuditRemarkAsync(dto);
+
+    /// <summary>
+    /// 添加备注页面基础信息
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("audit/remark/basedata")]
+    public async Task<Dictionary<string, object>> UpdateRedPackAuditRemarkBaseDataAsync()
+    {
+        return new Dictionary<string, object>()
+        {
+            { "failCase", EnumExts.GetDescriptions<ERedPackPickupFailCase>() }
+        };
+    }
+
+    /// <summary>
+    /// 添加补充发放信息页面基础信息
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("record/basedata")]
+    public async Task<Dictionary<string, object>> UpdateRedPackRecordBaseDataAsync()
+    { 
+        return new Dictionary<string, object>()
+        {
+            { "replenishType", _sysDic.SnapshotReplenishType }
+        };
+    }
+
+    /// <summary>
+    /// 添加补充发放信息
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("record")]
+    public async Task UpdateRedPackRecordAsync([FromBody] UpdateRedPackRecordInDto dto)
+        => await _redPackApplication.UpdateRedPackRecordAsync(dto);
+
+    /// <summary>
+    /// 红包审核通过或拒绝
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPut("audit")]
+    public async Task UpdateRedPackAuditAsync([FromBody] UpdateRedPackAuditInDto dto)
+        => await _redPackApplication.AuditRedPackAuditAsync(dto);
+
+    /// <summary>
+    /// 获取审核短信模板
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("audit/sms_template")]
+    public async Task<IList<GetRedPackAuditSMSTemplateOutDto>> GetRedPackAuditSMSTemplateAsync([FromQuery]GetRedPackAuditSMSTemplateInDto dto)
+        => await _redPackApplication.GetRedPackAuditSMSTemplateAsync(dto);
+
+    /// <summary>
+    /// 红包审批页面基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("audit/basedata")]
+    public async Task<Dictionary<string, object>> GetAuditBaseDataAsync()
+    {
+        var industry = await _industryRepository.Queryable()
+            .Select(m => new { m.Id, m.Name })
+            .ToListAsync();
+
+        var configAmount = await _industryRepository.Queryable()
+            .Select(m => m.CitizenReadPackAmount)
+            .Distinct()
+            .ToListAsync();
+        var sms = await _snapshotSMSTemplateRepository.Queryable()
+            .ToListAsync();
+        return new Dictionary<string, object>
+        {
+            { "industry", industry },
+            { "configAmount",  configAmount },
+            { "status", EnumExts.GetDescriptions<ERedPackAuditStatus>() }
+        };
+    }
+    #endregion
+
+    #region 红包发放记录
+
+    /// <summary>
+    /// 市民红包发放记录
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("record")]
+    public async Task<PagedDto<SnapshotRedPackRecordItemsOutDto>> GetRedPackRecordItemsAsync([FromQuery] SnapshotRedPackRecordItemsInDto dto)
+        => (await _redPackApplication.GetRedPackRecordItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 批量发送红包
+    /// </summary>
+    /// <param name="ids"></param>
+    /// <returns></returns>
+    [HttpPut("record/send_bath")]
+    public async Task<string> BathSendRedPackAsync([FromBody] List<string> ids)
+    {
+        return "ok";
+    }
+    #endregion
+}

+ 250 - 0
src/Hotline.Api/Controllers/Snapshot/SnapshotBulletinController.cs

@@ -0,0 +1,250 @@
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Article;
+using Hotline.Snapshot.Interfaces;
+using Microsoft.AspNetCore.Mvc;
+using Hotline.Repository.SqlSugar.Extensions;
+using Mapster;
+using Hotline.Application.Snapshot;
+using XF.Domain.Exceptions;
+using Hotline.Share.Enums.Article;
+using XF.Domain.Authentications;
+using Hotline.Caching.Interfaces;
+using Hotline.Settings;
+using Hotline.Article;
+using Hotline.Snapshot;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Api.Controllers.Snapshot;
+
+public class SnapshotBulletinController : BaseController
+{
+    private readonly ISnapshotBulletinRepository _bulletinRepository;
+    private readonly ISnapshotBulletinApplication _bulletinApplication;
+    private readonly ISessionContext _sessionContext;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly ISystemOrganizeRepository _organizeRepository;
+
+    public SnapshotBulletinController(ISnapshotBulletinRepository bulletinRepository, ISessionContext sessionContext, ISystemDicDataCacheManager systemDicDataCacheManager, ISystemOrganizeRepository organizeRepository, ISnapshotBulletinApplication bulletinApplication)
+    {
+        _bulletinRepository = bulletinRepository;
+        _sessionContext = sessionContext;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _organizeRepository = organizeRepository;
+        _bulletinApplication = bulletinApplication;
+    }
+
+    #region 公告
+
+    /// <summary>
+    /// 查询公告列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("bulletin/query")]
+    public async Task<PagedDto<SnapshotBulletinItemsOutDto>> QueryBulletinList([FromQuery] SnapshotBulletinItemsInDto dto)
+    {
+        var query = _bulletinRepository.Queryable()
+            .Includes(x => x.ExaminMan)
+            .WhereIF(!string.IsNullOrEmpty(dto.SnapshotBulletinTypeName), d => d.SnapshotBulletinTypeName.Contains(dto.SnapshotBulletinTypeName))
+            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Title.Contains(dto.Title))
+            .WhereIF(dto.BeginCreationTime.HasValue, d => d.BulletinTime >= dto.BeginCreationTime)
+            .WhereIF(dto.EndCreationTime.HasValue, d => d.BulletinTime <= dto.EndCreationTime)
+            .WhereIF(dto.State.HasValue, d => d.BulletinState == dto.State)
+            .OrderByDescending(d => d.CreationTime)
+            .Select<SnapshotBulletinItemsOutDto>();
+        return (await query
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted)).ToPaged();
+    }
+
+    /// <summary>
+    /// 公告详情(内部)
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("bulletin/entity/{id}")]
+    public async Task<SnapshotBulletinDetailOutDto> BulletinEntity(string id)
+    {
+        var model = await _bulletinRepository.Queryable()
+            .Includes(x => x.ExaminMan)
+            .FirstAsync(x => x.Id == id, HttpContext.RequestAborted);
+
+        if (model != null && !string.IsNullOrEmpty(model.Content))
+            model.Content = _bulletinApplication.GetSiteUrls(model.Content);
+
+        return model.Adapt<SnapshotBulletinDetailOutDto>();
+    }
+
+    /// <summary>
+    /// 公告上架或者下架
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("bulletin-arrive")]
+    public async Task BulletinArrive([FromBody] BulletinArriveDto dto)
+    {
+        var bulletin = await _bulletinRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+        if (bulletin == null)
+            throw UserFriendlyException.SameMessage("无效数据");
+
+        if (bulletin.BulletinState != EBulletinState.ReviewPass)
+            throw UserFriendlyException.SameMessage("当前状态不能操作上架或下架");
+
+        bulletin.IsArrive = dto.IsArrive;
+        if (bulletin.IsArrive == false)
+        {
+            bulletin.ExaminTime = null;
+            bulletin.ExaminManId = null;
+            bulletin.ExaminOpinion = null;
+            bulletin.CommitTime = null;
+            bulletin.BulletinState = EBulletinState.Draft;
+        }
+        await _bulletinRepository.UpdateAsync(bulletin, HttpContext.RequestAborted);
+    }
+
+
+    /// <summary>
+    /// 公告审核
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("bulletin/examine")]
+    public async Task ExamineBulletin([FromBody] ExamineBulletinDto dto)
+    {
+        var bulletin = await _bulletinRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+        if (bulletin == null)
+            throw UserFriendlyException.SameMessage("无效数据");
+
+        if (bulletin.BulletinState != EBulletinState.InReview)
+            throw UserFriendlyException.SameMessage("当前状态不能审核");
+
+        if (dto.IsPass)
+        {
+            bulletin.BulletinState = Share.Enums.Article.EBulletinState.ReviewPass;
+            bulletin.BulletinTime = DateTime.Now;
+            bulletin.ExaminOpinion = dto.Reason;
+            bulletin.ExaminTime = DateTime.Now;
+            bulletin.ExaminManId = _sessionContext.RequiredUserId;
+            await _bulletinRepository.UpdateAsync(bulletin, HttpContext.RequestAborted);
+            var publishBulletin = bulletin.Adapt<PublishBulletinDto>();
+
+            //await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlinePushBulletin, publishBulletin, cancellationToken: HttpContext.RequestAborted);
+
+            //todo await _bulletinService.PushBulletin(publishBulletin, HttpContext.RequestAborted);
+        }
+        else
+        {
+            bulletin.ExaminOpinion = dto.Reason;
+            bulletin.ExaminTime = DateTime.Now;
+            bulletin.ExaminManId = _sessionContext.RequiredUserId;
+            bulletin.BulletinState = Share.Enums.Article.EBulletinState.ReviewNoPass;
+            await _bulletinRepository.UpdateAsync(bulletin, HttpContext.RequestAborted);
+        }
+    }
+
+    /// <summary>
+    /// 提交公告
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("bulletin/commit")]
+    public async Task CommitBulletin(string id)
+    {
+        var bulletin = await _bulletinRepository.GetAsync(id, HttpContext.RequestAborted);
+        if (bulletin == null)
+            throw UserFriendlyException.SameMessage("无效数据");
+
+        if (bulletin.BulletinState != EBulletinState.Draft && bulletin.BulletinState != EBulletinState.ReviewNoPass)
+            throw UserFriendlyException.SameMessage("当前状态不能提交");
+
+        bulletin.BulletinState = EBulletinState.InReview;
+        bulletin.CommitTime = DateTime.Now;
+        await _bulletinRepository.UpdateAsync(bulletin, HttpContext.RequestAborted);
+    }
+
+    /// <summary>
+    /// 修改公告
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("bulletin/update")]
+    public async Task UpdateBulletin([FromBody] UpdateSnapshotBulletinInDto dto)
+    {
+        var bulletin = await _bulletinRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
+        if (bulletin == null)
+            throw UserFriendlyException.SameMessage("无效数据");
+
+        if (bulletin.BulletinState != EBulletinState.Draft && bulletin.BulletinState != EBulletinState.ReviewNoPass)
+            throw UserFriendlyException.SameMessage("当前状态不能修改");
+
+        dto.Adapt(bulletin);
+        await _bulletinRepository.UpdateAsync(bulletin, HttpContext.RequestAborted);
+    }
+
+    /// <summary>
+    /// 删除公告
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("bulletin/del/{id}")]
+    public async Task DelBulletin(string id)
+    {
+        var bulletin = await _bulletinRepository.GetAsync(id, HttpContext.RequestAborted);
+        if (bulletin == null)
+            throw UserFriendlyException.SameMessage("无效数据");
+
+        if (bulletin.BulletinState != EBulletinState.Draft)
+            throw UserFriendlyException.SameMessage("当前状态不能删除");
+
+        await _bulletinRepository.RemoveAsync(x => x.Id == id, false, HttpContext.RequestAborted);
+    }
+
+    /// <summary>
+    /// 新增公告
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("bulletin/add")]
+    public async Task AddBulletin([FromBody] AddSnapshotBulletinInDto dto)
+    {
+        var model = dto.Adapt<SnapshotBulletin>();
+        model.BulletinState = Share.Enums.Article.EBulletinState.Draft;
+        model.ReadedNum = 0;
+        if (model.BulletinTime.HasValue == false) model.BulletinTime = DateTime.Now;
+        await _bulletinRepository.AddAsync(model, HttpContext.RequestAborted);
+    }
+
+    /// <summary>
+    /// 列表页面基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("bulletin/listbasedata")]
+    public async Task<object> BulletinListBaseData()
+    {
+        var rsp = new
+        {
+            BulletinType = _systemDicDataCacheManager.SnapshotBulletinType,
+            BulletinSource = _systemDicDataCacheManager.SnapshotBulletinSource,
+            BulletinState = EnumExts.GetDescriptions<EBulletinState>()
+        };
+        return rsp;
+    }
+
+    /// <summary>
+    /// 新增页面基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("bulletin/addbasedata")]
+    public async Task<object> BulletinAddBaseData()
+    {
+        var rsp = new
+        {
+            BulletinType = _systemDicDataCacheManager.SnapshotBulletinType,
+            BulletinSource = _systemDicDataCacheManager.SnapshotBulletinSource,
+            OrgsOptions = await _organizeRepository.GetOrgJson(),
+        };
+        return rsp;
+    }
+    #endregion
+}

+ 226 - 15
src/Hotline.Api/Controllers/Snapshot/SnapshotController.cs

@@ -1,15 +1,29 @@
-using Hotline.Application.Snapshot;
+using Hotline.Api.Filter;
+using Hotline.Application.Orders;
+using Hotline.Application.Snapshot;
+using Hotline.Caching.Interfaces;
+using Hotline.File;
 using Hotline.Orders;
 using Hotline.Settings;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Article;
+using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Tools;
 using Mapster;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
 using System.ComponentModel.DataAnnotations;
 using System.Reflection;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers.Snapshot;
@@ -22,12 +36,24 @@ public class SnapshotController : BaseController
     private readonly IRepository<Order> _orderRepository;
     private readonly ISnapshotApplication _snapshotApplication;
     private readonly ISystemAreaDomainService _systemAreaDomainService;
+    private readonly IIndustryRepository _industryRepository;
+    private readonly IOrderDomainService _orderDomainService;
+    private readonly IFileRepository _fileRepository;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly ISessionContext _sessionContext;
 
-    public SnapshotController(IRepository<Order> orderRepository, ISnapshotApplication snapshotApplication, ISystemAreaDomainService systemAreaDomainService)
+    public SnapshotController(IRepository<Order> orderRepository, ISnapshotApplication snapshotApplication, ISystemAreaDomainService systemAreaDomainService, IIndustryRepository industryRepository, IOrderDomainService orderDomainService, IFileRepository fileRepository, IOrderSnapshotRepository orderSnapshotRepository, ISystemDicDataCacheManager systemDicDataCacheManager, ISessionContext sessionContext)
     {
         _orderRepository = orderRepository;
         _snapshotApplication = snapshotApplication;
         _systemAreaDomainService = systemAreaDomainService;
+        _industryRepository = industryRepository;
+        _orderDomainService = orderDomainService;
+        _fileRepository = fileRepository;
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _sessionContext = sessionContext;
     }
 
     /// <summary>
@@ -39,7 +65,6 @@ public class SnapshotController : BaseController
     public async Task<HomePageOutDto> GetHomePageAsync()
         => await _snapshotApplication.GetHomePageAsync();
 
-
     /// <summary>
     /// 行业界面基础信息
     /// </summary>
@@ -50,6 +75,73 @@ public class SnapshotController : BaseController
     public async Task<IndustryBaseOutDto> GetIndustryBaseAsync(string id)
         => await _snapshotApplication.GetIndustryBaseAsync(id, HttpContext.RequestAborted);
 
+    /// <summary>
+    /// 添加随手拍工单
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("order")]
+    [LogFilter("添加随手拍工单")]
+    public async Task<AddSnapshotOrderOutDto> AddOrderAsync([FromBody] AddSnapshotOrderInDto dto)
+    {
+        var ssp = _systemDicDataCacheManager.SourceChannel.FirstOrDefault(m => m.DicDataName == "随手拍") 
+            ?? throw UserFriendlyException.SameMessage("请添加[随手拍]来源.");
+        var order = dto.Adapt<Order>();
+        dto.ValidateObject();
+        var industry = await _industryRepository.GetAsync(dto.IndustryId, HttpContext.RequestAborted)
+            ?? throw UserFriendlyException.SameMessage("行业不存在:" + dto.IndustryId);
+        order.AcceptTypeCode = industry.AcceptTypeCode;
+        order.AcceptType = industry.AcceptType;
+        order.FromGender = EGender.Unknown;
+        order.Title = dto.GetTitle(industry.IndustryType, industry.AcceptType);
+        order.Content = dto.GetContent(industry.IndustryType);
+        order.FromPhone = _sessionContext.Phone;
+        order.Contact = _sessionContext.Phone;
+        order.SourceChannel = ssp.DicDataName;
+        order.SourceChannelCode = ssp.DicDataValue;
+        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;
+        orderSnapshot.IndustryName = industry.Name;
+        orderSnapshot.CompanyName = dto.CompanyName;
+        await _orderSnapshotRepository.AddAsync(orderSnapshot);
+        return order.Adapt<AddSnapshotOrderOutDto>();
+    }
+
+    /// <summary>
+    /// 获取行业集合
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("industry")]
+    [AllowAnonymous]
+    public async Task<IList<IndustryOutDto>> GetIndustresAsync()
+        => await _snapshotApplication.GetIndustresAsync();
+
+    /// <summary>
+    /// 获取公开的工单集合
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("order/published")]
+    [AllowAnonymous]
+    public async Task<IList<OrderPublishOutDto>> GetPublishOrderAsync([FromQuery] OrderPublishInDto dto)
+        => await _snapshotApplication.GetOrderPublishAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 获取公开的工单详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("order/published/{id}")]
+    [AllowAnonymous]
+    public async Task<OrderPublishDetailOutDto> GetOrderPublishDetailAsync(string id)
+        => await _snapshotApplication.GetOrderPublishDetailAsync(id, HttpContext.RequestAborted);
+
     /// <summary>
     /// 获取小程序公告列表
     /// </summary>
@@ -57,8 +149,17 @@ public class SnapshotController : BaseController
     /// <returns></returns>
     [HttpGet("bulletions")]
     [AllowAnonymous]
-    public virtual async Task<IReadOnlyList<BulletinOutDto>> QueryBulletinsAsync([FromQuery] BulletinInDto dto)
-        => await _snapshotApplication.GetBulletinsAsync(dto);
+    public async Task<IReadOnlyList<BulletinOutDto>> QueryBulletinsAsync([FromQuery] BulletinInDto dto)
+        => await _snapshotApplication.GetBulletinsAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 获取小程序首页弹窗
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("bulletions/popup")]
+    [AllowAnonymous]
+    public async Task<BulletinOutDto> GetBulletionPopupAsync()
+        => await _snapshotApplication.GetBulletionPopupAsync(HttpContext.RequestAborted);
 
     /// <summary>
     /// 公告详情
@@ -67,7 +168,7 @@ public class SnapshotController : BaseController
     /// <returns></returns>
     [HttpGet("bulletions/{id}")]
     [AllowAnonymous]
-    public virtual async Task<BulletinOutDto> QueryBulletionsDetailAsync(string id)
+    public async Task<BulletinOutDto> QueryBulletionsDetailAsync(string id)
         => await _snapshotApplication.GetBulletinsDetailAsync(id);
 
     /// <summary>
@@ -76,7 +177,7 @@ public class SnapshotController : BaseController
     /// <returns></returns>
     [AllowAnonymous]
     [HttpGet("user")]
-    public virtual async Task<SnapshotUserInfoOutDto> GetUserInfo()
+    public async Task<SnapshotUserInfoOutDto> GetUserInfo()
         => await _snapshotApplication.GetSnapshotUserInfoAsync();
 
     /// <summary>
@@ -85,8 +186,8 @@ public class SnapshotController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpGet("order")]
-    public virtual async Task<PagedDto<OrderOutDto>> QueryOrderListAsync([FromQuery] OrderInDto dto)
-        => await _snapshotApplication.GetSnapshotOrdersAsync(dto);
+    public async Task<IList<OrderOutDto>> QueryOrderListAsync([FromQuery] OrderInDto dto)
+        => await _snapshotApplication.GetSnapshotOrdersAsync(dto, HttpContext.RequestAborted);
 
     /// <summary>
     /// 获取我提交的线索详情
@@ -94,8 +195,17 @@ public class SnapshotController : BaseController
     /// <param name="id"></param>
     /// <returns></returns>
     [HttpGet("order/{id}")]
-    public virtual async Task<OrderDetailOutDto> QueryOrderListAsync([FromQuery] string id)
-        => await _snapshotApplication.GetSnapshotOrderDetailAsync(id);
+    public async Task<OrderPublishDetailOutDto> QueryOrderListAsync(string id)
+        => await _snapshotApplication.GetSnapshotOrderDetailAsync(id, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 获取回访详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("order/visit/{id}")]
+    public async Task<IList<OrderVisitItemsOutDto>> GetOrderVisitDetailAsync(string id)
+        => await _snapshotApplication.GetOrderVisitDetailAsync(id);
 
     /// <summary>
     /// 统计红包金额, 每月的总金额
@@ -103,8 +213,16 @@ public class SnapshotController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpGet("redpack")]
-    public virtual async Task<IReadOnlyList<RedPackDateOutDto>> QueryRedPackDateAsync([FromQuery] RedPackDateInDto dto)
-        => await _snapshotApplication.GetRedPackDateAsync(dto);
+    public async Task<IReadOnlyList<RedPackDateOutDto>> QueryRedPackDateAsync([FromQuery] RedPackDateInDto dto)
+        => await _snapshotApplication.GetRedPackDateAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 用户领取红包总金额
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("redpack/received")]
+    public async Task<string> GetRedPackReceivedTotalAsync()
+        => await _snapshotApplication.GetRedPackReceivedTotalAsync(HttpContext.RequestAborted);
 
     /// <summary>
     /// 获取当月详细红包列表
@@ -112,8 +230,8 @@ public class SnapshotController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpGet("redpack/month")]
-    public virtual async Task<PagedDto<RedPackOutDto>> QueryRedPackDateAsync([FromQuery] RedPacksInDto dto)
-        => await _snapshotApplication.GetRedPacksAsync(dto);
+    public async Task<IList<RedPackOutDto>> QueryRedPackDateAsync([FromQuery] RedPacksInDto dto)
+        => await _snapshotApplication.GetRedPacksAsync(dto, HttpContext.RequestAborted);
 
     /// <summary>
     /// 获取随手拍电气焊动火作业待处理工单数量
@@ -130,6 +248,99 @@ public class SnapshotController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpGet("area/tree")]
+    [AllowAnonymous]
     public async Task<List<SystemAreaOutDto>> GetAreaTreeAsync()
         => (await _systemAreaDomainService.GetAreaTree()).Adapt<List<SystemAreaOutDto>>();
+
+    /// <summary>
+    /// 批量添加从业人员
+    /// </summary>
+    /// <param name="dtos"></param>
+    /// <returns></returns>
+    [HttpPost("practitioner")]
+    public async Task AddPractitionerAsync([FromBody]IList<AddBatchPractitionerInDto> dtos)
+        => await _snapshotApplication.AddPractitionerAsync(dtos);
+
+    /// <summary>
+    /// 获取从业人员详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("practitioner/{id}")]
+    [AllowAnonymous]
+    public async Task<PractitionerDetailOutDto> GetPractitionerDetailAsync(string id)
+        => await _snapshotApplication.GetPractitionerDetailAsync(id, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 获取从业人员集合(每次随机)
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("practitioner")]
+    [AllowAnonymous]
+    public async Task<IList<PractitionerItemOutDto>> GetPractitionerDetailAsync([FromQuery]PractitionerItemInDto dto)
+        => await _snapshotApplication.GetPractitionerItemsAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 志愿者上报
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("report")]
+    [LogFilter("志愿者上报")]
+    public async Task<AddVolunteerReportOutDto> AddVolunteerReportAsync([FromBody] AddVolunteerReportInDto dto)
+        => await _snapshotApplication.AddVolunteerReportAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 志愿者上报页面基础数据
+    /// </summary>
+    /// <param name="id">行业Id</param>
+    /// <returns></returns>
+    [HttpGet("report/base")]
+    public Dictionary<string, dynamic> GetReportBaseAsync()
+    { 
+        return new Dictionary<string, dynamic>
+        {
+            { "jobType", _systemDicDataCacheManager.JobType },
+        };
+    }
+
+    /// <summary>
+    /// 用户自己保存邀请码
+    /// </summary>
+    /// <returns></returns>
+    [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("网格员系统回调")]
+    [AllowAnonymous]
+    public async Task SaveGuiderSystemReplayAsync([FromBody] GuiderSystemInDto dto)
+    {
+        await _snapshotApplication.SaveGuiderSystemReplyAsync(dto, HttpContext.RequestAborted);
+    }
 }

+ 206 - 0
src/Hotline.Api/Controllers/Snapshot/SnapshotOrderController.cs

@@ -0,0 +1,206 @@
+using Hotline.Application.FlowEngine;
+using Hotline.Application.Snapshot;
+using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
+using Hotline.FlowEngine.Definitions;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Repository.SqlSugar.Snapshot;
+using Hotline.Settings;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
+using Mapster;
+using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel;
+using System.Text;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Api.Controllers.Snapshot;
+
+/// <summary>
+/// 随手拍工单
+/// </summary>
+[Description("随手拍工单")]
+public class SnapshotOrderController : BaseController
+{
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly IOrderSnapshotApplication _orderSnapshotApplication;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly ISystemAreaDomainService _systemAreaDomainService;
+    private readonly IIndustryRepository _industryRepository;
+    private readonly ISessionContext _sessionContext;
+    private readonly IWorkflowDomainService _workflowDomainService;
+    private readonly IOrderRepository _orderRepository;
+    private readonly IWorkflowApplication _workflowApplication;
+    private readonly IRepository<WorkflowDefinition> _workflowDefinitionRepository;
+
+    public SnapshotOrderController(IOrderSnapshotRepository orderSnapshotRepository, IOrderSnapshotApplication orderSnapshotApplication, ISystemAreaDomainService systemAreaDomainService, ISystemDicDataCacheManager systemDicDataCacheManager, IIndustryRepository industryRepository, ISessionContext sessionContext, IWorkflowDomainService workflowDomainService, IOrderRepository orderRepository, IWorkflowApplication workflowApplication, IRepository<WorkflowDefinition> workflowDefinitionRepository)
+    {
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _orderSnapshotApplication = orderSnapshotApplication;
+        _systemAreaDomainService = systemAreaDomainService;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _industryRepository = industryRepository;
+        _sessionContext = sessionContext;
+        _workflowDomainService = workflowDomainService;
+        _orderRepository = orderRepository;
+        _workflowApplication = workflowApplication;
+        _workflowDefinitionRepository = workflowDefinitionRepository;
+    }
+
+    /// <summary>
+    /// 随手拍所有工单集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("order")]
+    public async Task<PagedDto<OrderSnapshotItemsOutDto>> GetOrderSnapshotItemsAsync([FromQuery] OrderSnapshotItemsInDto dto)
+        => (await _orderSnapshotApplication.GetOrderSnapshotItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 随手拍所有工单页面基础信息
+    /// </summary>
+    [HttpGet("order/basedata")]
+    public async Task<Dictionary<string, object>> GetOrderSnapshotItemsDatabaseAsync()
+    {
+        IList<Kv> steps = new List<Kv>();
+        await _workflowDefinitionRepository.Queryable()
+            .Where(m => m.Status == EDefinitionStatus.Enable && m.Code == "gdbl")
+            .OrderByDescending(m => m.Version)
+            .Select(m => new { m.Id, m.Steps})
+            .FirstAsync().Then(async workflowSteps =>
+            {
+                steps = workflowSteps.Steps.Adapt<IList<NameCodeDto>>()
+               .ToList().Select(m => new Kv 
+               {
+                   Key = m.Code,
+                   Value = m.Name,
+               }).ToList();
+            });
+        return new Dictionary<string, object>
+        {
+            { "orderStatus", EnumExts.GetDescriptions<EOrderStatus>()},
+            { "area", await _systemAreaDomainService.GetAreaKeyValue(parentId: "510300")},
+            { "steps", steps},
+            { "orderTags", _systemDicDataCacheManager.OrderTag},
+            { "industry", await _industryRepository.Queryable().Select(d => new { d.Id, d.Name, }).ToListAsync()}
+        };
+    }
+
+    /// <summary>
+    /// 获取网格员回复集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("guider/reply")]
+    public async Task<PagedDto<GuiderReplyItemsOutDto>> GetGuiderReplyItemsAsync([FromQuery] GuiderReplyItemsInDto dto)
+        => (await _orderSnapshotApplication.GetGuiderReplyItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 获取工单标记集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("sign")]
+    public async Task<PagedDto<SignOrderSnapshotItemsOutDto>> GetSignOrderSnapshotItemsAsync([FromQuery] SignOrderSnapshotItemsInDto dto)
+        => (await _orderSnapshotApplication.GetSignOrderSnapshotItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 获取工单标注日志集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("sign/log")]
+    public async Task<PagedDto<SignOrderSnapshotLogItemsOutDto>> GetSignOrderSnapshotLogItemsAsync([FromQuery] SignOrderSnapshotLogItemsInDto dto)
+        => (await _orderSnapshotApplication.GetSignOrderSnapshotLogItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 随手拍公开集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("publish")]
+    public async Task<PagedDto<GetOrderSnapshotPublishItemsOutDto>> GetOrderSnapshotPublishItemsAsync([FromQuery] GetOrderSnapshotPublishItemsInDto dto)
+        => (await _orderSnapshotApplication.GetOrderSnapshotPublishItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 随手拍公开详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    [HttpGet("publish/{id}")]
+    public async Task<GetOrderSnapshotPublishDetailOutDto> GetOrderSnapshotPublishDetailAsync(string id)
+        => await _orderSnapshotApplication.GetOrderSnapshotPublishDetailAsync(id);
+
+    /// <summary>
+    /// 添加公开审核
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("publish")]
+    public async Task AddOrderSnapshotPublishItemsAsync([FromBody] AddSnapshotOrderPublishInDto dto)
+         => await _orderSnapshotApplication.AddOrderPublishAsync(dto, HttpContext.RequestAborted);
+
+    /// <summary>
+    /// 随手拍公开审批集合
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("publish/audit")]
+    public async Task<PagedDto<GetOrderSnapshotPublishAuditItemsOutDto>> GetOrderSnapshotPublishAuditItemsAsync([FromQuery] GetOrderSnapshotPublishAuditItemsInDto dto)
+        => (await _orderSnapshotApplication.GetOrderSnapshotPublishAuditItemsAsync(dto).ToPagedListAsync(dto)).ToPaged();
+
+    /// <summary>
+    /// 审核随手拍公开申请通过/不通过
+    /// </summary>
+    /// <returns></returns>
+    [HttpPut("publish/status")]
+    public async Task UpdateOrderSnapshotPublishStatusAsync([FromBody] UpdateOrderSnapshotPublishStatusInDto dto)
+        => await _orderSnapshotApplication.UpdateOrderSnapshotPublishStatusAsync(dto);
+
+    /// <summary>
+    /// 随手拍公开审批详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    [HttpGet("publish/audit/{id}")]
+    public async Task<GetOrderSnapshotPublishAuditDetailOutDto> GetOrderSnapshotPublishAuditDetailAsync(string id)
+        => await _orderSnapshotApplication.GetOrderSnapshotPublishAuditDetailAsync(id);
+
+    /// <summary>
+    /// 批量设置随手拍公开申请不通过
+    /// </summary>
+    /// <returns></returns>
+    [HttpPut("publishs/status/refuse")]
+    public async Task UpdateOrderSnapshotPublishsStatusRefuseAsync(IList<string> ids)
+        => await _orderSnapshotApplication.UpdateOrderSnapshotPublishsStatusRefuseAsync(ids);
+
+    /// <summary>
+    /// 随手拍公开集合基础数据
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("publish/basedata")]
+    public async Task<Dictionary<string, object>> GetOrderSnapshotPublishItemsBasedataAsync()
+    {
+        var industry = await _industryRepository.Queryable()
+       .Select(d => new { d.Id, d.Name, })
+       .ToListAsync();
+
+        return new Dictionary<string, object>
+        {
+            { "publishStatus", EnumExts.GetDescriptions<EOrderSnapshotPublishStatus>()},
+            { "acceptCode", _systemDicDataCacheManager.AcceptType.Adapt<List<Kv>>() },
+            { "orderStatus", EnumExts.GetDescriptions<EOrderStatus>()},
+            { "area", await _systemAreaDomainService.GetAreaKeyValue(parentId: "510300")},
+            { "industry", industry}
+        };
+    }
+
+}

+ 4 - 0
src/Hotline.Api/Controllers/SysController.cs

@@ -119,6 +119,7 @@ namespace Hotline.Api.Controllers
         /// 获取菜单
         /// </summary>
         /// <returns></returns>
+        [Permission(EPermission.GetMenuJson)]
         [AllowAnonymous]
         [HttpGet("getmenujson")]
         public async Task<IReadOnlyList<SystemMenu>> GetMenuJson()
@@ -132,6 +133,7 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="dto"></param>
         /// <returns></returns>
+        //[Permission(EPermission.AddMenu)]
         [HttpPost("add-menu")]
         public async Task AddMenu([FromBody] AddMenuDto dto)
         {
@@ -144,6 +146,7 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="dto"></param>
         /// <returns></returns>
+        //[Permission(EPermission.UpdateMenu)]
         [HttpPost("update-menu")]
         public async Task UpdateMenu([FromBody] UpdateMenuDto dto)
         {
@@ -160,6 +163,7 @@ namespace Hotline.Api.Controllers
         /// </summary>
         /// <param name="id"></param>
         /// <returns></returns>
+        //[Permission(EPermission.RemoveMenu)]
         [HttpDelete("removemenu/{id}")]
         public async Task RemoveMenu(string id)
         {

+ 47 - 2
src/Hotline.Api/Controllers/TestController.cs

@@ -39,6 +39,7 @@ using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Realtime;
+using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.FlowEngine;
@@ -71,6 +72,7 @@ using XF.Domain.Repository;
 using static System.Runtime.InteropServices.JavaScript.JSType;
 using Order = Hotline.Orders.Order;
 using Hotline.Share.Dtos.Settings;
+using OrderDto = Hotline.Share.Dtos.Order.OrderDto;
 
 namespace Hotline.Api.Controllers;
 
@@ -141,6 +143,7 @@ public class TestController : BaseController
     private readonly IRepository<OldSendProData> _oldSendProDataRepository;
 	private readonly IOrderScreenRepository _orderScreenRepository;
 	private readonly IRepository<OrderVisit> _orderVisitRepository;
+    private readonly IThirdIdentiyService _thirdIdentiyService;
 
 
 	public TestController(
@@ -196,6 +199,7 @@ ICallApplication callApplication,
 ,
         ICallNativeRepository callNativeRepository,
         IRepository<OldSendProData> oldSendProDataRepository,
+        IThirdIdentiyService thirdIdentiyService,
 		IOrderScreenRepository orderScreenRepository,
 		IRepository<OrderVisit> orderVisitRepository
 		)
@@ -248,10 +252,27 @@ ICallApplication callApplication,
         _expireTime = expireTime;
         _callNativeRepository = callNativeRepository;
         _oldSendProDataRepository = oldSendProDataRepository;
+        _thirdIdentiyService = thirdIdentiyService;
         _orderScreenRepository = orderScreenRepository;
         _orderVisitRepository = orderVisitRepository;
 	}
 
+    /// <summary>
+    /// 测试获取电话号码
+    /// </summary>
+    /// <returns></returns>
+    [HttpGet("phonenumber_test")]
+    [AllowAnonymous]
+    public async Task<ThirdPhoneOutDto> GetPhoneNumberTest()
+    {
+        var inDto = new ThirdTokenDto
+        {
+            AppId = _systemSettingCacheManager.WxOpenAppId,
+            Secret = _systemSettingCacheManager.WxOpenAppSecret
+        };
+        return await _thirdIdentiyService.GetPhoneNumberAsync(inDto);
+    }
+
     /// <summary>
     /// 修改泸州省工单编码区域
     /// </summary>
@@ -526,8 +547,8 @@ ICallApplication callApplication,
         //var r = _timeLimitDomainService.CalcExpiredTime(DateTime.Now, EFlowDirection.CenterToCenter, batchId);
         //var r = _timeLimitDomainService.CalcEndTime(DateTime.Parse("2024-09-12 14:45:47"), Share.Enums.Settings.ETimeType.WorkDay, 2, 80, 50);
         //_capPublisher.PublishDelay((DateTime.Now.AddMinutes(2) - DateTime.Now), EventNames.OrderRelateCall, "123");
-        //var times = _expireTime.CalcExpiredTime(DateTime.Parse("2024-12-11 14:45:58"), DateTime.Parse("2024-12-11 14:45:58"), EFlowDirection.CenterToOrg,new OrderTimeClacInfo() { AcceptTypeCode= "20" });
-        var times = await _expireTime.CalcWorkTimeToDecimal(DateTime.Parse("2024-12-16 21:36:27"), DateTime.Parse("2024-12-17 12:47:05"), false);
+        var times =await _expireTime.CalcExpiredTime(DateTime.Parse("2024-12-31 13:25:13.137977"), DateTime.Parse("2024-12-31 13:25:13.137977"), EFlowDirection.CenterToOrg,new OrderTimeClacInfo() { AcceptTypeCode= "10" });
+        //var times = await _expireTime.CalcWorkTimeToDecimal(DateTime.Parse("2024-12-16 21:36:27"), DateTime.Parse("2024-12-17 12:47:05"), false);
         //await _capPublisher.PublishDelay(EventNames.OrderRelateCall, "123", cancellationToken: HttpContext.RequestAborted);
         return OpenResponse.Ok(times);
     }
@@ -1352,5 +1373,29 @@ ICallApplication callApplication,
         }
     }
 
+    /// <summary>
+    /// 老系统数据同步
+    /// </summary>
+    /// <returns></returns>
+	[HttpGet("old_data_synchronization/{no}")]
+	[AllowAnonymous]
+	public async Task oldDataSynchronization(string no) 
+    {
+        var orders = new List<Order>();
+        if (string.IsNullOrEmpty(no))
+        {
+            orders = await _orderRepository.Queryable().Where(x => x.No.Length == 12 ).ToListAsync();
+        }
+        else {
+			orders = await _orderRepository.Queryable().Where(x => x.No == no).ToListAsync();
+		}
+        foreach (var order in orders) {
+
+			//order.File()
+
+		}
+       
+    }
+
 
 }

+ 1 - 4
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>
@@ -37,10 +38,6 @@
     <None Remove="logs\**" />
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="Controllers\OrderControlls\" />
-  </ItemGroup>
-
   <ItemGroup>
     <Compile Remove="Controllers\Order\OrderComplementController.cs" />
     <Compile Remove="logs\**" />

+ 1 - 0
src/Hotline.Api/Realtimes/RealtimeMethods.cs

@@ -70,6 +70,7 @@
 
         public static string OrderHandlingDetail = "OrderHandlingDetail";
 
+        public static string OrderSecondaryHandlingDetail = "OrderSecondaryHandlingDetail";
         #endregion
 
         #region 司法大屏

+ 8 - 0
src/Hotline.Api/Realtimes/RealtimeService.cs

@@ -219,6 +219,14 @@ public class RealtimeService : IRealtimeService, IScopeDependency
     public Task OrderHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
         SendToGroupAsync(RealtimeGroupNames.BigDataScreen, RealtimeMethods.OrderHandlingDetail,obj, cancellationToken);
 
+    /// <summary>
+    /// 推送二次办理中工单概览
+    /// </summary>
+    /// <param name="obj"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public Task OrderSecondaryHandlingDetailAsync(object obj, CancellationToken cancellationToken) =>
+        SendToGroupAsync(RealtimeGroupNames.BigDataScreen, RealtimeMethods.OrderSecondaryHandlingDetail, obj, cancellationToken);
     #endregion
 
     /// <summary>

+ 18 - 8
src/Hotline.Api/StartupExtensions.cs

@@ -37,12 +37,16 @@ using Hotline.XingTang;
 using Hotline.Logger;
 using HotPot.Mvc.Filters;
 using Microsoft.AspNetCore.ResponseCompression;
-using Senparc.Weixin.RegisterServices;
-using Senparc.Weixin.AspNet;
 using Hotline.WeChat;
+using Hotline.Snapshot.Interfaces;
+using TianQue.Sdk;
+using Hotline.Settings.SystemLogDomain;
+using Hotline.Application.Snapshot;
 using Hotline.Orders;
 using XF.Domain.Repository.Events;
 using Hotline.Orders.DatabaseEventHandler;
+using Hotline.Snapshot;
+using Hotline.WeChat;
 
 
 namespace Hotline.Api;
@@ -132,6 +136,9 @@ internal static class StartupExtensions
         //mediatr
         services.RegisterMediatR(appConfiguration);
 
+        services.AddScoped<LoggingInterceptor>();
+        services.AddScoped<AsyncLoggingInterceptor>();
+
         //app scope
         switch (appConfiguration.AppScope)
         {
@@ -147,10 +154,13 @@ internal static class StartupExtensions
                 services.AddYbEnterpriseSdk(appConfiguration.YiBin.Enterprise.AddressUrl)
                     .AddKeyedScoped<ISessionContext, YbEnterpriseSessionContext>(YbEnterpriseSessionContext.Key)
                     .AddKeyedScoped<ISessionContext, ZzptSessionContext>(ZzptSessionContext.Key);
+                services.AddProxiedScoped<ISnapshotApplication, DefaultSnapshotApplication>();
                 break;
             case AppDefaults.AppScope.ZiGong:
+                services.AddProxiedScoped<ISnapshotApplication, ZiGongSnapshotApplication>();
                 break;
             case AppDefaults.AppScope.LuZhou:
+                services.AddProxiedScoped<ISnapshotApplication, DefaultSnapshotApplication>();
                 break;
         }
 
@@ -189,6 +199,7 @@ internal static class StartupExtensions
 
         //services.AddScoped(typeof(IUpdateDatabase<>), typeof(UpdateDatabase<>));
         services.AddScoped<IUpdateDatabaseEvent<OrderVisitDetail>, OrderVisitDetailEventHandler>();
+        // services.AddScoped<IUpdateDatabaseEvent<OrderSnapshot>, OrderSnapshotEventHandler>();
 
         //sqlsugar
         services.AddSqlSugar(configuration);
@@ -218,7 +229,9 @@ internal static class StartupExtensions
 
         services.AddScoped<Users.IThirdIdentiyService, WeChatService>();
 
-        services.AddSenparcWeixin(configuration);
+        services.AddWeChatService();
+
+        services.AddScoped<IGuiderSystemService, TiqnQueService>();
 
         //services.AddScoped<LogFilterAttribute>();
         //ServiceLocator.Instance = services.BuildServiceProvider();
@@ -256,14 +269,11 @@ internal static class StartupExtensions
         app.MapControllers()
             .RequireAuthorization();
         //app.MapSubscribeHandler();
+        app.UseWeChat();
 
-        var registerService = app.UseSenparcWeixin(app.Environment, null, null,
-            register => { },
-            (register, weixinSetting) => { }
-            );
         // 记录交互日志
         //app.UseRequestResponseLogging(app.Configuration);
-        
+
         app.UseResponseCompression();
 
         return app;

+ 3 - 3
src/Hotline.Api/config/appsettings.Development.json

@@ -1,7 +1,7 @@
 {
   "AllowedHosts": "*",
   "AppConfiguration": {
-    "AppScope": "YiBin",
+    "AppScope": "ZiGong",
     "YiBin": {
       "AreaCode": "511500",
       "CallCenterType": "TianRun", //XunShi、WeiErXin、TianRun、XingTang
@@ -62,13 +62,13 @@
     }
   },
   "ConnectionStrings": {
-    "Hotline": "PORT=5432;DATABASE=hotline;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
+    "Hotline": "PORT=5432;DATABASE=hotline_dev;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;"
   },
   "Cache": {
     "Host": "110.188.24.182",
     "Port": 50179,
     "Password": "fengwo123!$!$",
-    "Database": 3 //test:3, dev:5
+    "Database": 5 //test:3, dev:5
   },
   "Swagger": true,
   "AccLog":  false,

+ 0 - 2
src/Hotline.Application.Contracts/Validators/Snapshot/IndustryValidator.cs

@@ -12,7 +12,5 @@ public class IndustryValidator : AbstractValidator<AddIndustryDto>
     public IndustryValidator()
     {
         RuleFor(m => m.Name).NotEmpty().WithMessage("行业名称不能为空");
-        RuleFor(m => m.CitizenReadPackAmount).NotEqual(0).WithMessage("市民红包不能为空");
-        RuleFor(m => m.GuiderReadPackAmount).NotEqual(0).WithMessage("网格员红包不能为空");
     }
 }

+ 41 - 1
src/Hotline.Application.Tests/Application/DefaultCallApplicationTest.cs

@@ -8,6 +8,7 @@ using Hotline.Orders;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.CallCenter;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.DependencyInjection;
@@ -24,7 +25,7 @@ public class DefaultCallApplicationTest : TestBase
     public readonly IFixture _fixture;
     private readonly IOrderRepository _orderRepository;
 
-    public DefaultCallApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, XingTangCallApplication defaultCallApplication, IOrderVisitRepository orderVisitRepository, IRepository<CallNative> callNativeRepository, IOrderRepository orderRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    public DefaultCallApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, XingTangCallApplication defaultCallApplication, IOrderVisitRepository orderVisitRepository, IRepository<CallNative> callNativeRepository, IOrderRepository orderRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _fixture = new Fixture();
         _defaultCallApplication = defaultCallApplication;
@@ -98,6 +99,7 @@ public class DefaultCallApplicationTest : TestBase
 
     /// <summary>
     /// 测试三方通话后, 工单保存时没有关联到正确的通话记录, 使用本方法能不能修复
+    /// 测试如果通话记录有两通都是呼入的,就不需要修复了
     /// </summary>
     /// <returns></returns>
     [Fact]
@@ -108,6 +110,7 @@ public class DefaultCallApplicationTest : TestBase
             .With(m => m.Id, Ulid.NewUlid().ToString())
             .With(m => m.CallNo, callNo)
             .With(m => m.Direction, ECallDirection.In)
+            .With(m => m.IsDeleted, false)
             .Create();
         await _callNativeRepository.AddAsync(inDto);
 
@@ -115,6 +118,7 @@ public class DefaultCallApplicationTest : TestBase
             .With(m => m.Id, Ulid.NewUlid().ToString())
             .With(m => m.Direction, ECallDirection.Out)
             .With(m => m.CallNo, callNo)
+            .With(m => m.IsDeleted, false)
             .Create();
         await _callNativeRepository.AddAsync(inDto2);
 
@@ -132,6 +136,42 @@ public class DefaultCallApplicationTest : TestBase
         await _defaultCallApplication.OrderRelateCallHandlerAsync(orderId, CancellationToken.None);
         (await _orderRepository.Queryable().Where(m => m.Id == orderId).Select(m => m.CallId).FirstAsync())
             .ShouldBe(inDto.Id);
+
+        // 测试如果通话记录有两通都是呼入的,就不需要修复了
+
+        callNo = DateTime.Now.ToString("yyyyMMddhhmmss") + "Flow";
+        inDto = _fixture.Build<CallNative>()
+            .With(m => m.Id, Ulid.NewUlid().ToString())
+            .With(m => m.CallNo, callNo)
+            .With(m => m.Direction, ECallDirection.In)
+            .With(m => m.Duration, 20)
+            .With(m => m.IsDeleted, false)
+            .Create();
+        await _callNativeRepository.AddAsync(inDto);
+
+        inDto2 = _fixture.Build<CallNative>()
+            .With(m => m.Id, Ulid.NewUlid().ToString())
+            .With(m => m.Direction, ECallDirection.In)
+            .With(m => m.CallNo, callNo)
+            .With(m => m.Duration, 10)
+            .With(m => m.IsDeleted, false)
+            .Create();
+        await _callNativeRepository.AddAsync(inDto2);
+
+        orderId = await _orderRepository.Queryable()
+            .Where(m => string.IsNullOrEmpty(m.CallId))
+            .OrderByDescending(m => m.CreationTime)
+            .Select(m => m.Id)
+            .FirstAsync();
+
+        await _orderRepository.Updateable()
+            .SetColumns(m => m.CallId == inDto2.Id)
+            .Where(m => m.Id == orderId)
+            .ExecuteCommandAsync();
+
+        await _defaultCallApplication.OrderRelateCallHandlerAsync(orderId, CancellationToken.None);
+        (await _orderRepository.Queryable().Where(m => m.Id == orderId).Select(m => m.CallId).FirstAsync())
+            .ShouldBe(inDto2.Id);
     }
 
     //[Fact]

+ 100 - 0
src/Hotline.Application.Tests/Application/IndustryApplicationTest.cs

@@ -0,0 +1,100 @@
+using Hotline.Api.Controllers;
+using AutoFixture;
+using Hotline.Application.Snapshot;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Users;
+using Mapster;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Tests.Application;
+public class IndustryApplicationTest : TestBase
+{
+    private readonly IIndustryApplication _industryApplication;
+    private readonly IIndustryRepository _industryRepository;
+
+    public IndustryApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdIdentiyService, IThirdAccountRepository thirdAccountRepository, IIndustryApplication industryApplication, IIndustryRepository industryRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdIdentiyService, thirdAccountRepository)
+    {
+        _industryApplication = industryApplication;
+        _industryRepository = industryRepository;
+    }
+
+    [Fact]
+    public async Task UpdateIndustry_Test()
+    {
+        var industryItems = _industryApplication.GetIndustres(new IndustryListInDto(null, null)).ToList();
+        industryItems.NotNullOrEmpty().ShouldBeTrue("行业集合为空");
+        var industry = industryItems.First().Adapt<UpdateIndustryInDto>();
+        var item = await _industryRepository.GetAsync(industry.Id);
+        industry.ForeachClassProperties(async (industry, property, name, value) =>
+        {
+            if (name.ToUpper() == "ID") return true;
+            if (value is String)
+                industry.GetType().GetProperty(name).SetValue(industry, value + DateTime.Now.ToString("ss"));
+            return true;
+        });
+        await _industryApplication.UpdateIndustryAsync(industry, CancellationToken.None);
+        var updateIndustry = await _industryApplication.GetIndustryDetailAsync(item.Id);
+        updateIndustry.ForeachClassProperties(async (industry, property, name, value) =>
+        {
+            industry.GetType().GetProperty(name).GetValue(industry).ShouldBe(value);
+            return true;
+        });
+        await _industryApplication.UpdateIndustryAsync(item.Adapt<UpdateIndustryInDto>(), CancellationToken.None);
+    }
+
+    [Fact]
+    public async Task IndustryCase_Test()
+    {
+        var industry = await _industryApplication.GetIndustres(new IndustryListInDto(null, null)).ToListAsync();
+        var industryCase = new AddIndustryCaseDto
+        {
+            IndustryId = industry.First().Id,
+            Name = "单元测试" + DateTime.Now.ToString("ss"),
+            CitizenReadPackAmount = 10,
+            GuiderReadPackAmount = 10,
+            IsEnable = true,
+            DisplayOrder = 1
+        };
+        var caseId = await _industryApplication.AddIndustryCaseAsync(industryCase);
+        var items = await _industryApplication.GetIndustryCaseItems(new IndustryCaseItemInDto(null, null)).ToListAsync();
+        items.Count.ShouldNotBe(0);
+        items.Any(m => m.Id.IsNullOrEmpty()).ShouldBeFalse();
+        items.Any(m => m.Name.IsNullOrEmpty()).ShouldBeFalse();
+
+        var caseEntity = await _industryApplication.GetIndustryCaseAsync(caseId);
+        var updateDto = caseEntity.Adapt<UpdateIndustryCaseDto>();
+        updateDto.DisplayOrder = 2;
+        await _industryApplication.UpdateIndustryCaseAsync(updateDto);
+        var caseEntityUpdate = await _industryApplication.GetIndustryCaseAsync(caseId);
+        caseEntityUpdate.DisplayOrder.ShouldBe(updateDto.DisplayOrder);
+        caseEntityUpdate.Name.ShouldBe(updateDto.Name);
+    }
+
+    /// <summary>
+    /// 新增行业短信模板
+    /// 行业短信模板集合
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task SMSTemplate_Test()
+    {
+        var industryItems = await _industryApplication.GetIndustres(new IndustryListInDto(null, null)).ToListAsync();
+        var industry = industryItems.First();
+        var inDto = _fixture.Create<AddSnapshotSMSTemplateInDto>();
+        inDto.IndustryId = industry.Id;
+        var smsId = await _industryApplication.AddSMSTemplateAsync(inDto);
+        var items = await _industryApplication.GetSMSTemplates(new SnapshotSMSTemplateItemsInDto(null)).ToListAsync();
+        items.Count.ShouldNotBe(0);
+        var sms = items.Where(m => m.Id == smsId).First();
+        sms.Content.ShouldBe(inDto.Content);
+        sms.IndustryName.ShouldNotBeNullOrEmpty();
+        sms.IsEnable.ShouldBe(inDto.IsEnable);
+    }
+}

+ 2 - 1
src/Hotline.Application.Tests/Application/KnowApplicationTest.cs

@@ -6,6 +6,7 @@ using Hotline.KnowledgeBase;
 using Hotline.KnowledgeBase.Notifies;
 using Hotline.Share.Dtos.Knowledge;
 using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Mapster;
 using MediatR;
@@ -31,7 +32,7 @@ public class KnowApplicationTest : TestBase
     private readonly IRepository<KnowledgeWord> _knowledgeWordRepository;
     private readonly IRepository<KnowledgeHotWord> _knowledgeHotWordRepository;
 
-    public KnowApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IKnowApplication knowApplication, IRepository<KnowledgeRelationType> knowledgeRelationTypeRepository, IMediator mediator, IRepository<KnowledgeBase.Knowledge> knowledgeRepository, IKnowledgeDomainService knowledgeDomainService, IRepository<KnowledgeWord> knowledgeWordRepository, IRepository<KnowledgeHotWord> knowledgeHotWordRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    public KnowApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IKnowApplication knowApplication, IRepository<KnowledgeRelationType> knowledgeRelationTypeRepository, IMediator mediator, IRepository<KnowledgeBase.Knowledge> knowledgeRepository, IKnowledgeDomainService knowledgeDomainService, IRepository<KnowledgeWord> knowledgeWordRepository, IRepository<KnowledgeHotWord> knowledgeHotWordRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _knowApplication = knowApplication;
         _knowledgeRelationTypeRepository = knowledgeRelationTypeRepository;

+ 72 - 0
src/Hotline.Application.Tests/Application/OrderSnapshotApplicationTest.cs

@@ -0,0 +1,72 @@
+using Hotline.Api.Controllers;
+using Hotline.Application.Snapshot;
+using Hotline.Application.Tests.Mock;
+using Hotline.Caching.Interfaces;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Requests;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Users;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+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 OrderSnapshotApplicationTest : TestBase
+{
+    private readonly OrderServiceMock _orderServiceMock;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly IOrderSnapshotApplication _orderSnapshotApplication;
+    public OrderSnapshotApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdIdentiyService, IThirdAccountRepository thirdAccountRepository, OrderServiceMock orderServiceMock, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderSnapshotRepository orderSnapshotRepository, IOrderSnapshotApplication orderSnapshotApplication) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdIdentiyService, thirdAccountRepository)
+    {
+        _orderServiceMock = orderServiceMock;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _orderSnapshotApplication = orderSnapshotApplication;
+    }
+
+    /// <summary>
+    /// 随手拍办理流程
+    /// 工单标注 : 验证数据是否保存正确
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task SnapshotWrokflow_Test()
+    {
+        var snapshotLabels = _systemDicDataCacheManager.SnapshotOrderLabel;
+        var inputLable = snapshotLabels.Where(m => m.DicDataValue == "bss").ToList();
+        var order = _orderServiceMock.CreateSnapshotOrder(SetWeiXin)
+            .办理到工单标注(SetZuoXi)
+            .办理到派单员(SetZuoXi, false)
+            .办理到归档(SetZuoXi)
+            .发布工单(SetZuoXi, inputLable.Select(m => new Kv { Key = m.DicDataValue, Value = m.DicDataName}).ToList())
+            .GetCreateResult();
+        order.Id.ShouldNotBeNull();
+
+        var snapshot = await _orderSnapshotRepository.GetAsync(order.Id);
+        // 验证工单标注是否生效
+        snapshot.ShouldNotBeNull();
+        snapshot.IsTruthDepartment.ShouldBe(false);
+        snapshot.LabelName.ShouldBe(string.Join('|', inputLable.Select(m => m.DicDataName)));
+        snapshot.Labels.ShouldNotBeNull();
+        snapshot.Labels.Count.ShouldBe(inputLable.Count);
+
+        snapshot.IsSafetyDepartment.ShouldBe(false);
+
+        var labelLog = await _orderSnapshotApplication.GetSignOrderSnapshotLogItemsAsync(new SignOrderSnapshotLogItemsInDto()).ToPagedListAsync(new PagedRequest());
+        labelLog.Items.Any(m => m.Title.IsNullOrEmpty()).ShouldBe(false);
+        labelLog.Items.Any(m => m.SourceChannel.IsNullOrEmpty()).ShouldBe(false);
+    }
+}
+

+ 64 - 0
src/Hotline.Application.Tests/Application/RedPackApplicationTest.cs

@@ -0,0 +1,64 @@
+using Hotline.Api.Controllers;
+using Hotline.Application.Snapshot;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Users;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Shouldly;
+using SqlSugar;
+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 RedPackApplicationTest : TestBase
+{
+    private readonly IRedPackApplication _redPackApplication;
+    private readonly IRedPackRecordRepository _redPackRecordRepository;
+
+    public RedPackApplicationTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdIdentiyService, IThirdAccountRepository thirdAccountRepository, IRedPackApplication redPackApplication, IRedPackRecordRepository redPackRecordRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdIdentiyService, thirdAccountRepository)
+    {
+        _redPackApplication = redPackApplication;
+        _redPackRecordRepository = redPackRecordRepository;
+    }
+
+    /// <summary>
+    /// 获取审核集合
+    /// 获取审核短信模板
+    /// 审核通过
+    /// 获取红包记录
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task AuditRedPackAudit_Test()
+    {
+        var items = await _redPackApplication.GetRedPackAuditItemsAsync(new SnapshotOrderAuditItemsInDto(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 0)).ToListAsync();
+        var audit = items.First();
+        var smsTemplate = await _redPackApplication.GetRedPackAuditSMSTemplateAsync(new GetRedPackAuditSMSTemplateInDto(audit.OrderId, ERedPackAuditStatus.Agree));
+        var inDto = new UpdateRedPackAuditInDto
+        {
+            Status = ERedPackAuditStatus.Agree,
+            Opinion = "单元测试" + DateTime.Now.ToShortDateString(),
+            SMSTemplateId = smsTemplate.First().Id,
+            IsSendSms = true,
+            RedPackAuditId = audit.Id,
+        };
+        await _redPackApplication.AuditRedPackAuditAsync(inDto);
+        items = await _redPackApplication.GetRedPackAuditItemsAsync(new SnapshotOrderAuditItemsInDto(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, 1)).ToListAsync();
+        items.Any(m => m.Id == audit.Id).ShouldBeTrue();
+
+        var record = await _redPackRecordRepository.Queryable()
+            .Where(m => m.OrderId == audit.OrderId)
+            .FirstAsync();
+        record.ShouldNotBeNull();
+        var recordItems = await _redPackApplication.GetRedPackRecordItemsAsync(new SnapshotRedPackRecordItemsInDto { Status =2}).ToListAsync();
+        recordItems.Count.ShouldNotBe(0);
+    }
+}

+ 51 - 6
src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs

@@ -1,8 +1,11 @@
 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;
 using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Settings;
@@ -10,6 +13,7 @@ using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Enums.Snapshot;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Mapster;
 using Moq;
@@ -23,16 +27,28 @@ namespace Hotline.Application.Tests.Snapshot
     {
         private readonly Mock<IThirdIdentiyService> _thirdLoginServiceMock;
         private readonly Mock<IRepository<Industry>> _industryRepositoryMock;
-        private readonly Mock<IRepository<Article.Bulletin>> _bulletinRepositoryMock;
+        private readonly Mock<ISnapshotBulletinRepository> _bulletinRepositoryMock;
         private readonly Mock<ISessionContext> _sessionContextMock;
         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;
         private readonly Mock<ISystemDicDataCacheManager> _systemDicDataCacheManagerMock;
+        private readonly Mock<ISnapshotOrderPublishRepository> _snapshotOrderPublishRepositoryMock;
+        private readonly Mock<IRepository<WorkflowTrace>> _workflowTraceRepositoryMock;
+        private readonly Mock<IPractitionerRepository> _practitionerRepositoryMock;
+        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 Mock<IFileDomainService> _fileDomainServiceMock;
 
         private readonly DefaultSnapshotApplication _snapshotApplication;
 
@@ -40,18 +56,30 @@ namespace Hotline.Application.Tests.Snapshot
         {
             _thirdLoginServiceMock = new Mock<IThirdIdentiyService>();
             _industryRepositoryMock = new Mock<IRepository<Industry>>();
-            _bulletinRepositoryMock = new Mock<IRepository<Article.Bulletin>>();
+            _bulletinRepositoryMock = new Mock<ISnapshotBulletinRepository>();
             _sessionContextMock = new Mock<ISessionContext>();
             _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>();
             _systemDicDataCacheManagerMock = new Mock<ISystemDicDataCacheManager>();
+            _snapshotOrderPublishRepositoryMock = new Mock<ISnapshotOrderPublishRepository>();
+            _workflowTraceRepositoryMock = new Mock<IRepository<WorkflowTrace>>();
+            _practitionerRepositoryMock = new Mock<IPractitionerRepository>();
+            _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>();
+            _fileDomainServiceMock = new Mock<IFileDomainService>();
 
-            _snapshotApplication = new DefaultSnapshotApplication(
+        _snapshotApplication = new DefaultSnapshotApplication(
                 _thirdLoginServiceMock.Object,
                 _industryRepositoryMock.Object,
                 _bulletinRepositoryMock.Object,
@@ -63,7 +91,24 @@ namespace Hotline.Application.Tests.Snapshot
                 _systemSettingCacheManagerMock.Object,
                 _systemAreaDomainServiceMock.Object,
                 _fileRepositoryMock.Object,
-                _systemDicDataCacheManagerMock.Object
+                _systemDicDataCacheManagerMock.Object,
+                _snapshotOrderPublishRepositoryMock.Object,
+                _workflowTraceRepositoryMock.Object,
+                _practitionerRepositoryMock.Object,
+                _systemAreaRepositoryMock.Object,
+                _volunteerRepositoryMock.Object,
+                _volunteerReportRepositoryMock.Object,
+                _systemLogMock.Object,
+                _guiderSystemServiceMock.Object,
+                _capPublisherMock.Object,
+                _publisherMock.Object,
+                _guiderInfoRepositoryMock.Object,
+                _fileDomainServiceMock.Object,
+                null,
+                null,
+                null,
+                null,
+                null
             );
         }
 

+ 343 - 51
src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs

@@ -1,20 +1,30 @@
-using DocumentFormat.OpenXml.Wordprocessing;
+using AutoFixture;
+using DocumentFormat.OpenXml.Wordprocessing;
 using Hotline.Api.Controllers;
 using Hotline.Application.Identity;
 using Hotline.Application.Snapshot;
+using Hotline.Application.Tests.Mock;
+using Hotline.Caching.Interfaces;
 using Hotline.File;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Share.Dtos.Article;
 using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Enums;
+using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Snapshot;
 using Hotline.Share.Tools;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Snapshot.Notifications;
 using Hotline.Users;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.DependencyInjection;
 using Shouldly;
+using System;
+using XF.Domain.Authentications;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
 
@@ -27,8 +37,18 @@ public class SnapshotApplicationTest : TestBase
     private readonly IIndustryApplication _industryApplication;
     private readonly IIndustryRepository _industryRepository;
     private readonly IFileRepository _fileRepository;
+    private readonly OrderServiceMock _orderServiceMock;
+    private readonly IOrderRepository _orderRepository;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly ISessionContext _sessionContext;
+    private readonly IGuiderSystemService _guiderSystemService;
+    private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+    private readonly ICommunityInfoRepository _communityInfoRepository;
+    private readonly IIndustryLogRepository _industryLogRepository;
+    private readonly IRedPackApplication _redPackApplication;
+    private readonly IOrderSnapshotApplication _orderSnapshotApplication;
 
-    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) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    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, ICommunityInfoRepository communityInfoRepository, IIndustryLogRepository industryLogRepository, IRedPackApplication redPackApplication, IOrderSnapshotApplication orderSnapshotApplication) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _snapshotApplication = snapshotApplication;
         _identityAppService = identityAppService;
@@ -36,6 +56,38 @@ public class SnapshotApplicationTest : TestBase
         _industryApplication = industryApplication;
         _industryRepository = industryRepository;
         _fileRepository = fileRepository;
+        _orderServiceMock = orderServiceMock;
+        _orderRepository = orderRepository;
+        _orderSnapshotRepository = orderSnapshotRepository;
+        SetWeiXin();
+        _sessionContext = sessionContext;
+        _guiderSystemService = guiderSystemService;
+        _systemSettingCacheManager = systemSettingCacheManager;
+        _communityInfoRepository = communityInfoRepository;
+        _industryLogRepository = industryLogRepository;
+        _redPackApplication = redPackApplication;
+        _orderSnapshotApplication = orderSnapshotApplication;
+    }
+
+    /// <summary>
+    /// 随手拍公开集合
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task GetOrderSnapshotPublishItemsAsync()
+    {
+        var items = await _orderSnapshotApplication.GetOrderSnapshotPublishItemsAsync(new GetOrderSnapshotPublishItemsInDto()).ToPageListAsync(0, 10);
+        items.NotNullOrEmpty().ShouldBeTrue();
+        items = await _orderSnapshotApplication.GetOrderSnapshotPublishItemsAsync(new GetOrderSnapshotPublishItemsInDto 
+        {
+            OrderStatus = EOrderStatus.Filed
+        }).ToPageListAsync(0, 10);
+        items.NotNullOrEmpty().ShouldBeTrue();
+        items = await _orderSnapshotApplication.GetOrderSnapshotPublishItemsAsync(new GetOrderSnapshotPublishItemsInDto
+        {
+            IsPublished = true
+        }).ToPageListAsync(0, 10);
+        items.NotNullOrEmpty().ShouldBeTrue();
     }
 
     [Fact]
@@ -46,6 +98,106 @@ public class SnapshotApplicationTest : TestBase
         result.Industrys.First().DisplayOrder.ShouldBe(1, "排序异常");
     }
 
+    [Fact]
+    public async Task GetBulletionPopup_Test()
+    {
+        var item = await _snapshotApplication.GetBulletionPopupAsync(CancellationToken.None);
+        //item.ShouldNotBeNull();
+    }
+
+
+    /// <summary>
+    /// 添加随手拍公告
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task AddBulletin_Test()
+    {
+        var industry = await _industryRepository.Queryable()
+            .Where(m => m.BulletinTypeGuideId != null && m.BulletinTypeGuideName != null)
+            .OrderBy(m => m.DisplayOrder)
+            .FirstAsync();
+        if (industry == null)
+        {
+            return;
+        }
+        var inDto = new AddSnapshotBulletinInDto
+        {
+            Title = "单元测试" + DateTime.Now.ToLongDateTimeString(),
+            Content = "测试内容" + DateTime.Now.ToLongDateTimeString(),
+            BulletinTypeId = industry.BulletinTypeGuideId!,
+            BulletinTypeName = industry.BulletinTypeGuideName!
+        };
+        var bulletinId = await _snapshotApplication.AddBulletinAsync(inDto);
+        inDto = new AddSnapshotBulletinInDto
+        {
+            Title = "单元测试" + DateTime.Now.ToLongDateTimeString(),
+            Content = "测试内容" + DateTime.Now.ToLongDateTimeString(),
+            BulletinTypeId = industry.BulletinTypePublicityId!,
+            BulletinTypeName = industry.BulletinTypePublicityName!
+        };
+        bulletinId = await _snapshotApplication.AddBulletinAsync(inDto);
+        await _snapshotApplication.AuditBulletinAsync(new ExamineBulletinDto { Id = bulletinId, IsPass = true, Reason = "测试审核通过" });
+
+        var items = await _snapshotApplication.GetBulletinsAsync(new BulletinInDto { IndustryId = industry.Id }, CancellationToken.None);
+        items.Count.ShouldNotBe(0, "公告数量为0");
+    }
+
+    /// <summary>
+    /// 获取公开工单集合
+    /// 发布到公开工单
+    /// 审核通过公开工单
+    /// 获取特提参数
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task PublishOrder_Test()
+    {
+        var industry = await _industryRepository.Queryable()
+            .Where(m => m.IndustryType == EIndustryType.Clue)
+            .OrderBy(m => m.DisplayOrder)
+            .FirstAsync();
+
+        var order = _orderServiceMock.CreateSnapshotOrder(SetWeiXin)
+            .办理到一级部门(SetZuoXi)
+            .办理到二级部门(Set一级部门)
+            .办理一级部门汇总(Set二级部门)
+            .办理到归档(Set一级部门)
+            .GetCreateResult();
+        SetZuoXi();
+        var auditDetail = await _orderSnapshotApplication.GetOrderSnapshotPublishDetailAsync(order.Id);
+        var orderEntity = await _orderRepository.GetAsync(order.Id);
+        var orderSnapshot = await _orderSnapshotRepository.GetAsync(order.Id);
+        var inDto = new AddSnapshotOrderPublishInDto
+        {
+            ArrangeContent = auditDetail.Content,
+            ArrangeOpinion = auditDetail.FileOpinion,
+            ArrangeTitle = auditDetail.Title,
+            OrderId = auditDetail.Id,
+            ArrangeAddress = auditDetail.FullAddress,
+            HandleTime = DateTime.Now
+        };
+        var auditId = await _orderSnapshotApplication.AddOrderPublishAsync(inDto, CancellationToken.None);
+        var items = await _snapshotApplication.GetOrderPublishAsync(new OrderPublishInDto { IndustryId = industry.Id }, CancellationToken.None);
+        items.Any(m => m.No == order.No).ShouldBeFalse();
+        await _orderSnapshotApplication.UpdateOrderSnapshotPublishStatusAsync(new UpdateOrderSnapshotPublishStatusInDto { Id = auditId, Status = EOrderSnapshotPublishStatus.Agree });
+
+        items = await _snapshotApplication.GetOrderPublishAsync(new OrderPublishInDto
+        {
+            IndustryId = industry.Id
+        }, CancellationToken.None);
+        items.Any(m => m.No == order.No).ShouldBeTrue();
+
+        var orderPublishDetail = await _snapshotApplication.GetOrderPublishDetailAsync(items.OrderByDescending(m => m.HandleTime).First().Id, CancellationToken.None);
+        orderPublishDetail.ShouldNotBeNull();
+        orderPublishDetail.Workflow.Any(m => m.Name.IsNullOrEmpty()).ShouldBeFalse();
+        await _snapshotApplication.AddRedPardAsync(order.Id, CancellationToken.None);
+        var guiderItems = await _redPackApplication
+            .GetRedPackGuiderAuditItemsAsync(new SnapshotOrderGuiderAuditItemsInDto(null, null, -1))
+            .ToListAsync();
+        guiderItems.Count.ShouldNotBe(0);
+    }
+
     [Fact]
     public async Task GetBulletins_Test()
     {
@@ -54,7 +206,7 @@ public class SnapshotApplicationTest : TestBase
         {
             IndustryId = homePage.Industrys.First(m => m.Name == "文化旅游").Id,
         };
-        var items = await _snapshotApplication.GetBulletinsAsync(inDto);
+        var items = await _snapshotApplication.GetBulletinsAsync(inDto, CancellationToken.None);
         items.ShouldNotBeNull();
         items.Any().ShouldBe(true, "公告数据为空");
         items.Any(m => m.Title.IsNullOrEmpty()).ShouldBe(false, "标题错误");
@@ -62,18 +214,43 @@ public class SnapshotApplicationTest : TestBase
         items.Any(m => m.Id.IsNullOrEmpty()).ShouldBe(false, "Id错误");
     }
 
+    /// <summary>
+    /// 获取小程序个人中心
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetSnapshotUserInfo_Test()
     {
+        await _identityAppService.GetThredTokenAsync(new ThirdTokenInDto());
         var result = await _snapshotApplication.GetSnapshotUserInfoAsync();
         result.ShouldNotBeNull();
         result.PhoneNumber.ShouldNotBeNullOrEmpty();
+        result.PhoneNumberMask.Contains("***").ShouldBeTrue();
+    }
+
+    /// <summary>
+    /// 根据OpenId刷新token
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task RefreshTokenAsync()
+    {
+        var token = await _identityAppService.GetThredTokenAsync(new ThirdTokenInDto());
+        var newToken = await _identityAppService.RefreshTokenAsync(token.OpenId);
+        newToken.ShouldNotBeNull();
+        newToken.OpenId.ShouldBe(token.OpenId);
+        newToken.PhoneNumber.ShouldNotBeNullOrEmpty();
     }
 
+    /// <summary>
+    /// 请求第三方token
+    /// </summary>
+    /// <returns></returns>
     [Fact]
     public async Task GetThirdToken_Test()
     {
         var result = await _identityAppService.GetThredTokenAsync(new ThirdTokenInDto { LoginCode = "0c3Adhll2zDMBe413rnl2KvEym2AdhlH" });
+        result.PhoneNumber.ShouldNotBeNullOrEmpty();
     }
 
     [Theory]
@@ -83,12 +260,12 @@ public class SnapshotApplicationTest : TestBase
     {
         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();
+        var page = await _snapshotApplication.GetSnapshotOrdersAsync(dto, CancellationToken.None);
+        page.Count.ShouldNotBe(0);
+        page.FirstOrDefault()?.IndustryName.ShouldNotBeNullOrEmpty();
+        page.FirstOrDefault()?.OrderNo.ShouldNotBeNullOrEmpty();
+        page.FirstOrDefault()?.StatusText.ShouldNotBeNullOrEmpty();
+        page.FirstOrDefault()?.Area.ShouldNotBeNullOrEmpty();
     }
 
     [Theory]
@@ -99,73 +276,126 @@ public class SnapshotApplicationTest : TestBase
     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);
+        var page = await _snapshotApplication.GetSnapshotOrdersAsync(dto, CancellationToken.None);
+        page.Count.ShouldNotBe(0, $"状态:{status.GetDescription()} 数据为空");
     }
 
+    /// <summary>
+    /// 获取工单详情
+    /// </summary>
+    /// <returns></returns>
     [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);
+        var page = await _snapshotApplication.GetSnapshotOrdersAsync(new OrderInDto(), CancellationToken.None);
+        var id = page.First().Id;
+        var detail = await _snapshotApplication.GetSnapshotOrderDetailAsync(id, CancellationToken.None);
         detail.Id.ShouldBe(id);
-        detail.IndustryName.ShouldNotBeNull();
+        detail.Title.ShouldNotBeNullOrEmpty();
+        detail.Opinion.ShouldNotBeNullOrEmpty();
+        detail.Content.ShouldNotBeNullOrEmpty();
     }
 
+    /// <summary>
+    /// 红包列表记录
+    /// </summary>
+    /// <param name="count"></param>
+    /// <param name="exp"></param>
+    /// <returns></returns>
     [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}");
+        var items = await _snapshotApplication.GetRedPackDateAsync(new RedPackDateInDto { }, CancellationToken.None);
+    }
+
+    /// <summary>
+    /// 红包总额
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task GetRedPackReceivedTotal_Test()
+    {
+        var amount = await _snapshotApplication.GetRedPackReceivedTotalAsync(CancellationToken.None);
+    }
+
+    /// <summary>
+    /// 添加志愿者上报信息
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task AddVolunteerReport_Test()
+    {
+        await _snapshotApplication.AddVolunteerAsync(new AddVolunteerInDto { Name = _sessionContext.UserName, PhoneNumber = _sessionContext.Phone }, CancellationToken.None);
+        var inDto = _fixture.Create<AddVolunteerReportInDto>();
+        inDto.JobType = "电焊";
+        inDto.PhoneNumber = "13999989" + DateTime.Now.ToString("ss");
+        inDto.Name = "单元测试" + DateTime.Now.ToString("ss");
+        foreach (var item in inDto.Files)
+        {
+            item.FileName = DateTime.Now.ToShortTimeString() + "文件.doc";
+        }
+
+        var result = await _snapshotApplication.AddVolunteerReportAsync(inDto, CancellationToken.None);
+        result.Id.ShouldNotBeNull();
     }
 
+    /// <summary>
+    /// 保存用户邀请码
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task SaveInvitationCode_Test()
+    {
+        var code = DateTime.Now.ToShortTimeString();
+        await _snapshotApplication.SaveInvitationCodeAsync(new SaveInvitationCodeInDto { InvitationCode = code });
+        var third = await _thirdAccountRepository.GetByOpenIdAsync(_sessionContext.OpenId);
+        third.InvitationCode.ShouldBe(code);
+    }
+
+    /// <summary>
+    /// 获取志愿者集合
+    /// 获取志愿者详情
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task GetPractitionerItems_Test()
+    {
+        var items = await _snapshotApplication.GetPractitionerItemsAsync(new PractitionerItemInDto { AreaId = "510399" }, CancellationToken.None);
+        items.Count.ShouldNotBe(0);
+
+        var item = await _snapshotApplication.GetPractitionerDetailAsync(items.First().Id, CancellationToken.None);
+        item.Street.ShouldNotBeNullOrEmpty();
+        item.Name.ShouldNotBeNullOrEmpty();
+        item.SystemAreaName.ShouldNotBeNullOrEmpty();
+        item.SystemAreaName.ShouldNotBeNullOrEmpty();
+        item.Gender.ShouldNotBe(EGender.Unknown);
+        item.GenderTxt.ShouldNotBeNullOrEmpty();
+        item.PhoneNumber.ShouldNotBeNullOrEmpty();
+    }
+
+    /// <summary>
+    /// 统计红包数据
+    /// </summary>
+    /// <param name="status"></param>
+    /// <returns></returns>
     [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, "数据不应该为空");
+        var page = await _snapshotApplication.GetRedPacksAsync(new RedPacksInDto { Status = status }, CancellationToken.None);
+        //page.Count.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);
-        }
+        //detail.Id.ShouldNotBeNullOrEmpty();
+        //detail.Title.ShouldNotBeNullOrEmpty();
+        //detail.Content.ShouldNotBeNullOrEmpty();
     }
 
     /// <summary>
@@ -214,7 +444,7 @@ public class SnapshotApplicationTest : TestBase
                 file.Key.ShouldBe(industryId);
             }
             pageDto.Workplace.ShouldNotBeNull();
-            pageDto.WorkArea.ShouldNotBeNull();
+            pageDto.WorkplaceName.ShouldNotBeNull();
         }
         catch (Exception e)
         {
@@ -226,4 +456,66 @@ 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(SetWeiXin)
+            .GetCreateResult();
+        await _snapshotApplication.PostOrderGuiderSystemAsync(order.Id, CancellationToken.None);
+        var orderSnapshot = await _orderSnapshotRepository.GetAsync(order.Id);
+        orderSnapshot.IndustryName = "修改行业名称";
+        await _orderSnapshotRepository.UpdateAsync(orderSnapshot);
+        var industryLog = await _industryLogRepository.Queryable()
+            .Where(m => m.OrderId == order.Id)
+            .FirstAsync();
+        industryLog.ShouldNotBeNull();
+        industryLog.IndustryName.ShouldBe("修改行业名称");
+        await _orderSnapshotRepository.Updateable()
+            .SetColumns(m => m.IndustryName, industryLog.OldIndustryName)
+            .Where(m => m.Id == order.Id)
+            .ExecuteCommandAsync();
+        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 = "瓦市村民委员会" + DateTime.Now.ToString("ss"),
+            OrgFullName = "四川省/自贡市/沿滩区/永安镇/瓦市村村民委员会" + DateTime.Now.ToString("ss"),
+            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);
+        var orderReply = await _orderSnapshotRepository.GetByNetworkENumberAsync(replyDto.AppealNumber);
+        orderReply.IsDanger.ShouldBe(true);
+        orderReply.MemberMobile.ShouldBe(replyDto.MemberMobile);
+        orderReply.MemberName.ShouldBe(replyDto.MemberName);
+        orderReply.NetworkRemark.ShouldBe(replyDto.ReplyContent);
+        orderReply.ReplyDate.Value.ToString("yyyy-MM-dd hh:mm:ss").ShouldBe(replyDto.ReplyDate);
+        var community = await _communityInfoRepository.GetAsync(replyDto.OrgId);
+        community.ShouldNotBeNull();
+        community.Name.ShouldBe(replyDto.OrgName);
+        community.FullName.ShouldBe(replyDto.OrgFullName);
+    }
 }

+ 13 - 6
src/Hotline.Application.Tests/Application/SystemSettingCacheManagerTest.cs

@@ -4,6 +4,7 @@ using Hotline.Caching.Services;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Settings;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.DependencyInjection;
@@ -22,7 +23,7 @@ public class SystemSettingCacheManagerTest : TestBase
     private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
     private readonly IRepository<SystemSetting> _systemSettingRepository;
 
-    public SystemSettingCacheManagerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, ISystemSettingCacheManager systemSettingCacheManager, ISystemDicDataCacheManager systemDicDataCacheManager, IRepository<SystemSetting> systemSettingRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    public SystemSettingCacheManagerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, ISystemSettingCacheManager systemSettingCacheManager, ISystemDicDataCacheManager systemDicDataCacheManager, IRepository<SystemSetting> systemSettingRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _systemSettingCacheManager = systemSettingCacheManager;
         _systemDicDataCacheManager = systemDicDataCacheManager;
@@ -34,8 +35,8 @@ public class SystemSettingCacheManagerTest : TestBase
     {
         var dd = DateTime.Parse("11/19/2024 18:08:00");
         _systemSettingCacheManager.CallSyncUnPushDateTime.ShouldBe(DateTime.Parse("2024/11/19 18:08:00"));
-        var result = _systemSettingCacheManager.CancelPublishOrderEnabled;
-        result.ShouldBeTrue();
+        //var result = _systemSettingCacheManager.CancelPublishOrderEnabled;
+        //result.ShouldBeTrue();
         var seconds = _systemSettingCacheManager.VisitCallDelaySecond;
         seconds.ShouldBe(60);
 
@@ -50,15 +51,21 @@ public class SystemSettingCacheManagerTest : TestBase
         _systemSettingCacheManager.WxOpenAppSecret.ShouldNotBeNull();
         _systemSettingCacheManager.VisitCallDelaySecond.ShouldNotBe(0);
         _systemSettingCacheManager.AutomaticPublishOrder.ShouldBe(true);
-        _systemSettingCacheManager.CancelPublishOrderEnabled.ShouldBe(true);
+        //_systemSettingCacheManager.CancelPublishOrderEnabled.ShouldBe(true);
 
+        _systemDicDataCacheManager.RemoveSysDicDataCache(SysDicTypeConsts.Workplace);
         var workplace = _systemDicDataCacheManager.Workplace;
         workplace.ShouldNotBeNull();
         workplace.Count.ShouldNotBe(0);
 
-        _systemDicDataCacheManager.RemoveSysDicDataCache(SysDicTypeConsts.WorkArea);
-        var workArea = _systemDicDataCacheManager.WorkArea;
+        _systemDicDataCacheManager.RemoveSysDicDataCache(SysDicTypeConsts.WorkplaceName);
+        var workArea = _systemDicDataCacheManager.WorkplaceName;
         workArea.ShouldNotBeNull();
         workArea.Count.ShouldNotBe(0);
+
+        _systemDicDataCacheManager.RemoveSysDicDataCache(SysDicTypeConsts.SnapshotBulletinType);
+        var snapshotBulletinType = _systemDicDataCacheManager.SnapshotBulletinType;
+        snapshotBulletinType.ShouldNotBeNull();
+        snapshotBulletinType.Count.ShouldNotBe(0);
     }
 }

+ 9 - 10
src/Hotline.Application.Tests/Controller/DefaultSessionContext.cs

@@ -30,15 +30,14 @@ public class DefaultSessionContext : ISessionContext, IScopeDependency
     }
     public HttpContext? HttpContext { get => GetContext(); set => _content = value; }
 
-
-    public string? OpenId { get; init; }
+    public string? OpenId { get { return _contextAccessor.HttpContext.User.FindFirstValue(AppClaimTypes.OpenId); } init { } }
 
     /// <summary>
     /// Id of current tenant or null for host
     /// </summary>
     public string? UserId
     {
-        get { return _contextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); }
+        get { return _contextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier); }
         init { }
     }
 
@@ -49,7 +48,7 @@ public class DefaultSessionContext : ISessionContext, IScopeDependency
     public string RequiredUserId => _contextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
     public string? UserName
     {
-        get { return _contextAccessor.HttpContext.User.FindFirstValue(AppClaimTypes.UserDisplayName); }
+        get { return _contextAccessor.HttpContext?.User.FindFirstValue(AppClaimTypes.UserDisplayName); }
         init { }
     }
 
@@ -64,26 +63,26 @@ public class DefaultSessionContext : ISessionContext, IScopeDependency
     /// </summary>
     public string[] Roles
     {
-        get { return _contextAccessor.HttpContext.User.Claims.Where(d => d.Type == ClaimTypes.Role).Select(d => d.Value).ToArray(); }
+        get { return _contextAccessor.HttpContext?.User.Claims.Where(d => d.Type == ClaimTypes.Role).Select(d => d.Value).ToArray(); }
         init { }
     }
 
     public string? OrgId
     {
-        get { return _contextAccessor.HttpContext.User.FindFirstValue(AppClaimTypes.DepartmentId); }
+        get { return _contextAccessor.HttpContext?.User.FindFirstValue(AppClaimTypes.DepartmentId); }
         init { }
     }
 
-    public string RequiredOrgId => _contextAccessor.HttpContext.User.FindFirstValue(AppClaimTypes.DepartmentId);
+    public string RequiredOrgId => _contextAccessor.HttpContext?.User.FindFirstValue(AppClaimTypes.DepartmentId);
     public string? OrgName
     {
-        get { return _contextAccessor.HttpContext.User.FindFirstValue(AppClaimTypes.DepartmentName); }
+        get { return _contextAccessor.HttpContext?.User.FindFirstValue(AppClaimTypes.DepartmentName); }
         init { }
     }
 
     public int OrgLevel
     {
-        get { return _contextAccessor.HttpContext.User.FindIntValue(AppClaimTypes.DepartmentLevel); }
+        get { return _contextAccessor.HttpContext?.User.FindIntValue(AppClaimTypes.DepartmentLevel) ?? 0; }
         init { }
     }
 
@@ -110,7 +109,7 @@ public class DefaultSessionContext : ISessionContext, IScopeDependency
 
     public string? AreaId
     {
-        get { return _contextAccessor.HttpContext.User.FindFirstValue(AppClaimTypes.AreaId); }
+        get { return _contextAccessor.HttpContext?.User.FindFirstValue(AppClaimTypes.AreaId); }
         init { }
     }
     public string? ClientId

+ 37 - 0
src/Hotline.Application.Tests/Controller/IndustryControllerTest.cs

@@ -0,0 +1,37 @@
+using Hotline.Api.Controllers;
+using Hotline.Api.Controllers.Snapshot;
+using Hotline.Identity.Accounts;
+using Hotline.Identity.Roles;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Users;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+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.Controller;
+public class IndustryControllerTest : TestBase
+{
+    private readonly IndustryController _industryController;
+    public IndustryControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdIdentiyService, IThirdAccountRepository thirdAccountRepository, IndustryController industryController) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdIdentiyService, thirdAccountRepository)
+    {
+        _industryController = industryController;
+        _industryController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
+    }
+
+    [Fact]
+    public async Task GetBaseData_Test()
+    {
+        var result = await _industryController.GetBaseData();
+        result.Count.ShouldNotBe(0);
+    }
+}

+ 2 - 1
src/Hotline.Application.Tests/Controller/KnowledgeControllerTest.cs

@@ -8,6 +8,7 @@ using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Knowledge;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.KnowledgeBase;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
@@ -27,7 +28,7 @@ public class KnowledgeControllerTest : TestBase
     private readonly KnowledgeController _knowledgeController;
     private readonly IRepository<KnowledgeBase.Knowledge> _knowledgeRepository;
 
-    public KnowledgeControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, KnowledgeServiceMock knowledgeServiceMock, KnowledgeController knowledgeController, IRepository<KnowledgeBase.Knowledge> knowledgeRepository, IHttpContextAccessor httpContextAccessor) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    public KnowledgeControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, KnowledgeServiceMock knowledgeServiceMock, KnowledgeController knowledgeController, IRepository<KnowledgeBase.Knowledge> knowledgeRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _knowledgeServiceMock = knowledgeServiceMock;
         _knowledgeController = knowledgeController;

+ 61 - 3
src/Hotline.Application.Tests/Controller/OrderControllerTest.cs

@@ -15,12 +15,14 @@ using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Orders;
 using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.Snapshot;
 using Hotline.Settings;
 using Hotline.Settings.Hotspots;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Order.Publish;
+using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Dtos.TrCallCenter;
 using Hotline.Share.Dtos.Users;
 using Hotline.Share.Enums.CallCenter;
@@ -28,6 +30,7 @@ using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using MediatR;
 using Microsoft.AspNetCore.Http;
@@ -63,8 +66,10 @@ public class OrderControllerTest : TestBase
     private readonly IRepository<CallidRelation> _callIdRelationRepository;
     private readonly XingTangCallApplication _defaultCallApplication;
     private readonly ISqlSugarClient _capSqlClient;
+    private readonly IIndustryRepository _industryRepository;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
 
-    public OrderControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IRepository<Hotspot> hotspotRepository, OrderController orderController, IOrderRepository orderRepository, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, OrderServiceMock orderServiceMock, IRepository<OrderPublish> orderPublishRepository, INotificationHandler<EndWorkflowNotify> orderPublishEndWorkflowHandler, IOrderVisitRepository orderVisitRepository, IRepository<SystemSetting> systemSettingRepository, ISystemSettingCacheManager systemSettingCacheManager, IRepository<CallNative> callNativeRepository, IRepository<CallidRelation> callIdRelationRepository, XingTangCallApplication defaultCallApplication, ISugarUnitOfWork<CapDbContext> capDbContext, IHttpContextAccessor httpContextAccessor) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    public OrderControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IRepository<Hotspot> hotspotRepository, OrderController orderController, IOrderRepository orderRepository, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, OrderServiceMock orderServiceMock, IRepository<OrderPublish> orderPublishRepository, INotificationHandler<EndWorkflowNotify> orderPublishEndWorkflowHandler, IOrderVisitRepository orderVisitRepository, IRepository<SystemSetting> systemSettingRepository, ISystemSettingCacheManager systemSettingCacheManager, IRepository<CallNative> callNativeRepository, IRepository<CallidRelation> callIdRelationRepository, XingTangCallApplication defaultCallApplication, ISugarUnitOfWork<CapDbContext> capDbContext, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount, IIndustryRepository industryRepository, IOrderSnapshotRepository orderSnapshotRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _hotspotRepository = hotspotRepository;
         _orderController = orderController;
@@ -83,6 +88,8 @@ public class OrderControllerTest : TestBase
         _callIdRelationRepository = callIdRelationRepository;
         _defaultCallApplication = defaultCallApplication;
         _capSqlClient = capDbContext.Db;
+        _industryRepository = industryRepository;
+        _orderSnapshotRepository = orderSnapshotRepository;
     }
 
     /// <summary>
@@ -101,7 +108,7 @@ public class OrderControllerTest : TestBase
             .With(m => m.Id, Ulid.NewUlid().ToString())
             .With(m => m.CallNo, callNo)
             .With(m => m.Direction, ECallDirection.In)
-            .With(m => m.IsDeleted , false)
+            .With(m => m.IsDeleted, false)
             .Create();
         await _callNativeRepository.AddAsync(inDto);
 
@@ -110,7 +117,7 @@ public class OrderControllerTest : TestBase
             .With(m => m.Direction, ECallDirection.Out)
             .With(m => m.CallNo, callNo)
             .With(m => m.AudioFile, string.Empty)
-            .With(m => m.IsDeleted , false)
+            .With(m => m.IsDeleted, false)
             .Create();
         await _callNativeRepository.AddAsync(inDto2);
         var callOrder = new CallidRelation
@@ -166,6 +173,36 @@ public class OrderControllerTest : TestBase
         order.ShouldNotBeNull();
     }
 
+    /// <summary>
+    /// 创建随手拍工单
+    /// 验证随手拍工单是否能正常创建
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task OrderSnapshot_Test()
+    {
+        var industryItems = await _industryRepository.Queryable() .Select(d => new { d.Id, d.Name, }) .ToListAsync();
+        var industry = industryItems.First();
+
+        SetZuoXi();
+        var order = _orderServiceMock.CreateOrder(industryId:industry.Id, industryName: industry.Name)
+            .办理到派单员()
+            .办理到归档(SetPaiDanYuan, data =>
+            {
+                data.IsEvasive = true;
+                data.IsInactively = true;
+            })
+            .GetCreateResult();
+        var orderEntity = await _orderRepository.GetAsync(order.Id);
+        orderEntity.ShouldNotBeNull();
+
+        var snapshot = await _orderSnapshotRepository.GetAsync(m => m.Id == order.Id);
+        snapshot.ShouldNotBeNull();
+        snapshot.IndustryId.ShouldBe(industry.Id);
+        snapshot.IndustryName.ShouldBe(industry.Name);
+    }
+
+
     /// <summary>
     /// 验证中心直办工单归档后 自动发布
     /// 是否推诿, 是否不积极
@@ -328,4 +365,25 @@ public class OrderControllerTest : TestBase
         published.ShouldNotBeNull();
         published.IsDeleted.ShouldBeFalse();
     }
+
+    /// <summary>
+    /// 工单批量标注
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task OrderSignBath_Test()
+    {
+        var inDto = new OrderSignBathInDto
+        {
+            IsSafetyDepartment = true,
+            Remark = "单元测试标注",
+            OrderIds = new List<string>()
+        };
+        inDto.OrderIds.Add(_orderServiceMock.CreateSnapshotOrder(SetWeiXin).办理到工单标注(SetZuoXi).GetCreateResult().Id);
+        inDto.OrderIds.Add(_orderServiceMock.CreateSnapshotOrder(SetWeiXin).办理到工单标注(SetZuoXi).GetCreateResult().Id);
+        Set班长();
+        await _orderController.GetNextStepsWithRecommend(inDto.OrderIds.First());
+        var result = await _orderController.OrderSignBathAsync(inDto);
+        result.Contains("标注完成").ShouldBe(true);
+    }
 }

+ 54 - 3
src/Hotline.Application.Tests/Controller/SnapshotControllerTest.cs

@@ -1,9 +1,16 @@
-using Hotline.Api.Controllers;
+using AutoFixture;
+using Hotline.Api.Controllers;
 using Hotline.Api.Controllers.Snapshot;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
+using Hotline.Orders;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.DependencyInjection;
 using Shouldly;
 using System;
@@ -17,13 +24,23 @@ namespace Hotline.Application.Tests.Controller;
 public class SnapshotControllerTest : TestBase
 {
     private readonly SnapshotController _snapshotController;
-    public SnapshotControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, SnapshotController snapshotController) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    private readonly IOrderRepository _orderRepository;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly IIndustryRepository _industryRepository;
+    public SnapshotControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, SnapshotController snapshotController, IOrderRepository orderRepository, IOrderSnapshotRepository orderSnapshotRepository, IIndustryRepository industryRepository, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _snapshotController = snapshotController;
+        _snapshotController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
+        _orderRepository = orderRepository;
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _industryRepository = industryRepository;
     }
 
     [Fact]
-    public async Task GetAreaTreeTest()
+    public async Task GetAreaTree_Test()
     {
         var result = await _snapshotController.GetAreaTreeAsync();
         result.ShouldNotBeNull();
@@ -35,4 +52,38 @@ public class SnapshotControllerTest : TestBase
         zzz.ShouldNotBeNull();
     }
 
+    /// <summary>
+    /// 测试创建随手拍工单
+    /// </summary>
+    /// <returns></returns>
+    [Fact]
+    public async Task AddOrder_Test()
+    {
+        var homePage = await _snapshotController.GetHomePageAsync();
+        var industry = homePage.Industrys.Where(m => m.IndustryType == EIndustryType.Declare).FirstOrDefault();
+        var pageBase = await _snapshotController.GetIndustryBaseAsync(industry.Id);
+
+        var inDto = _fixture.Create<AddSnapshotOrderInDto>();
+        inDto.Street = "单元测试街道" + DateTime.Now.ToLongDateTimeString();
+        inDto.IndustryId = industry.Id;
+        inDto.Town = "仙市镇";
+        inDto.County = "沿滩区";
+        inDto.Description = "单元测试添加的时间描述";
+        inDto.IsSecret = false;
+        //inDto.JobType = 
+        foreach (var item in inDto.Files)
+        {
+            item.FileName = DateTime.Now.ToShortTimeString() + "文件.doc";
+        }
+
+        var order = await _snapshotController.AddOrderAsync(inDto);
+        var orderEntity = await _orderRepository.GetAsync(order.Id);
+        orderEntity.ShouldNotBeNull();
+        orderEntity.Latitude.ShouldBe(inDto.Latitude);
+        orderEntity.Longitude.ShouldBe(inDto.Longitude);
+        orderEntity.Title.ShouldNotBeNullOrEmpty();
+        orderEntity.Content.ShouldNotBeNullOrEmpty();
+        var orderSnapshotEntity = await _orderSnapshotRepository.GetAsync(order.Id);
+        orderSnapshotEntity.ShouldNotBeNull();
+    }
 }

+ 2 - 1
src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs

@@ -11,6 +11,7 @@ using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Tools;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using Mapster;
 using Microsoft.AspNetCore.Http;
@@ -28,7 +29,7 @@ public class OrderVisitDomainServiceTest : TestBase
     private readonly IOrderRepository _orderRepository;
     private readonly OrderServiceMock _orderServiceMock;
 
-    public OrderVisitDomainServiceTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher, IOrderRepository orderRepository, OrderServiceMock orderServiceMock, IHttpContextAccessor httpContextAccessor) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor)
+    public OrderVisitDomainServiceTest(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IOrderVisitDomainService orderVisitDomainService, IOrderVisitRepository orderVisitRepository, IRepository<OrderVisitDetail> orderVisitDetailRepository, Publisher publisher, IOrderRepository orderRepository, OrderServiceMock orderServiceMock, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService, IThirdAccountRepository thirdAccount) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _orderVisitDomainService = orderVisitDomainService;
         _orderVisitRepository = orderVisitRepository;

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

@@ -41,8 +41,8 @@
     <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="..\TianQue.Sdk\TianQue.Sdk.csproj" />
     <ProjectReference Include="..\Tr.Sdk\Tr.Sdk.csproj" />
-    <ProjectReference Include="..\Hotline.WeChat\Hotline.WeChat.csproj" />
     <ProjectReference Include="..\XF.Domain.Repository\XF.Domain.Repository.csproj" />
     <ProjectReference Include="..\XF.Domain\XF.Domain.csproj" />
   </ItemGroup>

+ 3 - 0
src/Hotline.Application.Tests/Infrastructure/TestSettingConstants.cs

@@ -7,11 +7,14 @@ using System.Threading.Tasks;
 namespace Hotline.Application.Tests.Infrastructure;
 public static class TestSettingConstants
 {
+    public const string WeiXinAccountName = "UnitTestWeiXin";
     public const string ZuoXiAccountName = "UnitTestZuoXi";
     public const string PaiDanYuanAccountName = "UnitTestPDY";
     public const string FirstOrgAccountName = "cs";
     public const string SecondOrgAccountName = "cs21";
     public const string BanZhangAccountName = "UnitTestBZ";
+
+    public const string GuiderAccountName = "UnitTestGuider";
 }
 
 public static class TestSessionConstants

+ 50 - 0
src/Hotline.Application.Tests/Infrastructure/TianQueTest.cs

@@ -0,0 +1,50 @@
+using Hotline.File;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TianQue.Sdk;
+
+namespace Hotline.Application.Tests.Infrastructure;
+public class TianQueTest
+{
+    private readonly IFileDomainService _fileDomainService;
+
+    public TianQueTest(IFileDomainService fileDomainService)
+    {
+        _fileDomainService = fileDomainService;
+    }
+
+    [Fact]
+    public void Test_GenerateNonce()
+    {
+        // Arrange
+        var nonce = SignUtils.GenerateNonce();
+
+        // Assert
+        Assert.NotNull(nonce);
+        Assert.Equal(64, nonce.Length);
+        // b55dfdedba900437d486e70e5fb78ed50afaeb910a2346f16ef03af656f8bb0b
+    }
+
+    [Fact]
+    public async Task PostAcceptInfo_Test()
+    {
+        // Arrange
+        //var tiqnQueService = new TiqnQueService();
+
+        //// Act
+        //var result = await tiqnQueService.PostAcceptInfo();
+
+        //// Assert
+        //Assert.Equal("ok", result);
+    }
+
+    [Fact]
+    public async Task GetFile_Test()
+    { 
+        //await _fileDomainService.GetNetworkFileAsync("http://10.0.188.11:1234/tqOssManager/getObjectByUri/sichuan/scgrid/jpg/2024/12/5/095020318625.jpg", "");
+    }
+
+}

+ 24 - 0
src/Hotline.Application.Tests/Infrastructure/WeiXinTest.cs

@@ -0,0 +1,24 @@
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.WeChat;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Tests.Infrastructure;
+public class WeiXinTest
+{
+    private readonly WeChatService _weChatService;
+
+    public WeiXinTest(WeChatService weChatService)
+    {
+        _weChatService = weChatService;
+    }
+
+    // [Fact]
+    public async Task GetPhoneNumber_Test()
+    {
+        var result = await _weChatService.GetPhoneNumberAsync(new ThirdTokenDto { AppId = "12333", Secret = "4444444" });
+    }
+}

+ 19 - 0
src/Hotline.Application.Tests/Mock/Interfaces/IOrderServiceStartWorkflow.cs

@@ -0,0 +1,19 @@
+using Hotline.Application.Tests.Dto;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Tests.Mock.Interfaces;
+
+
+public interface IOrderServiceStartWorkflow
+{
+    OrderServiceMock orderServiceMock { set;}
+
+    OrderServiceMock 办理到派单员(Action action = null);
+    OrderServiceMock 办理到工单标注(Action action = null);
+    CreateOrderOutDto GetCreateResult();
+    OrderServiceMock 办理到一级部门(Action action = null);
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 97 - 19
src/Hotline.Application.Tests/Mock/OrderServiceMock.cs


+ 145 - 0
src/Hotline.Application.Tests/Mock/OrderServiceStartWorkflow.cs

@@ -0,0 +1,145 @@
+using AutoFixture;
+using Hotline.Api.Controllers;
+using Hotline.Api.Controllers.Snapshot;
+using Hotline.Application.Tests.Dto;
+using Hotline.Application.Tests.Mock.Interfaces;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Tools;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+
+namespace Hotline.Application.Tests.Mock;
+public class OrderServiceStartWorkflow : IOrderServiceStartWorkflow
+{
+    public readonly IFixture _fixture;
+    public readonly ISessionContext _sessionContext;
+    private readonly OrderController _orderController;
+    private readonly SnapshotController _snapshotController;
+    public OrderServiceMock _orderServiceMock;
+
+    public OrderServiceStartWorkflow(ISessionContext sessionContext, OrderController orderController, SnapshotController snapshotController)
+    {
+        _orderController = orderController;
+        _orderController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
+        _snapshotController = snapshotController;
+        _snapshotController.ControllerContext = new ControllerContext
+        {
+            HttpContext = new DefaultHttpContext()
+        };
+        _fixture = new Fixture();
+
+
+        _sessionContext = sessionContext;
+    }
+
+    public OrderServiceMock orderServiceMock { set => _orderServiceMock = value; }
+
+    public CreateOrderOutDto GetCreateResult()
+    {
+        return _orderServiceMock.CreateOrderOutDto;
+    }
+
+    public OrderServiceMock 办理到一级部门(Action action = null)
+    {
+        action?.Invoke();
+        var stepNextInfo = _orderController.GetFlowStartOptions(_orderServiceMock.CreateOrderOutDto.Id).GetAwaiter().GetResult().ToJson().FromJson<NextStepsDto<NextStepOption>>();
+        var stepInfo = stepNextInfo.Steps.FirstOrDefault(m => m.Value == "一级部门");
+        var stepOrg = stepInfo.Items.FirstOrDefault(m => m.OrgName == "测试部门");
+
+        var handleDto = new StartWorkflowDto<OrderHandleFlowDto>
+        {
+            Data = new OrderHandleFlowDto
+            {
+                OrderId = _orderServiceMock.CreateOrderOutDto.Id,
+            },
+            Workflow = new BasicWorkflowDto
+            {
+                NextHandlers = [stepOrg],
+                NextStepCode = stepInfo.Key,
+                NextStepName = stepInfo.Value,
+                Opinion = "办理意见",
+                BackToCountersignEnd = false,
+                IsSms = false,
+                IsForwarded = false,
+                HandlerType = EHandlerType.OrgLevel,
+                BusinessType = EBusinessType.Department,
+            }
+        };
+        _orderController.StartFlow(handleDto).GetAwaiter().GetResult();
+        return _orderServiceMock;
+    }
+
+    public OrderServiceMock 办理到工单标注(Action action = null)
+    {
+        action?.Invoke();
+        var stepNextInfo = _orderController.GetFlowStartOptions(_orderServiceMock.CreateOrderOutDto.Id).GetAwaiter().GetResult().ToJson().FromJson<NextStepsDto<NextStepOption>>();
+        var stepInfo = stepNextInfo.Steps.FirstOrDefault(m => m.Value == "工单标记");
+        var stepOrg = stepInfo.Items.FirstOrDefault(m => m.Username == "单元测试班长");
+
+        var handleDto = new StartWorkflowDto<OrderHandleFlowDto>
+        {
+            Data = new OrderHandleFlowDto
+            {
+                OrderId = _orderServiceMock.CreateOrderOutDto.Id,
+                
+            },
+            Workflow = new BasicWorkflowDto
+            {
+                NextHandlers = [stepOrg],
+                NextStepCode = stepInfo.Key,
+                NextStepName = stepInfo.Value,
+                Opinion = "办理到派单组意见",
+                BackToCountersignEnd = false,
+                IsSms = false,
+                IsForwarded = false,
+                HandlerType = EHandlerType.OrgLevel,
+                BusinessType = EBusinessType.Send,
+                FlowDirection = EFlowDirection.CenterToCenter,
+            }
+        };
+        _orderController.StartFlow(handleDto).GetAwaiter().GetResult();
+        return _orderServiceMock;
+    }
+
+    public OrderServiceMock 办理到派单员(Action action = null)
+    {
+        action?.Invoke();
+        var stepNextInfo = _orderController.GetFlowStartOptions(_orderServiceMock.CreateOrderOutDto.Id).GetAwaiter().GetResult().ToJson().FromJson<NextStepsDto<NextStepOption>>();
+        var stepInfo = stepNextInfo.Steps.FirstOrDefault(m => m.Value == "派单组");
+        var stepOrg = stepInfo.Items.FirstOrDefault(m => m.Username == "单元测试派单员");
+
+        var handleDto = new StartWorkflowDto<OrderHandleFlowDto>
+        {
+            Data = new OrderHandleFlowDto
+            {
+                OrderId = _orderServiceMock.CreateOrderOutDto.Id,
+            },
+            Workflow = new BasicWorkflowDto
+            {
+                NextHandlers = [stepOrg],
+                NextStepCode = stepInfo.Key,
+                NextStepName = stepInfo.Value,
+                Opinion = "办理到派单组意见",
+                BackToCountersignEnd = false,
+                IsSms = false,
+                IsForwarded = false,
+                HandlerType = EHandlerType.OrgLevel,
+                BusinessType = EBusinessType.Send,
+                FlowDirection = EFlowDirection.CenterToCenter,
+            }
+        };
+        _orderController.StartFlow(handleDto).GetAwaiter().GetResult();
+        return _orderServiceMock;
+    }
+}

+ 6 - 2
src/Hotline.Application.Tests/Mock/ThirdTestService.cs

@@ -1,19 +1,23 @@
 using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
 using Hotline.Users;
+using XF.Domain.Dependency;
 
 namespace Hotline.Application.Tests.Mock;
-public class ThirdTestService : IThirdIdentiyService
+public class ThirdTestService : IThirdIdentiyService, IScopeDependency
 {
     public async Task<ThirdPhoneOutDto> GetPhoneNumberAsync(ThirdTokenDto dto)
     {
         return new ThirdPhoneOutDto
         {
-            PhoneNumber = "13800138000"
+            PhoneNumber = "138001389877"
         };
     }
 
     public async Task<ThirdTokenOutDto> GetTokenAsync(ThirdTokenDto dto)
     {
+        //var resultString = "{\"SessionKey\":\"letVB1m+8ZYsMjo8PxhwUw==\",\"OpenId\":\"oHBwj4_8hQG1Q00HyTO3d47RLuAA\"}";
+        //return resultString.FromJson<ThirdTokenOutDto>();
         return new ThirdTokenOutDto
         {
             SessionKey = "sessionKeyfjdklsafjdskla",

+ 0 - 10
src/Hotline.Application.Tests/Repository/FWMQRepositoryTest.cs

@@ -1,10 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Hotline.Application.Tests.Repository;
-internal class FWMQRepositoryTest
-{
-}

+ 19 - 6
src/Hotline.Application.Tests/Startup.cs

@@ -59,6 +59,12 @@ using Hotline.Application.Tests.SqlSuger;
 using Microsoft.AspNetCore.Http;
 using Hotline.WeChat;
 using Hotline.Api.Controllers.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using TianQue.Sdk;
+using Hotline.Snapshot.Notifications;
+using Hotline.File;
+using Hotline.Settings.SystemLogDomain;
+using Hotline.Application.Tests.Mock.Interfaces;
 using Hotline.Orders.DatabaseEventHandler;
 using Hotline.Orders;
 using XF.Domain.Repository.Events;
@@ -82,7 +88,7 @@ public class Startup
             {
                 config.AddJsonFile(JsonFile);
             })
-            .UseStartup<AspNetCoreStartup>()) ;
+            .UseStartup<AspNetCoreStartup>());
     }
 
     private class AspNetCoreStartup
@@ -94,7 +100,7 @@ public class Startup
 
         public IConfiguration Configuration { get; }
 
-        public void ConfigureServices(IServiceCollection services) 
+        public void ConfigureServices(IServiceCollection services)
         {
             AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
             var configuration = Configuration;
@@ -105,11 +111,11 @@ public class Startup
             var callCenterConfigurationSection = configuration.GetRequiredSection(nameof(CallCenterConfiguration));
             var callCenterConfiguration = callCenterConfigurationSection.Get<CallCenterConfiguration>();
 
-			services.Configure<AppConfiguration>(d => appConfigurationSection.Bind(d));
+            services.Configure<AppConfiguration>(d => appConfigurationSection.Bind(d));
             services.Configure<IdentityConfiguration>(d => configuration.GetSection(nameof(IdentityConfiguration)).Bind(d));
             services.Configure<CityBaseConfiguration>(d => configuration.GetSection(nameof(CityBaseConfiguration)).Bind(d));
 
-			services.RegisterMapper();
+            services.RegisterMapper();
             //services.AddControllers()
             //    .AddControllersAsServices();
 
@@ -117,8 +123,6 @@ public class Startup
             services.AddSqlSugar(configuration);
             services.AddCAPDb(configuration);
 
-            // application services
-            services.AddScoped<ISnapshotApplication, ZiGongSnapshotApplication>();
 
             //mq
             services.AddTestMq(configuration);
@@ -161,6 +165,7 @@ public class Startup
 
             //services.AddScoped<IThirdAccountRepository, ThirdAccountRepository>();
             services.AddApplication();
+            services.AddScoped<IOrderServiceStartWorkflow, OrderServiceStartWorkflow>();
             services.AddScoped<IExpireTimeSupplier, DaySupplier>();
             services.AddScoped<IExpireTimeSupplier, WorkDaySupplier>();
             services.AddScoped<IExpireTimeSupplier, HourSupplier>();
@@ -173,6 +178,7 @@ public class Startup
             services.AddScoped<IMediator, MediatorMock>();
             services.AddScoped<IExportApplication, ExportApplication>();
             services.AddScoped<OrderController>();
+            services.AddScoped<IndustryController>();
             services.AddScoped<UserController>();
             services.AddScoped<SnapshotController>();
             services.AddScoped<KnowledgeController>();
@@ -185,7 +191,14 @@ public class Startup
             services.AddScoped<OrderServiceMock>();
             services.AddScoped<KnowledgeServiceMock>();
             services.AddScoped<XingTangCallsSyncJob>();
+            services.AddScoped<IFileDomainService, FileDomainService>();
             services.AddXingTangDb(callCenterConfiguration.XingTang);
+            services.AddScoped<IGuiderSystemService, TiqnQueService>();
+            services.AddScoped<LoggingInterceptor>();
+            services.AddScoped<AsyncLoggingInterceptor>();
+
+            // application services
+            services.AddProxiedScoped<ISnapshotApplication, DefaultSnapshotApplication>();
             //ServiceLocator.Instance = services.BuildServiceProvider();
         }
 

+ 39 - 18
src/Hotline.Application.Tests/TestBase.cs

@@ -7,6 +7,7 @@ using Hotline.Settings;
 using Hotline.Share.Dtos.Users;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.User;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using IdentityModel;
 using Microsoft.AspNetCore.Http;
@@ -26,10 +27,14 @@ public class TestBase
     public readonly IFixture _fixture;
     private readonly IServiceScopeFactory _scopeFactory;
     public readonly IHttpContextAccessor _httpContextAccessor;
+    public readonly IThirdIdentiyService _thirdIdentiyService;
+    public readonly IThirdAccountRepository _thirdAccountRepository;
 
-    public TestBase(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor)
-    {
 
+    public TestBase(IAccountRepository accountRepository, IRepository<Role> roleRepository, UserController userController, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdIdentiyService, IThirdAccountRepository thirdAccountRepository)
+    {
+        _thirdAccountRepository = thirdAccountRepository;
+        _thirdIdentiyService = thirdIdentiyService;
         _fixture = new Fixture();
         _accountRepository = accountRepository;
         _roleRepository = roleRepository;
@@ -47,6 +52,7 @@ public class TestBase
             httpContextAccessor.HttpContext = new DefaultHttpContext();
             SetZuoXi();
         }
+
     }
 
     public void SetPaiDanYuan()
@@ -74,18 +80,29 @@ public class TestBase
         SetOperator("坐席", "市民热线服务中心", "单元测试坐席", "001", "13408389849", EUserType.Seat, TestSettingConstants.ZuoXiAccountName);
     }
 
+    public void SetWeiXin()
+    {
+        SetOperator("微信用户", "", "", "", "138001389877", EUserType.Normal, TestSettingConstants.WeiXinAccountName);
+    }
+
+    public void Set网格员()
+    {
+        SetOperator("网格员", "市民热线服务中心", "单元测试网格员", "001", "13408389849", EUserType.Normal, TestSettingConstants.GuiderAccountName);
+    }
+
     private void SetOperator(string displayName, string fullOrgName, string name, string orgId, string phoneNo, EUserType userType, string userName)
     {
         var account = _accountRepository.GetExtAsync(
             d => d.UserName == userName,
             d => d.Includes(x => x.Roles)).GetAwaiter().GetResult();
 
-        if (account == null)
+        var roleId = _roleRepository.Queryable()
+                       .Where(m => m.DisplayName == displayName)
+                       .Select(m => m.Id)
+                       .First();
+
+        if (account == null && roleId != null)
         {
-            var roleId = _roleRepository.Queryable()
-                .Where(m => m.DisplayName == displayName)
-                .Select(m => m.Id)
-                .First();
             var newUser = new AddUserDto
             {
                 FullOrgName = fullOrgName,
@@ -98,25 +115,29 @@ public class TestBase
                 UserName = userName
             };
             var accountId = _userController.Add(newUser).GetAwaiter().GetResult();
-            // TestSessionConstants.UserId = accountId;
             account = _accountRepository.GetExtAsync(
                 d => d.UserName == userName,
                 d => d.Includes(x => x.Roles)).GetAwaiter().GetResult();
         }
-        var user = _userRepository.GetAsync(account.Id).GetAwaiter().GetResult();
+        var user = _userRepository.Queryable()
+            .Includes(d => d.Organization)
+            .FirstAsync(d => d.Id == account.Id).GetAwaiter().GetResult();
+        var third = _thirdIdentiyService.GetTokenAsync(new Share.Dtos.Snapshot.ThirdTokenDto()).GetAwaiter().GetResult();
+        var thirdAccount = _thirdAccountRepository.Get(d => d.OpenId == third.OpenId);
 
-        List<Claim> userClaims = [ 
-            new(JwtClaimTypes.Subject, account.Id),
-            new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? string.Empty),
+        List<Claim> userClaims = [
+            new(JwtClaimTypes.Subject, account.Id ?? thirdAccount.Id),
+            new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? thirdAccount.PhoneNumber),
             new(ClaimTypes.NameIdentifier, user.Id),
             new(AppClaimTypes.UserDisplayName, account.Name),
-            new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty), 
-            new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString() ?? false.ToString()), 
-            new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty), 
-            new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty), 
-            new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty), 
-            new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty), 
+            new(AppClaimTypes.DepartmentId, user.OrgId ?? string.Empty),
+            new(AppClaimTypes.DepartmentIsCenter, user.Organization?.IsCenter.ToString() ?? false.ToString()),
+            new(AppClaimTypes.DepartmentName, user.Organization?.Name ?? string.Empty),
+            new(AppClaimTypes.DepartmentAreaCode, user.Organization?.AreaCode ?? string.Empty),
+            new(AppClaimTypes.DepartmentAreaName, user.Organization?.AreaName ?? string.Empty),
+            new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty),
             new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty),
+            new(AppClaimTypes.OpenId, thirdAccount?.OpenId ?? string.Empty),
         ];
         ClaimsIdentity identity = new ClaimsIdentity(userClaims);
         var principal = new ClaimsPrincipal(identity);

+ 19 - 4
src/Hotline.Application/Bigscreen/DataScreenRefreshService.cs

@@ -14,10 +14,12 @@ using System.Text;
 using System.Threading.Tasks;
 using Hotline.Settings;
 using XF.Domain.Constants;
+using XF.Domain.Repository;
+using Hotline.JudicialManagement;
 
 namespace Hotline.Application.Bigscreen
 {
-    public class DataScreenRefreshService: BackgroundService
+    public class DataScreenRefreshService : BackgroundService
     {
         private readonly IServiceScopeFactory _serviceScopeFactory;
 
@@ -33,7 +35,7 @@ namespace Hotline.Application.Bigscreen
             var _orderRepository = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
             var _mapper = scope.ServiceProvider.GetRequiredService<IMapper>();
             var systemSettingCacheManager = scope.ServiceProvider.GetRequiredService<ISystemSettingCacheManager>();
-           
+            var _orderSecondaryHandlingRepository = scope.ServiceProvider.GetRequiredService<IRepository<OrderSecondaryHandling>>();
             int times = int.Parse(systemSettingCacheManager.GetSetting(SettingConstants.BsDataStateChangedTimes)?.SettingValue[0]);
             while (!stoppingToken.IsCancellationRequested)
             {
@@ -95,7 +97,7 @@ namespace Hotline.Application.Bigscreen
 
                     await realtimeService.OrderCountStatisticsAsync(dto, stoppingToken);
                 }
-                catch{}
+                catch { }
 
                 try
                 {
@@ -110,7 +112,20 @@ namespace Hotline.Application.Bigscreen
 
                     await realtimeService.OrderHandlingDetailAsync(orderlist, stoppingToken);
                 }
-                catch{}
+                catch { }
+
+                try
+                {
+                    var list = await _orderSecondaryHandlingRepository.Queryable().Includes(x => x.Order)
+                              .Where(x => x.CreationTime.Date == DateTime.Now.Date)
+                              .OrderByDescending(x => x.CreationTime)
+                              .Take(50)
+                             .ToListAsync();
+                    var orderlist = _mapper.Map<List<OrderSecondaryHandlingDto>>(list);
+
+                    await realtimeService.OrderHandlingDetailAsync(orderlist, stoppingToken);
+                }
+                catch { }
 
                 await Task.Delay(times);
             }

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

@@ -542,6 +542,23 @@ public abstract class DefaultCallApplication : ICallApplication
         }
 
         var call = await _callNativeRepository.Queryable()
+            .Where(m => m.Id == orderCall.CallId)
+            .FirstAsync(cancellationToken);
+        // Order已经关联的通话记录是呼入的,并且有通话录音, 所以不需要再次关联
+        if (call != null && call.Direction == ECallDirection.In && call.AudioFile.NotNullOrEmpty())
+        {
+            // 推省上
+            await _capPublisher.PublishAsync(EventNames.HotlineCallConnectWithOrder, new PublishCallRecrodDto()
+            {
+                Order = (await _orderRepository.GetAsync(orderId, cancellationToken)).Adapt<OrderDto>(),
+                TrCallRecordDto = call.Adapt<TrCallDto>()
+            }, cancellationToken: cancellationToken);
+            var message = "Order已经关联的通话记录是呼入的,并且有通话录音, 所以不需要再次关联.(完成推省上)";
+            _systemLogRepository.Add("延迟更新工单通话", orderId, message, status: 1, ipUrl: orderCall.CallId);
+            return message;
+        }
+
+        call = await _callNativeRepository.Queryable()
             .Where(m => m.CallNo == orderCall.CallNo && m.Direction == ECallDirection.In)
             .OrderByDescending(m => m.Duration)
             .FirstAsync(cancellationToken);

+ 6 - 6
src/Hotline.Application/CallCenter/ICallApplication.cs

@@ -183,17 +183,17 @@ namespace Hotline.Application.CallCenter
         /// <returns></returns>
         Task<EVoiceEvaluate> GetReplyVoiceOrDefaultByOrderIdAsync(string orderId);
 
+        /// <summary>
+        /// 保存回访详情时发送延迟消息同步通话记录
+        /// 如果回访通话记录有多条, 需要关联通话时长最长的那条
+        /// </summary>
+        Task OrderVisitRelevanceCallIdAsync(VisitDto dto, CancellationToken cancellationToken);
+
         /// <summary>
         /// 根据 OrderId 返回用户电话评价枚举
         /// </summary>
         /// <param name="orderId"></param>
         /// <returns></returns>
         Task<ESeatEvaluate> GetSeatDefaultByOrderIdAsync(string orderId);
-
-        /// <summary>
-        /// 保存回访详情时发送延迟消息同步通话记录
-        /// 如果回访通话记录有多条, 需要关联通话时长最长的那条
-        /// </summary>
-        Task OrderVisitRelevanceCallIdAsync(VisitDto dto, CancellationToken cancellationToken);
     }
 }

+ 10 - 0
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -482,6 +482,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 CurrentStepType = startStepDefine.StepType,
                 CurrentHandlerType = startStepDefine.HandlerType,
                 CurrentStepBusinessType = startStepDefine.BusinessType,
+                CurrentTag = startStepDefine.Tag ?? string.Empty,
                 Steps = new List<NextStepOption> { nextStepOption }
             };
         }
@@ -497,6 +498,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             CurrentStepType = startStepDefine.StepType,
             CurrentHandlerType = startStepDefine.HandlerType,
             CurrentStepBusinessType = startStepDefine.BusinessType,
+            CurrentTag = startStepDefine.Tag ?? string.Empty,
             TimeTypeOptions = EnumExts.GetDescriptions<ETimeType>().ToList(),
             Steps = await GetConfigStepsAsync(definition.FlowType, startStepDefine.StepType, startStepDefine.BusinessType,
                 firstStepDefines, cancellationToken)
@@ -572,6 +574,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 StepType = stepDefine.StepType,
                 BusinessType = stepDefine.BusinessType,
                 HandlerType = stepDefine.HandlerType,
+                Tag = stepDefine.Tag ?? string.Empty,
                 OrgLevel = stepDefine.HandlerType is EHandlerType.OrgLevel ? orgLevel : null,
             };
             var orgs = await _organizeRepository.Queryable()
@@ -611,6 +614,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             CurrentStepBusinessType = currentStep.BusinessType,
             CurrentStepType = currentStep.StepType,
             CurrentHandlerType = currentStep.HandlerType,
+            CurrentTag = currentStep.Tag ?? string.Empty,
             TimeTypeOptions = EnumExts.GetDescriptions<ETimeType>().ToList(),
             IsMainHandlerShow = workflow.WorkflowDefinition.IsMainHandlerShow,
             StepId = currentStep.Id,
@@ -869,6 +873,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                     BusinessType = stepDefine.BusinessType,
                     //HandlerType = stepDefine.HandlerType,
                     HandlerType = EHandlerType.AssignedUser, //指定办理人(业务需求)
+                    Tag = stepDefine.Tag ?? string.Empty,
                     Items = new List<FlowStepHandler> { handler } //handlers
                 };
             }
@@ -901,6 +906,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 StepType = stepDefine.StepType,
                 BusinessType = stepDefine.BusinessType,
                 HandlerType = stepDefine.HandlerType,
+                Tag = stepDefine.Tag ?? string.Empty,
                 Items = handlers
             };
         }
@@ -1045,6 +1051,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             StepType = stepDefine.StepType,
             BusinessType = stepDefine.BusinessType,
             HandlerType = stepDefine.HandlerType,
+            Tag = stepDefine.Tag ?? string.Empty,
             OrgLevel = stepDefine.HandlerType is EHandlerType.OrgLevel ? orgLevel : null,
             Items = handlers
         };
@@ -1081,6 +1088,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             BusinessType = prevStep.BusinessType,
             //HandlerType = prevStep.HandlerType,
             HandlerType = EHandlerType.AssignedUser, //指定办理人(业务需求)
+            Tag = prevStep.Tag ?? string.Empty,
             Items = new List<FlowStepHandler> { handler } //handlers //new List<Kv> { new(prevStep.HandlerId, prevStep.HandlerName) },
         };
     }
@@ -1480,6 +1488,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             StepType = stepType,
             BusinessType = businessType,
             HandlerType = handlerType, //目前所有动态策略均属于部门等级
+            Tag = string.Empty,
             Items = items
         };
     }
@@ -1620,6 +1629,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             StepType = stepType,
             BusinessType = businessType,
             HandlerType = EHandlerType.OrgLevel, //目前所有动态策略均属于部门等级
+            Tag = string.Empty,
             Items = items
         };
     }

+ 32 - 13
src/Hotline.Application/Handlers/FlowEngine/WorkflowNextHandler.cs

@@ -1,6 +1,7 @@
 using DotNetCore.CAP;
 using Hotline.Application.JudicialManagement;
 using Hotline.Application.Quality;
+using Hotline.Application.Snapshot;
 using Hotline.Caching.Interfaces;
 using Hotline.Configurations;
 using Hotline.EventBus;
@@ -22,6 +23,8 @@ using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.Push;
 using Hotline.Share.Enums.Quality;
 using Hotline.Share.Mq;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Snapshot.Notifications;
 using Hotline.Users;
 using Mapster;
 using MapsterMapper;
@@ -31,6 +34,8 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using XF.Domain.Authentications;
 using XF.Domain.Entities;
+using XF.Domain.Exceptions;
+using XF.Domain.Extensions;
 using XF.Domain.Repository;
 
 namespace Hotline.Application.Handlers.FlowEngine;
@@ -39,6 +44,7 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
 {
     private readonly IOrderDomainService _orderDomainService;
     private readonly ICalcExpireTime _expireTime;
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
     private readonly IOrderRepository _orderRepository;
     private readonly ICapPublisher _capPublisher;
@@ -68,7 +74,8 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
         ISystemSettingCacheManager systemSettingCacheManager,
         Publisher publisher,
         IOptionsSnapshot<AppConfiguration> appOptions,
-        ICalcExpireTime expireTime)
+        ICalcExpireTime expireTime,
+        IOrderSnapshotRepository orderSnapshotRepository)
     {
         _orderDomainService = orderDomainService;
         _orderRepository = orderRepository;
@@ -85,6 +92,7 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
         _publisher = publisher;
         _appOptions = appOptions;
         _expireTime = expireTime;
+        _orderSnapshotRepository = orderSnapshotRepository;
     }
 
     /// <summary>Handles a notification</summary>
@@ -100,12 +108,8 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
             var data = notification.Dto;
             var assignInfo = notification.FlowAssignInfo;
 
-            var currentTag = string.IsNullOrEmpty(notification.Trace.Tag)
-                ? null
-                : System.Text.Json.JsonSerializer.Deserialize<DefinitionTag>(notification.Trace.Tag);
-            var nextTag = string.IsNullOrEmpty(notification.NextStepDefine.Tag)
-                ? null
-                : System.Text.Json.JsonSerializer.Deserialize<DefinitionTag>(notification.NextStepDefine.Tag);
+            var currentTag = notification.Trace.Tag.TryDeserialize<DefinitionTag>();
+            var nextTag = notification.NextStepDefine.Tag.TryDeserialize<DefinitionTag>();
 
             switch (workflow.ModuleCode)
             {
@@ -227,14 +231,29 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
                         ExpiredTimeChanged = isCenterToOrg,
                         HandlerOrgLevel = notification.HandlerOrgId.CalcOrgLevel()
                     }, cancellationToken: cancellationToken);
+
                     if (data.FlowDirection is EFlowDirection.CenterToOrg)
                         await _qualityApplication.AddQualityAsync(EQualitySource.Send, order.Id, cancellationToken);
 
+                    //随手拍推送网格员
+                    bool.TryParse(
+                        _systemSettingCacheManager.GetSetting(SettingConstants.Snapshot)?.SettingValue[0],
+                        out bool isSnapshotEnable);
+                    if (isSnapshotEnable)
+                    {
+                        if (string.Compare(TagDefaults.Wanggeyuan, notification.NextStepDefine.Tag,
+                                StringComparison.OrdinalIgnoreCase) == 0)
+                        {
+                            await _publisher.PublishAsync(new PostGuiderSystemNotification(order.Id),
+                                PublishStrategy.ParallelWhenAll, cancellationToken);
+                        }
+                    }
+
                     break;
                 case WorkflowModuleConsts.KnowledgeAdd:
                 case WorkflowModuleConsts.KnowledgeUpdate:
                 case WorkflowModuleConsts.KnowledgeOffshelf:
-				case WorkflowModuleConsts.KnowledgeDelete:
+                case WorkflowModuleConsts.KnowledgeDelete:
                     //var knowledgeWork = await _knowledgeWorkFlowRepository.Queryable().Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
                     var knowledge = await _knowledgeRepository.Queryable().Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
                     knowledge.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
@@ -324,14 +343,14 @@ public class WorkflowNextHandler : INotificationHandler<NextStepNotify>
                                         orderDelay.DelayApplyType = EDelayApplyType.ProvinceApply;
                                         orderDelay.IsProDelay = true;
                                         await _orderDelayRepository.UpdateAsync(orderDelay);
-										//省件延期--以省审批前一个节点整理的延期意见为准推送省上 宜宾
-										if (_appOptions.Value.IsYiBin)
+                                        //省件延期--以省审批前一个节点整理的延期意见为准推送省上 宜宾
+                                        if (_appOptions.Value.IsYiBin)
                                         {
-	                                        orderDelay.DelayReason = notification.Dto.Opinion;
+                                            orderDelay.DelayReason = notification.Dto.Opinion;
                                         }
 
-										//推送
-										var publishOrderDelay = _mapper.Map<PublishOrderDelayDto>(orderDelay);
+                                        //推送
+                                        var publishOrderDelay = _mapper.Map<PublishOrderDelayDto>(orderDelay);
                                         await _capPublisher.PublishAsync(EventNames.HotlineOrderApplyDelay, publishOrderDelay,
                                             cancellationToken: cancellationToken);
 

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

@@ -20,7 +20,6 @@
     <ProjectReference Include="..\Hotline.Application.Contracts\Hotline.Application.Contracts.csproj" />
     <ProjectReference Include="..\Hotline.NewRock\Hotline.NewRock.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
-    <ProjectReference Include="..\Hotline.WeChat\Hotline.WeChat.csproj" />
     <ProjectReference Include="..\Hotline.Wex\Hotline.Wex.csproj" />
     <ProjectReference Include="..\Hotline.XingTang\Hotline.XingTang.csproj" />
     <ProjectReference Include="..\Hotline.YbEnterprise.Sdk\Hotline.YbEnterprise.Sdk.csproj" />

+ 7 - 0
src/Hotline.Application/Identity/IIdentityAppService.cs

@@ -21,6 +21,13 @@ namespace Hotline.Application.Identity
         /// <exception cref="UserFriendlyException"></exception>
         Task<TokenOutDto> GetThredTokenAsync(ThirdTokenInDto dto);
 
+        /// <summary>
+        /// 根据OpenId刷新令牌
+        /// </summary>
+        /// <param name="openId"></param>
+        /// <returns></returns>
+        Task<TokenOutDto> RefreshTokenAsync(string openId);
+
         Task<string> LoginAsync(LoginDto dto, CancellationToken cancellationToken);
 
         Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken);

+ 49 - 5
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -18,11 +18,13 @@ using Hotline.Share.Enums.Snapshot;
 using Hotline.Share.Enums.User;
 using Hotline.Share.Tools;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using IdentityModel;
 using Mapster;
 using Microsoft.AspNetCore.Identity;
 using Microsoft.Extensions.Options;
+using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
@@ -48,7 +50,10 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
     private readonly IThirdIdentiyService _thirdIdentiyService;
     private readonly IThirdAccountRepository _thirdAccountRepository;
-    private readonly IRepository<GuiderInfo> _guiderInfoRepository;
+    private readonly IGuiderInfoRepository _guiderInfoRepository;
+    private readonly IVolunteerRepository _volunteerRepository;
+    private readonly ISystemLogRepository _systemLog;
+
 
     public IdentityAppService(
         IAccountRepository accountRepository,
@@ -65,7 +70,9 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         IThirdAccountRepository thirdAccountRepository,
         ISessionContext sessionContext,
         IRepository<Citizen> citizenRepository,
-        IRepository<GuiderInfo> guiderInfoRepository)
+        IGuiderInfoRepository guiderInfoRepository,
+        IVolunteerRepository volunteerRepository,
+        ISystemLogRepository systemLog)
     {
         _accountRepository = accountRepository;
         _accountDomainService = accountDomainService;
@@ -82,6 +89,8 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         _sessionContext = sessionContext;
         _citizenRepository = citizenRepository;
         _guiderInfoRepository = guiderInfoRepository;
+        _volunteerRepository = volunteerRepository;
+        _systemLog = systemLog;
     }
 
     public async Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken)
@@ -317,16 +326,41 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         }
         var thirdToken = await _thirdIdentiyService.GetTokenAsync(thirdDto);
         var phone = await _thirdIdentiyService.GetPhoneNumberAsync(thirdDto);
-        var thirdAccount = await _thirdAccountRepository.QueryByOpenIdAsync(thirdToken.OpenId);
+        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(thirdToken.OpenId);
 
         // 新用户注册
         if (thirdAccount is null)
         {
             thirdAccount = thirdToken.Adapt<ThirdAccount>();
+            thirdAccount.CitizenType = EReadPackUserType.Citizen;
             thirdAccount.PhoneNumber = phone.PhoneNumber;
+            var guider =  await _guiderInfoRepository.GetByPhoneNumberAsync(phone.PhoneNumber);
+            if (guider != null)
+            {
+                thirdAccount.CitizenType = EReadPackUserType.Guider;
+                thirdAccount.UserId = guider.Id;
+            }
+            else
+            {
+                var citizen = await _citizenRepository.Queryable().Where(m => m.PhoneNumber == phone.PhoneNumber).FirstAsync();
+                thirdAccount.UserId = citizen?.Id;
+            }
             thirdAccount.Id = await _thirdAccountRepository.AddAsync(thirdAccount);
         }
 
+        return await GetJwtToken(thirdAccount);
+    }
+
+    public async Task<TokenOutDto> RefreshTokenAsync(string openId)
+    {
+        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(openId)
+            ?? throw UserFriendlyException.SameMessage("未找到用户信息");
+
+        return await GetJwtToken(thirdAccount);
+    }
+
+    private async Task<TokenOutDto> GetJwtToken(ThirdAccount thirdAccount)
+    {
         var jwtOptions = _identityOptionsAccessor.Value.Jwt;
         var claims = new List<Claim>
         {
@@ -339,6 +373,16 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
         await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds));
         var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
-        return new TokenOutDto(thirdAccount.CitizenType, token);
+        var isVolunteer = await _volunteerRepository.IsVolunteerAsync(thirdAccount.PhoneNumber);
+        return new TokenOutDto()
+        {
+            UserType = thirdAccount.CitizenType,
+            Token = token,
+            IsVolunteer = isVolunteer,
+            OpenId = thirdAccount.OpenId,
+            PhoneNumber = thirdAccount.PhoneNumber,
+            InvitationCode = thirdAccount.InvitationCode,
+            UserName = thirdAccount.UserName
+        };
     }
-  }
+}

+ 13 - 10
src/Hotline.Application/JudicialManagement/EnforcementApplication.cs

@@ -16,9 +16,9 @@ namespace Hotline.Application.JudicialManagement
     public class EnforcementApplication : IEnforcementApplication, IScopeDependency
     {
         private readonly IRepository<JudicialManagementOrders> _judicialManagementOrdersRepository;
-        private readonly IRepository<EnforcementOrdersHandler> _enforcementOrdersHandlerRepository;
         private readonly IMapper _mapper;
         private readonly ISessionContext _sessionContext;
+        private readonly IRepository<LawEnforcementAgencies> _lawEnforcementAgenciesRepository;
 
         /// <summary>
         /// 
@@ -27,16 +27,17 @@ namespace Hotline.Application.JudicialManagement
         /// <param name="enforcementOrdersHandlerRepository"></param>
         /// <param name="mapper"></param>
         /// <param name="sessionContext"></param>
+        /// <param name="lawEnforcementAgenciesRepository"></param>
         public EnforcementApplication(
            IRepository<JudicialManagementOrders> judicialManagementOrdersRepository,
-           IRepository<EnforcementOrdersHandler> enforcementOrdersHandlerRepository,
            IMapper mapper,
-           ISessionContext sessionContext)
+           ISessionContext sessionContext,
+           IRepository<LawEnforcementAgencies> lawEnforcementAgenciesRepository)
         {
             _judicialManagementOrdersRepository = judicialManagementOrdersRepository;
-            _enforcementOrdersHandlerRepository = enforcementOrdersHandlerRepository;
             _mapper = mapper;
             _sessionContext = sessionContext;
+            _lawEnforcementAgenciesRepository = lawEnforcementAgenciesRepository;
         }
 
         /// <summary>
@@ -62,7 +63,8 @@ namespace Hotline.Application.JudicialManagement
                    .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
                      .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName == dto.FromName) //来电人姓名
                      .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode) //区域
-                     .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => SqlFunc.JsonListObjectAny(d.EnforcementOrdersHandler, "Value", dto.OrgCode))//执法部门
+                      .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => SqlFunc.JsonListObjectAny(d.EnforcementOrdersHandler, "Value", dto.OrgCode))//接办部门
+                       .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => SqlFunc.JsonListObjectAny(d.LawEnforcementAgencies, "Value", dto.LawEnforcementOrgCode))//执法部门
                      .OrderByDescending(d => d.CreationTime)
                     .MergeTable();
         }
@@ -97,9 +99,10 @@ namespace Hotline.Application.JudicialManagement
                       .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName.Contains(dto.NameOrNo!) || d.AcceptorStaffNo.Contains(dto.NameOrNo!)) //受理人/坐席
                       .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
                      .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
-                       .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName == dto.FromName) //来电人姓名
-                       .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode) //区域
-                       .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => SqlFunc.JsonListObjectAny(d.EnforcementOrdersHandler, "Value", dto.OrgCode))//执法部门
+                     .WhereIF(!string.IsNullOrEmpty(dto.FromName), d => d.FromName == dto.FromName) //来电人姓名
+                     .WhereIF(!string.IsNullOrEmpty(dto.AreaCode), d => d.AreaCode == dto.AreaCode) //区域
+                     .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => SqlFunc.JsonListObjectAny(d.EnforcementOrdersHandler, "Value", dto.OrgCode))//接办部门
+                       .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => SqlFunc.JsonListObjectAny(d.LawEnforcementAgencies, "Value", dto.LawEnforcementOrgCode))//执法部门
                        .OrderByDescending(d => d.CreationTime)
                       .MergeTable();
 
@@ -137,7 +140,7 @@ namespace Hotline.Application.JudicialManagement
         /// <returns></returns>
         public ISugarQueryable<EmDepartmentalProcessingStatisticsDto> GetDepartmentalProcessingStatisticsAsync(DateTime StartTime, DateTime EndTime)
         {
-            return _enforcementOrdersHandlerRepository.Queryable()
+            return _lawEnforcementAgenciesRepository.Queryable()
                    .LeftJoin<JudicialManagementOrders>((h, o) => h.OrderId == o.Id)
                    .Where((h, o) => o.CreationTime >= StartTime && o.CreationTime <= EndTime && o.IsItCounted == true)
                     .GroupBy((h, o) => new
@@ -164,7 +167,7 @@ namespace Hotline.Application.JudicialManagement
         /// <returns></returns>
         public ISugarQueryable<EnforcementOrderListDto> GetDepartmentalProcessingStatisticsOrderListAsync(QueryDepartmentalProcessingStatisticsDto dto)
         {
-            return _enforcementOrdersHandlerRepository.Queryable()
+            return _lawEnforcementAgenciesRepository.Queryable()
                  .LeftJoin<JudicialManagementOrders>((x, o) => x.OrderId == o.Id)
                  .Where((x, o) => o.CreationTime >= dto.StartTime && o.CreationTime <= dto.EndTime && o.Id != null && o.IsItCounted == true)
                  .WhereIF(!string.IsNullOrEmpty(dto.OrgCode) && dto.OrgCode == "001", (x, o) => x.OrgCode == dto.OrgCode)

+ 1 - 0
src/Hotline.Application/Knowledge/KnowApplication.cs

@@ -351,6 +351,7 @@ namespace Hotline.Application.Knowledge
                 .Includes(x => x.User)
                 .Includes(x => x.SystemOrganize)
                 .Includes(x => x.HotspotType)
+                .Includes(x => x.KnowledgeType)
                 .Where(x => x.IsDeleted == false)
                 .Where(x => x.Status == EKnowledgeStatus.OnShelf)
                 .Where(x => x.KnowledgeType.Any(t => t.KnowledgeType.KnowledgeTypeOrgs.Any(to => to.OrgId == _sessionContext.RequiredOrgId) || t.KnowledgeType.KnowledgeTypeOrgs.Any() == false))

+ 7 - 0
src/Hotline.Application/Mappers/MapperConfigs.cs

@@ -20,6 +20,7 @@ using Hotline.Share.Enums.Order;
 using Hotline.Snapshot;
 using Mapster;
 using XF.Domain.Entities;
+using Hotline.Share.Tools;
 
 namespace Hotline.Application.Mappers
 {
@@ -27,6 +28,12 @@ namespace Hotline.Application.Mappers
     {
         public void Register(TypeAdapterConfig config)
         {
+            config.ForType<SystemLog, SystemLogDto>()
+                .Map(dest => dest.ExecuteParam,
+                src => src.ExecuteParam == null 
+                ? null
+                : src.ExecuteParam.ToJson().FromJson<Dictionary<string, object>>());
+
             config.ForType<ExcelContent, Order>()
                 .Map(d => d.FirstVisitResult, x => x.VisitResult)
                 .IgnoreNullValues(true);

+ 0 - 2
src/Hotline.Application/Mappers/OrderMapperConfigs.cs

@@ -265,7 +265,5 @@ public class OrderMapperConfigs : IRegister
             .IgnoreIf((s, d) => s.OrgHandledAttitude == null, d => d.OrgHandledAttitude)
             .Map(d => d.OrgHandledAttitude, s => s.OrgHandledAttitude.Value)
             ;
-
-
     }
 }

+ 68 - 1
src/Hotline.Application/Mappers/SnapshotMapperConfigs.cs

@@ -1,4 +1,10 @@
-using Hotline.Share.Dtos.Snapshot;
+using AngleSharp.Text;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Tools;
 using Hotline.Snapshot;
 using Mapster;
 using System;
@@ -12,11 +18,72 @@ public class SnapshotMapperConfigs : IRegister
 {
     public void Register(TypeAdapterConfig config)
     {
+
+        config.ForType<SystemDicData, Kv>()
+            .Map(m => m.Key, b => b.DicDataValue)
+            .Map(m => m.Value, b => b.DicDataName);
+
+        config.ForType<SystemArea, Kv>()
+            .Map(m => m.Key, n => n.Id)
+            .Map(m => m.Value, n => n.AreaName);
+
+        config.ForType< SnapshotBulletin, SnapshotBulletinDetailOutDto>()
+         .Map(m => m.BulletinTypeId, n => n.SnapshotBulletinTypeId)
+         .Map(m => m.BulletinTypeName, n => n.SnapshotBulletinTypeName);
+
+        config.ForType<UpdateSnapshotBulletinInDto, SnapshotBulletin>()
+            .Map(m => m.SnapshotBulletinTypeId, n => n.BulletinTypeId)
+            .Map(m => m.SnapshotBulletinTypeName, n => n.BulletinTypeName);
+
+        config.ForType<AddSnapshotBulletinInDto, SnapshotBulletin>()
+            .Map(m => m.SnapshotBulletinTypeId, n => n.BulletinTypeId)
+            .Map(m => m.SnapshotBulletinTypeName, n => n.BulletinTypeName);
+
+        config.ForType<GuiderSystemInDto, CommunityInfo>()
+            .Map(dest => dest.Name, src => src.OrgName)
+            .Map(dest => dest.ParentCode, src => src.ParentOrgId)
+            .Map(dest => dest.Id, src => src.OrgId)
+            .Map(dest => dest.FullName, src => src.OrgFullName)
+            .AfterMapping((dest, src) => 
+            {
+                src.Id = dest.OrgId;
+            });
+
         config.ForType<AddIndustryDto, Industry>();
         config.ForType<Hotline.File.File, IndustryFileDto>()
             .Map(m => m.AdditionId, n => n.Additions);
 
         config.ForType<IndustryFileDto, Hotline.File.File>()
             .Map(m => m.Additions, n => n.AdditionId);
+
+        config.ForType<AddSnapshotOrderInDto, Order>()
+            .Map(m => m.Contact, n => n.PhoneNumber)
+            .Map(m => m.FromName, n => n.Name)
+            .Map(m => m.FromPhone, n => n.PhoneNumber);
+
+        config.ForType<SnapshotOrderPublish, OrderPublishDetailOutDto>()
+            .Map(m => m.Title, n => n.ArrangeTitle)
+            .Map(m => m.Content, n => n.ArrangeContent)
+            .Map(m => m.Opinion, n => n.ArrangeOpinion);
+
+        config.ForType<Order, OrderPublishDetailOutDto>()
+            .Map(m => m.Opinion, n => n.ActualOpinion);
+
+        config.ForType<AddBatchPractitionerInDto, 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 == "1")
+            .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.CommunityId, n => n.OrgId)
+            .Map(m => m.NetworkRemark, n => n.ReplyContent);
     }
 }

+ 1 - 1
src/Hotline.Application/Handlers/Order/AddOrderPushMessageNotifyHandler.cs → src/Hotline.Application/Orders/Handles/OrderHandler/AddOrderPushMessageNotifyHandler.cs

@@ -6,7 +6,7 @@ using Hotline.Share.Enums.Push;
 using MediatR;
 using Microsoft.Extensions.Logging;
 
-namespace Hotline.Application.Handlers.Order
+namespace Hotline.Application.Orders.Handles.Order
 {
     /// <summary>
     /// 新增工单发送短信

+ 1 - 1
src/Hotline.Application/Handlers/Order/GetOrderDetailNotifyHandler.cs → src/Hotline.Application/Orders/Handles/OrderHandler/GetOrderDetailNotifyHandler.cs

@@ -7,7 +7,7 @@ using Hotline.FlowEngine.Workflows;
 using Hotline.Orders.Notifications;
 using MediatR;
 
-namespace Hotline.Application.Handlers.Order
+namespace Hotline.Application.Orders.Handles.Order
 {
     public class GetOrderDetailNotifyHandler : INotificationHandler<GetOrderDetailNotify>
     {

+ 1 - 1
src/Hotline.Application/Handlers/Order/OrderRelateCallHandler.cs → src/Hotline.Application/Orders/Handles/OrderHandler/OrderRelateCallHandler.cs

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
 using XF.Domain.Dependency;
 using XF.Domain.Repository;
 
-namespace Hotline.Application.Handlers.Order
+namespace Hotline.Application.Orders.Handles.Order
 {
     public class OrderRelateCallHandler : ICapSubscribe, ITransientDependency
     {

+ 1 - 1
src/Hotline.Application/Handlers/Order/OrderVisitSmsHandler.cs → src/Hotline.Application/Orders/Handles/OrderHandler/OrderVisitSmsHandler.cs

@@ -17,7 +17,7 @@ using MediatR;
 using XF.Domain.Dependency;
 using XF.Domain.Repository;
 
-namespace Hotline.Application.Handlers.Order;
+namespace Hotline.Application.Orders.Handles.Order;
 public class OrderVisitSmsHandler : INotificationHandler<ReceiveMessageNotify>
 {
     private readonly IOrderVisitDomainService _orderVisitDomainService;

+ 3 - 3
src/Hotline.Application/Handlers/Order/TranspondCityNotifyHandler.cs → src/Hotline.Application/Orders/Handles/OrderHandler/TranspondCityNotifyHandler.cs

@@ -13,9 +13,9 @@ using System;
 using Hotline.DI;
 using XF.Domain.Repository;
 
-namespace Hotline.Application.Handlers.Order
+namespace Hotline.Application.Orders.Handles.Order
 {
-   // [Injection(AppScopes = EAppScope.YiBin)]
+    // [Injection(AppScopes = EAppScope.YiBin)]
     public class TranspondCityNotifyHandler : INotificationHandler<OrderStartWorkflowNotify>
     {
         private readonly IMapper _mapper;
@@ -63,7 +63,7 @@ namespace Hotline.Application.Handlers.Order
                     if (order.Transpond.HasValue && order.Transpond.Value)
                     {
                         var orderDto = _mapper.Map<OrderDto>(order);
-                        await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderTranspondCity, orderDto);
+                        await _capPublisher.PublishAsync(Share.Mq.EventNames.HotlineOrderTranspondCity, orderDto);
                         //保存本地数据
                         TranspondCityRawData cityRawData = new TranspondCityRawData
                         {

+ 1 - 1
src/Hotline.Application/Handlers/Order/UpdateOrderPushMessageNotifyHandler.cs → src/Hotline.Application/Orders/Handles/OrderHandler/UpdateOrderPushMessageNotifyHandler.cs

@@ -6,7 +6,7 @@ using Hotline.Share.Enums.Push;
 using MediatR;
 using Microsoft.Extensions.Logging;
 
-namespace Hotline.Application.Handlers.Order
+namespace Hotline.Application.Orders.Handles.Order
 {
     public class UpdateOrderPushMessageNotifyHandler : INotificationHandler<UpdateOrderNotify>
     {

+ 9 - 9
src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenEndWorkflowHandler.cs → src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenEndWorkflowHandler.cs

@@ -1,25 +1,25 @@
 using DotNetCore.CAP;
+using Hotline.Configurations;
 using Hotline.FlowEngine.Notifications;
 using Hotline.FlowEngine.WorkflowModules;
 using Hotline.Orders;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
 using MapsterMapper;
 using MediatR;
-using Hotline.Share.Dtos;
-using XF.Domain.Repository;
-using XF.Domain.Authentications;
-using Hotline.Configurations;
 using Microsoft.Extensions.Options;
+using XF.Domain.Authentications;
+using XF.Domain.Repository;
 
-namespace Hotline.Application.Orders.OrderScreenHandler;
+namespace Hotline.Application.Orders.Handles.OrderScreenHandler;
 public class OrderScreenEndWorkflowHandler : INotificationHandler<EndWorkflowNotify>
 {
     private readonly IOrderRepository _orderRepository;
     private readonly ICapPublisher _capPublisher;
     private readonly IMapper _mapper;
     private readonly IRepository<OrderVisitDetail> _orderVisitedDetailRepository;
-    private readonly IRepository<OrderScreen> _orderScreenRepository;
+    private readonly IRepository<Hotline.Orders.OrderScreen> _orderScreenRepository;
     private readonly IRepository<OrderVisit> _orderVisitRepository;
     private readonly ISessionContext _sessionContext;
     private readonly IRepository<OrderScreenDetail> _orderScreenDetailRepository;
@@ -30,7 +30,7 @@ public class OrderScreenEndWorkflowHandler : INotificationHandler<EndWorkflowNot
         ICapPublisher capPublisher,
         IMapper mapper,
         IRepository<OrderVisitDetail> orderVisitedDetailRepository,
-        IRepository<OrderScreen> orderScreenRepository,
+        IRepository<Hotline.Orders.OrderScreen> orderScreenRepository,
         IRepository<OrderVisit> orderVisitRepository,
         ISessionContext sessionContext,
         IRepository<OrderScreenDetail> orderScreenDetailRepository,
@@ -97,7 +97,7 @@ public class OrderScreenEndWorkflowHandler : INotificationHandler<EndWorkflowNot
                                 //获取回访明细
                                 var visitDe = visit.OrderVisitDetails.First(x => x.Id == screen.VisitDetailId);
                                 //推省上
-                                await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderScreenApplyed,
+                                await _capPublisher.PublishAsync(Share.Mq.EventNames.HotlineOrderScreenApplyed,
                                     new PublishVisitDto()
                                     {
                                         Order = _mapper.Map<OrderDto>(visit.Order),
@@ -113,7 +113,7 @@ public class OrderScreenEndWorkflowHandler : INotificationHandler<EndWorkflowNot
                                     });
 
                                 //推门户
-                                await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
+                                await _capPublisher.PublishAsync(Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
                                 {
                                     Id = visit.Id,
                                     Order = _mapper.Map<OrderDto>(visit.Order),

+ 101 - 0
src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs

@@ -0,0 +1,101 @@
+using DotNetCore.CAP;
+using Hotline.Configurations;
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.WorkflowModules;
+using Hotline.Orders;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Mq;
+using MapsterMapper;
+using MediatR;
+using Microsoft.Extensions.Options;
+using XF.Domain.Authentications;
+using XF.Domain.Extensions;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Orders.Handles.OrderScreen;
+public class OrderScreenNextWorkflowHandler : INotificationHandler<NextStepNotify>
+{
+    private readonly ICapPublisher _capPublisher;
+    private readonly IMapper _mapper;
+    private readonly IOrderScreenRepository _orderScreenRepository;
+    private readonly ISessionContext _sessionContext;
+    private readonly IRepository<OrderScreenDetail> _orderScreenDetailRepository;
+    private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+
+    public OrderScreenNextWorkflowHandler(
+        ICapPublisher capPublisher,
+        IMapper mapper,
+        IOrderScreenRepository orderScreenRepository,
+        ISessionContext sessionContext,
+        IOptionsSnapshot<AppConfiguration> appOptions,
+        IRepository<OrderScreenDetail> orderScreenDetailRepository)
+    {
+        _capPublisher = capPublisher;
+        _mapper = mapper;
+        _orderScreenRepository = orderScreenRepository;
+        _sessionContext = sessionContext;
+        _orderScreenDetailRepository = orderScreenDetailRepository;
+        _appOptions = appOptions;
+    }
+
+    /// <summary>Handles a notification</summary>
+    /// <param name="notification">The notification</param>
+    /// <param name="cancellationToken">Cancellation token</param>
+    public async Task Handle(NextStepNotify notification, CancellationToken cancellationToken)
+    {
+        if (notification.Workflow.ModuleCode == WorkflowModuleConsts.OrderScreen)
+        {
+            var workflow = notification.Workflow;
+            var nextTag = notification.NextStepDefine.Tag.TryDeserialize<DefinitionTag>();
+            var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order)
+                .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
+            if (screen != null)
+            {
+                screen.Status = EScreenStatus.Approval;
+                screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
+                //如果下个节点是省审批,则修改为省甄别
+                if (nextTag is not null && nextTag.Type == TagDefaults.TagType.Org && nextTag.Value == TagDefaults.TagValue.Province)
+                    screen.IsProScreen = true;
+                await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
+            }
+
+            if (nextTag is not null && nextTag.Type == TagDefaults.TagType.Org)
+            {
+                switch (nextTag.Value)
+                {
+                    case TagDefaults.TagValue.Province:
+                        if (screen != null)
+                        {
+                            var screenDto = _mapper.Map<OrderScreenListDto>(screen);
+                            if (screen.Order != null && screen.Order.Source == ESource.ProvinceStraight)
+                            {
+                                var screenOrderDto = _mapper.Map<OrderDto>(screen.Order);
+                                //省件甄别--以省审批前一个节点整理的甄别意见为准推送省上 宜宾
+                                if (_appOptions.Value.IsYiBin)
+                                {
+                                    screenDto.Content = notification.Dto.Opinion;
+                                    screenDto.Files = new List<Share.Dtos.File.FileDto>();
+                                }
+                                //推省上
+                                _capPublisher.Publish(EventNames.HotlineOrderScreenApply, new PublishScreenDto()
+                                {
+                                    Order = screenOrderDto,
+                                    Screen = screenDto,
+                                    ClientGuid = ""
+                                });
+                            }
+                        }
+
+                        break;
+                }
+            }
+            OrderScreenDetail detail = new OrderScreenDetail
+            {
+                ScreenId = screen.Id
+            };
+            detail.Audit(_sessionContext.UserId, _sessionContext.UserName, _sessionContext.OrgId, _sessionContext.OrgName, 1);
+            await _orderScreenDetailRepository.AddAsync(detail, cancellationToken);
+        }
+    }
+}

+ 37 - 0
src/Hotline.Application/Orders/Handles/OrderScreenHandler/OrderScreenStartWorkflowHandler.cs

@@ -0,0 +1,37 @@
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.WorkflowModules;
+using Hotline.Orders;
+using MediatR;
+
+namespace Hotline.Application.Orders.Handles.OrderScreen;
+
+public class OrderScreenStartWorkflowHandler : INotificationHandler<StartWorkflowNotify>
+{
+    private readonly IOrderScreenRepository _orderScreenRepository;
+
+    public OrderScreenStartWorkflowHandler(
+        IOrderScreenRepository orderScreenRepository
+    )
+    {
+        _orderScreenRepository = orderScreenRepository;
+    }
+
+    /// <summary>Handles a notification</summary>
+    /// <param name="notification">The notification</param>
+    /// <param name="cancellationToken">Cancellation token</param>
+    public async Task Handle(StartWorkflowNotify notification, CancellationToken cancellationToken)
+    {
+        if (notification.Workflow.ModuleCode == WorkflowModuleConsts.OrderScreen)
+        {
+            var workflow = notification.Workflow;
+            var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order)
+                .Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
+            if (screen != null)
+            {
+                screen.WorkflowId = workflow.Id;
+                screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
+                await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
+            }
+        }
+    }
+}

+ 53 - 0
src/Hotline.Application/Orders/Handles/SnapshotHandler/GuiderSystemTimeoutHandler.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Application.Orders;
+using Hotline.Authentications;
+using Hotline.Caching.Interfaces;
+using Hotline.EventBus;
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Enums.FlowEngine;
+using Hotline.Snapshot.Notifications;
+using MediatR;
+using XF.Domain.Exceptions;
+
+namespace Hotline.Application.Orders.Handles.Snapshot
+{
+    /// <summary>
+    /// 需求:坐席派给网格员的安全隐患工单若未推送成功超过4小时或者网格员超过4小时没回复,则自动流转到标注节点待标注列表
+    /// </summary>
+    public class GuiderSystemTimeoutHandler : INotificationHandler<GuiderSystemTimeOutBackNotification>
+    {
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IOrderApplication _orderApplication;
+
+        public GuiderSystemTimeoutHandler(
+            ISystemSettingCacheManager systemSettingCacheManager,
+            IOrderApplication orderApplication
+            )
+        {
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _orderApplication = orderApplication;
+        }
+
+        /// <summary>Handles a notification</summary>
+        /// <param name="notification">The notification</param>
+        /// <param name="cancellationToken">Cancellation token</param>
+        public async Task Handle(GuiderSystemTimeOutBackNotification notification, CancellationToken cancellationToken)
+        {
+            bool.TryParse(
+                _systemSettingCacheManager.GetSetting(SettingConstants.Snapshot)?.SettingValue[0],
+                out bool isSnapshotEnable);
+            if (isSnapshotEnable)
+            {
+                await _orderApplication.HandleFromWanggeyuanToMaskAsync(notification.OrderId, cancellationToken);
+            }
+        }
+    }
+}

+ 7 - 0
src/Hotline.Application/Orders/IOrderApplication.cs

@@ -10,6 +10,7 @@ using Hotline.Share.Dtos.Order.Publish;
 using Hotline.Share.Enums.Settings;
 using Hotline.Share.Requests;
 using SqlSugar;
+using XF.Domain.Authentications;
 
 namespace Hotline.Application.Orders
 {
@@ -408,5 +409,11 @@ namespace Hotline.Application.Orders
         /// <param name="dto"></param>
         /// <returns></returns>
         ISugarQueryable<OrderTsDetailsDto> QueryKnowledgeQuoteList(PagedKeywordRequest dto);
+
+        /// <summary>
+        /// 将工单从网格员节点办理至工单标记节点
+        /// </summary>
+        /// <returns></returns>
+        Task HandleFromWanggeyuanToMaskAsync(string orderId, CancellationToken cancellation);
     }
 }

+ 16 - 0
src/Hotline.Application/Orders/IOrderCarbonCopyApplication.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Orders;
+using Hotline.Share.Dtos.Order.CarbonCopy;
+using SqlSugar;
+
+namespace Hotline.Application.Orders
+{
+    public interface IOrderCarbonCopyApplication
+    {
+        ISugarQueryable<OrderCarboncopy> Query(QueryCarbonCopyRequest request);
+    }
+}

+ 72 - 5
src/Hotline.Application/Orders/OrderApplication.cs

@@ -45,6 +45,7 @@ using PanGu;
 using SqlSugar;
 using System.Data;
 using System.Dynamic;
+using Hotline.FlowEngine.Notifications;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Entities;
@@ -1276,7 +1277,6 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    /// <exception cref="NotImplementedException"></exception>
     public async Task VisitPushSMSAsync(VisitSmsInDto dto, CancellationToken cancellationToken)
     {
         var orderVisitList = await _orderVisitRepository.Queryable()
@@ -3405,6 +3405,60 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return query;
     }
 
+    /// <summary>
+    /// 将工单从网格员节点办理至工单标记节点(按照老系统实现方案:办理人统一处理为坐席)
+    /// </summary>
+    /// <returns></returns>
+    public async Task HandleFromWanggeyuanToMaskAsync(string orderId, CancellationToken cancellationToken)
+    {
+        var order = await _orderRepository.GetAsync(orderId, cancellationToken);
+        if (order is null)
+            throw new UserFriendlyException("无效工单编号");
+
+        var workflow = await _workflowDomainService.GetWorkflowAsync(order.WorkflowId, withDefine: true,
+            withSteps: true, cancellationToken: cancellationToken);
+        var currentStep = workflow.Steps.FirstOrDefault(d => string.CompareOrdinal(d.Tag, TagDefaults.Wanggeyuan) == 0);
+        if (currentStep is null)
+            throw new UserFriendlyException($"未查询到网格员节点, workflowId: {workflow.Id}");
+        if (currentStep.Status == EWorkflowStepStatus.Handled)
+            throw new UserFriendlyException($"网格员节点已办理完, workflowId: {workflow.Id}");
+        var markStepDefine = workflow.WorkflowDefinition.FindStepDefineByTag(d =>
+                string.Compare(d.Tag, TagDefaults.OrderMark, StringComparison.OrdinalIgnoreCase) == 0);
+        if (markStepDefine is null)
+            throw new UserFriendlyException("未配置工单标记节点或未正确配置tag");
+
+        var nextDto = new NextWorkflowDto
+        {
+            WorkflowId = workflow.Id,
+            StepId = currentStep.Id,
+            NextStepCode = markStepDefine.Code,
+            NextStepName = markStepDefine.Name,
+            FlowDirection = EFlowDirection.CenterToOrg,
+            HandlerType = markStepDefine.HandlerType,
+            StepType = markStepDefine.StepType,
+            IsSms = false,
+            NextHandlers = new(),
+            IsStartCountersign = false,
+            BusinessType = markStepDefine.BusinessType,
+        };
+
+        var startStep = workflow.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
+        if (startStep is null)
+            throw new UserFriendlyException($"未查询到开始节点, workflowId: {workflow.Id}");
+        var operater = new FakeSessionContext
+        {
+            UserId = startStep.HandlerId,
+            UserName = startStep.HandlerName,
+            OrgId = startStep.HandlerOrgId,
+            OrgName = startStep.HandlerOrgName,
+        };
+        var isAutoFillSummaryOpinion = bool.Parse(_systemSettingCacheManager
+            .GetSetting(SettingConstants.IsAutoFillSummaryOpinion).SettingValue[0]);
+
+        await _workflowDomainService.NextAsync(operater, nextDto, order.ExpiredTime, isAutoFillSummaryOpinion,
+            cancellationToken);
+    }
+
     #region private
     /// <summary>
     /// 接受外部工单(除省平台)
@@ -3422,7 +3476,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         if (order == null)
         {
             order = _mapper.Map<Order>(dto);
-
+            if (order.IsSecret == true)
+            {
+                order.FocusOnEventsName = "保密";
+                order.FocusOnEvents = "99";
+            }
             order.InitId();
             if (files != null && files.Any())
                 order.FileJson = await _fileRepository.AddFileAsync(files, order.Id, "", cancellationToken);
@@ -3457,6 +3515,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         {
             order = _mapper.Map<Order>(dto);
             order.InitId();
+
+            //如果手里类型不等于空 并且受理类型名称等于空
             if (!string.IsNullOrEmpty(order.AcceptTypeCode))
             {
                 var acceptModel = _sysDicDataCacheManager.AcceptType.Where(x => x.DicDataValue == order.AcceptTypeCode).FirstOrDefault();
@@ -3465,13 +3525,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                     order.AcceptType = acceptModel.DicDataName;
                 }
             }
-            if (order.IsSecret == true)
+            if (order.IsSecret == true && _appOptions.Value.IsYiBin == false)
             {
                 order.FocusOnEventsName = "保密";
                 order.FocusOnEvents = "99";
             }
-
-
             if (files != null && files.Any())
                 order.FileJson = await _fileRepository.AddFileAsync(files, order.Id, "", cancellationToken);
             await _orderDomainService.AddAsync(order, cancellationToken: cancellationToken);
@@ -3869,6 +3927,15 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         //    (string.IsNullOrEmpty(d.WorkflowId) && (string.IsNullOrEmpty(d.SignerId) || d.SignerId == _sessionContextProvider.SessionContext.RequiredUserId))
         //);
 
+        //随手拍
+        bool.TryParse(
+            _systemSettingCacheManager.GetSetting(SettingConstants.Snapshot)?.SettingValue[0],
+            out bool isSnapshotEnable);
+        if (isSnapshotEnable && !string.IsNullOrEmpty(dto.IndustryId))
+        {
+            query.Where(d => d.OrderSnapshot.IndustryId == dto.IndustryId);
+        }
+
         return query
             //// 交办件:已派单其他节点的工单,该选项卡下工单若办结就不显示
             //.WhereIF(dto.TypeCode.HasValue == true && dto.TypeCode == 1, d => /*d.ProcessType == EProcessType.Jiaoban &&*/ d.Status < EOrderStatus.Filed)

+ 41 - 0
src/Hotline.Application/Orders/OrderCarbonCopyApplication.cs

@@ -0,0 +1,41 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos.Order.CarbonCopy;
+using SqlSugar;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.Orders;
+
+public class OrderCarbonCopyApplication : IOrderCarbonCopyApplication, IScopeDependency
+{
+    private readonly ISessionContext _sessionContext;
+    private readonly IRepository<OrderCarboncopy> _orderccRepository;
+
+    public OrderCarbonCopyApplication(
+        ISessionContext sessionContext,
+        IRepository<OrderCarboncopy> orderccRepository)
+    {
+        _sessionContext = sessionContext;
+        _orderccRepository = orderccRepository;
+    }
+
+    public ISugarQueryable<OrderCarboncopy> Query(QueryCarbonCopyRequest dto)
+    {
+        return _orderccRepository.Queryable()
+            .Includes(d => d.Order)
+            .Where(d => (!string.IsNullOrEmpty(d.UserId) && d.UserId == _sessionContext.UserId)
+                        || (!string.IsNullOrEmpty(d.OrgId) && d.OrgId == _sessionContext.OrgId)
+                        || (!string.IsNullOrEmpty(d.RoleId) && _sessionContext.Roles.Contains(d.RoleId)))
+            .WhereIF(!string.IsNullOrEmpty(dto.No), d => d.Order.No.Contains(dto.No))//工单编码
+            .WhereIF(!string.IsNullOrEmpty(dto.Title), d => d.Order.Title.Contains(dto.Title!)) //标题
+            .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), d => d.Order.AcceptTypeCode == dto.AcceptType) //受理类型
+            .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), d => d.Order.HotspotSpliceName != null && d.Order.HotspotSpliceName.Contains(dto.Hotspot))
+            .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.Order.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
+            .WhereIF(dto.CreationTimeStart.HasValue, d => d.Order.CreationTime >= dto.CreationTimeStart) //受理时间开始
+            .WhereIF(dto.CreationTimeEnd.HasValue, d => d.Order.CreationTime <= dto.CreationTimeEnd) //受理时间结束
+            .WhereIF(dto.ExpiredTimeStart.HasValue, d => d.Order.ExpiredTime >= dto.ExpiredTimeStart) //超期时间开始
+            .WhereIF(dto.ExpiredTimeEnd.HasValue, d => d.Order.ExpiredTime <= dto.ExpiredTimeEnd) //超期时间结束
+        ;
+    }
+}

+ 5 - 1
src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenNextWorkflowHandler.cs

@@ -75,8 +75,12 @@ public class OrderScreenNextWorkflowHandler : INotificationHandler<NextStepNotif
 								//省件甄别--以省审批前一个节点整理的甄别意见为准推送省上 宜宾
 								if (_appOptions.Value.IsYiBin)
 								{
+                                    screenDto.Content = notification.Dto.Opinion;
+                                    screenDto.Files = new List<Share.Dtos.File.FileDto>();
+								}
+								if (_appOptions.Value.IsLuZhou)
+								{
 									screenDto.Content = notification.Dto.Opinion;
-									screenDto.Files = new List<Share.Dtos.File.FileDto>();
 								}
 								if (_appOptions.Value.IsLuZhou)
 								{

+ 0 - 37
src/Hotline.Application/Orders/OrderScreenHandler/OrderScreenStartWorkflowHandler.cs

@@ -1,37 +0,0 @@
-using Hotline.FlowEngine.Notifications;
-using Hotline.FlowEngine.WorkflowModules;
-using Hotline.Orders;
-using MediatR;
-
-namespace Hotline.Application.Orders.OrderScreenHandler;
-
-public class OrderScreenStartWorkflowHandler : INotificationHandler<StartWorkflowNotify>
-{
-	private readonly IOrderScreenRepository _orderScreenRepository;
-
-	public OrderScreenStartWorkflowHandler(
-		IOrderScreenRepository orderScreenRepository
-	)
-	{
-		_orderScreenRepository = orderScreenRepository;
-	}
-
-	/// <summary>Handles a notification</summary>
-	/// <param name="notification">The notification</param>
-	/// <param name="cancellationToken">Cancellation token</param>
-	public async Task Handle(StartWorkflowNotify notification, CancellationToken cancellationToken)
-	{
-		if (notification.Workflow.ModuleCode == WorkflowModuleConsts.OrderScreen)
-		{
-			var workflow = notification.Workflow;
-			var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order)
-				.Where(x => x.Id == workflow.ExternalId).FirstAsync(cancellationToken);
-			if (screen != null)
-			{
-				screen.WorkflowId = workflow.Id;
-				screen.Flowed(workflow.FlowedUserIds, workflow.FlowedOrgIds, workflow.HandlerUsers, workflow.HandlerOrgs);
-				await _orderScreenRepository.UpdateAsync(screen, cancellationToken);
-			}
-		}
-	}
-}

+ 64 - 65
src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs

@@ -231,71 +231,70 @@ namespace Hotline.Application.Orders
         }
 
 
-		/// <summary>
-		/// 获取申请列表
-		/// </summary>
-		/// <returns></returns>
-		public ISugarQueryable<OrderVisitDetail> ApplyQuery(MayScreenListDto dto, CancellationToken cancellationToken)
-		{
-			//dto.CreationTimeEnd = DateTime.Now;
-			////dto.CreationTimeStart = DateTime.Now;
-			//dto.CreationTimeStart = _timeLimitDomainService.CalcWorkTimeReduce(DateTime.Now, 5);
-			var isAdmin = _orderDomainService.IsCheckAdmin();
-			var query = _orderVisitedDetailRepository.Queryable(false, true)
-				.Includes(x => x.OrderVisit)
-				.Includes(x => x.OrderVisit, y => y.Order)
-				.Includes(x => x.OrderVisit, y => y.Employee)
-				.Includes(x => x.SecondaryHandling)
-				.Where(x => x.OrderVisit.Order.IsProvince == false)
-				.LeftJoin<OrderScreen>((x, s) => x.Id == s.VisitDetailId && s.Status < EScreenStatus.End && s.IsDeleted == false)
-				//.Where(x => SqlFunc.Subqueryable<OrderScreen>().Where(os => x.Id == os.VisitDetailId && os.Status < EScreenStatus.End && os.IsDeleted == false).Any() || SqlFunc.Subqueryable<OrderScreen>().Where(os => x.Id == os.VisitDetailId && os.IsDeleted == false).NotAny())
-				//.Where((x, s) => s.Id == null && (x.SecondaryHandling.State == ESecondaryHandlingState.NotApply || x.SecondaryHandling.Id == null))
-				.Where(x => SqlFunc.Subqueryable<OrderSecondaryHandling>().Where(osh => osh.VisitDetailId == x.Id && osh.State == ESecondaryHandlingState.NotApply).NotAny())
-				//.Where(x => x.OrderVisit.VisitTime < dto.CreationTimeEnd && x.OrderVisit.VisitTime > dto.CreationTimeStart)
-				.WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order!.No!.Contains(dto.No!))
-				.WhereIF(dto.IsProvince.HasValue, x => x.OrderVisit.Order!.IsProvince == dto.IsProvince)
-				.WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order!.Title!.Contains(dto.Title!))
-				.WhereIF(!string.IsNullOrEmpty(dto.SourceChannel), x => x.OrderVisit.Order!.SourceChannelCode! == dto.SourceChannel!)
-				.WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.OrderVisit.Order!.AcceptTypeCode! == dto.AcceptType!)
-				.WhereIF(dto.CounterSignType.HasValue, x => x.OrderVisit.Order!.CounterSignType == dto.CounterSignType)
-				.WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.OrderVisit.Order!.OrgLevelOneName!.Contains(dto.OrgLevelOneName!))
-				.WhereIF(!string.IsNullOrEmpty(dto.CurrentHandleOrgName), x => x.OrderVisit.Order!.CurrentHandleOrgName!.Contains(dto.CurrentHandleOrgName!))
-				.WhereIF(dto.CurrentHandleTime.HasValue && dto.EndCurrentHandleTime.HasValue, x => x.OrderVisit.Order!.ActualHandleTime >= dto.CurrentHandleTime && x.OrderVisit.Order!.ActualHandleTime <= dto.EndCurrentHandleTime)
-				.WhereIF(dto.FiledTime.HasValue && dto.EndFiledTime.HasValue, x => x.OrderVisit.Order!.FiledTime >= dto.FiledTime && x.OrderVisit.Order!.FiledTime <= dto.EndFiledTime)
-				.WhereIF(dto.CreationTime.HasValue && dto.EndCreationTime.HasValue, x => x.OrderVisit.Order!.CreationTime >= dto.CreationTime && x.OrderVisit.Order!.CreationTime <= dto.EndCreationTime)
-				.WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue, x => x.OrderVisit.VisitTime >= dto.VisitTime && x.OrderVisit.VisitTime <= dto.EndVisitTime)
-				.WhereIF(!string.IsNullOrEmpty(dto.VisitOrgName), x => x.VisitOrgName!.Contains(dto.VisitOrgName!))
-				.WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults)
-				.WhereIF(!string.IsNullOrEmpty(dto.OrgHandledAttitude), x => SqlFunc.JsonListObjectAny(x.OrgHandledAttitude, "Key", dto.OrgHandledAttitude))
-				.WhereIF(!string.IsNullOrEmpty(dto.OrgNoSatisfiedReason), x => SqlFunc.JsonField(x.OrgNoSatisfiedReason, "Key") == dto.OrgNoSatisfiedReason)
-				//.Where(x => SqlFunc.Subqueryable<OrderVisit>().Where(ov => ov.Id == x.VisitId && ov.VisitState == EVisitState.Visited && ov.IsCanHandle).Any())
-				.Where((x, s) => x.OrderVisit.VisitState == EVisitState.Visited && x.OrderVisit.IsCanHandle)
-				.WhereIF(!isAdmin, (x, s) => (x.OrderVisit.Order.CounterSignType == ECounterSignType.Department || x.OrderVisit.Order.CounterSignType == null) && x.VisitOrgCode.StartsWith(_sessionContext.OrgId))
-				;
-			if (_sessionContext.OrgId != null && !_sessionContext.OrgIsCenter)
-			{
-				query.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-						(x, s) => x.OrderVisit.Order.Title.Contains(dto.Keyword!) ||
-								  x.OrderVisit.Order.No.Contains(dto.Keyword!))
-					.Where((x, s) => x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode.StartsWith(_sessionContext.OrgId) && (
-						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2"
-						|| SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
-					));
-			}
-			else
-			{
-				query.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-						(x, s) => x.OrderVisit.Order.Title.Contains(dto.Keyword!) ||
-								  x.OrderVisit.Order.No.Contains(dto.Keyword!))
-					.Where((x, s) => x.VisitTarget == EVisitTarget.Org && (
-						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2"
-						|| SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
-					));
-			}
+        /// <summary>
+        /// 获取申请列表
+        /// </summary>
+        /// <returns></returns>
+        public ISugarQueryable<OrderVisitDetail> ApplyQuery(MayScreenListDto dto, CancellationToken cancellationToken)
+        {
+            //dto.CreationTimeEnd = DateTime.Now;
+            ////dto.CreationTimeStart = DateTime.Now;
+            //dto.CreationTimeStart = _timeLimitDomainService.CalcWorkTimeReduce(DateTime.Now, 5);
+            var isAdmin = _orderDomainService.IsCheckAdmin();
+            var query = _orderVisitedDetailRepository.Queryable(false, true)
+                .Includes(x => x.OrderVisit)
+                .Includes(x => x.OrderVisit, y => y.Order)
+                .Includes(x => x.OrderVisit, y => y.Employee)
+                .Includes(x => x.SecondaryHandling)
+                .Where(x => x.OrderVisit.Order.IsProvince == false)
+                .LeftJoin<OrderScreen>((x, s) => x.Id == s.VisitDetailId && s.Status < EScreenStatus.End && s.IsDeleted == false)
+                //.Where((x, s) => s.Id == null && (x.SecondaryHandling.State == ESecondaryHandlingState.NotApply || x.SecondaryHandling.Id == null))
+                .Where(x=> SqlFunc.Subqueryable<OrderSecondaryHandling>().Where(osh => osh.VisitDetailId == x.Id &&  osh.State == ESecondaryHandlingState.NotApply).NotAny())
+                //.Where(x => x.OrderVisit.VisitTime < dto.CreationTimeEnd && x.OrderVisit.VisitTime > dto.CreationTimeStart)
+                .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order!.No!.Contains(dto.No!))
+                .WhereIF(dto.IsProvince.HasValue, x => x.OrderVisit.Order!.IsProvince == dto.IsProvince)
+                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order!.Title!.Contains(dto.Title!))
+                .WhereIF(!string.IsNullOrEmpty(dto.SourceChannel), x => x.OrderVisit.Order!.SourceChannelCode! == dto.SourceChannel!)
+                .WhereIF(!string.IsNullOrEmpty(dto.AcceptType), x => x.OrderVisit.Order!.AcceptTypeCode! == dto.AcceptType!)
+                .WhereIF(dto.CounterSignType.HasValue, x => x.OrderVisit.Order!.CounterSignType == dto.CounterSignType)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.OrderVisit.Order!.OrgLevelOneName!.Contains(dto.OrgLevelOneName!))
+                .WhereIF(!string.IsNullOrEmpty(dto.CurrentHandleOrgName), x => x.OrderVisit.Order!.CurrentHandleOrgName!.Contains(dto.CurrentHandleOrgName!))
+                .WhereIF(dto.CurrentHandleTime.HasValue && dto.EndCurrentHandleTime.HasValue, x => x.OrderVisit.Order!.ActualHandleTime >= dto.CurrentHandleTime && x.OrderVisit.Order!.ActualHandleTime <= dto.EndCurrentHandleTime)
+                .WhereIF(dto.FiledTime.HasValue && dto.EndFiledTime.HasValue, x => x.OrderVisit.Order!.FiledTime >= dto.FiledTime && x.OrderVisit.Order!.FiledTime <= dto.EndFiledTime)
+                .WhereIF(dto.CreationTime.HasValue && dto.EndCreationTime.HasValue, x => x.OrderVisit.Order!.CreationTime >= dto.CreationTime && x.OrderVisit.Order!.CreationTime <= dto.EndCreationTime)
+                .WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue, x => x.OrderVisit.VisitTime >= dto.VisitTime && x.OrderVisit.VisitTime <= dto.EndVisitTime)
+                .WhereIF(!string.IsNullOrEmpty(dto.VisitOrgName), x => x.VisitOrgName!.Contains(dto.VisitOrgName!))
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgHandledAttitude), x => SqlFunc.JsonListObjectAny(x.OrgHandledAttitude, "Key", dto.OrgHandledAttitude))
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgNoSatisfiedReason), x => SqlFunc.JsonField(x.OrgNoSatisfiedReason, "Key") == dto.OrgNoSatisfiedReason)
+                //.Where(x => SqlFunc.Subqueryable<OrderVisit>().Where(ov => ov.Id == x.VisitId && ov.VisitState == EVisitState.Visited && ov.IsCanHandle).Any())
+                .Where((x, s) => x.OrderVisit.VisitState == EVisitState.Visited && x.OrderVisit.IsCanHandle)
+                .WhereIF(!isAdmin, (x, s) => (x.OrderVisit.Order.CounterSignType ==  ECounterSignType.Department || x.OrderVisit.Order.CounterSignType == null) && x.VisitOrgCode.StartsWith(_sessionContext.OrgId))
+                ;
+            if (_sessionContext.OrgId != null && !_sessionContext.OrgIsCenter)
+            {
+                query.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
+                        (x, s) => x.OrderVisit.Order.Title.Contains(dto.Keyword!) ||
+                                  x.OrderVisit.Order.No.Contains(dto.Keyword!))
+                    .Where((x, s) => x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode.StartsWith(_sessionContext.OrgId) && (
+                        SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
+                        SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2"
+                        || SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
+                        SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
+                    ));
+            }
+            else
+            {
+                query.WhereIF(!string.IsNullOrEmpty(dto.Keyword),
+                        (x, s) => x.OrderVisit.Order.Title.Contains(dto.Keyword!) ||
+                                  x.OrderVisit.Order.No.Contains(dto.Keyword!))
+                    .Where((x, s) => x.VisitTarget == EVisitTarget.Org && (
+                        SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
+                        SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2"
+                        || SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
+                        SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
+                    ));
+            }
 
 			return query.OrderByDescending((x, s) => x.CreationTime);
 		}

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

@@ -1,9 +1,14 @@
-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;
 using Hotline.Settings;
+using Hotline.Share.Dtos.Snapshot;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using System;
 using System.Collections.Generic;
@@ -20,7 +25,7 @@ namespace Hotline.Application.Snapshot;
 public class DefaultSnapshotApplication : SnapshotApplicationBase
     , ISnapshotApplication, IScopeDependency
 {
-    public DefaultSnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, IRepository<Article.Bulletin> bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager)
+    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, IFileDomainService fileDomainService, ICommunityInfoRepository communityInfoRepository, IRedPackAuditRepository redPackAuditRepository, IOrderVisitRepository orderVisitRepository, IOrderVisitDetailRepository orderVisitDetailRepository, IRedPackGuiderAuditRepository redPackGuiderAuditRepository) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager, snapshotOrderPublishRepository, workflowTraceRepository, practitionerRepository, systemAreaRepository, volunteerRepository, volunteerReportRepository, systemLog, guiderSystemService, capPublisher, publisher, guiderInfoRepository, fileDomainService, communityInfoRepository, redPackAuditRepository, orderVisitRepository, orderVisitDetailRepository, redPackGuiderAuditRepository)
     {
     }
 }

+ 164 - 0
src/Hotline.Application/Snapshot/IIndustryApplication.cs

@@ -1,4 +1,6 @@
 using Hotline.Share.Dtos.Snapshot;
+using Hotline.Snapshot;
+using SqlSugar;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -13,4 +15,166 @@ public interface IIndustryApplication
     /// </summary>
     /// <returns></returns>
     Task<string> AddIndustryAsync(AddIndustryDto dto, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 获取行业集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<IndustryItemsOutDto> GetIndustres(IndustryListInDto dto);
+
+    /// <summary>
+    /// 获取行业详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<IndustryDetailOutDto> GetIndustryDetailAsync(string id);
+
+    /// <summary>
+    /// 修改行业
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    Task UpdateIndustryAsync(UpdateIndustryInDto dto, CancellationToken requestAborted);
+
+    /// <summary>
+    /// 行业线索集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<IndustryCaseItemOutDto> GetIndustryCaseItems(IndustryCaseItemInDto dto);
+
+    /// <summary>
+    /// 添加行业线索
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<string> AddIndustryCaseAsync(AddIndustryCaseDto dto);
+
+    /// <summary>
+    /// 修改行业线索
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdateIndustryCaseAsync(UpdateIndustryCaseDto dto);
+
+    /// <summary>
+    /// 获取行业线索详情
+    /// </summary>
+    /// <param name="caseId"></param>
+    /// <returns></returns>
+    Task<IndustryCase> GetIndustryCaseAsync(string caseId);
+
+    /// <summary>
+    /// 添加行业模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<string> AddSMSTemplateAsync(AddSnapshotSMSTemplateInDto dto);
+
+    /// <summary>
+    /// 行业模板集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<SnapshotSMSTemplateItemsOutDto> GetSMSTemplates(SnapshotSMSTemplateItemsInDto dto);
+
+    /// <summary>
+    /// 修改行业模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdateSMSTemplateAsync(UpdateSnapshotSMSTemplateInDto dto);
+
+    /// <summary>
+    /// 短信详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<SnapshotSMSTemplateItemsOutDto> GetSMSTemplateDetailAsync(string id);
+
+    /// <summary>
+    /// 区域从业人员集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<PractitionerItemsOutDto> GetPractitionerItemsAsync(PractitionerItemsInDto dto);
+
+    /// <summary>
+    /// 删除区域从业人员
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task DeletePractitionerAsync(IList<string> id);
+
+    /// <summary>
+    /// 修改区域从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdatePractitionerAsync(UpdatePractitionerInDto dto);
+
+    /// <summary>
+    /// 添加区域从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<string> AddPractitionerAsync(AddPractitionerInDto dto);
+
+    /// <summary>
+    /// 区域从业人员详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<PractitionerItemsOutDto> GetPractitionerAsync(string id);
+
+    /// <summary>
+    /// 志愿者集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<VolunteerItemsOutDto> GetVolunteerItemsAsync(VolunteerItemsInDto dto);
+
+    /// <summary>
+    /// 添加志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<string> AddVolunteerAsync(AddVolunteerInDto dto);
+
+    /// <summary>
+    /// 批量删除志愿者
+    /// </summary>
+    /// <param name="ids"></param>
+    /// <returns></returns>
+    Task DeleteVolunteerAsync(IList<string> ids);
+
+    /// <summary>
+    /// 志愿者详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<Volunteer> GetVolunteerAsync(string id);
+
+    /// <summary>
+    /// 修改志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdateVolunteerAsync(UpdateVolunteerInDto dto);
+
+    /// <summary>
+    /// 志愿者上报集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<VolunteerReportItemsOutDto> GetVolunteerReportItemsAsync(VolunteerReportItemsInDto dto);
+
+    /// <summary>
+    /// 行业修改记录
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<IndustryLogItemsOutDto> GetIndustryLogItemsAsync(IndustryLogItemsInDto dto);
 }

+ 113 - 0
src/Hotline.Application/Snapshot/IOrderSnapshotApplication.cs

@@ -0,0 +1,113 @@
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Snapshot;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Snapshot;
+public interface IOrderSnapshotApplication
+{
+
+    /// <summary>
+    /// 随手拍公开审批详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<GetOrderSnapshotPublishAuditDetailOutDto> GetOrderSnapshotPublishAuditDetailAsync(string id);
+
+    /// <summary>
+    /// 添加公开工单
+    /// </summary>
+    /// <param name="addSnapshotOrderPublishInDto"></param>
+    /// <param name="none"></param>
+    /// <returns></returns>
+    Task<string> AddOrderPublishAsync(AddSnapshotOrderPublishInDto addSnapshotOrderPublishInDto, CancellationToken none);
+
+    /// <summary>
+    /// 获取网格员回复集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<GuiderReplyItemsOutDto> GetGuiderReplyItemsAsync(GuiderReplyItemsInDto dto);
+
+    /// <summary>
+    /// 随手拍公开审批集合
+    /// </summary>
+    /// <returns></returns>
+    ISugarQueryable<GetOrderSnapshotPublishAuditItemsOutDto> GetOrderSnapshotPublishAuditItemsAsync(GetOrderSnapshotPublishAuditItemsInDto dto);
+
+    /// <summary>
+    /// 随手拍公开详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    Task<GetOrderSnapshotPublishDetailOutDto> GetOrderSnapshotPublishDetailAsync(string id);
+
+    /// <summary>
+    /// 随手拍公开集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<GetOrderSnapshotPublishItemsOutDto> GetOrderSnapshotPublishItemsAsync(GetOrderSnapshotPublishItemsInDto dto);
+
+    /// <summary>
+    /// 获取工单标记集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<SignOrderSnapshotItemsOutDto> GetSignOrderSnapshotItemsAsync(SignOrderSnapshotItemsInDto dto);
+
+    /// <summary>
+    /// 批量设置随手拍公开申请不通过
+    /// </summary>
+    /// <returns></returns>
+    Task UpdateOrderSnapshotPublishsStatusRefuseAsync(IList<string> ids);
+
+    /// <summary>
+    /// 审核随手拍公开申请通过/不通过
+    /// </summary>
+    /// <returns></returns>
+    Task UpdateOrderSnapshotPublishStatusAsync(UpdateOrderSnapshotPublishStatusInDto dto);
+
+    /// <summary>
+    /// 随手拍工单标记
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="snapshotLabels"></param>
+    /// <returns></returns>
+    Task UpdateLabelAsync(string id, IList<Kv>? snapshotLabels);
+
+    /// <summary>
+    /// 随手拍工单办理数据保存
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task SaveOrderWorkflowInfo(NextWorkflowDto<OrderHandleFlowDto> dto);
+
+    /// <summary>
+    /// 获取随手拍工单标记日志
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<SignOrderSnapshotLogItemsOutDto> GetSignOrderSnapshotLogItemsAsync(SignOrderSnapshotLogItemsInDto dto);
+
+    /// <summary>
+    /// 返回办理页面基础数据
+    /// </summary>
+    /// <param name="rsp"></param>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    Task GetNextStepsDatabaseAsync(NextStepsWithOpinionDto<RecommendStepOption> rsp, string orderId);
+
+    /// <summary>
+    /// 随手拍工单集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<OrderSnapshotItemsOutDto> GetOrderSnapshotItemsAsync(OrderSnapshotItemsInDto dto);
+}

+ 87 - 0
src/Hotline.Application/Snapshot/IRedPackApplication.cs

@@ -0,0 +1,87 @@
+using Hotline.Share.Dtos.Snapshot;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Snapshot;
+public interface IRedPackApplication
+{
+    /// <summary>
+    /// 审核红包发放
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task AuditRedPackAuditAsync(UpdateRedPackAuditInDto dto);
+
+    /// <summary>
+    /// 网格员红包审核通过或拒绝
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task AuditRedPackGuiderAuditAsync(UpdateRedPackGuiderAuditInDto dto);
+
+    /// <summary>
+    /// 获取特提参数
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<GetAuditBackBaseDataOutDto> GetAuditBackBaseDataAsync(string id);
+
+    /// <summary>
+    /// 获取审核详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    Task<SnapshotOrderAuditDetailOutDto> GetRedPackAuditDetailAsync(string id);
+
+    /// <summary>
+    /// 获取市民红包审批列表
+    /// </summary>
+    /// <returns></returns>
+    ISugarQueryable<SnapshotOrderAuditItemsOutDto> GetRedPackAuditItemsAsync(SnapshotOrderAuditItemsInDto dto);
+
+    /// <summary>
+    /// 获取审核短信模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<IList<GetRedPackAuditSMSTemplateOutDto>> GetRedPackAuditSMSTemplateAsync(GetRedPackAuditSMSTemplateInDto dto);
+
+    /// <summary>
+    /// 获取网格员审核详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<SnapshotOrderAuditDetailOutDto> GetRedPackGuiderAuditDetailAsync(string id);
+
+    /// <summary>
+    /// 网格员红包审核集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<SnapshotOrderGuiderAuditItemsOutDto> GetRedPackGuiderAuditItemsAsync(SnapshotOrderGuiderAuditItemsInDto dto);
+
+    /// <summary>
+    /// 市民红包发放记录
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    ISugarQueryable<SnapshotRedPackRecordItemsOutDto> GetRedPackRecordItemsAsync(SnapshotRedPackRecordItemsInDto dto);
+
+    /// <summary>
+    /// 审核添加备注
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdateRedPackAuditRemarkAsync(UpdateRedPackAuditRemarkInDto dto);
+
+    /// <summary>
+    /// 添加补充发放信息
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdateRedPackRecordAsync(UpdateRedPackRecordInDto dto);
+}

+ 155 - 6
src/Hotline.Application/Snapshot/ISnapshotApplication.cs

@@ -1,6 +1,8 @@
-using Hotline.Share.Dtos;
+using Hotline.Settings.SystemLogDomain;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Article;
 using Hotline.Share.Dtos.Snapshot;
+using Hotline.Snapshot;
 
 namespace Hotline.Application.Snapshot;
 public interface ISnapshotApplication
@@ -17,38 +19,44 @@ public interface ISnapshotApplication
     /// <returns></returns>
     Task<HomePageOutDto> GetHomePageAsync();
 
+    /// <summary>
+    /// 获取行业集合
+    /// </summary>
+    /// <returns></returns>
+    Task<IList<IndustryOutDto>> GetIndustresAsync();
+
     /// <summary>
     /// 获取小程序公告列表
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    Task<IReadOnlyList<BulletinOutDto>> GetBulletinsAsync(BulletinInDto dto);
+    Task<IReadOnlyList<BulletinOutDto>> GetBulletinsAsync(BulletinInDto dto, CancellationToken cancellationToken);
     
     /// <summary>
     /// 获取工单列表
     /// </summary>
-    Task<PagedDto<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto);
+    Task<IList<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto, CancellationToken cancellationToken);
 
     /// <summary>
     /// 获取工单详情
     /// </summary>
     /// <param name="id"></param>
     /// <returns></returns>
-    Task<OrderDetailOutDto> GetSnapshotOrderDetailAsync(string id);
+    Task<OrderPublishDetailOutDto> GetSnapshotOrderDetailAsync(string id, CancellationToken cancellationToken);
 
     /// <summary>
     /// 统计红包金额, 每月的总金额
     /// </summary>
     /// <param name="count"></param>
     /// <returns></returns>
-    Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto);
+    Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto, CancellationToken cancellationToken);
 
     /// <summary>
     /// 获取当月详细红包列表
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    Task<PagedDto<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto);
+    Task<IList<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto, CancellationToken cancellationToken);
 
     /// <summary>
     /// 获取公告详情
@@ -64,4 +72,145 @@ public interface ISnapshotApplication
     /// <param name="requestAborted"></param>
     /// <returns></returns>
     Task<IndustryBaseOutDto> GetIndustryBaseAsync(string id, CancellationToken requestAborted);
+
+    /// <summary>
+    /// 添加随手拍公告
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<string> AddBulletinAsync(AddSnapshotBulletinInDto dto);
+
+    /// <summary>
+    /// 审核公告
+    /// </summary>
+    /// <param name="examineBulletinDto"></param>
+    /// <returns></returns>
+    Task AuditBulletinAsync(ExamineBulletinDto examineBulletinDto);
+
+    /// <summary>
+    /// 获取公开的工单集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    Task<IList<OrderPublishOutDto>> GetOrderPublishAsync(OrderPublishInDto dto, CancellationToken requestAborted);
+
+    /// <summary>
+    /// 获取公开的工单详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    Task<OrderPublishDetailOutDto> GetOrderPublishDetailAsync(string id, CancellationToken requestAborted);
+
+    /// <summary>
+    /// 获取用户领取的红包总金额
+    /// </summary>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    Task<string> GetRedPackReceivedTotalAsync(CancellationToken requestAborted);
+
+    /// <summary>
+    /// 获取从业人员集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task<IList<PractitionerItemOutDto>> GetPractitionerItemsAsync(PractitionerItemInDto dto, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 获取从业人员详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task<PractitionerDetailOutDto> GetPractitionerDetailAsync(string id, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 批量添加从业人员
+    /// </summary>
+    /// <param name="dtos"></param>
+    /// <returns></returns>
+    Task AddPractitionerAsync(IList<AddBatchPractitionerInDto> dtos);
+
+    /// <summary>
+    /// 添加志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task<string> AddVolunteerAsync(AddVolunteerInDto dto, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 志愿者上报
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    Task<AddVolunteerReportOutDto> AddVolunteerReportAsync(AddVolunteerReportInDto dto, CancellationToken requestAborted);
+
+    /// <summary>
+    /// 保存用户自己的邀请码
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task SaveInvitationCodeAsync(SaveInvitationCodeInDto dto);
+
+    /// <summary>
+    /// 获取小程序首页弹窗
+    /// </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);
+
+    /// <summary>
+    /// 同步社区信息
+    /// </summary>
+    /// <param name="community"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task SyncCommunityInfoAsync(CommunityInfo community, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 生成用户红包审核数据
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    Task<string> AddRedPardAsync(string id, CancellationToken cancellationToken);
+
+    /// <summary>
+    /// 获取回访详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<IList<OrderVisitItemsOutDto>> GetOrderVisitDetailAsync(string id);
 }

+ 17 - 0
src/Hotline.Application/Snapshot/ISnapshotBulletinApplication.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Snapshot;
+public interface ISnapshotBulletinApplication
+{
+
+    /// <summary>
+    /// 处理通知公告图片附件路径
+    /// </summary>
+    /// <param name="sHtmlText"></param>
+    /// <returns></returns>
+    string GetSiteUrls(string sHtmlText);
+}

+ 350 - 3
src/Hotline.Application/Snapshot/IndustryApplication.cs

@@ -1,27 +1,49 @@
-using Hotline.File;
+using DocumentFormat.OpenXml.Office2010.Excel;
+using Hotline.Caching.Interfaces;
+using Hotline.File;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Tools;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Tools;
 using Mapster;
+using SqlSugar;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
 using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
 
 namespace Hotline.Application.Snapshot;
 public class IndustryApplication : IIndustryApplication, IScopeDependency
 {
     private readonly IIndustryRepository _industryRepository;
     private readonly IFileRepository _fileRepository;
+    private readonly ISystemSettingCacheManager _sysSetting;
+    private readonly IIndustryCaseRepository _industryCaseRepository;
+    private readonly ISnapshotSMSTemplateRepository _snapshotSMSTemplateRepository;
+    private readonly IPractitionerRepository _practitionerRepository;
+    private readonly IVolunteerRepository _volunteerRepository;
+    private readonly IVolunteerReportRepository _volunteerReportRepository;
+    private readonly IIndustryLogRepository _industryLogRepository;
 
-    public IndustryApplication(IIndustryRepository industryRepository, IFileRepository fileRepository)
+    public IndustryApplication(IIndustryRepository industryRepository, IFileRepository fileRepository, ISystemSettingCacheManager sysSetting, IIndustryCaseRepository industryCaseRepository, ISnapshotSMSTemplateRepository snapshotSMSTemplateRepository, IPractitionerRepository practitionerRepository, IVolunteerRepository volunteerRepository, IVolunteerReportRepository volunteerReportRepository, IIndustryLogRepository industryLogRepository)
     {
         _industryRepository = industryRepository;
         _fileRepository = fileRepository;
+        _sysSetting = sysSetting;
+        _industryCaseRepository = industryCaseRepository;
+        _snapshotSMSTemplateRepository = snapshotSMSTemplateRepository;
+        _practitionerRepository = practitionerRepository;
+        _volunteerRepository = volunteerRepository;
+        _volunteerReportRepository = volunteerReportRepository;
+        _industryLogRepository = industryLogRepository;
     }
-
     /// <summary>
     /// 新增行业
     /// </summary>
@@ -40,4 +62,329 @@ public class IndustryApplication : IIndustryApplication, IScopeDependency
         }
         return id;
     }
+
+    public ISugarQueryable<IndustryItemsOutDto> GetIndustres(IndustryListInDto dto)
+    {
+        var query = _industryRepository.Queryable()
+            .WhereIF(dto.Name.NotNullOrEmpty(), m => m.Name.Contains(dto.Name))
+            .WhereIF(dto.ApproveOrgName.NotNullOrEmpty(), m => m.ApproveOrgName.Contains(dto.ApproveOrgName))
+            .OrderByDescending(m => m.CreationTime)
+            .Select<IndustryItemsOutDto>();
+
+
+        return query;
+    }
+
+    public async Task<IndustryDetailOutDto> GetIndustryDetailAsync(string id)
+    {
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
+        var industry = await _industryRepository.GetAsync(id);
+        var files = await _fileRepository.GetByKeyAsync(id, CancellationToken.None);
+        var outDto = industry.Adapt<IndustryDetailOutDto>();
+        outDto.Files = files.Adapt<IList<IndustryFileDto>>();
+        return outDto;
+    }
+
+    /// <summary>
+    /// 修改行业
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    /// <exception cref="NotImplementedException"></exception>
+    public async Task UpdateIndustryAsync(UpdateIndustryInDto dto, CancellationToken requestAborted)
+    {
+        dto.ValidateObject();
+        var entity = await _industryRepository.GetAsync(dto.Id) ?? throw UserFriendlyException.SameMessage($"行业不存在 {dto.Id}");
+        if (dto.Files.NotNullOrEmpty())
+        {
+            var fileEntities = dto.Files.Adapt<List<File.File>>();
+            fileEntities.ForEach(m => m.Key = entity.Id);
+            await _fileRepository.Removeable().Where(m => m.Key == entity.Id).ExecuteCommandAsync(requestAborted);
+            await _fileRepository.AddRangeAsync(fileEntities, requestAborted);
+        }
+        dto.Adapt(entity);
+        await _industryRepository.UpdateAsync(entity, requestAborted);
+    }
+
+    #region 行业线索
+    public ISugarQueryable<IndustryCaseItemOutDto> GetIndustryCaseItems(IndustryCaseItemInDto dto)
+    {
+        var query = _industryCaseRepository.Queryable()
+            .LeftJoin<Industry>((c, i) => c.IndustryId == i.Id)
+            .WhereIF(dto.IndustryName.NotNullOrEmpty(), (c, i) => i.Name.Contains(dto.IndustryName))
+            .WhereIF(dto.CaseName.NotNullOrEmpty(), (c, i) => c.Name.Contains(dto.CaseName))
+            .OrderByDescending((c, i) => c.CreationTime)
+            .Select<IndustryCaseItemOutDto>((c, i) =>
+            new IndustryCaseItemOutDto
+            {
+                Id = c.Id,
+                Name = c.Name,
+                IndustryId = i.Id,
+                IndustryName = i.Name
+            }, true);
+        return query;
+    }
+
+    /// <summary>
+    /// 添加行业线索
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<string> AddIndustryCaseAsync(AddIndustryCaseDto dto)
+    {
+        dto.ValidateObject();
+        var entity = dto.Adapt<IndustryCase>();
+        return await _industryCaseRepository.AddAsync(entity);
+    }
+
+    /// <summary>
+    /// 修改行业线索
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task UpdateIndustryCaseAsync(UpdateIndustryCaseDto dto)
+    {
+        dto.ValidateObject();
+        var entity = await _industryCaseRepository.GetAsync(dto.Id)
+            ?? throw UserFriendlyException.SameMessage($"行业线索不存在 {dto.Id}");
+        dto.Adapt(entity);
+        await _industryCaseRepository.UpdateAsync(entity);
+    }
+
+    public async Task<IndustryCase> GetIndustryCaseAsync(string caseId)
+    {
+        return await _industryCaseRepository.GetAsync(caseId);
+    }
+    #endregion
+
+    #region 行业短信模板
+
+    /// <summary>
+    /// 行业模板集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<SnapshotSMSTemplateItemsOutDto> GetSMSTemplates(SnapshotSMSTemplateItemsInDto dto)
+    {
+        var query = _snapshotSMSTemplateRepository.Queryable()
+            .LeftJoin<Industry>((s, i) => s.IndustryId == i.Id)
+            .WhereIF(dto.IndustryName.NotNullOrEmpty(), (s, i) => i.Name.Contains(dto.IndustryName))
+            .OrderByDescending((s, i) => s.DisplayOrder)
+            .Select<SnapshotSMSTemplateItemsOutDto>();
+        return query;
+    }
+
+    /// <summary>
+    /// 添加行业模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<string> AddSMSTemplateAsync(AddSnapshotSMSTemplateInDto dto)
+    {
+        dto.ValidateObject();
+        var entity = dto.Adapt<SnapshotSMSTemplate>();
+        return await _snapshotSMSTemplateRepository.AddAsync(entity);
+    }
+
+    /// <summary>
+    /// 修改行业模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task UpdateSMSTemplateAsync(UpdateSnapshotSMSTemplateInDto dto)
+    {
+        dto.ValidateObject();
+        var entity = await _snapshotSMSTemplateRepository.GetAsync(dto.Id)
+            ?? throw UserFriendlyException.SameMessage($"行业短信模板不存在 {dto.Id}");
+        dto.Adapt(entity);
+        await _snapshotSMSTemplateRepository.UpdateAsync(entity);
+    }
+
+    /// <summary>
+    /// 短信详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<SnapshotSMSTemplateItemsOutDto> GetSMSTemplateDetailAsync(string id)
+    {
+        return await _snapshotSMSTemplateRepository.Queryable()
+            .LeftJoin<Industry>((s, i) => s.IndustryId == i.Id)
+            .Where((s, i) => s.Id == id)
+            .Select<SnapshotSMSTemplateItemsOutDto>()
+            .FirstAsync();
+    }
+
+    #endregion
+
+    #region 区域从业人员
+    /// <summary>
+    /// 区域从业人员集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    /// <exception cref="NotImplementedException"></exception>
+    public ISugarQueryable<PractitionerItemsOutDto> GetPractitionerItemsAsync(PractitionerItemsInDto dto)
+    {
+        var query = _practitionerRepository.Queryable()
+            .WhereIF(dto.Name.NotNullOrEmpty(), m => m.Name.Contains(dto.Name!))
+            .WhereIF(dto.SystemAreaName.NotNullOrEmpty(), m => m.SystemAreaName.Contains(dto.SystemAreaName!))
+            .WhereIF(dto.PhoneNumber.NotNullOrEmpty(), m => m.PhoneNumber.Contains(dto.PhoneNumber!))
+            .OrderByDescending(m => m.CreationTime)
+            .Select<PractitionerItemsOutDto>();
+
+        return query;
+    }
+
+    /// <summary>
+    /// 添加区域从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<string> AddPractitionerAsync(AddPractitionerInDto dto)
+    {
+        dto.ValidateObject();
+        var entity = dto.Adapt<Practitioner>();
+        return await _practitionerRepository.AddAsync(entity);
+    }
+
+    /// <summary>
+    /// 区域从业人员详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<PractitionerItemsOutDto> GetPractitionerAsync(string id)
+    {
+        return (await _practitionerRepository.GetAsync(id)).Adapt<PractitionerItemsOutDto>();
+    }
+
+    /// <summary>
+    /// 删除区域从业人员
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task DeletePractitionerAsync(IList<string> id)
+    {
+        await _practitionerRepository.Updateable()
+            .SetColumns(m => m.IsDeleted, true)
+            .Where(m => id.Contains(m.Id))
+            .ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 修改区域从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task UpdatePractitionerAsync(UpdatePractitionerInDto dto)
+    {
+        dto.ValidateObject();
+
+        var entity = await _practitionerRepository.GetAsync(dto.Id) ?? throw UserFriendlyException.SameMessage($"从业人员不存在 {dto.Id}");
+        dto.Adapt(entity);
+        await _practitionerRepository.UpdateAsync(entity);
+    }
+
+    #endregion
+
+    #region 志愿者
+
+    /// <summary>
+    /// 志愿者集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    /// <exception cref="NotImplementedException"></exception>
+    public ISugarQueryable<VolunteerItemsOutDto> GetVolunteerItemsAsync(VolunteerItemsInDto dto)
+    {
+        var query = _volunteerRepository.Queryable()
+            .WhereIF(dto.Name.NotNullOrEmpty(), m => m.Name.Contains(dto.Name))
+            .WhereIF(dto.PhoneNumber.NotNullOrEmpty(), m => m.PhoneNumber.Contains(dto.PhoneNumber))
+            .OrderByDescending(m => m.CreationTime)
+            .Select<VolunteerItemsOutDto>();
+        return query;
+    }
+
+    /// <summary>
+    /// 添加志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<string> AddVolunteerAsync(AddVolunteerInDto dto)
+    {
+        var entity = dto.Adapt<Volunteer>();
+        entity.Id = await _volunteerRepository.AddAsync(entity);
+        return entity.Id;
+    }
+
+    /// <summary>
+    /// 批量删除志愿者
+    /// </summary>
+    /// <param name="ids"></param>
+    /// <returns></returns>
+    public async Task DeleteVolunteerAsync(IList<string> ids)
+    {
+        await _volunteerRepository.Updateable()
+            .SetColumns(m => m.IsDeleted, true)
+            .Where(m => ids.Contains(m.Id))
+            .ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 志愿者详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<Volunteer> GetVolunteerAsync(string id)
+    {
+        return await _volunteerRepository.GetAsync(id);
+    }
+
+    /// <summary>
+    /// 修改志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task UpdateVolunteerAsync(UpdateVolunteerInDto dto)
+    {
+        dto.ValidateObject();
+        var entity = await _volunteerRepository.GetAsync(dto.Id) ?? throw UserFriendlyException.SameMessage($"志愿者不存在 {dto.Id}");
+        dto.Adapt(entity);
+        await _volunteerRepository.UpdateAsync(entity);
+    }
+
+    /// <summary>
+    /// 志愿者上报集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<VolunteerReportItemsOutDto> GetVolunteerReportItemsAsync(VolunteerReportItemsInDto dto)
+    {
+        var query = _volunteerReportRepository.Queryable()
+            .WhereIF(dto.Name.NotNullOrEmpty(), m => m.Name.Contains(dto.Name))
+            .WhereIF(dto.PhoneNumber.NotNullOrEmpty(), m => m.PhoneNumber.Contains(dto.PhoneNumber))
+            .OrderByDescending(m => m.CreationTime)
+            .Select<VolunteerReportItemsOutDto>();
+        return query;
+    }
+
+    /// <summary>
+    /// 行业修改记录
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<IndustryLogItemsOutDto> GetIndustryLogItemsAsync(IndustryLogItemsInDto dto)
+    {
+        var query = _industryLogRepository.Queryable()
+            .WhereIF(dto.No.NotNullOrEmpty(), m => m.No.Contains(dto.No))
+            .WhereIF(dto.ChangeName.NotNullOrEmpty(), m => m.CreatorName.Contains(dto.ChangeName))
+            .WhereIF(dto.IndustryName.NotNullOrEmpty(), m => m.IndustryName.Contains(dto.IndustryName))
+            .WhereIF(dto.oldIndustryName.NotNullOrEmpty(), m => m.OldIndustryName.Contains(dto.oldIndustryName))
+            .WhereIF(dto.BeginTime.HasValue && dto.EndTime.HasValue, m => m.CreationTime >= dto.BeginTime.Value && m.CreationTime <= dto.EndTime.Value)
+            .Select<IndustryLogItemsOutDto>();
+        return query;
+    }
+
+    #endregion
 }

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

@@ -0,0 +1,109 @@
+using DotNetCore.CAP;
+using Hotline.Authentications;
+using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
+using Hotline.FlowEngine.Notifications;
+using Hotline.Orders;
+using Hotline.Repository.SqlSugar.Orders;
+using Hotline.Settings;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Mq;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+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 Hotline.Application.Orders;
+using Hotline.FlowEngine.Workflows;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+
+namespace Hotline.Application.Snapshot.Notifications;
+
+public class SnapshotHandler : ICapSubscribe, IScopeDependency
+{
+    private readonly ISnapshotApplication _snapshotApplication;
+
+    public SnapshotHandler(ISnapshotApplication snapshotApplication)
+    {
+        _snapshotApplication = snapshotApplication;
+    }
+
+    /// <summary>
+    /// 推送成功后发送的延迟消息;
+    /// 延迟检查网格员是否回复了工单;
+    /// <param name="message"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    [CapSubscribe(EventNames.GuiderSystemReplyDelay)]
+    public async Task PostGuiderSystemDelayedNotificationHandler(PostGuiderSystemDelayed message, CancellationToken cancellationToken)
+    {
+        await _snapshotApplication.GuiderSystemReplyDelayAsync(message.OrderId, cancellationToken);
+    }
+
+    /// <summary>
+    /// 工单归档后生成红包数据
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    [CapSubscribe(EventNames.HotlineOrderFiled)]
+    public async void SnapshotOrderEndAsync(OrderFlowDto dto, CancellationToken cancellationToken)
+    {
+        await _snapshotApplication.AddRedPardAsync(dto.Order.Id, cancellationToken);
+    }
+}
+
+/// <summary>
+/// 网格员办结事件处理
+/// 把网格员信息回填到系统中, 和小程序账号绑定
+/// 同步社区信息
+/// </summary>
+public class GuiderSystemFieldNotificationHandler : INotificationHandler<GuiderSystemFieldNotification>
+{
+    private readonly ISnapshotApplication _snapshotApplication;
+    private readonly IOrderApplication _orderApplication;
+
+    public GuiderSystemFieldNotificationHandler(
+        ISnapshotApplication snapshotApplication1,
+        IOrderApplication orderApplication)
+    {
+        _snapshotApplication = snapshotApplication1;
+        _orderApplication = orderApplication;
+    }
+
+    public async Task Handle(GuiderSystemFieldNotification notification, CancellationToken cancellationToken)
+    {
+        await _snapshotApplication.SyncGuiderInfoAsync(notification.OrderSnapshot.Id, cancellationToken);
+        await _snapshotApplication.SyncCommunityInfoAsync(notification.CommunityInfo, cancellationToken);
+
+
+        //流程流转至派单组
+        await _orderApplication.HandleFromWanggeyuanToMaskAsync(notification.OrderSnapshot.Id, cancellationToken);
+    }
+}
+
+/// <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);
+    }
+}

+ 425 - 0
src/Hotline.Application/Snapshot/OrderSnapshotApplication.cs

@@ -0,0 +1,425 @@
+using Hotline.Caching.Interfaces;
+using Hotline.File;
+using Hotline.FlowEngine.Notifications;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.File;
+using Hotline.Share.Dtos.FlowEngine;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.FlowEngine;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Hotline.Tools;
+using Mapster;
+using Novacode.NETCorePort;
+using NPOI.POIFS.Properties;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Application.Snapshot;
+public class OrderSnapshotApplication : IOrderSnapshotApplication, IScopeDependency
+{
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly IOrderRepository _orderRepository;
+    private readonly ISnapshotOrderPublishRepository _snapshotOrderPublishRepository;
+    private readonly ISessionContext _sessionContext;
+    private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+    private readonly IIndustryCaseRepository _industryCaseRepository;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly IIndustryRepository _industryRepository;
+    private readonly IFileRepository _fileRepository;
+    private readonly ISnapshotLabelLogRepository _snapshotLabelLogRepository;
+
+    public OrderSnapshotApplication(IOrderSnapshotRepository orderSnapshotRepository, IOrderRepository orderRepository, ISnapshotOrderPublishRepository snapshotOrderPublishRepository, ISessionContext sessionContext, ISystemSettingCacheManager systemSettingCacheManager, IIndustryCaseRepository industryCaseRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IIndustryRepository industryRepository, IFileRepository fileRepository, ISnapshotLabelLogRepository snapshotLabelLogRepository)
+    {
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _orderRepository = orderRepository;
+        _snapshotOrderPublishRepository = snapshotOrderPublishRepository;
+        _sessionContext = sessionContext;
+        _systemSettingCacheManager = systemSettingCacheManager;
+        _industryCaseRepository = industryCaseRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _industryRepository = industryRepository;
+        _fileRepository = fileRepository;
+        _snapshotLabelLogRepository = snapshotLabelLogRepository;
+    }
+
+    /// <summary>
+    /// 添加公开工单
+    /// </summary>
+    /// <param name="addSnapshotOrderPublishInDto"></param>
+    /// <param name="none"></param>
+    /// <returns></returns>
+    public async Task<string> AddOrderPublishAsync(AddSnapshotOrderPublishInDto dto, CancellationToken cancellation)
+    {
+        dto.ValidateObject();
+        var snapshotOrder = await _orderSnapshotRepository.GetAsync(dto.OrderId)
+            ?? throw UserFriendlyException.SameMessage("工单不存在");
+        var order = await _orderRepository.Queryable()
+            .Where(m => m.Id == dto.OrderId)
+            .Select(m => new { m.Id, m.No })
+            .FirstAsync(cancellation)
+            ?? throw UserFriendlyException.SameMessage("工单不存在");
+        var entity = dto.Adapt<SnapshotOrderPublish>();
+        entity.Id = order.Id;
+        entity.IndustryId = snapshotOrder.IndustryId;
+        entity.IndustryName = snapshotOrder.IndustryName;
+        entity.No = order.No;
+        entity.Status = EOrderSnapshotPublishStatus.Pending;
+        return await _snapshotOrderPublishRepository.AddAsync(entity, cancellation);
+    }
+
+    /// <summary>
+    /// 获取网格员回复集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<GuiderReplyItemsOutDto> GetGuiderReplyItemsAsync(GuiderReplyItemsInDto dto)
+    {
+        var query = _orderSnapshotRepository.Queryable()
+            .LeftJoin<Order>((snapshot, order) => snapshot.Id == order.Id)
+            .Where((snapshot, order) => snapshot.ReplyResultType != null)
+            .WhereIF(dto.No.NotNullOrEmpty(), (snapshot, order) => order.No.Contains(dto.No))
+            .WhereIF(dto.Title.NotNullOrEmpty(), (snapshot, order) => order.Title.Contains(dto.Title))
+            .WhereIF(dto.GuiderName.NotNullOrEmpty(), (snapshot, order) => snapshot.MemberName.Contains(dto.GuiderName))
+            .WhereIF(dto.GuiderPhoneNumber.NotNullOrEmpty(), (snapshot, order) => snapshot.MemberMobile.Contains(dto.GuiderPhoneNumber))
+            .WhereIF(dto.NewtorkENumber.NotNullOrEmpty(), (snapshot, order) => snapshot.NetworkENumber.Contains(dto.NewtorkENumber))
+            .WhereIF(dto.Satus.HasValue, (snapshot, order) => snapshot.ReplyResultType == dto.Satus)
+            .WhereIF(dto.BeginCreationTime.HasValue && dto.EndCreationTime.HasValue, (snapshot, order) => order.CreationTime >= dto.BeginCreationTime && order.CreationTime <= dto.EndCreationTime)
+            .Select((snapshot, order) => new GuiderReplyItemsOutDto
+            {
+                No = order.No,
+                Title = order.Title
+            }, true);
+
+        return query;
+    }
+
+    /// <summary>
+    /// 随手拍公开审批集合
+    /// </summary>
+    /// <returns></returns>
+    public ISugarQueryable<GetOrderSnapshotPublishAuditItemsOutDto> GetOrderSnapshotPublishAuditItemsAsync(GetOrderSnapshotPublishAuditItemsInDto dto)
+    {
+        var query = _snapshotOrderPublishRepository.Queryable()
+            .LeftJoin<Order>((publish, order) => publish.OrderId == order.Id)
+            .WhereIF(dto.Status == 0, publish => publish.Status == EOrderSnapshotPublishStatus.Pending)
+            .WhereIF(dto.Status == 1, publish => publish.Status == EOrderSnapshotPublishStatus.Agree)
+            .WhereIF(dto.Status == 2, publish => publish.Status == EOrderSnapshotPublishStatus.Refuse)
+            .OrderByDescending((publish, order) => new { publish.Status, publish.CreationTime })
+            .Select((publish, order) => new GetOrderSnapshotPublishAuditItemsOutDto
+            {
+                Id = publish.Id,
+                No = order.No,
+                Title = order.Title
+            }, true);
+
+        return query;
+    }
+
+    /// <summary>
+    /// 随手拍公开审批详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<GetOrderSnapshotPublishAuditDetailOutDto> GetOrderSnapshotPublishAuditDetailAsync(string id)
+    {
+        var publish = await _snapshotOrderPublishRepository.GetAsync(id);
+
+        var order = await _orderRepository.Queryable()
+            .Where(m => m.Id == publish.OrderId)
+            .Select<GetOrderSnapshotPublishAuditDetailOutDto>()
+            .FirstAsync();
+
+        publish.Adapt(order);
+        order.Id = publish.Id;
+        return order;
+    }
+
+    /// <summary>
+    /// 随手拍公开详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    public async Task<GetOrderSnapshotPublishDetailOutDto> GetOrderSnapshotPublishDetailAsync(string id)
+    {
+        return await _orderRepository.Queryable()
+            .Where(m => m.Id == id)
+            .Select<GetOrderSnapshotPublishDetailOutDto>()
+            .FirstAsync();
+    }
+
+    /// <summary>
+    /// 随手拍公开集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<GetOrderSnapshotPublishItemsOutDto> GetOrderSnapshotPublishItemsAsync(GetOrderSnapshotPublishItemsInDto dto)
+    {
+        var query = _orderSnapshotRepository.Queryable(includeDeleted: true)
+            .LeftJoin<Order>((snapshot, order) => snapshot.Id == order.Id)
+            .LeftJoin<SnapshotOrderPublish>((snapshot, order, publish) => order.Id == publish.OrderId)
+            .WhereIF(dto.IndustryId.NotNullOrEmpty(), snapshot => snapshot.IndustryId == dto.IndustryId)
+            .WhereIF(dto.AreaCode.NotNullOrEmpty(), (snapshot, order) => order.AreaCode == dto.AreaCode)
+            .WhereIF(dto.AcceptTypeCode.NotNullOrEmpty(), (snapshot, order) => order.AcceptTypeCode == dto.AcceptTypeCode)
+            .WhereIF(dto.BeginCreationTime.HasValue && dto.EndCreationTime.HasValue, (snapshot, order) => order.CreationTime >= dto.BeginCreationTime && order.CreationTime <= dto.EndCreationTime)
+            .WhereIF(dto.Contact.NotNullOrEmpty(), (snapshot, order) => order.Contact.Contains(dto.Contact))
+            .WhereIF(dto.FromPhone.NotNullOrEmpty(), (snapshot, order) => order.FromPhone.Contains(dto.FromPhone))
+            .WhereIF(dto.IsPublished.HasValue, (snapshot, order, publish) => publish.Status == EOrderSnapshotPublishStatus.Agree)
+            .WhereIF(dto.No.NotNullOrEmpty(), (snapshot, order) => order.No.Contains(dto.No))
+            .WhereIF(dto.OrderStatus.HasValue, (snapshot, order) => order.Status == dto.OrderStatus)
+            .WhereIF(dto.Title.NotNullOrEmpty(), (snapshot, order) => order.Title.Contains(dto.Title))
+            //.Where((snapshot, order, publish) => order.Status == EOrderStatus.Filed)
+            .OrderByDescending((snapshot, order, publish) => new { publish.Status, snapshot.CreationTime })
+            .Select((snapshot, order, publish) =>
+            new GetOrderSnapshotPublishItemsOutDto
+            {
+                PublishStatus = publish.Status
+            }, true);
+
+        return query;
+    }
+
+    /// <summary>
+    /// 获取工单标记集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<SignOrderSnapshotItemsOutDto> GetSignOrderSnapshotItemsAsync(SignOrderSnapshotItemsInDto dto)
+    {
+        var query = _orderSnapshotRepository.Queryable()
+            .LeftJoin<Order>((snapshot, order) => snapshot.Id == order.Id)
+            .LeftJoin<WorkflowStep>((snapshot, order, step) => step.ExternalId == order.Id)
+            .WhereIF(dto.Status == 0, (snapshot, order, step) => step.Tag == TagDefaults.OrderMark) // 全部
+            .WhereIF(dto.Status == 1, (snapshot, order, step) => step.Tag == TagDefaults.OrderMark && step.Status != EWorkflowStepStatus.Handled && step.HandlerId == _sessionContext.UserId) // 待标记
+            .WhereIF(dto.Status == 2, (snapshot, order, step) => step.Tag == TagDefaults.OrderMark && step.Status == EWorkflowStepStatus.Handled) // 已标记
+            .WhereIF(dto.No.NotNullOrEmpty(), (snapshot, order, step) => order.No.Contains(dto.No))
+            .WhereIF(dto.Title.NotNullOrEmpty(), (snapshot, order, step) => order.Title.Contains(dto.Title))
+            .OrderByDescending((snapshot, order, step) => snapshot.CreationTime)
+            .Select((snapshot, order) => new SignOrderSnapshotItemsOutDto
+            {
+                OrderId = snapshot.Id,
+                No = order.No,
+                Title = order.Title,
+                Content = order.Content,
+                FullAddress = order.FullAddress,
+                CreationTime = order.CreationTime,
+                County = order.County,
+                IsSafetyDepartment = snapshot.IsSafetyDepartment,
+                NetworkENumber = snapshot.NetworkENumber,
+                NetworkRemark = snapshot.NetworkRemark,
+                ReplyDate = snapshot.ReplyDate,
+                SignTime = snapshot.SignTime,
+                SignUserName = snapshot.SignUserName
+            });
+        return query;
+    }
+
+    /// <summary>
+    /// 批量设置随手拍公开申请不通过
+    /// </summary>
+    /// <returns></returns>
+    public async Task UpdateOrderSnapshotPublishsStatusRefuseAsync(IList<string> ids)
+    {
+        await _snapshotOrderPublishRepository.Updateable()
+            .SetColumns(m => m.Status, EOrderSnapshotPublishStatus.Refuse)
+            .SetColumns(m => m.RefuseRemark, _sessionContext.UserName + "批量设置不公开")
+            .SetColumns(m => m.RefuseAuditTime, DateTime.Now)
+            .Where(m => ids.Contains(m.Id))
+            .ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 审核随手拍公开申请通过/不通过
+    /// </summary>
+    /// <returns></returns>
+    public async Task UpdateOrderSnapshotPublishStatusAsync(UpdateOrderSnapshotPublishStatusInDto dto)
+    {
+        var audit = await _snapshotOrderPublishRepository.GetAsync(dto.Id)
+            ?? throw UserFriendlyException.SameMessage("审核记录不存在");
+        audit.Status = dto.Status;
+        audit.AuditTime = DateTime.Now;
+        await _snapshotOrderPublishRepository.UpdateAsync(audit);
+    }
+
+    /// <summary>
+    /// 随手拍工单标记
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="snapshotLabels"></param>
+    /// <returns></returns>
+    public async Task UpdateLabelAsync(string id, IList<Kv>? labels)
+    {
+        if (_systemSettingCacheManager.Snapshot == false) return;
+
+        var snapshot = await _orderSnapshotRepository.GetAsync(m => m.Id == id);
+        if (snapshot == null) return;
+        if (snapshot != null && labels.IsNullOrEmpty())
+        {
+            throw UserFriendlyException.SameMessage("随手拍工单标记不能为空");
+        }
+        snapshot.Labels = labels;
+        snapshot.LabelName = string.Join('|', labels.Select(m => m.Value));
+        //if (labels.Any(m => m.Key == "yzg"))
+        //    snapshot.IsRectifyDepartment = true;
+        //if (labels.Any(m => m.Key == "wzg"))
+        //    snapshot.IsRectifyDepartment = false;
+        if (labels.Any(m => m.Key == "ss"))
+            snapshot.IsTruthDepartment = true;
+        if (labels.Any(m => m.Key == "bss"))
+            snapshot.IsTruthDepartment = false;
+        await _orderSnapshotRepository.UpdateAsync(snapshot);
+
+        var entity = new SnapshotLabelLog { 
+        OrderId = snapshot.Id,
+        LabelName = snapshot.LabelName,
+        Labels = labels
+        };
+        await _snapshotLabelLogRepository.AddAsync(entity);
+    }
+
+    public async Task SaveOrderWorkflowInfo(NextWorkflowDto<OrderHandleFlowDto> dto)
+    {
+        var snapshot = await _orderSnapshotRepository.GetAsync(dto.Data.OrderId);
+        if (snapshot is null) return;
+
+        // 标记节点标记随手拍安全生产
+        if (TagDefaults.OrderMark.ToLower() == dto.Workflow.CurrentTag?.ToLower())
+        {
+            snapshot.IsSafetyDepartment = dto.Data.IsSafetyDepartment;
+            snapshot.SignUserId = _sessionContext.UserId;
+            snapshot.SignUserName = _sessionContext.UserName;
+            snapshot.SignTime = DateTime.Now;
+            snapshot.SignRemark = dto.Workflow.Remark;
+            await _orderSnapshotRepository.UpdateAsync(snapshot);
+            return;
+        }
+
+        // 归档节点保存随手拍数据
+        if (dto.Data.IsDangerDepartment.HasValue && dto.Data.IsRectifyDepartment.HasValue)
+        {
+            snapshot.IsDangerDepartment = dto.Data.IsDangerDepartment.Value;
+            snapshot.IsRectifyDepartment = dto.Data.IsRectifyDepartment.Value;
+            snapshot.IndustryCase = dto.Data.IndustryCase;
+            snapshot.IsAward = dto.Data.IsAward;
+            snapshot.AwardOpenBack = dto.Data.AwardOpenBack;
+            snapshot.AwardName = dto.Data.AwardName;
+            snapshot.AwardBankCardNo = dto.Data.AwardBankCardNo;
+            snapshot.ReplenishTypeId = dto.Data.ReplenishTypeId;
+            if (snapshot.ReplenishTypeId.NotNullOrEmpty())
+            {
+                snapshot.ReplenishTypeName = _systemDicDataCacheManager
+                    .SnapshotReplenishType
+                    .First(m => m.DicDataValue == snapshot.ReplenishTypeId).DicDataName;
+            }
+            snapshot.AwardAmount = dto.Data.AwardAmount;
+            await _orderSnapshotRepository.UpdateAsync(snapshot);
+            return;
+        }
+
+        // 电气焊工单办理
+        if (dto.Data.VerifyType.NotNullOrEmpty() || dto.Data.IsCheckList.HasValue || dto.Data.CompliantType.HasValue)
+        {
+            snapshot.VerifyType = dto.Data.VerifyType;
+            snapshot.IsCheckList = dto.Data.IsCheckList;
+            snapshot.CompliantType = dto.Data.CompliantType;
+            await _orderSnapshotRepository.UpdateAsync(snapshot);
+            return;
+        }
+    }
+
+    /// <summary>
+    /// 获取工单标注日志集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<SignOrderSnapshotLogItemsOutDto> GetSignOrderSnapshotLogItemsAsync(SignOrderSnapshotLogItemsInDto dto)
+    {
+        var query = _snapshotLabelLogRepository.Queryable()
+            .LeftJoin<Order>((log, order) => log.OrderId == order.Id)
+            .LeftJoin<OrderSnapshot>((log, order, snapshot) => log.OrderId == snapshot.Id)
+            .WhereIF(dto.No.NotNullOrEmpty(), (log, order) => order.No.Contains(dto.No))
+            .WhereIF(dto.Title.NotNullOrEmpty(), (log, order) => order.Title.Contains(dto.Title))
+            .WhereIF(dto.Contact.NotNullOrEmpty(), (log, order)=> order.Contact.Contains(dto.Contact))
+            .WhereIF(dto.FromName.NotNullOrEmpty(), (log, order)=> order.FromName.Contains(dto.FromName))
+            .WhereIF(dto.Label.NotNullOrEmpty(), (log, order)=> log.LabelName.Contains(dto.Label))
+            .WhereIF(dto.SignName.NotNullOrEmpty(), (log, order) => log.CreatorName.Contains(dto.SignName))
+            .WhereIF(dto.BeginSignTime.HasValue && dto.EndSignTime.HasValue, (log, order) => log.CreationTime >= dto.BeginSignTime && log.CreationTime <= dto.EndSignTime)
+            .Select((log, order, snapshot) => new SignOrderSnapshotLogItemsOutDto
+            {
+                OrderId = order.Id,
+                SignName = log.CreatorName,
+                SignTime = log.CreationTime,
+                IndustryName = snapshot.IndustryName
+            }, true);
+        return query;
+    }
+
+    /// <summary>
+    /// 返回办理页面基础数据
+    /// </summary>
+    /// <param name="rsp"></param>
+    /// <param name="orderId"></param>
+    /// <returns></returns>
+    public async Task GetNextStepsDatabaseAsync(NextStepsWithOpinionDto<RecommendStepOption> rsp, string orderId)
+    {
+        if (_systemSettingCacheManager.Snapshot == false) return;
+
+        await _orderSnapshotRepository.Queryable()
+            .Where(m => m.Id == orderId)
+            .Select(m => new { m.Id, m.IndustryId, m.IsSafetyDepartment })
+            .FirstAsync()
+            .Then(async snapshot =>
+            {
+                if (snapshot.IsSafetyDepartment.HasValue && snapshot.IsSafetyDepartment == true)
+                {
+                    rsp.IsSafetyDepartment = true;
+                }
+
+                await _industryRepository.Queryable()
+                    .Where(m => m.Id == snapshot.IndustryId)
+                    .Select(m => new { m.Id, m.IndustryType })
+                    .FirstAsync()
+                    .Then(async industry =>
+                    {
+                        await _fileRepository.Queryable()
+                        .Where(m => m.Classify == EIndustryType.Declare.ToString())
+                        .Select(m => new FileJson { Id = m.Id, FileName = m.Name, Path = m.Path, FileType = m.Type })
+                        .ToListAsync()
+                        .Then(async file =>
+                        {
+                            rsp.DocumentFiles = file;
+                        });
+                    });
+            }
+            );
+
+
+        rsp.IndustryCase = await _industryCaseRepository.Queryable()
+            .Where(m => m.IsEnable == true)
+            .Select(m => new Kv { Key = m.Id, Value = m.Name })
+            .ToListAsync();
+
+        rsp.SnapshotReplenishType = _systemDicDataCacheManager.SnapshotReplenishType;
+        rsp.CompliantType = EnumExts.GetDescriptions<ECompliantType>();
+
+    }
+
+    public ISugarQueryable<OrderSnapshotItemsOutDto> GetOrderSnapshotItemsAsync(OrderSnapshotItemsInDto dto)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 448 - 0
src/Hotline.Application/Snapshot/RedPackApplication.cs

@@ -0,0 +1,448 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Mapster;
+using SqlSugar;
+using SqlSugar.Extensions;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Application.Snapshot;
+public class RedPackApplication : IRedPackApplication, IScopeDependency
+{
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
+    private readonly IOrderRepository _orderRepository;
+    private readonly ISnapshotSMSTemplateRepository _snapshotSMSTemplateRepository;
+    private readonly IIndustryRepository _industryRepository;
+    private readonly IRedPackAuditRepository _redPackAuditRepository;
+    private readonly IRedPackRecordRepository _redPackRecordRepository;
+    private readonly IRepository<OrderSpecial> _orderSpecialRepository;
+    private readonly ISessionContext _sessionContext;
+    private readonly IRedPackGuiderAuditRepository _redPackGuiderAuditRepository;
+    private readonly IThirdAccountRepository _thirdAccountRepository;
+
+    public RedPackApplication(IOrderSnapshotRepository orderSnapshotRepository, ISnapshotSMSTemplateRepository snapshotSMSTemplateRepository, IOrderRepository orderRepository, IIndustryRepository industryRepository, IRedPackAuditRepository redPackAuditRepository, IRedPackRecordRepository redPackRecordRepository, IRepository<OrderSpecial> orderSpecialRepository, ISessionContext sessionContext, IRedPackGuiderAuditRepository redPackGuiderAuditRepository, IThirdAccountRepository thirdAccountRepository)
+    {
+        _orderSnapshotRepository = orderSnapshotRepository;
+        _snapshotSMSTemplateRepository = snapshotSMSTemplateRepository;
+        _orderRepository = orderRepository;
+        _industryRepository = industryRepository;
+        _redPackAuditRepository = redPackAuditRepository;
+        _redPackRecordRepository = redPackRecordRepository;
+        _orderSpecialRepository = orderSpecialRepository;
+        _sessionContext = sessionContext;
+        _redPackGuiderAuditRepository = redPackGuiderAuditRepository;
+        _thirdAccountRepository = thirdAccountRepository;
+    }
+
+    /// <summary>
+    /// 审核红包发放
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task AuditRedPackAuditAsync(UpdateRedPackAuditInDto dto)
+    {
+        var redPackAudit = await _redPackAuditRepository.GetAsync(dto.RedPackAuditId) ?? throw UserFriendlyException.SameMessage("审核记录不存在");
+        if (redPackAudit.Status != ERedPackAuditStatus.Pending) throw UserFriendlyException.SameMessage("已审核, 不可重复审核");
+        redPackAudit.SMSTemplateId = dto.SMSTemplateId;
+        redPackAudit.Status = dto.Status;
+        redPackAudit.Remark = dto.Opinion;
+        redPackAudit.IsSendSMS = dto.IsSendSms;
+        var order = await _orderRepository.Queryable()
+            .Where(m => m.Id == redPackAudit.OrderId)
+            .Select(m => new { m.Id, m.No, m.FromName, m.FromPhone })
+            .FirstAsync() ?? throw UserFriendlyException.SameMessage("工单不存在");
+        if (dto.Status == ERedPackAuditStatus.Agree)
+        {
+            var third = await _thirdAccountRepository.GetByPhoneNumberAsync(order.FromPhone);
+            var entity = new RedPackRecord
+            {
+                OrderId = redPackAudit.OrderId,
+                RedPackAuditId = redPackAudit.Id,
+                PeopleType = EReadPackUserType.Citizen,
+                Name = order.FromName,
+                PhoneNumber = order.FromPhone,
+                WXOpenId = third?.OpenId,
+                No = order.No,
+                PickupStatus = ERedPackPickupStatus.Unreceived,
+                DistributionState = EReadPackSendStatus.Unsend,
+            };
+            if (redPackAudit.ApprovedAmount.HasValue)
+                entity.Amount = redPackAudit.ApprovedAmount.Value;
+            await _redPackRecordRepository.AddAsync(entity);
+        }
+        await _redPackAuditRepository.UpdateAsync(redPackAudit);
+    }
+
+    /// <summary>
+    /// 网格员审核红包发放
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task AuditRedPackGuiderAuditAsync(UpdateRedPackGuiderAuditInDto dto)
+    {
+        var redPackAudit = await _redPackGuiderAuditRepository.GetAsync(dto.RedPackAuditId) ?? throw UserFriendlyException.SameMessage("审核记录不存在");
+        var orgName = _sessionContext.OrgName ?? string.Empty;
+        RedPackRecord? entity = null;
+        if (orgName.Contains("应急管理局"))
+        {
+            if (redPackAudit.LevelTwoStatus != ERedPackAuditStatus.Pending) throw UserFriendlyException.SameMessage("已审核, 不可重复审核");
+            redPackAudit.LevelTwoStatus = dto.Status;
+
+            if (dto.Status == ERedPackAuditStatus.Agree)
+            {
+                var order = await _orderRepository.Queryable()
+                    .Where(m => m.Id == redPackAudit.OrderId)
+                    .Select(m => new { m.Id, m.No, m.FromName, m.FromPhone })
+                    .FirstAsync();
+                entity = new RedPackRecord
+                {
+                    OrderId = redPackAudit.OrderId,
+                    RedPackAuditId = redPackAudit.Id,
+                    PeopleType = EReadPackUserType.Guider,
+                    Name = order.FromName,
+                    PhoneNumber = order.FromPhone,
+                    No = order.No,
+                    PickupStatus = ERedPackPickupStatus.Unreceived,
+                    DistributionState = EReadPackSendStatus.Unsend,
+                };
+                if (redPackAudit.ApprovedAmount.HasValue)
+                    entity.Amount = redPackAudit.ApprovedAmount.Value;
+            }
+        }
+        else
+        {
+            if (redPackAudit.LevelOneStatus != ERedPackAuditStatus.Pending) throw UserFriendlyException.SameMessage("已审核, 不可重复审核");
+            redPackAudit.LevelOneStatus = dto.Status;
+        }
+        redPackAudit.Remark = dto.Opinion;
+        await _redPackGuiderAuditRepository.UpdateAsync(redPackAudit);
+        if (entity != null)
+            await _redPackRecordRepository.AddAsync(entity);
+    }
+
+
+    /// <summary>
+    /// 获取网格员审核详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    public async Task<SnapshotOrderAuditDetailOutDto> GetRedPackGuiderAuditDetailAsync(string id)
+    {
+        var order = await _orderRepository.GetAsync(id) ?? throw UserFriendlyException.SameMessage("工单不存在");
+        var outDto = new SnapshotOrderAuditDetailOutDto { Order = order.Adapt<SnapshotOrderAuditOrderDetailOutDto>() };
+        var industry = await _industryRepository.Queryable(includeDeleted: true)
+            .LeftJoin<OrderSnapshot>((i, o) => i.Id == o.IndustryId)
+            .Select((i, o) => new { i.Id, i.GuiderReadPackAmount })
+            .FirstAsync();
+        outDto.Amount = industry.GuiderReadPackAmount;
+        outDto.RedPackTxt = $"{order.FromPhone}【】元; ";
+        var dayStart = DateTime.Now.ToString("yyyy-MM-dd 00:00:00").ObjToDate();
+        var dayEnd = DateTime.Now.ToString("yyyy-MM-dd 23:59:59").ObjToDate();
+        var query = _redPackGuiderAuditRepository.Queryable()
+            .Where(m => m.PhoneNumber == order.FromPhone && m.LevelTwoAuditTime >= dayStart && m.LevelTwoAuditTime <= dayEnd && m.LevelTwoStatus == ERedPackAuditStatus.Agree);
+
+        var totalAmount = await query.SumAsync(m => m.ApprovedAmount);
+        if (totalAmount != 0)
+            outDto.RedPackTxt = $"{order.FromPhone}【{totalAmount}】元; ";
+        var count = await query.CountAsync();
+        if (count != 0)
+            outDto.RedPackTxt += $"今天审批【{count}】个";
+
+        outDto.AuditComBox = EnumExts.GetDescriptions<ERedPackAuditStatus>()
+            .Where(m => m.Key.ToString() != "0").ToList();
+        return outDto;
+    }
+
+
+    /// <summary>
+    /// 获取审核详情
+    /// </summary>
+    /// <param name="id">工单Id</param>
+    /// <returns></returns>
+    public async Task<SnapshotOrderAuditDetailOutDto> GetRedPackAuditDetailAsync(string id)
+    {
+        var order = await _orderRepository.GetAsync(id) ?? throw UserFriendlyException.SameMessage("工单不存在");
+        var outDto = new SnapshotOrderAuditDetailOutDto { Order = order.Adapt<SnapshotOrderAuditOrderDetailOutDto>() };
+        var industry = await _industryRepository.Queryable(includeDeleted: true)
+            .LeftJoin<OrderSnapshot>((i, o) => i.Id == o.IndustryId)
+            .Select((i, o) => new { i.Id, i.CitizenReadPackAmount })
+            .FirstAsync();
+        outDto.Amount = industry.CitizenReadPackAmount;
+        outDto.RedPackTxt = $"{order.FromPhone}【】元; ";
+        var dayStart = DateTime.Now.ToString("yyyy-MM-dd 00:00:00").ObjToDate();
+        var dayEnd = DateTime.Now.ToString("yyyy-MM-dd 23:59:59").ObjToDate();
+        var query = _redPackAuditRepository.Queryable()
+            .Where(m => m.PhoneNumber == order.FromPhone && m.AuditTime >= dayStart && m.AuditTime <= dayEnd && m.Status == ERedPackAuditStatus.Agree);
+
+        var totalAmount = await query.SumAsync(m => m.ApprovedAmount);
+        if (totalAmount != 0)
+            outDto.RedPackTxt = $"{order.FromPhone}【{totalAmount}】元; ";
+        var count = await query.CountAsync();
+        if (count != 0)
+            outDto.RedPackTxt += $"今天审批【{count}】个";
+
+        outDto.AuditComBox = EnumExts.GetDescriptions<ERedPackAuditStatus>()
+            .Where(m => m.Key.ToString() != "0").ToList();
+        return outDto;
+    }
+
+    /// <summary>
+    /// 红包审核集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<SnapshotOrderAuditItemsOutDto> GetRedPackAuditItemsAsync(SnapshotOrderAuditItemsInDto dto)
+    {
+        ERedPackAuditStatus? status = null;
+        if (dto.Status != -1 && Enum.TryParse<ERedPackAuditStatus>(dto.Status.ToString(), out var statusParse))
+        {
+            status = statusParse;
+        }
+
+        var query = _redPackAuditRepository.Queryable(includeDeleted: true)
+            .LeftJoin<Order>((redPackAudit, order) => redPackAudit.OrderId == order.Id)
+            .LeftJoin<OrderSnapshot>((redPackAudit, order, snapshot) => redPackAudit.OrderId == snapshot.Id)
+            .LeftJoin<RedPackRecord>((redPackAudit, order, snapshot, record) => redPackAudit.Id == record.OrderId)
+            .LeftJoin<Industry>((redPackAudit, order, snapshot, record, industry) => snapshot.IndustryId == industry.Id)
+            .Where((redPackAudit, order, snapshot, record, industry) => order.Status == EOrderStatus.Filed)
+            .WhereIF(dto.No.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => order.No.Contains(dto.No))
+            .WhereIF(dto.Title.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => order.Title.Contains(dto.Title))
+            .WhereIF(dto.FromPhone.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => order.FromPhone.Contains(dto.FromPhone))
+            .WhereIF(dto.BeginCreationTime.HasValue && dto.EndCreationTime.HasValue, (redPackAudit, order, snapshot, record, industry) => order.CreationTime <= dto.EndCreationTime && order.CreationTime >= dto.BeginCreationTime)
+            .WhereIF(dto.BeginFiledTime.HasValue && dto.EndFiledTime.HasValue, (redPackAudit, order, snapshot, record, industry) => order.FiledTime <= dto.EndFiledTime && order.FiledTime >= dto.BeginFiledTime)
+            .WhereIF(dto.IsDeal.HasValue, (redPackAudit, order, snapshot, record, industry) => snapshot.IsDeal == dto.IsDeal)
+            .WhereIF(dto.IsTruth.HasValue, (redPackAudit, order, snapshot, record, industry) => snapshot.IsTruth == dto.IsTruth)
+            .WhereIF(dto.IsTruthDepartment.HasValue, (redPackAudit, order, snapshot, record, industry) => snapshot.IsTruthDepartment == dto.IsTruthDepartment)
+            .WhereIF(dto.BeginAuditTime.HasValue && dto.EndAuditTime.HasValue, (redPackAudit, order, snapshot, record, industry) => redPackAudit.AuditTime <= dto.EndAuditTime && redPackAudit.AuditTime >= dto.BeginAuditTime)
+            .WhereIF(dto.IsIssued.HasValue, (redPackAudit, order, snapshot, record, industry) => redPackAudit.IsIssued == dto.IsIssued)
+            .WhereIF(dto.IndustryId.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => snapshot.IndustryId == dto.IndustryId)
+            .WhereIF(dto.ConfigAmount.HasValue, (redPackAudit, order, snapshot, record, industry) => industry.CitizenReadPackAmount == dto.ConfigAmount)
+            .WhereIF(dto.AcutalAmount.HasValue, (redPackAudit, order, snapshot, record, industry) => redPackAudit.AcutalAmount == dto.AcutalAmount)
+            .WhereIF(dto.ApprovedAmount.HasValue, (redPackAudit, order, snapshot, record, industry) => redPackAudit.ApprovedAmount == dto.ApprovedAmount)
+            .WhereIF(dto.IsDanger.HasValue, (redPackAudit, order, snapshot, record, industry) => snapshot.IsDanger == dto.IsDanger)
+            .WhereIF(status.HasValue, (redPackAudit, order, snapshot, record, industry) => redPackAudit.Status == status)
+            .Select((redPackAudit, order, snapshot, record, industry) => new SnapshotOrderAuditItemsOutDto
+            {
+                Id = redPackAudit.Id,
+                RedPackAuditId = redPackAudit.Id,
+                OrderId = order.Id,
+                No = order.No,
+                Title = order.Title,
+                IndustryName = industry.Name,
+                IndustryId = industry.Id,
+                SourceChannel = order.SourceChannel,
+                SourceChannelCode = order.SourceChannelCode,
+                Status = order.Status,
+                IsDanger = snapshot.IsDanger,
+                FromPhone = order.FromPhone,
+                FromName = order.FromName,
+                AuditTime = redPackAudit.AuditTime,
+                ApprovedAmount = redPackAudit.ApprovedAmount,
+                AcutalAmount = redPackAudit.AcutalAmount,
+                IsIssued = redPackAudit.IsIssued,
+                RecordRemark = record.Remark,
+                County = order.County,
+                // IsRectify = s.IsRepetition
+                IsDeal = snapshot.IsDeal,
+                NetworkENumber = snapshot.NetworkENumber,
+                IsTruth = snapshot.IsTruth,
+                IsTruthDepartment = snapshot.IsTruthDepartment,
+                IsRepetition = snapshot.IsRepetition,
+                CreationTime = order.CreationTime,
+                OrgLevelOneCode = order.OrgLevelOneCode,
+                OrgLevelOneName = order.OrgLevelOneName,
+                AuditId = redPackAudit.AuditId,
+                AuditName = redPackAudit.AuditName,
+                AuditOrgId = redPackAudit.AuditOrgId,
+                AuditOrgName = redPackAudit.AuditOrgName,
+                AuditRemark = redPackAudit.AuditRemark,
+                BankCardNo = record.BankCardNo,
+                OpenBank = record.OpenBank,
+                AuditStatus = redPackAudit.Status,
+            });
+        return query;
+    }
+
+    /// <summary>
+    /// 网格员审核集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<SnapshotOrderGuiderAuditItemsOutDto> GetRedPackGuiderAuditItemsAsync(SnapshotOrderGuiderAuditItemsInDto dto)
+    {
+        var areaCode = _sessionContext.OrgAreaCode;
+        if (_sessionContext.OrgIsCenter)
+        {
+            areaCode = null;
+        }
+        ERedPackAuditStatus? status = null;
+        if (dto.Status != -1 && Enum.TryParse<ERedPackAuditStatus>(dto.Status.ToString(), out var statusParse))
+        {
+            status = statusParse;
+        }
+
+        var query = _redPackGuiderAuditRepository.Queryable(includeDeleted: true)
+            .LeftJoin<Order>((redPackAudit, order) => redPackAudit.OrderId == order.Id)
+            .LeftJoin<OrderSnapshot>((redPackAudit, order, snapshot) => redPackAudit.OrderId == snapshot.Id)
+            .LeftJoin<RedPackRecord>((redPackAudit, order, snapshot, record) => redPackAudit.Id == record.OrderId)
+            .LeftJoin<Industry>((redPackAudit, order, snapshot, record, industry) => snapshot.IndustryId == industry.Id)
+            .Where((redPackAudit, order, snapshot, record, industry) => order.Status == EOrderStatus.Filed)
+            .WhereIF(dto.No.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => order.No.Contains(dto.No))
+            .WhereIF(dto.Title.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => order.Title.Contains(dto.Title))
+            .WhereIF(status.HasValue, (redPackAudit, order, snapshot, record, industry) => redPackAudit.LevelOneStatus == status)
+            .WhereIF(areaCode.NotNullOrEmpty(), (redPackAudit, order, snapshot, record, industry) => order.AreaCode == areaCode);
+
+        var orgName = _sessionContext.OrgName ?? string.Empty;
+        if (orgName.Contains("应急管理局") && status != null)
+            query.Where((redPackAudit, order, snapshot, record, industry) => redPackAudit.LevelTwoStatus == status);
+        else if (status != null)
+            query.Where((redPackAudit, order, snapshot, record, industry) => redPackAudit.LevelOneStatus == status);
+
+        return query.Select((redPackAudit, order, snapshot, record, industry) => new SnapshotOrderGuiderAuditItemsOutDto
+        {
+            Id = redPackAudit.Id,
+            RedPackAuditId = redPackAudit.Id,
+            OrderId = order.Id,
+            No = order.No,
+            Title = order.Title,
+            SourceChannel = order.SourceChannel,
+            SourceChannelCode = order.SourceChannelCode,
+            Status = order.Status,
+            FromPhone = order.FromPhone,
+            FromName = order.FromName,
+            ApprovedAmount = redPackAudit.ApprovedAmount,
+            IsIssued = redPackAudit.IsIssued,
+            County = order.County,
+            IsDeal = snapshot.IsDeal,
+            NetworkENumber = snapshot.NetworkENumber,
+            IsTruth = snapshot.IsTruth,
+            IsTruthDepartment = snapshot.IsTruthDepartment,
+            IsRepetition = snapshot.IsRepetition,
+            IsRectify = snapshot.IsRectifyDepartment,
+            CreationTime = order.CreationTime,
+            LevelOneAuditName = redPackAudit.LevelOneAuditName,
+            LevelOneAuditOrgName = redPackAudit.LevelOneAuditOrgName,
+            LevelOneAuditRemark = redPackAudit.LevelOneAuditRemark,
+            LevelOneAuditTime = redPackAudit.LevelOneAuditTime,
+            NetworkRemark = snapshot.NetworkRemark,
+            LevelOneStatus = redPackAudit.LevelOneStatus,
+            LevelTwoStatus = redPackAudit.LevelTwoStatus,
+            LevelTwoAuditTime = redPackAudit.LevelTwoAuditTime,
+            LevelTwoAuditRemark = redPackAudit.LevelTwoAuditRemark,
+        }, true);
+    }
+
+    /// <summary>
+    /// 获取审核短信模板
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<IList<GetRedPackAuditSMSTemplateOutDto>> GetRedPackAuditSMSTemplateAsync(GetRedPackAuditSMSTemplateInDto dto)
+    {
+        var items = await _snapshotSMSTemplateRepository.Queryable(includeDeleted: true)
+            .LeftJoin<Industry>((sms, industry) => sms.IndustryId == industry.Id)
+            .LeftJoin<OrderSnapshot>((sms, industry, snapshot) => snapshot.IndustryId == industry.Id)
+            .Where((sms, industry, snapshot) => (sms.IsPublic == true || sms.IndustryId == snapshot.IndustryId)
+             && sms.Status == dto.Status && sms.IsEnable == true)
+            .Select<GetRedPackAuditSMSTemplateOutDto>()
+            .Distinct()
+            .ToListAsync();
+        return items;
+    }
+
+    /// <summary>
+    /// 审核添加备注
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task UpdateRedPackAuditRemarkAsync(UpdateRedPackAuditRemarkInDto dto)
+    {
+        var audit = await _redPackAuditRepository.GetAsync(dto.RedPackAuditId) ?? throw UserFriendlyException.SameMessage("审核记录不存在");
+        audit.AcutalAmount = dto.AcutalAmount;
+        if (dto.IsSend == false)
+        {
+            await _redPackRecordRepository.Queryable()
+                .Where(m => m.RedPackAuditId == audit.Id)
+                .FirstAsync()
+                .Then(async (record) =>
+                {
+                    record.FailCase = dto.FailCase;
+                    await _redPackRecordRepository.UpdateAsync(record);
+                });
+        }
+        audit.SendRemarks = dto.SendRemarks;
+        await _redPackAuditRepository.UpdateAsync(audit);
+    }
+
+    public async Task UpdateRedPackRecordAsync(UpdateRedPackRecordInDto dto)
+    {
+        var record = await _redPackRecordRepository.Queryable()
+            .Where(m => m.RedPackAuditId == dto.RedPackAuditId)
+            .FirstAsync() ?? throw UserFriendlyException.SameMessage("审核记录不存在");
+        var audit = await _redPackAuditRepository.GetAsync(dto.RedPackAuditId) ?? throw UserFriendlyException.SameMessage("审核记录不存在");
+
+        dto.Adapt(record);
+        await _redPackRecordRepository.UpdateAsync(record);
+
+        dto.Adapt(audit);
+        await _redPackAuditRepository.UpdateAsync(audit);
+
+        if (dto.IsSendSMS)
+        {
+            // TODO: 发送短信
+        }
+    }
+
+    #region 红包发放记录
+
+    /// <summary>
+    /// 市民红包发放记录
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<SnapshotRedPackRecordItemsOutDto> GetRedPackRecordItemsAsync(SnapshotRedPackRecordItemsInDto dto)
+    {
+        var query = _redPackRecordRepository.Queryable()
+            .WhereIF(dto.BeginCreationTime.HasValue && dto.EndCreationTime.HasValue, m => m.CreationTime >= dto.BeginCreationTime && m.CreationTime <= dto.EndCreationTime)
+            .WhereIF(dto.No.NotNullOrEmpty(), m => m.No.Contains(dto.No))
+            .WhereIF(dto.PhoneNumber.NotNullOrEmpty(), m => m.PhoneNumber.Contains(dto.PhoneNumber))
+            .Select<SnapshotRedPackRecordItemsOutDto>();
+
+        if (dto.Status == 3)
+            query = query.Where(m => m.PickupStatus == ERedPackPickupStatus.Excuse);
+        else
+        {
+            if (Enum.TryParse(dto.Status.ToString(), out EReadPackSendStatus status))
+                query = query.Where(m => m.DistributionState == status);
+        }
+        return query;
+    }
+
+    /// <summary>
+    /// 获取特提参数
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<GetAuditBackBaseDataOutDto> GetAuditBackBaseDataAsync(string id)
+    {
+        var order = await _orderRepository.Queryable()
+            .Where(m => m.Id == id)
+            .Select<GetAuditBackBaseDataOutDto>()
+            .FirstAsync()
+            ?? throw UserFriendlyException.SameMessage("工单不存在");
+
+        order.SpecialNum = await _orderSpecialRepository.CountAsync(x => x.OrderId == id);
+        return order;
+    }
+
+
+    #endregion
+}

+ 636 - 59
src/Hotline.Application/Snapshot/SnapshotApplicationBase.cs

@@ -21,6 +21,21 @@ using XF.Domain.Exceptions;
 using Hotline.Settings;
 using Hotline.Share.Dtos.Settings;
 using Hotline.File;
+using Hotline.Share.Enums.Article;
+using Hotline.Tools;
+using Hotline.Snapshot.Interfaces;
+using DotNetCore.CAP;
+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;
+using Hotline.Settings.SystemLogDomain;
+using XF.Utility.EnumExtensions;
 
 namespace Hotline.Application.Snapshot;
 
@@ -31,7 +46,7 @@ public abstract class SnapshotApplicationBase
 {
     private readonly IThirdAccountRepository _thirdAccountRepository;
     private readonly IRepository<Order> _orderRepository;
-    private readonly IRepository<Article.Bulletin> _bulletinRepository;
+    private readonly ISnapshotBulletinRepository _bulletinRepository;
 
     /// <summary>
     /// 行业
@@ -40,13 +55,30 @@ 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;
     private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly ISnapshotOrderPublishRepository _snapshotOrderPublishRepository;
+    private readonly IRepository<WorkflowTrace> _workflowTraceRepository;
+    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;
+    private readonly IFileDomainService _fileDomainService;
+    private readonly ICommunityInfoRepository _communityInfoRepository;
+    private readonly IRedPackAuditRepository _redPackAuditRepository;
+    private readonly IRedPackGuiderAuditRepository _redPackGuiderAuditRepository;
+    private readonly IOrderVisitRepository _orderVisitRepository;
+    private readonly IOrderVisitDetailRepository _orderVisitDetailRepository;
 
-    public SnapshotApplicationBase(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, IRepository<Article.Bulletin> bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager)
+    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, IFileDomainService fileDomainService, ICommunityInfoRepository communityInfoRepository, IRedPackAuditRepository redPackAuditRepository, IOrderVisitRepository orderVisitRepository, IOrderVisitDetailRepository orderVisitDetailRepository, IRedPackGuiderAuditRepository redPackGuiderAuditRepository)
     {
         _thirdLoginService = thirdLoginService;
         _industryRepository = industryRepository;
@@ -56,20 +88,38 @@ public abstract class SnapshotApplicationBase
         _orderRepository = orderRepository;
         _thirdAccountRepository = thirdAccountRepository;
         _orderSnapshotRepository = orderSnapshotRepository;
-        _systemSettingCacheManager = systemSettingCacheManager;
+        _sysSetting = systemSettingCacheManager;
         _systemAreaDomainService = systemAreaDomainService;
         _fileRepository = fileRepository;
         _systemDicDataCacheManager = systemDicDataCacheManager;
+        _snapshotOrderPublishRepository = snapshotOrderPublishRepository;
+        _workflowTraceRepository = workflowTraceRepository;
+        _practitionerRepository = practitionerRepository;
+        _systemAreaRepository = systemAreaRepository;
+        _volunteerRepository = volunteerRepository;
+        _volunteerReportRepository = volunteerReportRepository;
+        _systemLog = systemLog;
+        _guiderSystemService = guiderSystemService;
+        _capPublisher = capPublisher;
+        _publisher = publisher;
+        _guiderInfoRepository = guiderInfoRepository;
+        _fileDomainService = fileDomainService;
+        _communityInfoRepository = communityInfoRepository;
+        _redPackAuditRepository = redPackAuditRepository;
+        _orderVisitRepository = orderVisitRepository;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+        _redPackGuiderAuditRepository = redPackGuiderAuditRepository;
     }
 
+    #region 小程序
     /// <summary>
     /// 获取随手拍小程序首页数据
     /// </summary>
     /// <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)
@@ -88,11 +138,40 @@ 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
         };
     }
 
+    /// <summary>
+    /// 获取小程序首页弹窗
+    /// </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)
+            .OrderByDescending(m => m.CreationTime)
+            .Select<BulletinOutDto>()
+            .FirstAsync(requestAborted);
+        return item;
+    }
+
+    /// <summary>
+    /// 获取行业集合
+    /// </summary>
+    /// <returns></returns>
+    public async Task<IList<IndustryOutDto>> GetIndustresAsync()
+    {
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        var fileDownloadApi = fileServiceUrl + _sysSetting.FileDownloadApi;
+        var items = await _industryRepository.Queryable()
+            .Where(m => m.IsEnable)
+            .OrderBy(m => m.DisplayOrder)
+            .ToListAsync(m => new IndustryOutDto());
+        return items;
+    }
+
     /// <summary>
     /// 行业页面基础数据
     /// </summary>
@@ -101,23 +180,89 @@ 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);
 
+        var bulletinId = await _bulletinRepository.Queryable()
+            .Where(m => m.SnapshotBulletinTypeId == indurstry.BulletinTypeGuideId && m.BulletinState == EBulletinState.ReviewPass)
+            .OrderByDescending(m => m.CreationTime)
+            .Select(m => m.Id)
+            .FirstAsync(requestAborted);
         var outDto = new IndustryBaseOutDto
         {
             Industry = indurstry.Adapt<IndustryOutDto>()
         };
+        outDto.BulletinId = bulletinId;
         if (indurstry.IndustryType == EIndustryType.Declare)
         {
             outDto.AreaTree = (await _systemAreaDomainService.GetAreaTree(parentId: "510300")).Adapt<List<SystemAreaOutDto>>();
             outDto.Files = (await _fileRepository.GetByKeyAsync(indurstry.Id, requestAborted)).Adapt<List<IndustryFileDto>>();
             outDto.Files.ToList().ForEach(m => m.Url = fileDownloadApi + m.AdditionId);
-            outDto.WorkArea = _systemDicDataCacheManager.WorkArea.Adapt<List<SystemDicDataOutDto>>();
-            outDto.Workplace = _systemDicDataCacheManager.Workplace.Adapt<List<SystemDicDataOutDto>>();
+            outDto.WorkplaceName = _systemDicDataCacheManager.WorkplaceName;
+            outDto.Workplace = _systemDicDataCacheManager.Workplace;
+            outDto.JobType = _systemDicDataCacheManager.JobType;
+            outDto.BusinessUnitType = _systemDicDataCacheManager.BusinessUnitType;
+        }
+        return outDto;
+    }
+
+
+    /// <summary>
+    /// 获取公开工单集合
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    public async Task<IList<OrderPublishOutDto>> GetOrderPublishAsync(OrderPublishInDto dto, CancellationToken requestAborted)
+    {
+        dto.ValidateObject();
+        var items = await _snapshotOrderPublishRepository.Queryable()
+            .LeftJoin<Order>((s, o) => s.Id == o.Id)
+            .Where((s, o) => s.IndustryId == dto.IndustryId && s.Status == EOrderSnapshotPublishStatus.Agree)
+            .WhereIF(dto.Keyword.NotNullOrEmpty(), (s, o) => s.No.Contains(dto.Keyword) || s.ArrangeTitle.Contains(dto.Keyword))
+            .Select<OrderPublishOutDto>((s, o) => new OrderPublishOutDto
+            {
+                Title = s.ArrangeTitle
+            }, true)
+            .ToFixedListAsync(dto, requestAborted);
+
+        return items;
+    }
+
+    /// <summary>
+    /// 获取公开的工单详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    public async Task<OrderPublishDetailOutDto> GetOrderPublishDetailAsync(string id, CancellationToken requestAborted)
+    {
+        var order = await _orderRepository.GetAsync(id) ??
+            throw UserFriendlyException.SameMessage("工单不存在");
+
+        var outDto = order.Adapt<OrderPublishDetailOutDto>();
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        if (outDto.FileJson != null)
+        {
+            foreach (var item in outDto.FileJson)
+            {
+                item.Path = fileServiceUrl + item.Path;
+            }
         }
+        var traces = await _workflowTraceRepository.Queryable()
+            .Where(m => m.ExternalId == order.Id && m.Status == EWorkflowStepStatus.Handled)
+            .OrderBy(m => m.AcceptTime)
+            .ToListAsync(requestAborted);
+        var centre = traces.Where(m => m.StepType == EStepType.End || m.StepType == EStepType.Start || m.BusinessType == EBusinessType.Send || m.BusinessType == EBusinessType.Seat || m.BusinessType == EBusinessType.File)
+            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
+            .ToList();
+        outDto.Workflow = traces.Where(m => !centre.Select(s => s.Id).ToList().Contains(m.Id))
+            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
+            .ToList();
+        outDto.Workflow.AddRange(centre);
+        outDto.Workflow = outDto.Workflow.OrderBy(m => m.HandleTime).ToList();
         return outDto;
     }
 
@@ -125,13 +270,13 @@ public abstract class SnapshotApplicationBase
     /// 获取随手拍小程序公告
     /// </summary>
     /// <returns></returns>
-    public async Task<IReadOnlyList<BulletinOutDto>> GetBulletinsAsync(BulletinInDto dto)
+    public async Task<IReadOnlyList<BulletinOutDto>> GetBulletinsAsync(BulletinInDto dto, CancellationToken cancellationToken)
     {
         var items = await _bulletinRepository.Queryable()
-            .Where(m => m.BulletinState == Share.Enums.Article.EBulletinState.ReviewPass)
-            .LeftJoin<Industry>((bulletin, industry) => bulletin.BulletinTypeId == industry.BulletinTypePublicityId)
+            .Where(m => m.BulletinState == EBulletinState.ReviewPass)
+            .LeftJoin<Industry>((bulletin, industry) => bulletin.SnapshotBulletinTypeId == industry.BulletinTypePublicityId)
             .Where((bulletin, industry) => industry.Id == dto.IndustryId)
-            .ToPageListAsync(dto.PageIndex, dto.PageSize);
+            .ToFixedListAsync(dto, cancellationToken);
 
         return items.Adapt<IReadOnlyList<BulletinOutDto>>();
     }
@@ -143,10 +288,10 @@ public abstract class SnapshotApplicationBase
     public async Task<SnapshotUserInfoOutDto> GetSnapshotUserInfoAsync()
     {
         var openId = _sessionContext.OpenId;
-        var thirdAccount = await _thirdAccountRepository.QueryByOpenIdAsync(openId);
+        var thirdAccount = await _thirdAccountRepository.GetByOpenIdAsync(openId);
         var dayTime = DateTime.Now;
         var readPack = await _redPackRecordRepository.Queryable()
-            .Where(m => m.WXOpenId == openId && m.PickupStatus == Share.Enums.Snapshot.ERedPackPickupStatus.Received)
+            .Where(m => m.WXOpenId == openId && m.PickupStatus == ERedPackPickupStatus.Received)
             .Where(m => m.CreationTime.Date == dayTime.Date)
             .Select(m => SqlFunc.AggregateSum(m.Amount))
             .FirstAsync();
@@ -155,9 +300,9 @@ public abstract class SnapshotApplicationBase
             .Where(m => m.Contact == thirdAccount.PhoneNumber)
             .Select(m => new SnapshotUserInfoOutDto
             {
-                NoReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status < Share.Enums.Order.EOrderStatus.Filed, 1, 0)),
-                ReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status >= Share.Enums.Order.EOrderStatus.Filed, 1, 0)),
-                AppraiseCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status == Share.Enums.Order.EOrderStatus.Visited, 1, 0)),
+                NoReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status < EOrderStatus.Filed, 1, 0)),
+                ReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status >= EOrderStatus.Filed, 1, 0)),
+                AppraiseCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status == EOrderStatus.Visited, 1, 0)),
             }).FirstAsync();
 
         outDto.DayAmount = readPack;
@@ -166,20 +311,18 @@ public abstract class SnapshotApplicationBase
         return outDto;
     }
 
-    public async Task<PagedDto<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto)
+    /// <summary>
+    /// 获取工单列表
+    /// </summary>
+    public async Task<IList<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto, CancellationToken cancellationToken)
     {
-        var member = await _thirdAccountRepository.QueryByOpenIdAsync(_sessionContext.OpenId);
-        if (member == null)
-        {
-            return new PagedDto<OrderOutDto>(0, new List<OrderOutDto>());
-        }
-        var (total, items) = await _orderSnapshotRepository.Queryable()
-            .LeftJoin<Order>((snapshot, order) => snapshot.OrderId == order.Id)
-            .Where((snapshot, order) => order.Contact == member.PhoneNumber)
+        var items = await _orderSnapshotRepository.Queryable()
+            .LeftJoin<Order>((snapshot, order) => snapshot.Id == order.Id)
+            .Where((snapshot, order) => order.Contact == _sessionContext.Phone)
             .WhereIF(dto.Status == EOrderQueryStatus.Appraise, (snapshot, order) => order.Status == EOrderStatus.Visited)
             .WhereIF(dto.Status == EOrderQueryStatus.NoReply, (snapshot, order) => order.Status < EOrderStatus.Filed)
             .WhereIF(dto.Status == EOrderQueryStatus.Reply, (snapshot, order) => order.Status >= EOrderStatus.Filed)
-            .WhereIF(dto.KeyWords.NotNullOrEmpty(), (snapshot, order) => order.Title.Contains(dto.KeyWords))
+            .WhereIF(dto.KeyWords.NotNullOrEmpty(), (snapshot, order) => order.Title.Contains(dto.KeyWords) || order.No.Contains(dto.KeyWords))
             .Select((snapshot, order) => new OrderOutDto
             {
                 Id = snapshot.Id,
@@ -190,29 +333,97 @@ public abstract class SnapshotApplicationBase
                 CreationTime = order.CreationTime,
                 Area = order.City
             })
-            .ToPagedListAsync(dto.PageIndex, dto.PageSize);
+            .ToFixedListAsync(dto, cancellationToken);
 
-        return new PagedDto<OrderOutDto>(total, items);
+        return items;
     }
 
-    public async Task<OrderDetailOutDto> GetSnapshotOrderDetailAsync(string id)
+    /// <summary>
+    /// 获取工单详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<OrderPublishDetailOutDto> GetSnapshotOrderDetailAsync(string id, CancellationToken cancellationToken)
     {
-        var detail = await _orderSnapshotRepository.Queryable()
-            .Where(m => m.Id == id)
-            .LeftJoin<Order>((snapshot, order) => snapshot.OrderId == order.Id)
-            .Select((snapshot, order) => new OrderDetailOutDto
+        var order = await _orderRepository.GetAsync(id) ??
+            throw UserFriendlyException.SameMessage("工单不存在");
+
+        var outDto = order.Adapt<OrderPublishDetailOutDto>();
+        var fileServiceUrl = _sysSetting.FileServerUrl;
+        if (outDto.FileJson != null)
+        {
+            foreach (var item in outDto.FileJson)
             {
-                Id = snapshot.Id,
-                OrderNo = order.No,
-                Title = order.Title,
-                Status = order.Status,
-                IndustryName = snapshot.IndustryName,
-                CreationTime = order.CreationTime,
-                Area = order.City
-            })
-            .FirstAsync();
+                item.Path = fileServiceUrl + item.Path;
+            }
+        }
 
-        return detail;
+        await _orderVisitRepository.Queryable()
+            .Where(m => m.OrderId == id).FirstAsync(cancellationToken)
+            .Then(orderVisit =>
+            {
+                if (orderVisit.NowEvaluate != null)
+                {
+                    outDto.IsVisit = true;
+                }
+            });
+
+        await _redPackAuditRepository.Queryable()
+            .Where(m => m.OrderId == id)
+            .FirstAsync(cancellationToken)
+            .Then(redPack =>
+            {
+                outDto.AuditRemark = redPack.AuditRemark;
+                outDto.RedPackStatus = redPack.Status;
+            });
+
+        var traces = await _workflowTraceRepository.Queryable()
+            .Where(m => m.ExternalId == order.Id && m.Status == EWorkflowStepStatus.Handled)
+            .OrderBy(m => m.AcceptTime)
+            .ToListAsync(cancellationToken);
+        var centre = traces.Where(m => m.StepType == EStepType.End || m.StepType == EStepType.Start || m.BusinessType == EBusinessType.Send || m.BusinessType == EBusinessType.Seat || m.BusinessType == EBusinessType.File)
+            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
+            .ToList();
+        outDto.Workflow = traces.Where(m => !centre.Select(s => s.Id).ToList().Contains(m.Id))
+            .Select(m => new SnapshotWorkflow(m.Id, m.Name, m.HandleTime.Value))
+            .ToList();
+        outDto.Workflow.AddRange(centre);
+        outDto.Workflow = outDto.Workflow.OrderBy(m => m.HandleTime).ToList();
+        return outDto;
+    }
+
+    /// <summary>
+    /// 获取回访详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    public async Task<IList<OrderVisitItemsOutDto>> GetOrderVisitDetailAsync(string id)
+    { 
+        var orderVisitId = await _orderVisitRepository.Queryable()
+            .Where(m => m.OrderId == id)
+            .Select(m => m.Id)
+            .FirstAsync();
+        if (orderVisitId.IsNullOrEmpty()) return [];
+        var orderVisitDetail = await _orderVisitDetailRepository.Queryable()
+            .Where(m => m.VisitId == orderVisitId)
+            .ToListAsync();
+        if (orderVisitDetail.IsNullOrEmpty()) return [];
+        var seat = orderVisitDetail.Where(m => m.VisitTarget == EVisitTarget.Seat).First();
+        var item = new OrderVisitItemsOutDto();
+        if (seat != null && seat.SeatEvaluate.HasValue)
+        {
+            item.SeatEvaluate = seat.SeatEvaluate.Value.GetDescription();
+        }
+        var org = orderVisitDetail.Where(m => m.VisitTarget == EVisitTarget.Org).First();
+        if (org != null)
+        { 
+            if (org.OrgProcessingResults != null) item.OrgProcessingResults = org.OrgProcessingResults.Value;
+            if (org.OrgHandledAttitude != null) item.OrgHandledAttitude = org.OrgHandledAttitude.Value;
+        }
+        return
+        [
+            item
+        ];
     }
 
     /// <summary>
@@ -220,23 +431,35 @@ public abstract class SnapshotApplicationBase
     /// </summary>
     /// <param name="dto"></param>
     /// <returns></returns>
-    public async Task<PagedDto<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto)
+    public async Task<IList<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto, CancellationToken cancellationToken)
     {
-        var openId = _sessionContext.OpenId;
-        var (total, items) = await _redPackRecordRepository.Queryable()
-            .Where(m => m.WXOpenId == openId)
+        var items = await _redPackRecordRepository.Queryable(includeDeleted: true)
+            .Where(m => m.IsDeleted == false)
+            .Where(m => m.WXOpenId == _sessionContext.OpenId || m.PhoneNumber == _sessionContext.Phone)
             .Where(m => m.PickupStatus == dto.Status)
             .Where(m => m.CreationTime.ToString("yyyy-MM") == dto.Time)
             .LeftJoin<Order>((red, order) => red.OrderId == order.Id)
             .Select((red, order) => new RedPackOutDto
             {
+                OrderId = order.Id,
                 Amount = red.Amount,
                 Title = order.Title,
                 CreationTime = red.CreationTime
             })
-            .ToPagedListAsync(dto.PageIndex, dto.PageSize);
+            .ToFixedListAsync(dto, cancellationToken);
+
+        return items;
+    }
 
-        return new PagedDto<RedPackOutDto>(total, items);
+    /// <summary>
+    /// 获取用户领取过的红包总金额
+    /// </summary>
+    /// <returns></returns>
+    public async Task<string> GetRedPackReceivedTotalAsync(CancellationToken cancellationToken)
+    {
+        var member = await _thirdAccountRepository.GetAsync(m => m.OpenId == _sessionContext.OpenId || m.PhoneNumber == _sessionContext.Phone, cancellationToken)
+            ?? throw UserFriendlyException.SameMessage("用户不存在");
+        return member.TotalAmount.ToYuanFinance();
     }
 
     /// <summary>
@@ -244,11 +467,11 @@ public abstract class SnapshotApplicationBase
     /// </summary>
     /// <param name="count"></param>
     /// <returns></returns>
-    public async Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto)
+    public async Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto, CancellationToken cancellationToken)
     {
         var openId = _sessionContext.OpenId;
         var item = await _redPackRecordRepository.Queryable()
-            .Where(m => m.WXOpenId == openId)
+            .Where(m => m.WXOpenId == openId || m.PhoneNumber == _sessionContext.Phone)
             .Where(m => m.PickupStatus == dto.Status)
             .GroupBy(m => m.CreationTime.ToString("yyyy-MM"))
             .OrderByDescending(m => m.CreationTime)
@@ -257,12 +480,16 @@ public abstract class SnapshotApplicationBase
                 CreationTime = SqlFunc.AggregateMax(m.CreationTime.Date),
                 Amount = SqlFunc.AggregateSum(m.Amount)
             })
-            .Take(dto.Count)
-            .ToListAsync();
+            .ToListAsync(cancellationToken);
 
         return item;
     }
 
+    /// <summary>
+    /// 获取随手拍公告详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
     public async Task<BulletinOutDto> GetBulletinsDetailAsync(string id)
     {
         var detail = await _bulletinRepository.Queryable()
@@ -272,9 +499,359 @@ public abstract class SnapshotApplicationBase
             {
                 Id = m.Id,
                 Title = m.Title,
-                Content = m.Content
+                Content = m.Content,
+                CreationTime = m.CreationTime
             })
             .FirstAsync();
         return detail;
     }
+
+    /// <summary>
+    /// 保存用户自己的邀请码
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task SaveInvitationCodeAsync(SaveInvitationCodeInDto dto)
+    {
+        dto.ValidateObject();
+        var third = await _thirdAccountRepository.GetByOpenIdAsync(_sessionContext.OpenId)
+            ?? throw UserFriendlyException.SameMessage("用户不存在");
+        third.InvitationCode = dto.InvitationCode;
+        await _thirdAccountRepository.UpdateAsync(third);
+    }
+
+    #endregion
+
+    #region 网格员
+    /// <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>
+    [LogToData("ReplyCode")]
+    public async Task SaveGuiderSystemReplyAsync(GuiderSystemInDto dto, CancellationToken token)
+    {
+        var orderSnapshot = await _orderSnapshotRepository.GetByNetworkENumberAsync(dto.AppealNumber)
+            ?? throw UserFriendlyException.SameMessage("工单不存在");
+        dto.Adapt(orderSnapshot);
+        if (dto.ReplyFileList.NotNullOrEmpty())
+        {
+            foreach (var file in dto.ReplyFileList)
+            {
+                await _fileDomainService.GetNetworkFileAsync(file, orderSnapshot.Id, token);
+            }
+        }
+        await _orderSnapshotRepository.UpdateAsync(orderSnapshot);
+        // 网格员办结
+        if (orderSnapshot.ReplyResultType == EGuiderSystemReplyType.Field)
+        {
+            await _publisher.PublishAsync(new GuiderSystemFieldNotification(orderSnapshot, dto.Adapt<CommunityInfo>()), 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);
+    }
+
+    /// <summary>
+    /// 同步社区信息
+    /// </summary>
+    /// <param name="entity"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task SyncCommunityInfoAsync(CommunityInfo community, CancellationToken cancellationToken)
+    {
+        var entity = await _communityInfoRepository.GetAsync(community.Id, cancellationToken);
+        if (entity == null)
+        {
+            community.UniqueKey = community.GetUniqueKey();
+            await _communityInfoRepository.AddAsync(community);
+            return;
+        }
+        if (entity.UniqueKey != community.GetUniqueKey())
+        {
+            await _communityInfoRepository.UpdateAsync(community, cancellationToken);
+        }
+    }
+    #endregion
+
+    #region 从业人员
+
+    /// <summary>
+    /// 添加从业人员
+    /// </summary>
+    /// <param name="dtos"></param>
+    /// <returns></returns>
+    public async Task AddPractitionerAsync(IList<AddBatchPractitionerInDto> dtos)
+    {
+        foreach (var item in dtos)
+        {
+            try
+            {
+                var entity = item.Adapt<Practitioner>();
+                switch (item.Gender.Trim())
+                {
+                    case "男":
+                        entity.Gender = EGender.Male;
+                        break;
+                    case "女":
+                        entity.Gender = EGender.Female;
+                        break;
+                    default:
+                        entity.Gender = EGender.Unknown;
+                        break;
+                }
+                var area = await _systemAreaRepository.Queryable()
+                    .Where(m => m.AreaName == item.AreaName)
+                    .FirstAsync();
+                entity.SystemAreaId = area.Id;
+                entity.SystemAreaName = area.AreaName;
+                await _practitionerRepository.AddAsync(entity);
+
+            }
+            catch (Exception e)
+            {
+                var msg = e.Message;
+            }
+        }
+    }
+
+    /// <summary>
+    /// 获取从业人员
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task<IList<PractitionerItemOutDto>> GetPractitionerItemsAsync(PractitionerItemInDto dto, CancellationToken cancellationToken)
+    {
+        var items = await _practitionerRepository.Queryable()
+            .Where(m => m.SystemAreaId == dto.AreaId)
+            .OrderBy("RANDOM()")
+            .Select<PractitionerItemOutDto>()
+            .Take(dto.Count)
+            .ToListAsync(cancellationToken);
+        return items;
+    }
+
+    /// <summary>
+    /// 获取从业人员详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <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>();
+    }
+    #endregion
+
+    #region 志愿者
+
+    /// <summary>
+    /// 添加志愿者
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task<string> AddVolunteerAsync(AddVolunteerInDto dto, CancellationToken cancellationToken)
+    {
+        var entity = dto.Adapt<Volunteer>();
+        entity.Id = await _volunteerRepository.AddAsync(entity);
+        return entity.Id;
+    }
+
+    /// <summary>
+    /// 志愿者上报
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="requestAborted"></param>
+    /// <returns></returns>
+    public async Task<AddVolunteerReportOutDto> AddVolunteerReportAsync(AddVolunteerReportInDto dto, CancellationToken requestAborted)
+    {
+        var volunteer = await _volunteerRepository.GetByPhoneAsync(_sessionContext.Phone)
+                ?? throw UserFriendlyException.SameMessage("提交失败!您不是志愿者.");
+
+        var entity = dto.Adapt<VolunteerReport>();
+        entity.Volunteer = volunteer.Name;
+        entity.VolunteerPhone = volunteer.PhoneNumber;
+
+        entity.Id = await _volunteerReportRepository.AddAsync(entity, requestAborted);
+        await _fileRepository.AddFileAsync(dto.Files, entity.Id, requestAborted);
+        return entity.Adapt<AddVolunteerReportOutDto>();
+    }
+    #endregion
+
+
+    #region 红包
+    /// <summary>
+    /// 生成用户红包审核数据
+    /// </summary>
+    /// <param name="id"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    [LogToData]
+    public async Task<string> AddRedPardAsync(string orderId, CancellationToken cancellationToken)
+    {
+        var order = await _orderRepository.Queryable()
+            .Where(m => m.Id == orderId)
+            .Select(m => new { m.Id, m.Status, m.No , m.FromPhone})
+            .FirstAsync(cancellationToken) ?? throw new UserFriendlyException($"{orderId} 工单不存在");
+        var snapshot = await _orderSnapshotRepository.GetAsync(orderId) ?? throw new UserFriendlyException("工单不存在");
+        if (order.Status != EOrderStatus.Filed) return $"{order.No} 工单状态非 {EOrderStatus.Filed} 不处理;";
+        var redPack = await _redPackAuditRepository.GetByOrderIdAsync(orderId, cancellationToken);
+        if (redPack != null) return $"{order.No} 工单已存在红包信息,不处理;";
+        var industry = await _industryRepository.Queryable()
+            .Where(m => m.Id == snapshot.IndustryId)
+            .FirstAsync(cancellationToken);
+        var entity = new RedPackAudit
+        {
+            OrderId = order.Id,
+            Status = ERedPackAuditStatus.Pending,
+            ShouldAmount = industry.CitizenReadPackAmount,
+            PhoneNumber = order.FromPhone,
+            IsSend = false,
+        };
+        await _redPackAuditRepository.AddAsync(entity, cancellationToken);
+        var guiderAudit = entity.Adapt<RedPackGuiderAudit>();
+        guiderAudit.ShouldAmount = industry.GuiderReadPackAmount;
+        guiderAudit.PhoneNumber = snapshot.MemberMobile;
+        await _redPackGuiderAuditRepository.AddAsync(guiderAudit, cancellationToken);
+        return "ok";
+    }
+    #endregion
+
+
+    #region 随手拍公告
+    /// <summary>
+    /// 添加随手拍公告
+    /// </summary>
+    /// <returns></returns>
+    public async Task<string> AddBulletinAsync(AddSnapshotBulletinInDto dto)
+    {
+        dto.ValidateObject();
+        var entity = dto.Adapt<SnapshotBulletin>();
+        entity.BulletinState = EBulletinState.InReview;
+        entity.Id = await _bulletinRepository.AddAsync(entity);
+        return entity.Id;
+    }
+
+    /// <summary>
+    /// 审核公告
+    /// </summary>
+    /// <param name="examineBulletinDto"></param>
+    /// <returns></returns>
+    public async Task AuditBulletinAsync(ExamineBulletinDto dto)
+    {
+        var bulletin = await _bulletinRepository.GetAsync(dto.Id)
+            ?? throw UserFriendlyException.SameMessage("无效数据");
+        if (bulletin.BulletinState != EBulletinState.InReview)
+            throw UserFriendlyException.SameMessage("当前状态不能审核");
+
+        bulletin.ExaminOpinion = dto.Reason;
+        bulletin.ExaminTime = DateTime.Now;
+        bulletin.ExaminManId = _sessionContext.RequiredUserId;
+        if (dto.IsPass)
+        {
+            bulletin.BulletinState = EBulletinState.ReviewPass;
+        }
+        else
+        {
+            bulletin.BulletinState = EBulletinState.ReviewNoPass;
+        }
+        await _bulletinRepository.UpdateAsync(bulletin);
+    }
+    #endregion
 }

+ 68 - 0
src/Hotline.Application/Snapshot/SnapshotBulletinApplication.cs

@@ -0,0 +1,68 @@
+using Hotline.Caching.Interfaces;
+using Hotline.Settings;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Snapshot;
+public class SnapshotBulletinApplication : ISnapshotBulletinApplication, IScopeDependency
+{
+
+    private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+
+    public SnapshotBulletinApplication(ISystemSettingCacheManager systemSettingCacheManager)
+    {
+        _systemSettingCacheManager = systemSettingCacheManager;
+    }
+
+    /// <summary>
+    /// 处理通知公告图片附件路径
+    /// </summary>
+    /// <param name="sHtmlText"></param>
+    /// <returns></returns>
+    public string GetSiteUrls(string sHtmlText)
+    {
+        sHtmlText = sHtmlText.Replace("&lt;", "<").Replace("&gt;", ">");
+
+        //临时内容
+        System.Text.StringBuilder sb = new StringBuilder();
+        sb.Append(sHtmlText);
+        // 定义正则表达式用来匹配 img 标签
+        Regex regImg = new Regex(@"<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
+        // 搜索匹配的字符串
+        MatchCollection matches = regImg.Matches(sHtmlText);
+
+        // 定义正则表达式用来匹配 video 标签
+        Regex regvideo = new Regex(@"<video\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
+        // 搜索匹配的字符串
+        MatchCollection matchesvideo = regvideo.Matches(sHtmlText);
+
+        var strSiteUrl = _systemSettingCacheManager.GetSetting(SettingConstants.OldFilesUrls)?.SettingValue[0];
+
+        // 取得匹配项列表 视频
+        foreach (Match match in matchesvideo)
+        {
+            if (-1 == match.Groups["imgUrl"].Value.IndexOf("http"))
+            {
+                sb.Replace(match.Groups["imgUrl"].Value, strSiteUrl + match.Groups["imgUrl"].Value);
+                sb.Replace(strSiteUrl + strSiteUrl + match.Groups["imgUrl"].Value, strSiteUrl + match.Groups["imgUrl"].Value);
+            }
+        }
+
+        // 取得匹配项列表
+        foreach (Match match in matches)
+        {
+            if (-1 == match.Groups["imgUrl"].Value.IndexOf("http"))
+            {
+                sb.Replace(match.Groups["imgUrl"].Value, strSiteUrl + match.Groups["imgUrl"].Value);
+                sb.Replace(strSiteUrl + strSiteUrl + match.Groups["imgUrl"].Value, strSiteUrl + match.Groups["imgUrl"].Value);
+            }
+        }
+
+        return sb.ToString();
+    }
+}

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

@@ -1,9 +1,14 @@
-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;
 using Hotline.Settings;
+using Hotline.Share.Dtos.Snapshot;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using System;
 using System.Collections.Generic;
@@ -19,7 +24,7 @@ namespace Hotline.Application.Snapshot;
 [Injection(AppScopes = EAppScope.ZiGong)]
 public class ZiGongSnapshotApplication : SnapshotApplicationBase, ISnapshotApplication, IScopeDependency
 {
-    public ZiGongSnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, IRepository<Article.Bulletin> bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository, ISystemSettingCacheManager systemSettingCacheManager, ISystemAreaDomainService systemAreaDomainService, IFileRepository fileRepository, ISystemDicDataCacheManager systemDicDataCacheManager) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager)
+    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, IFileDomainService fileDomainService, ICommunityInfoRepository communityInfoRepository, IRedPackAuditRepository redPackAuditRepository, IOrderVisitRepository orderVisitRepository, IOrderVisitDetailRepository orderVisitDetailRepository, IRedPackGuiderAuditRepository redPackGuiderAuditRepository) : base(thirdLoginService, industryRepository, bulletinRepository, sessionContext, redPackRecordRepository, orderRepository, thirdAccountRepository, orderSnapshotRepository, systemSettingCacheManager, systemAreaDomainService, fileRepository, systemDicDataCacheManager, snapshotOrderPublishRepository, workflowTraceRepository, practitionerRepository, systemAreaRepository, volunteerRepository, volunteerReportRepository, systemLog, guiderSystemService, capPublisher, publisher, guiderInfoRepository, fileDomainService, communityInfoRepository, redPackAuditRepository, orderVisitRepository, orderVisitDetailRepository, redPackGuiderAuditRepository)
     {
     }
 }

+ 40 - 30
src/Hotline.Application/StatisticalReport/OrderReportApplication.cs

@@ -57,21 +57,22 @@ namespace Hotline.Application.StatisticalReport
         private readonly IRepository<User> _userRepository;
         private readonly IRepository<Account> _accountRepository;
         private readonly IRepository<StatisticsDepart> _statisticsDepartRepository;
-
-        /// <summary>
-        /// 
-        /// </summary>
-        /// <param name="orderRepository"></param>
-        /// <param name="orderVisitDetailRepository"></param>
-        /// <param name="orderDelayRepository"></param>
-        /// <param name="mapper"></param>
-        /// <param name="orderPublishRepository"></param>
-        /// <param name="sessionContext"></param>
-        /// <param name="workflowTraceRepository"></param>
-        /// <param name="orderScreenRepository"></param>
-        /// <param name="orderSecondaryHandlingApplication"></param>
-        /// <param name="timeLimitDomainService"></param>
-        public OrderReportApplication(
+		private readonly IRepository<SystemOrganize> _systemOrganizerepository;
+
+		/// <summary>
+		/// 
+		/// </summary>
+		/// <param name="orderRepository"></param>
+		/// <param name="orderVisitDetailRepository"></param>
+		/// <param name="orderDelayRepository"></param>
+		/// <param name="mapper"></param>
+		/// <param name="orderPublishRepository"></param>
+		/// <param name="sessionContext"></param>
+		/// <param name="workflowTraceRepository"></param>
+		/// <param name="orderScreenRepository"></param>
+		/// <param name="orderSecondaryHandlingApplication"></param>
+		/// <param name="timeLimitDomainService"></param>
+		public OrderReportApplication(
             IOrderRepository orderRepository,
             IRepository<OrderVisitDetail> orderVisitDetailRepository,
             IRepository<OrderDelay> orderDelayRepository,
@@ -88,8 +89,9 @@ namespace Hotline.Application.StatisticalReport
             ISystemSettingCacheManager systemSettingCacheManager,
             IRepository<User> userRepository,
             IRepository<Account> accountRepository,
-            IRepository<StatisticsDepart> statisticsDepartRepository
-            )
+            IRepository<StatisticsDepart> statisticsDepartRepository,
+			IRepository<SystemOrganize> systemOrganizerepository
+			)
         {
             _orderRepository = orderRepository;
             _orderVisitDetailRepository = orderVisitDetailRepository;
@@ -108,7 +110,9 @@ namespace Hotline.Application.StatisticalReport
             _userRepository = userRepository;
             _accountRepository = accountRepository;
             _statisticsDepartRepository = statisticsDepartRepository;
-        }
+			_systemOrganizerepository = systemOrganizerepository;
+
+		}
         /// <summary>
         /// 部门办件统计表---新
         /// </summary>
@@ -142,15 +146,19 @@ namespace Hotline.Application.StatisticalReport
 
             if (await _statisticsDepartRepository.Queryable().Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime).AnyAsync())
             {
-                var oldList = await _statisticsDepartRepository.Queryable()
-                    .LeftJoin<SystemOrganize>((x, o) => x.DepartmentId == o.oldBmid)
-                    .Where((x, o) => x.Time >= dto.StartTime && x.Time <= dto.EndTime)
-                    .WhereIF(IsCenter == false, (x, o) => o.Id.StartsWith(_sessionContext.RequiredOrgId))
-                    .GroupBy((x, o) => o.Id.Substring(SqlFunc.MappingColumn<int>("0"),
-                        SqlFunc.MappingColumn<int>(orgLength.ToString())))
-                    .Select((x, o) => new DepartmentalProcessingStatisticsDataDto
+                var len = orgLength.ToString();
+
+				var oldList = await _systemOrganizerepository.Queryable()
+                    .LeftJoin<StatisticsDepart>((o,x) => x.DepartmentId == o.oldBmid)
+                    .Where((o, x) => x.Time >= dto.StartTime && x.Time <= dto.EndTime)
+                    .WhereIF(IsCenter == false, (o, x) => o.Id.StartsWith(_sessionContext.RequiredOrgId))
+                    //.MergeTable()
+                    //.Where((o, x) => string.IsNullOrEmpty(o.Id) == false )
+                    .GroupBy((o, x) => o.Id.Substring(SqlFunc.MappingColumn<int>("0"),
+                        SqlFunc.MappingColumn<int>(len)))
+                    .Select((o, x) => new DepartmentalProcessingStatisticsDataDto
                     {
-                        OrgCode = o.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(orgLength.ToString())),
+                        OrgCode = o.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(len)),
                         YbOrderCountNum = SqlFunc.AggregateSum(x.OrderAlreadyNum),
                         ZbOrderCountNum = SqlFunc.AggregateSum(x.OrderWaitNum),
                         CompleteOnTime = 0,
@@ -246,15 +254,17 @@ namespace Hotline.Application.StatisticalReport
 
             if (await _statisticsDepartRepository.Queryable().Where(x => x.Time >= dto.StartTime && x.Time <= dto.EndTime).AnyAsync())
             {
-                var oldList = await _statisticsDepartRepository.Queryable()
+				var len = orgLength.ToString();
+				var oldList = await _statisticsDepartRepository.Queryable()
                     .LeftJoin<SystemOrganize>((x, o) => x.DepartmentId == o.oldBmid)
                     .Where((x, o) => x.Time >= dto.StartTime && x.Time <= dto.EndTime)
                     .Where((x, o) => o.Id.StartsWith(_sessionContext.RequiredOrgId))
-                    .GroupBy((x, o) => o.Id.Substring(SqlFunc.MappingColumn<int>("0"),
-                        SqlFunc.MappingColumn<int>(orgLength.ToString())))
+					.Where((x, o) => string.IsNullOrEmpty(o.Id) == false)
+					.GroupBy((x, o) => o.Id.Substring(SqlFunc.MappingColumn<int>("0"),
+                        SqlFunc.MappingColumn<int>(len)))
                     .Select((x, o) => new DepartmentalProcessingStatisticsDataDto
                     {
-                        OrgCode = o.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(orgLength.ToString())),
+                        OrgCode = o.Id.Substring(SqlFunc.MappingColumn<int>("0"), SqlFunc.MappingColumn<int>(len)),
                         YbOrderCountNum = SqlFunc.AggregateSum(x.OrderAlreadyNum),
                         ZbOrderCountNum = SqlFunc.AggregateSum(x.OrderWaitNum),
                         CompleteOnTime = 0,

+ 4 - 0
src/Hotline.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs

@@ -141,6 +141,7 @@ namespace Hotline.Repository.SqlSugar.Extensions
 
                 var types = typeof(User).Assembly.GetTypes()
                     .Where(d => d.GetInterfaces().Any(x => x == typeof(ITable) && !d.IsAbstract))
+                    .Where(d => d.Name != "Order" && d.Name != "OrderCopy" && d.Name != "Workflow" && d.Name != "WorkflowStep" && d.Name != "WorkflowTrace")
                     .Distinct()
                     .ToArray();
 
@@ -284,6 +285,9 @@ namespace Hotline.Repository.SqlSugar.Extensions
 
             db.Aop.DataExecuting = (oldValue, entityInfo) =>
             {
+                services.BuildServiceProvider()
+                   .GetService<DatabaseEventDispatcher>()?
+                   .Dispatch(oldValue, entityInfo.PropertyName, entityInfo.EntityValue, entityInfo.OperationType);
                 //inset生效
                 if (entityInfo.PropertyName == "CreationTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
                 {

+ 24 - 3
src/Hotline.Repository.SqlSugar/File/FileRepository.cs

@@ -3,6 +3,8 @@ using Hotline.Orders;
 using Hotline.Repository.SqlSugar.DataPermissions;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
+using Hotline.Share.Dtos.Snapshot;
+using Mapster;
 using MapsterMapper;
 using Microsoft.AspNetCore.Http;
 using Newtonsoft.Json;
@@ -15,7 +17,7 @@ using XF.Domain.Repository;
 
 namespace Hotline.Repository.SqlSugar.File
 {
-	public class FileRepository : BaseRepository<Hotline.File.File>, IFileRepository, IScopeDependency
+    public class FileRepository : BaseRepository<Hotline.File.File>, IFileRepository, IScopeDependency
 	{
 		private readonly ISessionContext _sessionContext;
 		private readonly IMapper _mapper;
@@ -25,7 +27,25 @@ namespace Hotline.Repository.SqlSugar.File
 			_mapper = mapper;
 		}
 
-		public async Task<List<FileJson>> AddFileAsync(List<FileDto> files, string id, string flowId = "", CancellationToken cancellationToken = default) 
+        public async Task<List<FileJson>> AddFileAsync(IList<SnapshotFileInDto> files, string id, CancellationToken requestAborted)
+        {
+			var entities = files.Adapt<List<Hotline.File.File>>();
+			foreach (var file in entities)
+			{
+				var names = file.FileName.Split(".");
+                file.Name = names[0];
+                file.Type = names[1];
+				file.Key = id;
+                file.OrgName = _sessionContext.OrgName;
+                file.OrgId = _sessionContext.OrgId;
+                file.UserId = _sessionContext.UserId;
+                file.UserName = _sessionContext.UserName;
+            }
+            await AddRangeAsync(entities, requestAborted);
+            return entities.Select(x => new FileJson { Id = x.Id, FileId = x.Additions, Path = x.Path, FileName = x.Name, FileType = x.Type }).ToList();
+        }
+
+        public async Task<List<FileJson>> AddFileAsync(List<FileDto> files, string id, string flowId = "", CancellationToken cancellationToken = default) 
 		{
 			List<Hotline.File.File> newFiles =  new List<Hotline.File.File>();
 			var classify = files[0].Classify;
@@ -79,5 +99,6 @@ namespace Hotline.Repository.SqlSugar.File
 			}
 			return dto;
 		}
-	}
+
+    }
 }

+ 2 - 1
src/Hotline.Repository.SqlSugar/Orders/OrderVisitDetailRepository.cs

@@ -6,9 +6,10 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using XF.Domain.Dependency;
 
 namespace Hotline.Repository.SqlSugar.Orders;
-public class OrderVisitDetailRepository : BaseRepository<OrderVisitDetail>, IOrderVisitDetailRepository
+public class OrderVisitDetailRepository : BaseRepository<OrderVisitDetail>, IOrderVisitDetailRepository, IScopeDependency
 {
     public OrderVisitDetailRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
     {

+ 18 - 0
src/Hotline.Repository.SqlSugar/Snapshot/CommunityInfoRepository.cs

@@ -0,0 +1,18 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+public class CommunityInfoRepository : BaseRepository<CommunityInfo>, ICommunityInfoRepository, IScopeDependency
+{
+    public CommunityInfoRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+}

+ 23 - 0
src/Hotline.Repository.SqlSugar/Snapshot/GuiderInfoRepository.cs

@@ -0,0 +1,23 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+public class GuiderInfoRepository : BaseRepository<GuiderInfo>, IGuiderInfoRepository, IScopeDependency
+{
+    public GuiderInfoRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+
+    public async Task<GuiderInfo> GetByPhoneNumberAsync(string phoneNumber)
+    {
+        return await Queryable().Where(m => m.PhoneNumber == phoneNumber).FirstAsync();
+    }
+}

+ 19 - 0
src/Hotline.Repository.SqlSugar/Snapshot/IndustryCaseRepository.cs

@@ -0,0 +1,19 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+public class IndustryCaseRepository : BaseRepository<IndustryCase>, IIndustryCaseRepository, IScopeDependency
+{
+    public IndustryCaseRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+}
+

+ 18 - 0
src/Hotline.Repository.SqlSugar/Snapshot/IndustryLogRepository.cs

@@ -0,0 +1,18 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+public class IndustryLogRepository : BaseRepository<IndustryLog>, IIndustryLogRepository,  IScopeDependency
+{
+    public IndustryLogRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+}

+ 1 - 0
src/Hotline.Repository.SqlSugar/Snapshot/IndustryRepository.cs

@@ -1,6 +1,7 @@
 using Hotline.Orders;
 using Hotline.Repository.SqlSugar.DataPermissions;
 using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
 using SqlSugar;
 using System;
 using System.Collections.Generic;

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

@@ -0,0 +1,46 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Share.Dtos;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Snapshot.Interfaces;
+using Microsoft.AspNetCore.Http;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+public class OrderSnapshotRepository : BaseRepository<OrderSnapshot>, IOrderSnapshotRepository, IScopeDependency
+{
+    private readonly ISessionContext _sessionContext;
+    public OrderSnapshotRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder, ISessionContext sessionContext) : base(uow, dataPermissionFilterBuilder)
+    {
+        _sessionContext = sessionContext;
+    }
+
+    public async Task<OrderSnapshot> GetByNetworkENumberAsync(string appealNumber)
+    {
+        return await Queryable().Where(m => m.NetworkENumber == appealNumber).FirstAsync();
+    }
+
+    public async Task<OrderSnapshot> UpdateSafetyAsync(string orderId, bool isSafety, string remark)
+    {
+        OrderSnapshot order = null;
+        await GetAsync(orderId)
+            .Then(async orderSnapshot =>
+            {
+                orderSnapshot.IsSafetyDepartment = isSafety;
+                orderSnapshot.SignUserId = _sessionContext.UserId;
+                orderSnapshot.SignUserName = _sessionContext.UserName;
+                orderSnapshot.SignTime = DateTime.Now;
+                orderSnapshot.SignRemark = remark;
+                await UpdateAsync(orderSnapshot, CancellationToken.None);
+                order = orderSnapshot;
+            });
+        return order;
+    }
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott