Эх сурвалжийг харах

Merge branch 'feature/snapshot' into test

qinchaoyue 3 сар өмнө
parent
commit
a7d1fb719d
31 өөрчлөгдсөн 772 нэмэгдсэн , 277 устгасан
  1. 1 1
      src/Hotline.Api/Controllers/OrderController.cs
  2. 70 0
      src/Hotline.Api/Filter/LogFilterAlphaAttribute.cs
  3. 1 1
      src/Hotline.Application.Tests/Application/KnowApplicationTest.cs
  4. 109 109
      src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs
  5. 17 5
      src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs
  6. 1 1
      src/Hotline.Application.Tests/Controller/KnowledgeControllerTest.cs
  7. 8 5
      src/Hotline.Application.Tests/Controller/OrderControllerTest.cs
  8. 14 1
      src/Hotline.Application.Tests/Controller/SnapshotControllerTest.cs
  9. 2 2
      src/Hotline.Application.Tests/Domain/LuZhouExpireTimeTest.cs
  10. 148 49
      src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs
  11. 5 5
      src/Hotline.Application.Tests/Domain/YiBinExpireTimeTest.cs
  12. 9 9
      src/Hotline.Application.Tests/Domain/ZiGongExpireTimeTest.cs
  13. 1 1
      src/Hotline.Application.Tests/Hotline.Application.Tests.csproj
  14. 1 0
      src/Hotline.Application.Tests/Mock/Interfaces/IOrderServiceStartWorkflow.cs
  15. 41 0
      src/Hotline.Application.Tests/Mock/OptionsSnapshotMock.cs
  16. 32 0
      src/Hotline.Application.Tests/Mock/OrderServiceStartWorkflow.cs
  17. 3 1
      src/Hotline.Application.Tests/Startup.cs
  18. 51 6
      src/Hotline.Application.Tests/TestBase.cs
  19. 2 1
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  20. 4 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  21. 22 0
      src/Hotline.Repository.SqlSugar/System/SettingOrderVisitSmsReplyRuleRepository.cs
  22. 43 1
      src/Hotline.Repository.SqlSugar/System/SystemLogRepository.cs
  23. 2 0
      src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs
  24. 5 0
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  25. 3 2
      src/Hotline/Orders/IOrderVisitDomainService.cs
  26. 0 20
      src/Hotline/Orders/OrderVisitDetail.cs
  27. 51 56
      src/Hotline/Orders/OrderVisitDomainService.cs
  28. 12 0
      src/Hotline/Settings/ISettingOrderVisitSmsReplyRuleRepository.cs
  29. 6 1
      src/Hotline/Settings/ISystemLogRepository.cs
  30. 107 0
      src/Hotline/Settings/SettingOrderVisitSmsReplyRule.cs
  31. 1 0
      src/Hotline/Snapshot/OrderSnapshot.cs

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

@@ -1310,7 +1310,7 @@ public class OrderController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpPost("visit")]
-    [LogFilter("工单回访")]
+    [LogFilterAlpha("工单回访")]
     public async Task Visit([FromBody] VisitDto dto)
     {
         // 发送延迟关联通话记录消息

+ 70 - 0
src/Hotline.Api/Filter/LogFilterAlphaAttribute.cs

@@ -0,0 +1,70 @@
+using Hotline.Settings;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace Hotline.Api.Filter;
+
+public class LogFilterAlphaAttribute : ActionFilterAttribute
+{
+    private ILogger<LogFilterAlphaAttribute> _logger;
+    public string Name { get; set; }
+
+    public bool Record { get; set; }
+
+    public LogFilterAlphaAttribute(string name = "", bool record = true)
+    {
+        Name = name;
+        Record = record;
+    }
+
+    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
+    {
+        var repository = context.HttpContext.RequestServices.GetService<ISystemLogRepository>();
+        _logger = context.HttpContext.RequestServices.GetService<ILogger<LogFilterAlphaAttribute>>();
+
+        if (Record == false)
+        {
+            await next();
+        }
+        else
+        {
+            var request = context.HttpContext.Request;
+            var systemLog = new SystemLog
+            {
+                Name = this.Name,
+            };
+            GetMethod(context, systemLog);
+            var systemId = await repository.AddAsync(systemLog);
+
+            var resultContext = await next();
+
+            var result = context.Result as ObjectResult;
+            if (result != null)
+                await repository.UpdateResultAsync(systemId, result);
+        }
+    }
+
+    private void GetMethod(ActionExecutingContext context, SystemLog systemLog)
+    {
+        var request = context.HttpContext.Request;
+        try
+        {
+            systemLog.ExecuteUrl = request.Method.ToUpper() + "|" + request.Path;
+            if (request.Method.ToUpper() == "GET")
+            {
+                systemLog.ExecuteParam = request.QueryString.ToString();
+            }
+            else
+            {
+                if (request.ContentType?.IndexOf("multipart/form-data", StringComparison.Ordinal) == -1)
+                {
+                    systemLog.ExecuteParam = context.ActionArguments.Values.FirstOrDefault();
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            _logger.LogError(e, "GetMethod 异常");
+        }
+    }
+}

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

@@ -67,7 +67,7 @@ public class KnowApplicationTest : TestBase
         items[4] = "大开";
     }
 
-    [Fact]
+    //[Fact]
     public async Task GetPageViewList_Test()
     {
         //var r = await _knowledgeRelationTypeRepository.Queryable()

+ 109 - 109
src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs

@@ -23,119 +23,119 @@ using Xunit;
 
 namespace Hotline.Application.Tests.Snapshot
 {
-    public class SnapshotApplicationTest
-    {
-        private readonly Mock<IThirdIdentiyService> _thirdLoginServiceMock;
-        private readonly Mock<IIndustryRepository> _industryRepositoryMock;
-        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<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;
+    //public class SnapshotApplicationTest
+    //{
+    //    private readonly Mock<IThirdIdentiyService> _thirdLoginServiceMock;
+    //    private readonly Mock<IIndustryRepository> _industryRepositoryMock;
+    //    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<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;
+    //    private readonly DefaultSnapshotApplication _snapshotApplication;
 
-        public SnapshotApplicationTest()
-        {
-            _thirdLoginServiceMock = new Mock<IThirdIdentiyService>();
-            _industryRepositoryMock = new Mock<IIndustryRepository>();
-            _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<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>();
+    //    public SnapshotApplicationTest()
+    //    {
+    //        _thirdLoginServiceMock = new Mock<IThirdIdentiyService>();
+    //        _industryRepositoryMock = new Mock<IIndustryRepository>();
+    //        _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<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(
-                _thirdLoginServiceMock.Object,
-                _industryRepositoryMock.Object,
-                _bulletinRepositoryMock.Object,
-                _sessionContextMock.Object,
-                _redPackRecordRepositoryMock.Object,
-                _orderRepositoryMock.Object,
-                _thirdAccountRepositoryMock.Object,
-                _orderSnapshotRepositoryMock.Object,
-                _systemSettingCacheManagerMock.Object,
-                _systemAreaDomainServiceMock.Object,
-                _fileRepositoryMock.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,
-                null,
-                null
-            );
-        }
+    //    _snapshotApplication = new DefaultSnapshotApplication(
+    //            _thirdLoginServiceMock.Object,
+    //            _industryRepositoryMock.Object,
+    //            _bulletinRepositoryMock.Object,
+    //            _sessionContextMock.Object,
+    //            _redPackRecordRepositoryMock.Object,
+    //            _orderRepositoryMock.Object,
+    //            _thirdAccountRepositoryMock.Object,
+    //            _orderSnapshotRepositoryMock.Object,
+    //            _systemSettingCacheManagerMock.Object,
+    //            _systemAreaDomainServiceMock.Object,
+    //            _fileRepositoryMock.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,
+    //            null,
+    //            null
+    //        );
+    //    }
 
-        [Fact]
-        public async Task GetDeclareAsync_WithValidId_ReturnsDeclareBaseOutDto()
-        {
-            // Arrange
-            var id = "123";
-            var cancellationToken = CancellationToken.None;
-            var industry = new Industry { Id = id, IndustryType = EIndustryType.Declare };
-            var areaTree = new List<SystemArea>();
-            var files = new List<Hotline.File.File>();
-            _industryRepositoryMock.Setup(r => r.GetAsync(id, cancellationToken)).ReturnsAsync(industry);
-            _systemAreaDomainServiceMock.Setup(s => s.GetAreaTree(0, "510300")).ReturnsAsync(areaTree);
-            _fileRepositoryMock.Setup(f => f.GetByKeyAsync(id, cancellationToken)).ReturnsAsync(files);
+    //    [Fact]
+    //    public async Task GetDeclareAsync_WithValidId_ReturnsDeclareBaseOutDto()
+    //    {
+    //        // Arrange
+    //        var id = "123";
+    //        var cancellationToken = CancellationToken.None;
+    //        var industry = new Industry { Id = id, IndustryType = EIndustryType.Declare };
+    //        var areaTree = new List<SystemArea>();
+    //        var files = new List<Hotline.File.File>();
+    //        _industryRepositoryMock.Setup(r => r.GetAsync(id, cancellationToken)).ReturnsAsync(industry);
+    //        _systemAreaDomainServiceMock.Setup(s => s.GetAreaTree(0, "510300")).ReturnsAsync(areaTree);
+    //        _fileRepositoryMock.Setup(f => f.GetByKeyAsync(id, cancellationToken)).ReturnsAsync(files);
 
-            // Act
-            var result = await _snapshotApplication.GetIndustryBaseAsync(id, cancellationToken);
+    //        // Act
+    //        var result = await _snapshotApplication.GetIndustryBaseAsync(id, cancellationToken);
 
-            // Assert
-            Assert.NotNull(result);
-            Assert.Equal(industry.Id, result.Industry.Id);
-            //Assert.Equal(industry.IndustryType, result.IndustryType);
-            Assert.Equal(areaTree.Adapt<List<SystemAreaOutDto>>(), result.AreaTree);
-            Assert.Equal(files.Adapt<List<IndustryFileDto>>(), result.Files);
-        }
-    }
+    //        // Assert
+    //        Assert.NotNull(result);
+    //        Assert.Equal(industry.Id, result.Industry.Id);
+    //        //Assert.Equal(industry.IndustryType, result.IndustryType);
+    //        Assert.Equal(areaTree.Adapt<List<SystemAreaOutDto>>(), result.AreaTree);
+    //        Assert.Equal(files.Adapt<List<IndustryFileDto>>(), result.Files);
+    //    }
+    //}
 }

+ 17 - 5
src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs

@@ -201,6 +201,16 @@ public class SnapshotApplicationTest : TestBase
     [Fact]
     public async Task GetBulletins_Test()
     {
+        var whly = await _industryRepository.GetAsync(m => m.Name == "文化旅游");
+        if (whly == null)
+        {
+            var temp = await _industryRepository.Queryable().Where(m => m.IndustryType == EIndustryType.Clue).FirstAsync();
+            temp.Id = null;
+            temp.Name = "文化旅游";
+            temp.IndustryType = EIndustryType.Clue;
+            await _industryRepository.AddAsync(temp);
+        }
+
         var homePage = await _snapshotApplication.GetHomePageAsync();
         var inDto = new BulletinInDto
         {
@@ -253,19 +263,20 @@ public class SnapshotApplicationTest : TestBase
         result.PhoneNumber.ShouldNotBeNullOrEmpty();
     }
 
-    [Theory]
-    [InlineData("")]
-    [InlineData("测")]
-    public async Task SnapshotOrder_Test(string key)
+    [Fact]
+    public async Task SnapshotOrder_Test()
     {
         var dto = new OrderInDto();
-        dto.KeyWords = key;
         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();
+        var key = page.FirstOrDefault()?.Title.Substring(0, 1);
+        dto.KeyWords = key;
+        page = await _snapshotApplication.GetSnapshotOrdersAsync(dto, CancellationToken.None);
+        page.Count.ShouldNotBe(0);
     }
 
     [Theory]
@@ -479,6 +490,7 @@ public class SnapshotApplicationTest : TestBase
     public async Task Snapshot_Test()
     {
         var order = _orderServiceMock.CreateSnapshotOrder(SetWeiXin)
+            .办理到网格员(SetZuoXi)
             .GetCreateResult();
         await _snapshotApplication.PostOrderGuiderSystemAsync(order.Id, CancellationToken.None);
         var orderSnapshot = await _orderSnapshotRepository.GetAsync(order.Id);

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

@@ -43,7 +43,7 @@ public class KnowledgeControllerTest : TestBase
     /// 批量审核知识
     /// </summary>
     /// <returns></returns>
-    [Fact]
+    //[Fact]
     public async Task KnowledgeBatchAudit_TestAsync()
     {
         SetPaiDanYuan();

+ 8 - 5
src/Hotline.Application.Tests/Controller/OrderControllerTest.cs

@@ -74,10 +74,11 @@ public class OrderControllerTest : TestBase
     private readonly IOrderSnapshotRepository _orderSnapshotRepository;
     private readonly ISystemLogRepository _systemLogRepository;
     private readonly IOrderVisitDomainService _orderVisitDomainService;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
 
     public OrderControllerTest(IAccountRepository accountRepository, IRepository<Role> roleRepository,
-        UserController userController, IRepository<Hotspot> hotspotRepository, OrderController orderController, 
-        IOrderRepository orderRepository, IServiceScopeFactory scopeFactory, IRepository<User> userRepository, 
+        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,
@@ -85,7 +86,7 @@ public class OrderControllerTest : TestBase
         IRepository<CallidRelation> callIdRelationRepository, XingTangCallApplication defaultCallApplication,
         ISugarUnitOfWork<CapDbContext> capDbContext, IHttpContextAccessor httpContextAccessor, IThirdIdentiyService thirdService,
         IThirdAccountRepository thirdAccount, IIndustryRepository industryRepository, IOrderSnapshotRepository orderSnapshotRepository,
-        ISystemLogRepository systemLogRepository, IOrderVisitDomainService orderVisitDomainService, IRepository<OrderVisitDetail> orderVisitDetailRepository)
+        ISystemLogRepository systemLogRepository, IOrderVisitDomainService orderVisitDomainService, IRepository<OrderVisitDetail> orderVisitDetailRepository, ISystemDicDataCacheManager systemDicDataCacheManager)
         : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _hotspotRepository = hotspotRepository;
@@ -110,6 +111,7 @@ public class OrderControllerTest : TestBase
         _systemLogRepository = systemLogRepository;
         _orderVisitDomainService = orderVisitDomainService;
         _orderVisitDetailRepository = orderVisitDetailRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
     }
 
     /// <summary>
@@ -198,8 +200,9 @@ public class OrderControllerTest : TestBase
         var visitDetail = visitDetaila.Adapt<List<VisitDetailDto>>();
         visitDetail.ForEach(m => { 
             m.SeatEvaluate = ESeatEvaluate.DefaultSatisfied;
-            m.OrgProcessingResults = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>("0");
-            m.OrgHandledAttitude = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>("0");
+            var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt("0");
+            m.OrgProcessingResults = smsReply.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
+            m.OrgHandledAttitude = smsReply.GetOrgHandledAttitude(_systemDicDataCacheManager.VisitSatisfaction);
         });
 
         var callNo = DateTime.Now.ToString("yyyyMMddhhmmss") + "Flow";

+ 14 - 1
src/Hotline.Application.Tests/Controller/SnapshotControllerTest.cs

@@ -1,4 +1,5 @@
 using AutoFixture;
+using EasyCaching.Core;
 using Hotline.Api.Controllers;
 using Hotline.Api.Controllers.Snapshot;
 using Hotline.Identity.Accounts;
@@ -27,7 +28,10 @@ public class SnapshotControllerTest : TestBase
     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)
+    private readonly IEasyCachingProvider _easyCaching;
+    private readonly IRedisCachingProvider _redisCaching;
+
+    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, IEasyCachingProvider easyCaching, IRedisCachingProvider redisCaching) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _snapshotController = snapshotController;
         _snapshotController.ControllerContext = new ControllerContext
@@ -37,6 +41,8 @@ public class SnapshotControllerTest : TestBase
         _orderRepository = orderRepository;
         _orderSnapshotRepository = orderSnapshotRepository;
         _industryRepository = industryRepository;
+        _easyCaching = easyCaching;
+        _redisCaching = redisCaching;
     }
 
     [Fact]
@@ -59,6 +65,12 @@ public class SnapshotControllerTest : TestBase
     [Fact]
     public async Task AddOrder_Test()
     {
+        var cacheKey = "Hotline:CollectionJobType";
+        await _easyCaching.RemoveAsync(cacheKey, CancellationToken.None);
+        var exist = await _easyCaching.ExistsAsync(cacheKey);
+        await _redisCaching.KeyDelAsync(cacheKey);
+        var existRedis = await _redisCaching.KeyExistsAsync(cacheKey);
+
         var homePage = await _snapshotController.GetHomePageAsync();
         var industry = homePage.Industrys.Where(m => m.IndustryType == EIndustryType.Declare).FirstOrDefault();
         var pageBase = await _snapshotController.GetIndustryBaseAsync(industry.Id);
@@ -86,5 +98,6 @@ public class SnapshotControllerTest : TestBase
         var orderSnapshotEntity = await _orderSnapshotRepository.GetAsync(order.Id);
         orderSnapshotEntity.ShouldNotBeNull();
         orderSnapshotEntity.JobType.ShouldBe(inDto.JobType);
+        orderSnapshotEntity.JobTypeName.ShouldBe("气割");
     }
 }

+ 2 - 2
src/Hotline.Application.Tests/Domain/LuZhouExpireTimeTest.cs

@@ -26,8 +26,8 @@ public class LuZhouExpireTimeTest
     }
 
     [Theory]
-    [InlineData("2024-09-04 14:00:00", 2, "10", "2024-09-06 14:00:00", "2024/9/6 10:24:00", "2024/9/5 14:00:00", "2个工作日")]
-    [InlineData("2024-09-04 14:01:01", 3, "10", "2024-09-09 14:01:01", "2024/9/6 17:37:01", "2024/9/6 9:31:01", "3个工作日")]
+    [InlineData("2024-09-04 14:00:00", 2, "10", "2024-09-06 13:59:59", "2024/9/6 10:24:00", "2024/9/5 14:00:00", "2个工作日")]
+    [InlineData("2024-09-04 14:01:01", 3, "10", "2024-09-09 14:01:00", "2024/9/6 17:37:01", "2024/9/6 9:31:01", "3个工作日")]
     public async Task CalcEndTime_Test(string begin, int count, string busCode, string expiredTime, string nearlyExpiredTime, string nearlyExpiredTimeOne, string timeText)
     {
         var beginTime = DateTime.Parse(begin);

+ 148 - 49
src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs

@@ -1,11 +1,14 @@
 using Hotline.Api.Controllers;
 using Hotline.Application.Tests.Mock;
+using Hotline.Caching.Interfaces;
+using Hotline.Configurations;
 using Hotline.EventBus;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Orders;
 using Hotline.Push.FWMessage;
 using Hotline.Push.Notifies;
+using Hotline.Settings;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.Order;
@@ -16,6 +19,7 @@ using Hotline.Users;
 using Mapster;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
 using Shouldly;
 using XF.Domain.Repository;
 
@@ -28,8 +32,12 @@ public class OrderVisitDomainServiceTest : TestBase
     private readonly Publisher _publisher;
     private readonly IOrderRepository _orderRepository;
     private readonly OrderServiceMock _orderServiceMock;
+    private readonly ISettingOrderVisitSmsReplyRuleRepository _settingOrderVisitSmsReplyRuleRepository;
+    private readonly ISystemDicDataCacheManager _systemDicDataCacheManager;
+    private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+    private readonly ISystemLogRepository _logRepository;
 
-    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)
+    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, ISettingOrderVisitSmsReplyRuleRepository settingOrderVisitSmsReplyRuleRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOptionsSnapshot<AppConfiguration> appOptions, ISystemLogRepository logRepository) : base(accountRepository, roleRepository, userController, scopeFactory, userRepository, httpContextAccessor, thirdService, thirdAccount)
     {
         _orderVisitDomainService = orderVisitDomainService;
         _orderVisitRepository = orderVisitRepository;
@@ -37,6 +45,10 @@ public class OrderVisitDomainServiceTest : TestBase
         _publisher = publisher;
         _orderRepository = orderRepository;
         _orderServiceMock = orderServiceMock;
+        _settingOrderVisitSmsReplyRuleRepository = settingOrderVisitSmsReplyRuleRepository;
+        _systemDicDataCacheManager = systemDicDataCacheManager;
+        _appOptions = appOptions;
+        _logRepository = logRepository;
     }
 
 
@@ -52,13 +64,14 @@ public class OrderVisitDomainServiceTest : TestBase
         visit.ShouldNotBeNull("缺少测试数据");
 
         var msg = new MessageDto()
-        { 
+        {
             ExternalId = visit.Id,
         };
 
         await _orderVisitDomainService.UpdateSmsReplyDefaultAsync(msg);
         var replyTxt = "默认满意";
-        var kv = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>("默认满意");
+        var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt("默认满意");
+        var kv = smsReply.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
         var order = await _orderRepository.Queryable()
             .Includes(m => m.OrderVisits)
             .Where(m => m.OrderVisits.Any(o => o.Id == visit.Id))
@@ -72,8 +85,8 @@ public class OrderVisitDomainServiceTest : TestBase
         {
             if (detail.VisitTarget == EVisitTarget.Seat)
             {
-                var seatEvaluate = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt);
-                var voiceEvaluate = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt);
+                var seatEvaluate = smsReply.SeatEvaluate;
+                var voiceEvaluate = smsReply.VoiceEvaluate;
 
                 detail.SeatEvaluate.ShouldBe(seatEvaluate);
                 detail.VoiceEvaluate.ShouldBe(voiceEvaluate);
@@ -97,16 +110,22 @@ public class OrderVisitDomainServiceTest : TestBase
     }
 
     [Theory]
-    [InlineData("4", "SMSUnsatisfied", "2", "不满意")]
-    [InlineData("1", "Visited", "5", "非常满意")]
-    [InlineData("非常满意", "Visited", "5", "非常满意")]
-    [InlineData("满意", "Visited", "4", "满意")]
-    [InlineData("一般", "Visited", "4", "满意")]
-    [InlineData("不满意", "SMSUnsatisfied", "2", "不满意")]
-    [InlineData("非常不满意", "SMSUnsatisfied", "2", "不满意")]
-    [InlineData("0", "Visited", "0", "默认满意")]
-    public async Task UpdateSmsReply_Test(string content, string visitState, string orgResuktKey, string orgResuktValue)
+    [InlineData("4", "SMSUnsatisfied", "2", "不满意", "不满意", "Published")]
+    [InlineData("1", "Visited", "5", "非常满意", "非常满意")]
+    [InlineData("2", "Visited", "4", "满意", "满意")]
+    [InlineData("3", "Visited", "4", "满意", "一般")]
+    [InlineData("5", "SMSUnsatisfied", "2", "不满意", "不满意", "Published")]
+    [InlineData("默认满意", "Visited", "0", "默认满意", "超过48小时自动回访")]
+    [InlineData("都没有办理好", "SMSUnsatisfied", "", "", "都没有办理好", "Published", "YiBin")]
+    [InlineData("1", "Visited", "4", "满意", "满意", "Visited", "YiBin")]
+    [InlineData("2", "SMSUnsatisfied", "2", "不满意", "不满意", "Published", "YiBin")]
+    public async Task UpdateSmsReply_Test(string content, string visitState, string orgResuktKey, string orgResuktValue, string visitContent, string visited = "Visited", string appScope = "ZiGong")
     {
+        if (appScope != "ZiGong")
+            ChangeAppScopeYiBin();
+        else
+            ChangeAppScopeZiGong();
+        var appScopeFile = _appOptions.Value.AppScope;
         SetZuoXi();
         var order = _orderServiceMock.CreateOrder()
             .办理到一级部门()
@@ -126,72 +145,152 @@ public class OrderVisitDomainServiceTest : TestBase
         if (visit == null) return;
         visit.ShouldNotBeNull("缺少测试数据");
 
-        var message = new MessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content , TelNumber = visit.Order.Contact};
+        var message = new MessageDto { ExternalId = visit.Id, IsSmsReply = true, SmsReplyContent = content, TelNumber = visit.Order.Contact };
         var dto = new PushReceiveMessageDto();
         await _orderVisitDomainService.UpdateSmsReplyAsync(message);
         visit = _orderVisitRepository.Get(visit.Id);
         visit.VisitState.ShouldBe(visitState.ToEnum<EVisitState>());
-        visit.NowEvaluate.ShouldNotBeNull();
-        visit.NowEvaluate.Key.ShouldBe(orgResuktKey);
-        visit.NowEvaluate.Value.ShouldBe(orgResuktValue);
+        if (orgResuktKey.NotNullOrEmpty())
+        {
+            visit.NowEvaluate.ShouldNotBeNull();
+            visit.NowEvaluate.Key.ShouldBe(orgResuktKey);
+            visit.NowEvaluate.Value.ShouldBe(orgResuktValue);
+        }
         var orderEntity = await _orderRepository.GetAsync(order.Id);
         orderEntity.ShouldNotBeNull();
-        if (content == "4" || content == "5" || content == "不满意" || content == "非常不满意")
+        orderEntity.Status.ShouldBe(visited.ToEnum<EOrderStatus>());
+        if (content == "4" || content == "5" || content == "不满意" || content == "非常不满意" || content == "都没有办理好")
         {
             visit.VisitType.ShouldBeNull();
-            orderEntity.Status.ShouldNotBe(EOrderStatus.Visited);
-        }
-        else 
-        {
-            orderEntity.Status.ShouldBe(EOrderStatus.Visited);
         }
 
+        var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt(content);
+        var log = _logRepository.Queryable().Where(m => m.Name == "短信回访-回复内容匹配结果" && m.Remark == order.No).First();
+        log.ShouldNotBeNull();
         await _orderVisitDetailRepository.Queryable()
             .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Org)
             .FirstAsync()
             .Then(org =>
             {
-                org.OrgProcessingResults.ShouldNotBeNull();
-                org.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
-                org.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
-
-                // 验证跟新工单上的字段是否成功
-                orderEntity.OrgProcessingResults.ShouldNotBeNull();
-                orderEntity.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
-                orderEntity.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
-
-                org.OrgHandledAttitude.ShouldNotBeNull();
-                org.OrgHandledAttitude.Key.ShouldBe(orgResuktKey);
-                org.OrgHandledAttitude.Value.ShouldBe(orgResuktValue);
+                if (content != "都没有办理好")
+                {
+                    org.OrgProcessingResults.ShouldNotBeNull();
+                    org.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
+                    org.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
+                    org.VisitContent.ShouldBe(visitContent);
+
+                    // 验证跟新工单上的字段是否成功
+                    if (new string[] { "4", "5", "不满意", "非常不满意"}.Contains(content) == false)
+                    {
+                        if (appScope != "YiBin" && content != "2")
+                        {
+                            orderEntity.OrgProcessingResults.ShouldNotBeNull();
+                            orderEntity.OrgProcessingResults.Key.ShouldBe(orgResuktKey);
+                            orderEntity.OrgProcessingResults.Value.ShouldBe(orgResuktValue);
+                        }
+                    }
+
+                    if (appScope != "YiBin")
+                    {
+                        org.OrgHandledAttitude.ShouldNotBeNull();
+                        org.OrgHandledAttitude.Key.ShouldBe(orgResuktKey);
+                        org.OrgHandledAttitude.Value.ShouldBe(orgResuktValue);
+                    }
+                    if (smsReply.IsReplyToOrgVisitContent == true)
+                    {
+                        org.VisitContent = content;
+                    }
+                }
                 return Task.CompletedTask;
             });
+        await _orderVisitDetailRepository.Queryable()
+          .Where(m => m.VisitId == visit.Id && m.VisitTarget == EVisitTarget.Seat)
+          .FirstAsync()
+          .Then(seat =>
+          {
+              if (content != "都没有办理好")
+              {
+
+                  // 验证跟新工单上的字段是否成功
+                  if (new string[] { "4", "5", "不满意", "非常不满意", "都没有办理好" }.Contains(content) == true)
+                  {
+                      seat.SeatEvaluate.ShouldNotBe(ESeatEvaluate.NoSatisfied);
+                      seat.SeatEvaluate.ShouldNotBe(ESeatEvaluate.VeryNoSatisfied);
+                      seat.VoiceEvaluate.ShouldNotBe(EVoiceEvaluate.NoSatisfied);
+                      seat.VoiceEvaluate.ShouldNotBe(EVoiceEvaluate.VeryNoSatisfied);
+                  }
+                  else
+                  {
+                      if (smsReply.SeatEvaluate != null)
+                          seat.SeatEvaluate.ShouldBe(smsReply.SeatEvaluate);
+                      if (smsReply.VoiceEvaluate != null)
+                          seat.VoiceEvaluate.ShouldBe(smsReply.VoiceEvaluate);
+                  }
+
+              }
+              return Task.CompletedTask;
+          });
     }
 
     [Theory]
-    [InlineData("1", "非常满意", "Visited", "VerySatisfied", "VerySatisfied", "非常满意|5")]
-    [InlineData("2", "满意", "Visited", "Satisfied", "Satisfied", "满意|4")]
-    [InlineData("3", "一般", "Visited", "Normal", "Normal", "满意|4")]
-    [InlineData("4", "不满意", "SMSUnsatisfied", "NoSatisfied", "NoSatisfied", "不满意|2")]
-    [InlineData("5", "非常不满意", "SMSUnsatisfied", "NoSatisfied", "VeryNoSatisfied", "不满意|2")]
-    [InlineData("默认满意", "默认满意", "Visited", "DefaultSatisfied", "DefaultSatisfied", "默认满意|0")]
+    [InlineData("1", "非常满意", "Visited", "", "", "非常满意|5")]
+    [InlineData("2", "满意", "Visited", "", "", "满意|4")]
+    [InlineData("3", "一般", "Visited", "", "", "满意|4")]
+    [InlineData("4", "不满意", "SMSUnsatisfied", "", "", "不满意|2")]
+    [InlineData("5", "不满意", "SMSUnsatisfied", "", "", "不满意|2")]
+    //[InlineData("默认满意", "超过48小时自动回访", "Visited", "DefaultSatisfied", "DefaultSatisfied", "默认满意|0")]
+    [InlineData("默认满意", "超过48小时自动回访", "Visited", "", "", "默认满意|0")]
+    [InlineData("1非常满意", "非常满意", "Visited", "", "", "非常满意|5")]
     public void GetVisitEvaluateByReplyTxt_Test(string replyTxt, string assertReplyTxt, string visitState, string seatEvaluate, string voiceEvaluate, string kv)
     {
-        var replyString = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<string>(replyTxt);
+        var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt(replyTxt);
+        var replyString = smsReply.VisitContent;
         replyString.ShouldBe(assertReplyTxt);
 
-        var visitStateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVisitState>(replyTxt);
+        var visitStateEnum = smsReply.VisitState;
         visitStateEnum.ShouldBe(visitState.ToEnum<EVisitState>());
 
-        var seatEvaluateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt);
-        seatEvaluateEnum.ShouldBe(seatEvaluate.ToEnum<ESeatEvaluate>());
+        if (seatEvaluate.NotNullOrEmpty())
+        {
+            var seatEvaluateEnum = smsReply.SeatEvaluate;
+            seatEvaluateEnum.ShouldBe(seatEvaluate.ToEnum<ESeatEvaluate>());
+        }
 
-        var voiceEvaluateEnum = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(replyTxt);
-        voiceEvaluateEnum.ShouldBe(voiceEvaluate.ToEnum<EVoiceEvaluate>());
+        if (voiceEvaluate.NotNullOrEmpty())
+        {
+            var voiceEvaluateEnum = smsReply.VoiceEvaluate;
+            voiceEvaluateEnum.ShouldBe(voiceEvaluate.ToEnum<EVoiceEvaluate>());
+        }
 
-        var kvResult = _orderVisitDomainService.GetVisitEvaluateByReplyTxt<Kv>(replyTxt);
+        var kvResult = smsReply.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
         var sp = kv.Split('|');
         var kV = new Kv(sp[1].ToString(), sp[0].ToString());
         kvResult.Key.ShouldBe(kV.Key);
         kvResult.Value.ShouldBe(kV.Value);
     }
+
+    [Fact]
+    public async Task Init_SettingOrderVisitSmsReplyRule_Data()
+    {
+        IEnumerable<SettingOrderVisitSmsReplyRule> entities = [
+            new () {Name = "非常满意的规则", AppScope = "ZiGong", ReplyRegular = "1", VisitContent = "非常满意", VisitState = EVisitState.Visited, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults=5, OrgHandledAttitude = 5, VisitType = EVisitType.SmsVisit, SortOrder = 1},
+            new () {Name = "满意的规则", AppScope = "ZiGong", ReplyRegular = "2", VisitContent = "满意", VisitState = EVisitState.Visited, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults=4, OrgHandledAttitude = 4 , VisitType = EVisitType.SmsVisit, SortOrder = 2},
+            new () {Name = "一般的规则", AppScope = "ZiGong", ReplyRegular = "3", VisitContent = "一般", VisitState = EVisitState.Visited, SeatEvaluate = null, VoiceEvaluate = null,OrgProcessingResults= 4, OrgHandledAttitude = 4 , VisitType = EVisitType.SmsVisit, SortOrder = 3},
+            new () {Name = "不满意的规则", AppScope = "ZiGong", ReplyRegular = "4", VisitContent = "不满意", VisitState = EVisitState.SMSUnsatisfied, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults=2, OrgHandledAttitude = 2 , VisitType = null, SortOrder = 4},
+            new () {Name = "非常不满意的规则", AppScope = "ZiGong", ReplyRegular = "5", VisitContent = "不满意", VisitState = EVisitState.SMSUnsatisfied, SeatEvaluate = null, VoiceEvaluate = null,OrgProcessingResults=2, OrgHandledAttitude = 2, VisitType = null, SortOrder = 5},
+            new () {Name = "默认满意的规则", AppScope = "ZiGong", ReplyRegular = "默认满意", VisitContent = "超过48小时自动回访", VisitState = EVisitState.Visited, SeatEvaluate = null, VoiceEvaluate = null,OrgProcessingResults=0, OrgHandledAttitude = 0, VisitType = EVisitType.SmsVisit, SortOrder = 0},
+            new () {Name = "用户回答非1,2,3,4,5的匹配规则", AppScope = "ZiGong", ReplyRegular = "^(?!.*[12345]).*$", VisitContent = "默认满意", VisitState = EVisitState.Visited, SeatEvaluate = null, VoiceEvaluate = null,OrgProcessingResults=0, OrgHandledAttitude = 0, VisitType = EVisitType.SmsVisit, SortOrder = 6},
+            new () {Name = "对部门处理结果满意", AppScope = "YiBin", ReplyRegular = "1", VisitContent = "满意", VisitState = EVisitState.Visited, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults = 4 , OrgHandledAttitude = null, VisitType = EVisitType.SmsVisit, SortOrder = 1},
+            new () {Name = "默认满意的规则", AppScope = "YiBin", ReplyRegular = "默认满意", VisitContent = "超过48小时自动回访", VisitState = EVisitState.Visited, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults=0, OrgHandledAttitude = 0 , VisitType = EVisitType.SmsVisit, SortOrder = 0},
+            new () {Name = "对部门处理结果不满意的规则", AppScope = "YiBin", ReplyRegular = "2", VisitContent = "不满意", VisitState = EVisitState.SMSUnsatisfied, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults=2, OrgHandledAttitude = null , VisitType = null, SortOrder = 2},
+            new () {Name = "用户回复的非1和2的规则", AppScope = "YiBin", ReplyRegular = "^(?!.*[12]).*$", VisitContent = "不满意", VisitState = EVisitState.SMSUnsatisfied, SeatEvaluate = null,VoiceEvaluate = null,OrgProcessingResults= null, OrgHandledAttitude = null , VisitType = null, SortOrder = 3, IsReplyToOrgVisitContent = true},
+            ];
+
+        foreach (var item in entities)
+        {
+            item.UniqueKey = (item.AppScope + item.ReplyRegular).GetMD5();
+            if (await _settingOrderVisitSmsReplyRuleRepository.AnyAsync(m => m.UniqueKey == item.UniqueKey) == false)
+                await _settingOrderVisitSmsReplyRuleRepository.AddAsync(item);
+        }
+    }
 }

+ 5 - 5
src/Hotline.Application.Tests/Domain/YiBinExpireTimeTest.cs

@@ -30,7 +30,7 @@ public class YiBinExpireTimeTest
     }
 
     [Theory]
-    [InlineData("2024-09-23 15:07:40", 5, "15", "2024-09-29 15:07:40", "5个工作日")]
+    [InlineData("2024-09-23 15:07:40", 5, "15", "2024-09-29 15:07:39", "5个工作日")]
     public async Task CalcEndTimeWorkDay_Test(string begin, int count, string busCode, string expiredTime, string timeText)
     {
         var beginTime = DateTime.Parse(begin);
@@ -40,7 +40,7 @@ public class YiBinExpireTimeTest
     }
 
     [Theory]
-    [InlineData("2024-09-25 13:16:33", 5, "15", "2024-10-08 13:16:33", "5个工作日")]
+    [InlineData("2024-09-25 13:16:33", 5, "15", "2024-10-08 13:16:32", "5个工作日")]
     public async Task CalcEndTimeWorkDayDelay_Test(string begin, int count, string busCode, string expiredTime, string timeText)
     {
         var beginTime = DateTime.Parse(begin);
@@ -51,8 +51,8 @@ public class YiBinExpireTimeTest
 
 
     [Theory]
-    [InlineData("2024-09-04 14:00:00", 2, "10", "2024-09-06 14:00:00", "2024/9/6 10:24:00", "2024/9/5 14:00:00", "2个工作日")]
-    [InlineData("2024-09-04 14:01:01", 3, "10", "2024-09-09 14:01:01", "2024/9/6 17:37:01", "2024/9/6 9:31:01", "3个工作日")]
+    [InlineData("2024-09-04 14:00:00", 2, "10", "2024-09-06 13:59:59", "2024/9/6 10:24:00", "2024/9/5 14:00:00", "2个工作日")]
+    [InlineData("2024-09-04 14:01:01", 3, "10", "2024-09-09 14:01:00", "2024/9/6 17:37:01", "2024/9/6 9:31:01", "3个工作日")]
     public async Task CalcEndTime_Test(string begin, int count, string busCode, string expiredTime, string nearlyExpiredTime, string nearlyExpiredTimeOne, string timeText)
     {
         var beginTime = DateTime.Parse(begin);
@@ -67,7 +67,7 @@ public class YiBinExpireTimeTest
 
     [Theory]
     [InlineData("2024-09-04 14:00:00", "CenterToOrg", true, "2024-09-05 14:00:00")]
-    [InlineData("2024-10-03 09:23:46", "CenterToCenter", false, "2024-10-14 9:00:00")]
+    [InlineData("2024-10-03 09:23:46", "CenterToCenter", false, "2024-10-12 18:00:00")]
     public async Task CalcExpiredTime_Test(string beginTxt, string flowTxt, bool is24Hour , string expected)
     {
         var beginTime = DateTime.Parse(beginTxt);

+ 9 - 9
src/Hotline.Application.Tests/Domain/ZiGongExpireTimeTest.cs

@@ -29,15 +29,15 @@ public class ZiGongExpireTimeTest
     }
 
     [Theory]
-    [InlineData("企业咨询件单元测试",     false, "Enterprise","10","互联网", "2024-09-04 14:00:00", "CenterToOrg", "2024-09-05 14:00:00")]
-    [InlineData("企业建议件单元测试",     false, "Enterprise","15","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 14:00:00")]
-    [InlineData("企业求助件单元测试",     false, "Enterprise","20","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 14:00:00")]
-    [InlineData("企业表扬件单元测试",     false, "Enterprise","25","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 14:00:00")]
-    [InlineData("企业举报件单元测试",     false, "Enterprise","30","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 14:00:00")]
-    [InlineData("企业投诉件单元测试",     false, "Enterprise","35","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 14:00:00")]
-    [InlineData("四川省12345咨询件单测试",false, "Citizen", "10","四川省12345", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-06 14:00:00")]
-    [InlineData("四川省12345建议件单测试",false, "Citizen", "15","四川省12345", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-10 14:00:00")]
-    [InlineData("中心到中心24小时",       true, "Citizen", "10","四川省12345", "2024-09-12 14:00:00", "CenterToCenter", "2024-09-13 14:00:00")]
+    [InlineData("企业咨询件单元测试",     false, "Enterprise","10","互联网", "2024-09-04 14:00:00", "CenterToOrg", "2024-09-05 13:59:59")]
+    [InlineData("企业建议件单元测试",     false, "Enterprise","15","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 13:59:59")]
+    [InlineData("企业求助件单元测试",     false, "Enterprise","20","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 13:59:59")]
+    [InlineData("企业表扬件单元测试",     false, "Enterprise","25","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 13:59:59")]
+    [InlineData("企业举报件单元测试",     false, "Enterprise","30","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 13:59:59")]
+    [InlineData("企业投诉件单元测试",     false, "Enterprise","35","互联网", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-09 13:59:59")]
+    [InlineData("四川省12345咨询件单测试",false, "Citizen", "10","四川省12345", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-06 13:59:59")]
+    [InlineData("四川省12345建议件单测试",false, "Citizen", "15","四川省12345", "2024-09-05 14:00:00", "CenterToOrg", "2024-09-10 13:59:59")]
+    [InlineData("中心到中心24小时",      true,  "Citizen", "10","四川省12345", "2024-09-12 14:00:00", "CenterToCenter", "2024-09-13 14:00:00")]
     public async Task CalcExpiredTime_Test(string title, bool is24,  string identityType,string acceptTypeCode, string sourceChannel, string beginTxt, string flowTxt, string expected)
     {
         var beginTime = DateTime.Parse(beginTxt);

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

@@ -15,7 +15,7 @@
 
   <ItemGroup>
     <Content Include="appsettings.Development.json">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
       <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
       <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
     </Content>

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

@@ -14,6 +14,7 @@ public interface IOrderServiceStartWorkflow
 
     OrderServiceMock 办理到派单员(Action action = null);
     OrderServiceMock 办理到工单标注(Action action = null);
+    OrderServiceMock 办理到网格员(Action action = null);
     CreateOrderOutDto GetCreateResult();
     OrderServiceMock 办理到一级部门(Action action = null);
 }

+ 41 - 0
src/Hotline.Application.Tests/Mock/OptionsSnapshotMock.cs

@@ -0,0 +1,41 @@
+using Hotline.Configurations;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+using static Hotline.AppDefaults;
+
+namespace Hotline.Application.Tests.Mock;
+public class OptionsSnapshotMock : IOptionsSnapshot<AppConfiguration>
+{
+    private readonly IHttpContextAccessor _contextAccessor;
+    private readonly IConfiguration _configuration;
+
+    public OptionsSnapshotMock(IHttpContextAccessor contextAccessor, IConfiguration configuration)
+    {
+        _contextAccessor = contextAccessor;
+        _configuration = configuration;
+    }
+
+    public AppConfiguration Value
+    {
+        get
+        {
+            var appConfiguration = _configuration.GetRequiredSection(nameof(AppConfiguration)).Get<AppConfiguration>();
+            if (_contextAccessor != null && _contextAccessor.HttpContext != null)
+                appConfiguration.AppScope = _contextAccessor.HttpContext.User.FindFirstValue("AppScope");
+            return appConfiguration;
+        }
+    }
+
+    public AppConfiguration Get(string? name)
+    {
+        throw new NotImplementedException();
+    }
+}

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

@@ -142,4 +142,36 @@ public class OrderServiceStartWorkflow : IOrderServiceStartWorkflow
         _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;
+    }
 }

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

@@ -67,6 +67,7 @@ using Hotline.Application.Tests.Mock.Interfaces;
 using Hotline.Orders;
 using Hotline.Orders.DatabaseEventHandler;
 using XF.Domain.Repository.Events;
+using Microsoft.Extensions.Options;
 
 namespace Hotline.Application.Tests;
 public class Startup
@@ -85,7 +86,7 @@ public class Startup
             .UseTestServerAndAddDefaultHttpClient()
             .ConfigureAppConfiguration((hostingContext, config) =>
             {
-                config.AddJsonFile(JsonFile);
+                config.AddJsonFile(JsonFile, optional: true, reloadOnChange: true);
             })
             .UseStartup<AspNetCoreStartup>());
     }
@@ -113,6 +114,7 @@ public class Startup
             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.AddScoped<IOptionsSnapshot<AppConfiguration>, OptionsSnapshotMock>();
 
             services.RegisterMapper();
             //services.AddControllers()

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

@@ -1,18 +1,25 @@
-using AutoFixture;
+using Amazon.Auth.AccessControlPolicy;
+using AutoFixture;
+using DocumentFormat.OpenXml.Spreadsheet;
 using Hotline.Api.Controllers;
 using Hotline.Application.Tests.Infrastructure;
+using Hotline.Configurations;
 using Hotline.Identity.Accounts;
 using Hotline.Identity.Roles;
 using Hotline.Settings;
 using Hotline.Share.Dtos.Users;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Enums.User;
+using Hotline.Snapshot;
 using Hotline.Snapshot.Interfaces;
 using Hotline.Users;
 using IdentityModel;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
 using System.Security.Claims;
 using XF.Domain.Authentications;
 using XF.Domain.Repository;
@@ -57,6 +64,40 @@ public class TestBase
 
     }
 
+    public void ChangeAppScopeYiBin()
+    {
+        ChangeAppScope("YiBin");
+    }
+
+    public void ChangeAppScope(string appScope)
+    {
+        var changeDto = _httpContextAccessor.HttpContext.User;
+        List<Claim> userClaims = [
+            new(JwtClaimTypes.Subject, changeDto.FindFirstValue(JwtClaimTypes.Subject)),
+            new(JwtClaimTypes.PhoneNumber, changeDto.FindFirstValue(JwtClaimTypes.PhoneNumber)),
+            new(ClaimTypes.NameIdentifier, changeDto.FindFirstValue(ClaimTypes.NameIdentifier)),
+            new(AppClaimTypes.UserDisplayName, changeDto.FindFirstValue(AppClaimTypes.UserDisplayName)),
+            new(AppClaimTypes.DepartmentId, changeDto.FindFirstValue(AppClaimTypes.DepartmentId)),
+            new(AppClaimTypes.DepartmentIsCenter, changeDto.FindFirstValue(AppClaimTypes.DepartmentIsCenter)),
+            new(AppClaimTypes.DepartmentName, changeDto.FindFirstValue(AppClaimTypes.DepartmentName)),
+            new(AppClaimTypes.DepartmentAreaCode, changeDto.FindFirstValue(AppClaimTypes.DepartmentAreaCode)),
+            new(AppClaimTypes.DepartmentAreaName, changeDto.FindFirstValue(AppClaimTypes.DepartmentAreaName)),
+            new(AppClaimTypes.DepartmentLevel, changeDto.FindFirstValue(AppClaimTypes.DepartmentLevel)),
+            new(AppClaimTypes.AreaId, changeDto.FindFirstValue(AppClaimTypes.AreaId)),
+            new(AppClaimTypes.OpenId, changeDto.FindFirstValue(AppClaimTypes.OpenId)),
+            new("AppScope", appScope)
+        ];
+        ClaimsIdentity identity = new ClaimsIdentity(userClaims);
+        var principal = new ClaimsPrincipal(identity);
+        _httpContextAccessor.HttpContext.User = principal;
+    }
+
+    public void ChangeAppScopeZiGong()
+    {
+        ChangeAppScope("ZiGong");
+    }
+
+
     public void SetPaiDanYuan()
     {
         SetOperator("派单员", "市民热线服务中心", "单元测试派单员", "001", "13408389849", EUserType.Normal, TestSettingConstants.PaiDanYuanAccountName);
@@ -127,6 +168,14 @@ public class TestBase
         var third = _thirdIdentiyService.GetTokenAsync(new Share.Dtos.Snapshot.ThirdTokenDto()).GetAwaiter().GetResult();
         var thirdAccount = _thirdAccountRepository.Get(d => d.OpenId == third.OpenId);
 
+        var appScope = "ZiGong";
+        if (_httpContextAccessor != null && _httpContextAccessor.HttpContext != null)
+        {
+            var m = _httpContextAccessor.HttpContext.User.FindFirstValue("AppScope");
+            if (m != null)
+                appScope = m;
+        }
+
         List<Claim> userClaims = [
             new(JwtClaimTypes.Subject, account.Id ?? thirdAccount.Id),
             new(JwtClaimTypes.PhoneNumber, account.PhoneNo ?? thirdAccount.PhoneNumber),
@@ -140,14 +189,10 @@ public class TestBase
             new(AppClaimTypes.DepartmentLevel, user.Organization?.Level.ToString() ?? string.Empty),
             new(AppClaimTypes.AreaId, user.OrgId?.GetHigherOrgId() ?? string.Empty),
             new(AppClaimTypes.OpenId, thirdAccount?.OpenId ?? string.Empty),
+            new("AppScope", appScope)
         ];
         ClaimsIdentity identity = new ClaimsIdentity(userClaims);
         var principal = new ClaimsPrincipal(identity);
         _httpContextAccessor.HttpContext.User = principal;
-
-        //TestSessionConstants.UserId = account.Id;
-        //TestSessionConstants.Roles = account.Roles.Select(m => m.Id).ToArray();
-        //TestSessionConstants.UserName = account.UserName;
-        //TestSessionConstants.OrgId = user.OrgId;
     }
 }

+ 2 - 1
src/Hotline.Application/CallCenter/DefaultCallApplication.cs

@@ -437,7 +437,8 @@ public abstract class DefaultCallApplication : ICallApplication
 
         try
         {
-            return _orderVisitDomainService.GetVisitEvaluateByReplyTxt<EVoiceEvaluate>(callNative.ReplyTxt!.Trim());
+            var smsReply = _orderVisitDomainService.GetVisitEvaluateByReplyTxt(callNative.ReplyTxt!.Trim());
+            return smsReply!.VoiceEvaluate!.Value;
         }
         catch (UserFriendlyException)
         {

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

@@ -42,6 +42,10 @@ namespace Hotline.Application.Mappers
                 .Map(s => s.Key, d => d.DicDataValue)
                 .Map(s => s.Value, d => d.DicDataName);
 
+            config.ForType<SystemDicDataOutDto, Kv>()
+                .Map(s => s.Key, d => d.DicDataValue)
+                .Map(s => s.Value, d => d.DicDataName);
+
             config.ForType<Message, ReceiveMessageNotify>()
                 .Map(m => m.NotifyDto, d => d);
 

+ 22 - 0
src/Hotline.Repository.SqlSugar/System/SettingOrderVisitSmsReplyRuleRepository.cs

@@ -0,0 +1,22 @@
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Settings;
+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.System;
+public class SettingOrderVisitSmsReplyRuleRepository : BaseRepository<SettingOrderVisitSmsReplyRule>, ISettingOrderVisitSmsReplyRuleRepository, IScopeDependency
+{
+    public SettingOrderVisitSmsReplyRuleRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder) : base(uow, dataPermissionFilterBuilder)
+    {
+    }
+
+    public List<SettingOrderVisitSmsReplyRule> GetByAppScope(string appscope)
+    {
+        return Queryable().Where(x => x.AppScope == appscope).OrderBy(x => x.SortOrder).ToList();
+    }
+}

+ 43 - 1
src/Hotline.Repository.SqlSugar/System/SystemLogRepository.cs

@@ -1,6 +1,7 @@
 using Hotline.Repository.SqlSugar.DataPermissions;
 using Hotline.Settings;
 using Hotline.Share.Tools;
+using Microsoft.AspNetCore.Mvc;
 using SqlSugar;
 using System;
 using System.Collections.Generic;
@@ -18,7 +19,7 @@ public class SystemLogRepository : BaseRepository<SystemLog>, ISystemLogReposito
     {
     }
 
-    public void Add(string name, string executeParam = "", string remark = "", [CallerMemberName]string executeUrl = "", int status = 0, string ipUrl = "", string executeResult = "")
+    public void Add(string name, string executeParam = "", string remark = "", [CallerMemberName] string executeUrl = "", int status = 0, string ipUrl = "", string executeResult = "")
     {
         try
         {
@@ -43,4 +44,45 @@ public class SystemLogRepository : BaseRepository<SystemLog>, ISystemLogReposito
             // ignore
         }
     }
+
+    public void Add(string name, object executeParam = null, string remark = "", [CallerMemberName] string executeUrl = "", int status = 0, string ipUrl = "", string executeResult = "")
+    {
+        try
+        {
+            var entity = new SystemLog
+            {
+                Name = name,
+                ExecuteParam = executeParam,
+                ExecuteResult = executeResult,
+                ExecuteUrl = executeUrl,
+                Remark = remark,
+                Status = status,
+                IpUrl = ipUrl
+            };
+            if (executeUrl.IsNullOrEmpty())
+            {
+                entity.ExecuteUrl = new StackTrace().GetFrame(1).GetMethod().Name;
+            }
+            AddAsync(entity).GetAwaiter().GetResult();
+        }
+        catch
+        {
+            // ignore
+        }
+    }
+
+    public async Task UpdateResultAsync(string id, ObjectResult result)
+    {
+        try
+        {
+            await Updateable()
+                .SetColumns(m => m.ExecuteResult, result)
+                .Where(m => m.Id == id)
+                .ExecuteCommandAsync();
+        }
+        catch (Exception e)
+        {
+            var msg = e.Message;
+        }
+    }
 }

+ 2 - 0
src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs

@@ -70,5 +70,7 @@ namespace Hotline.Caching.Interfaces
         /// 工单标签
         /// </summary>
         IReadOnlyCollection<SystemDicDataOutDto> OrderTag { get; }
+
+        IReadOnlyCollection<SystemDicDataOutDto> VisitSatisfaction { get; }
     }
 }

+ 5 - 0
src/Hotline/Caching/Services/SysDicDataCacheManager.cs

@@ -132,6 +132,11 @@ namespace Hotline.Caching.Services
         /// </summary>
         public IReadOnlyCollection<SystemDicDataOutDto> OrderTag => GetOrAdd(SysDicTypeConsts.OrderTag);
 
+        /// <summary>
+        /// 回访满意度
+        /// </summary>
+        public IReadOnlyCollection<SystemDicDataOutDto> VisitSatisfaction => GetOrAdd(SysDicTypeConsts.VisitSatisfaction);
+
         public void RemoveSysDicDataCache(string code)
         {
             _cacheSysDicData.Remove(code);

+ 3 - 2
src/Hotline/Orders/IOrderVisitDomainService.cs

@@ -1,4 +1,5 @@
-using Hotline.Share.Dtos.Push;
+using Hotline.Settings;
+using Hotline.Share.Dtos.Push;
 
 namespace Hotline.Orders;
 public interface IOrderVisitDomainService
@@ -9,7 +10,7 @@ public interface IOrderVisitDomainService
     /// <typeparam name="T"></typeparam>
     /// <param name="replyTxt"></param>
     /// <returns></returns>
-    T GetVisitEvaluateByReplyTxt<T>(string replyTxt);
+    SettingOrderVisitSmsReplyRule? GetVisitEvaluateByReplyTxt(string replyTxt);
 
     /// <summary>
     /// 收到用户回复的短信后回填回访信息

+ 0 - 20
src/Hotline/Orders/OrderVisitDetail.cs

@@ -99,26 +99,6 @@ namespace Hotline.Orders
         /// </summary>
         public EVisitTarget VisitTarget { get; set; }
 
-        /// <summary>
-        /// 短信回访回填
-        /// </summary>
-        /// <param name="visitSatisfactionKv"></param>
-        public void ReplyBackfill(Kv visitSatisfactionKv)
-        {
-            this.OrgProcessingResults = visitSatisfactionKv;
-            this.VisitContent = visitSatisfactionKv.Value;
-        }
-
-        /// <summary>
-        /// 短信回访回填
-        /// </summary>
-        /// <param name="visitSatisfactionKv"></param>
-        public void ReplyBackfill(ESeatEvaluate seatEvaluate)
-        {
-            this.SeatEvaluate ??= seatEvaluate;
-            VisitContent = seatEvaluate.GetDescription();
-        }
-
 		/// <summary>
 		/// 截至甄别时间
 		/// </summary>

+ 51 - 56
src/Hotline/Orders/OrderVisitDomainService.cs

@@ -14,6 +14,8 @@ using Hotline.EventBus;
 using Hotline.Orders.Notifications;
 using Hotline.Configurations;
 using Microsoft.Extensions.Options;
+using Hotline.Settings;
+using System.Text.RegularExpressions;
 
 namespace Hotline.Orders;
 public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependency
@@ -26,8 +28,10 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     private readonly ICapPublisher _capPublisher;
     private readonly Publisher _publisher;
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+    private readonly ISettingOrderVisitSmsReplyRuleRepository _settingOrderVisitSmsReplyRuleRepository;
+    private readonly ISystemLogRepository _logRepository;
 
-    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository, ICapPublisher capPublisher, Publisher publisher, IOptionsSnapshot<AppConfiguration> appOptions)
+    public OrderVisitDomainService(IRepository<OrderVisitDetail> orderVisitDetailRepository, ILogger<OrderVisitDomainService> logger, IRepository<Order> orderRepository, ISystemDicDataCacheManager systemDicDataCacheManager, IOrderVisitRepository orderVisitRepository, ICapPublisher capPublisher, Publisher publisher, IOptionsSnapshot<AppConfiguration> appOptions, ISettingOrderVisitSmsReplyRuleRepository settingOrderVisitSmsReplyRuleRepository, ISystemLogRepository logRepository)
     {
         _orderVisitDetailRepository = orderVisitDetailRepository;
         _logger = logger;
@@ -37,6 +41,8 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
         _capPublisher = capPublisher;
         _publisher = publisher;
         _appOptions = appOptions;
+        _settingOrderVisitSmsReplyRuleRepository = settingOrderVisitSmsReplyRuleRepository;
+        _logRepository = logRepository;
     }
 
     /// <summary>
@@ -54,7 +60,7 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .Where(m => m.Order.Contact == data.TelNumber)
             .Where(m => m.VisitState == EVisitState.SMSVisiting)
             .ToListAsync();
-            
+
         foreach (var orderVisit in orderVisits)
         {
             await UpdateSmsReplyAsync(orderVisit, data.SmsReplyContent!.Trim());
@@ -68,45 +74,13 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     /// <param name="replyTxt"></param>
     /// <returns></returns>
     /// <exception cref="UserFriendlyException"></exception>
-    public T GetVisitEvaluateByReplyTxt<T>(string replyTxt)
+    public SettingOrderVisitSmsReplyRule GetVisitEvaluateByReplyTxt(string replyTxt)
     {
-        Dictionary<string, string> ReplyToEnumMap = new()
-        {
-            { "1", $"非常满意|{EVisitState.Visited}|{ESeatEvaluate.VerySatisfied}|{EVoiceEvaluate.VerySatisfied}|5" },
-            { "2", $"满意|{EVisitState.Visited}|{ESeatEvaluate.Satisfied}|{EVoiceEvaluate.Satisfied}|4"},
-            { "3", $"一般|{EVisitState.Visited}|{ESeatEvaluate.Normal}|{EVoiceEvaluate.Normal}|4"},
-            { "4", $"不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.NoSatisfied}|2"},
-            { "5", $"非常不满意|{EVisitState.SMSUnsatisfied}|{ESeatEvaluate.NoSatisfied}|{EVoiceEvaluate.VeryNoSatisfied}|2"},
-            { "0", $"默认满意|{EVisitState.Visited}|{ESeatEvaluate.DefaultSatisfied}|{EVoiceEvaluate.DefaultSatisfied}|0"},
-        };
+        var replyToEnumMap = _settingOrderVisitSmsReplyRuleRepository.GetByAppScope(_appOptions.Value.AppScope);
+        if (replyToEnumMap.IsNullOrEmpty()) throw new UserFriendlyException("系统未配置短信回访规则");
         replyTxt = replyTxt.Trim();
-        if (ReplyToEnumMap.TryGetValue(replyTxt, out var result) == false)
-        {
-            var m = ReplyToEnumMap.FirstOrDefault(item => item.Value.StartsWith(replyTxt));
-            replyTxt = m.Key;
-            result = m.Value;
-        }
-
-        if (result.IsNullOrEmpty()) throw new UserFriendlyException($"用户回复内容异常; replyTxt: {replyTxt}");
-        var replySplit = result.Split("|");
-
-        if (typeof(T) == typeof(string))
-            return (T)(replySplit[0] as object);
-
-        if (typeof(T) == typeof(EVisitState))
-            return (T)Enum.Parse(typeof(T), replySplit[1]);
-
-        if (typeof(T) == typeof(ESeatEvaluate))
-            return (T)Enum.Parse(typeof(T), replySplit[2]);
-
-        if (typeof(T) == typeof(EVoiceEvaluate))
-            return (T)Enum.Parse(typeof(T), replySplit[3]);
-
-        if (typeof(T) == typeof(Kv))
-            return _systemDicDataCacheManager.GetVisitSatisfaction()
-           .First(m => m.DicDataValue == replySplit[4]).Adapt<T>();
-
-        return default;
+        return replyToEnumMap.FirstOrDefault(m => new Regex(m.ReplyRegular).IsMatch(replyTxt)) 
+            ?? throw new UserFriendlyException($"用户回复内容异常; replyTxt: {replyTxt}");
     }
 
     /// <summary>
@@ -124,7 +98,7 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .Then(async orderVisit =>
             {
                 // 默认满意
-                await UpdateSmsReplyAsync(orderVisit, "0");
+                await UpdateSmsReplyAsync(orderVisit, "默认满意");
             });
     }
 
@@ -138,18 +112,24 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
     private async Task UpdateSmsReplyAsync(OrderVisit orderVisit, string replyTxt)
     {
         orderVisit.Order ??= await _orderRepository.GetAsync(orderVisit.OrderId);
-        orderVisit.VisitType = EVisitType.SmsVisit;
-        if (new string[] { "4", "5" , "不满意", "非常不满意" }.Contains(replyTxt))
-        {
-            // “短信不满意待回访”状态下,由其他方式再次进行回访,回访方式需更新为最新的回访方式
-            // 故在此置为空
-            orderVisit.VisitType = null;
-        }
-        var visitSatisfactionKv = GetVisitEvaluateByReplyTxt<Kv>(replyTxt);
-        orderVisit.NowEvaluate = visitSatisfactionKv;
+        //orderVisit.VisitType = EVisitType.SmsVisit;
+        //if (new string[] { "4", "5", "不满意", "非常不满意" }.Contains(replyTxt))
+        //{
+        //    // “短信不满意待回访”状态下,由其他方式再次进行回访,回访方式需更新为最新的回访方式
+        //    // 故在此置为空
+        //    orderVisit.VisitType = null;
+        //}
+        var smsReplyRule = GetVisitEvaluateByReplyTxt(replyTxt);
+        _logRepository.Add("短信回访-回复内容匹配结果", smsReplyRule, orderVisit.No, orderVisit.OrderId ,1, orderVisit.Id, replyTxt);
+        var visitSatisfactionKv = smsReplyRule.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
+        if (smsReplyRule.VisitType != null)
+            orderVisit.VisitType = smsReplyRule.VisitType.Value;
+        if (smsReplyRule.OrgProcessingResults != null)
+            orderVisit.NowEvaluate = visitSatisfactionKv;
 
         orderVisit.VisitTime = DateTime.Now;
-        orderVisit.VisitState = GetVisitEvaluateByReplyTxt<EVisitState>(replyTxt);
+        if (smsReplyRule.VisitState != null)
+            orderVisit.VisitState = smsReplyRule.VisitState.Value;
         await _orderVisitRepository.UpdateAsync(orderVisit, ignoreNullColumns: false);
 
         if (orderVisit.VisitState == EVisitState.Visited)
@@ -167,9 +147,18 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .ToListAsync();
         foreach (var item in detailOrg)
         {
-            item.ReplyBackfill(visitSatisfactionKv);
-            if (_appOptions.Value.IsZiGong)
-                item.OrgHandledAttitude = visitSatisfactionKv;
+            if (smsReplyRule.OrgProcessingResults != null)
+            {
+                var processingResults = smsReplyRule.GetOrgProcessingResults(_systemDicDataCacheManager.VisitSatisfaction);
+                item.OrgProcessingResults = processingResults;
+                item.VisitContent = processingResults?.Value;
+            }
+            if (smsReplyRule.VisitContent != null)
+                item.VisitContent = smsReplyRule.VisitContent;
+            if (smsReplyRule.OrgHandledAttitude != null)
+                item.OrgHandledAttitude = smsReplyRule.GetOrgHandledAttitude(_systemDicDataCacheManager.VisitSatisfaction);
+            if (smsReplyRule.IsReplyToOrgVisitContent != null && smsReplyRule.IsReplyToOrgVisitContent == true)
+                item.VisitContent = replyTxt;
         }
         await _orderVisitDetailRepository.UpdateRangeAsync(detailOrg);
 
@@ -178,12 +167,19 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
             .FirstAsync()
             .Then(async detailSeat =>
             {
-                detailSeat.ReplyBackfill(GetVisitEvaluateByReplyTxt<ESeatEvaluate>(replyTxt));
-                await _orderVisitDetailRepository.UpdateAsync(detailSeat);
+                if (smsReplyRule.SeatEvaluate != null || smsReplyRule.VoiceEvaluate != null)
+                {
+                    if (smsReplyRule.SeatEvaluate != null)
+                        detailSeat.SeatEvaluate ??= smsReplyRule.SeatEvaluate;
+                    if (smsReplyRule.VoiceEvaluate != null)
+                        detailSeat.VoiceEvaluate ??= smsReplyRule.VoiceEvaluate;
+                    await _orderVisitDetailRepository.UpdateAsync(detailSeat);
+                }
             });
 
 
         if (orderVisit.VisitState != EVisitState.Visited) return;
+
         orderVisit.Order.Visited(visitSatisfactionKv.Key, visitSatisfactionKv.Value);
         await _orderRepository.UpdateAsync(orderVisit.Order);
         var orderDto = orderVisit.Order.Adapt<OrderDto>();
@@ -207,6 +203,5 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
                 }, cancellationToken: CancellationToken.None);
 
         }
-
     }
 }

+ 12 - 0
src/Hotline/Settings/ISettingOrderVisitSmsReplyRuleRepository.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Settings;
+public interface ISettingOrderVisitSmsReplyRuleRepository : IRepository<SettingOrderVisitSmsReplyRule>
+{
+    List<SettingOrderVisitSmsReplyRule> GetByAppScope(string appscope);
+}

+ 6 - 1
src/Hotline/Settings/ISystemLogRepository.cs

@@ -1,4 +1,5 @@
-using System;
+using Microsoft.AspNetCore.Mvc;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.CompilerServices;
@@ -19,4 +20,8 @@ public interface ISystemLogRepository : IRepository<SystemLog>
     /// <param name="status">状态(0失败 1成功)</param>
     /// <returns></returns>
     void Add(string name, string executeParam = "", string remark = "", [CallerMemberName]string executeUrl = "", int status = 0, string ipUrl = "", string executeResult = "");
+
+    void Add(string name, object executeParam = null, string remark = "", [CallerMemberName]string executeUrl = "", int status = 0, string ipUrl = "", string executeResult = "");
+
+    Task UpdateResultAsync(string id, ObjectResult result);
 }

+ 107 - 0
src/Hotline/Settings/SettingOrderVisitSmsReplyRule.cs

@@ -0,0 +1,107 @@
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Settings;
+using Hotline.Share.Enums.Order;
+using Mapster;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Settings;
+
+/// <summary>
+/// 短信回访规则设置
+/// </summary>
+[Description("短信回访规则设置")]
+public class SettingOrderVisitSmsReplyRule : FullStateEntity
+{
+    /// <summary>
+    /// 名字
+    /// </summary>
+    [SugarColumn(ColumnDescription = "规则名字")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 回访方式
+    /// </summary>
+    [SugarColumn(IsNullable = true, ColumnDescription = "回访方式(不需要更新就填NULL)")]
+    public EVisitType? VisitType { get; set; }
+
+    /// <summary>
+    /// 回复内容匹配正则
+    /// </summary>
+    [SugarColumn(ColumnDescription = "回复内容匹配正则")]
+    public string ReplyRegular { get; set; }
+
+    /// <summary>
+    /// 当前系统环境:ZiGong,YiBin,LuZhou
+    /// </summary>
+    [SugarColumn(ColumnDescription = "当前系统环境")]
+    public string AppScope { get; set; }
+
+    /// <summary>
+    /// 唯一标识(ReplyRegular + AppScope 的 MD5)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "唯一标识")]
+    public string UniqueKey { get; set; }
+
+    /// <summary>
+    /// 回访状态(不需要更新就填NULL)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "回访状态(不需要更新就填NULL)")]
+    public EVisitState? VisitState { get; set; }
+
+    /// <summary>
+    /// 话务员评价(话务评价)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "话务员评价(不需要更新就填NULL)")]
+    public ESeatEvaluate? SeatEvaluate { get; set; }
+
+    /// <summary>
+    /// 语音评价(话务评价)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "语音评价(不需要更新就填NULL)")]
+    public EVoiceEvaluate? VoiceEvaluate { get; set; }
+
+    /// <summary>
+    /// 部门办件态度
+    /// </summary>
+    [SugarColumn(ColumnDescription = "部门办件态度(不需要更新就填NULL)")]
+    public int? OrgHandledAttitude { get; set; }
+
+    /// <summary>
+    /// 部门办件结果
+    /// </summary>
+    [SugarColumn(ColumnDescription = "部门办件结果(不需要更新就填NULL)")]
+    public int? OrgProcessingResults { get; set; }
+
+    /// <summary>
+    /// 回访内容
+    /// </summary>
+    [SugarColumn(ColumnDescription = "回访内容(不需要更新就填NULL)")]
+    public string? VisitContent { get; set; }
+
+    /// <summary>
+    /// 是否回填用户短信内容到部门回访内容
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否回填用户短信内容到部门回访内容")]
+    public bool? IsReplyToOrgVisitContent { get; set; }
+
+    /// <summary>
+    /// 匹配的顺序,从小到大
+    /// </summary>
+
+    [SugarColumn(ColumnDescription = "匹配的顺序,从小到大")]
+    public int SortOrder { get; set; }
+
+    public Kv? GetOrgHandledAttitude(IReadOnlyCollection<SystemDicDataOutDto> visitSatisfaction)
+    {
+        if (this.OrgHandledAttitude == null) return null;
+        return visitSatisfaction.First(m => m.DicDataValue == OrgHandledAttitude.ToString()).Adapt<Kv>();
+    }
+
+    public Kv? GetOrgProcessingResults(IReadOnlyCollection<SystemDicDataOutDto> visitSatisfaction)
+    {
+        if (OrgProcessingResults == null) return null;
+        return visitSatisfaction.First(m => m.DicDataValue == OrgProcessingResults.ToString()).Adapt<Kv>();
+    }
+}

+ 1 - 0
src/Hotline/Snapshot/OrderSnapshot.cs

@@ -50,6 +50,7 @@ public class OrderSnapshot : CreationSoftDeleteEntity
     /// <summary>
     /// 作业类型
     /// </summary>
+    [SugarColumn(ColumnDescription ="作业类型")]
     public string? JobTypeName { get; set; }
 
     /// <summary>