Bläddra i källkod

Merge branch 'test' of http://110.188.24.182:10023/Fengwo/hotline into test

田爽 3 månader sedan
förälder
incheckning
574283b3d3
48 ändrade filer med 894 tillägg och 531 borttagningar
  1. 1 1
      src/Hotline.Api/Controllers/ExportData/ExportDataController.cs
  2. 2 0
      src/Hotline.Api/Controllers/KnowledgeController.cs
  3. 17 3
      src/Hotline.Api/Controllers/OrderController.cs
  4. 0 193
      src/Hotline.Api/Controllers/TestController.cs
  5. 2 2
      src/Hotline.Api/Controllers/WebPortalController.cs
  6. 97 0
      src/Hotline.Api/Filter/LogFilterAlphaAttribute.cs
  7. 27 27
      src/Hotline.Api/Middleware/HeaderMiddleware.cs
  8. 1 10
      src/Hotline.Api/StartupExtensions.cs
  9. 2 2
      src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs
  10. 1 1
      src/Hotline.Application.Tests/Application/KnowApplicationTest.cs
  11. 109 109
      src/Hotline.Application.Tests/Application/SnapshotApplicationMockTest.cs
  12. 17 5
      src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs
  13. 1 1
      src/Hotline.Application.Tests/Controller/KnowledgeControllerTest.cs
  14. 8 5
      src/Hotline.Application.Tests/Controller/OrderControllerTest.cs
  15. 14 1
      src/Hotline.Application.Tests/Controller/SnapshotControllerTest.cs
  16. 2 2
      src/Hotline.Application.Tests/Domain/LuZhouExpireTimeTest.cs
  17. 148 49
      src/Hotline.Application.Tests/Domain/OrderVisitDomainServiceTest.cs
  18. 5 5
      src/Hotline.Application.Tests/Domain/YiBinExpireTimeTest.cs
  19. 9 9
      src/Hotline.Application.Tests/Domain/ZiGongExpireTimeTest.cs
  20. 1 1
      src/Hotline.Application.Tests/Hotline.Application.Tests.csproj
  21. 1 0
      src/Hotline.Application.Tests/Mock/Interfaces/IOrderServiceStartWorkflow.cs
  22. 41 0
      src/Hotline.Application.Tests/Mock/OptionsSnapshotMock.cs
  23. 32 0
      src/Hotline.Application.Tests/Mock/OrderServiceStartWorkflow.cs
  24. 3 1
      src/Hotline.Application.Tests/Startup.cs
  25. 51 6
      src/Hotline.Application.Tests/TestBase.cs
  26. 2 1
      src/Hotline.Application/CallCenter/DefaultCallApplication.cs
  27. 1 1
      src/Hotline.Application/Caselibrary/CaseApplication.cs
  28. 2 2
      src/Hotline.Application/Jobs/XingTangTelOperationSyncJob.cs
  29. 4 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  30. 1 1
      src/Hotline.Application/Planlibrary/PlanApplication.cs
  31. 7 3
      src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs
  32. 22 0
      src/Hotline.Repository.SqlSugar/System/SettingOrderVisitSmsReplyRuleRepository.cs
  33. 55 1
      src/Hotline.Repository.SqlSugar/System/SystemLogRepository.cs
  34. 5 0
      src/Hotline.Share/Dtos/Bi/BiOrderDto.cs
  35. 4 3
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  36. 1 1
      src/Hotline.Share/Dtos/WebPortal/WebFlowAcceptDto.cs
  37. 1 1
      src/Hotline.Share/Hotline.Share.csproj
  38. 6 1
      src/Hotline.Share/Requests/PagedKeywordRequest.cs
  39. 2 0
      src/Hotline/Caching/Interfaces/ISysDicDataCacheManager.cs
  40. 5 0
      src/Hotline/Caching/Services/SysDicDataCacheManager.cs
  41. 3 2
      src/Hotline/Orders/IOrderVisitDomainService.cs
  42. 2 4
      src/Hotline/Orders/OrderDomainService.cs
  43. 0 20
      src/Hotline/Orders/OrderVisitDetail.cs
  44. 51 56
      src/Hotline/Orders/OrderVisitDomainService.cs
  45. 12 0
      src/Hotline/Settings/ISettingOrderVisitSmsReplyRuleRepository.cs
  46. 8 1
      src/Hotline/Settings/ISystemLogRepository.cs
  47. 107 0
      src/Hotline/Settings/SettingOrderVisitSmsReplyRule.cs
  48. 1 0
      src/Hotline/Snapshot/OrderSnapshot.cs

+ 1 - 1
src/Hotline.Api/Controllers/ExportData/ExportDataController.cs

@@ -40,7 +40,7 @@ public class ExportDataController : BaseController
     /// 动态导出数据
     /// </summary>
     /// <returns></returns>
-    [HttpPost]
+    [HttpPost, HttpGet]
     public async Task<FileStreamResult> HandleExportPost()
     {
         var fullPath = HttpContext.Request.Path.Value;

+ 2 - 0
src/Hotline.Api/Controllers/KnowledgeController.cs

@@ -1953,6 +1953,8 @@ namespace Hotline.Api.Controllers
 
             var approves = await _knowledgeApproRepository.Queryable()
                 .Includes(d => d.Knowledge, x => x.KnowledgeTypes)
+                .Includes(d => d.Knowledge, x => x.User)
+                .Includes(d => d.Knowledge, x => x.SourceOrganize)
                 .Where(d => request.ApproveIds.Contains(d.Id) && d.KnowledgeApproveStatus == EKnowledgeApproveStatus.Unhandle)
                 .ToListAsync(HttpContext.RequestAborted);
 

+ 17 - 3
src/Hotline.Api/Controllers/OrderController.cs

@@ -152,6 +152,7 @@ public class OrderController : BaseController
     private readonly IOrderSnapshotRepository _orderSnapshotRepository;
     private readonly IOrderSnapshotApplication _orderSnapshotApplication;
     private readonly IIndustryRepository _industryRepository;
+    private readonly IRepository<SystemDicData> _sysDicDataRepository;
 
     public OrderController(
         IOrderDomainService orderDomainService,
@@ -222,7 +223,8 @@ public class OrderController : BaseController
         ISystemLogApplication systemLogApplication,
         IOrderSnapshotRepository orderSnapshotRepository,
         IIndustryRepository industryRepository,
-        IOrderSnapshotApplication orderSnapshotApplication)
+        IOrderSnapshotApplication orderSnapshotApplication,
+         IRepository<SystemDicData> sysDicDataRepository)
     {
         _orderDomainService = orderDomainService;
         _orderRepository = orderRepository;
@@ -293,6 +295,7 @@ public class OrderController : BaseController
         _orderSnapshotRepository = orderSnapshotRepository;
         _industryRepository = industryRepository;
         _orderSnapshotApplication = orderSnapshotApplication;
+        _sysDicDataRepository = sysDicDataRepository;
     }
 
     #endregion
@@ -1307,7 +1310,7 @@ public class OrderController : BaseController
     /// </summary>
     /// <returns></returns>
     [HttpPost("visit")]
-    [LogFilter("工单回访")]
+    [LogFilterAlpha("工单回访")]
     public async Task Visit([FromBody] VisitDto dto)
     {
         // 发送延迟关联通话记录消息
@@ -3695,7 +3698,7 @@ public class OrderController : BaseController
     /// <param name="dto"></param>
     /// <returns></returns>
     [HttpPost]
-    [LogFilter("新增工单")]
+    [LogFilterAlpha("新增工单")]
     public async Task<object> Add([FromBody] AddOrderDto dto)
     {
         dto.InitAddress();
@@ -3747,6 +3750,11 @@ public class OrderController : BaseController
                 orderTags.Add(new SystemDicData() { Id = item });
             }
             order.OrderTags = orderTags;
+            var dicData = await _sysDicDataRepository.Queryable().Where(p => dto.Tags.Contains(p.Id)).Select(p => p.DicDataValue).ToListAsync();
+            if (dicData != null && dicData.Any())
+            {
+                order.OrderTagCode = String.Join(", ", dicData);
+            }
         }
 
         await _orderDomainService.AddAsync(order, true, HttpContext.RequestAborted);
@@ -3993,7 +4001,13 @@ public class OrderController : BaseController
                 orderTags.Add(new SystemDicData() { Id = item });
             }
             order.OrderTags = orderTags;
+            var dicData = await _sysDicDataRepository.Queryable().Where(p => dto.Tags.Contains(p.Id)).Select(p => p.DicDataValue).ToListAsync();
+            if (dicData != null && dicData.Any())
+            {
+                order.OrderTagCode = String.Join(", ", dicData);
+            }
         }
+        else order.OrderTagCode = string.Empty;
 
         //处理工单的期满时间
         //首先是工单编辑页面提交的、流程已经开启、工单未归档的工单才能修改期满时间

+ 0 - 193
src/Hotline.Api/Controllers/TestController.cs

@@ -1102,199 +1102,6 @@ ICallApplication callApplication,
             "RGDH9951150024082950312",
 "RGDH9951150024080750198",
 "RGDH9951150024083050009",
-"RGDH9951150024083150167",
-"RGDH9951150024083150102",
-"RGDH9951150024083050052",
-"RGDH9951150024080750184",
-"RGDH9951150024083050108",
-"RGDH9951150024083050314",
-"RGDH9951150024083050363",
-"RGDH9951150024083150243",
-"RGDH9951150024082850303",
-"QT9951150024082150023",
-"RGDH9951150024080750185",
-"RGDH9951150024083150065",
-"RGDH9951150024082850032",
-"RGDH9951150024082950295",
-"RGDH9951150024080750189",
-"RGDH9951150024082650189",
-"RGDH9951150024083050008",
-"RGDH9951150024082950036",
-"RGDH9951150024082850341",
-"RGDH9951150024080750215",
-"RGDH9951150024081450005",
-"RGDH9951150024080750212",
-"RGDH9951150024083050137",
-"RGDH9951150024082450124",
-"RGDH9951150024083150188",
-"RGDH9951150024083150083",
-"RGDH9951150024082850082",
-"RGDH9951150024080750174",
-"RGDH9951150024082950371",
-"RGDH9951150024082950114",
-"RGDH9951150024083050339",
-"RGDH9951150024080750217",
-"RGDH9951150024082950040",
-"RGDH9951150024083050031",
-"RGDH9951150024083150267",
-"RGDH9951150024083050064",
-"RGDH9951150024072650086",
-"RGDH9951150024082950360",
-"RGDH9951150024080750195",
-"RGDH9951150024080750210",
-"RGDH9951150024082950250",
-"RGDH9951150024083050145",
-"RGDH9951150024083050356",
-"RGDH9951150024080750194",
-"RGDH9951150024080750173",
-"RGDH9951150024080750213",
-"RGDH9951150024080750218",
-"RGDH9951150024083150271",
-"RGDH9951150024083150134",
-"RGDH9951150024080750179",
-"RGDH9951150024083050140",
-"RGDH9951150024083150262",
-"RGDH9951150024083050119",
-"RGDH9951150024083150177",
-"RGDH9951150024083050146",
-"RGDH9951150024080750223",
-"RGDH9951150024080750221",
-"RGDH9951150024083150130",
-"RGDH9951150024080750209",
-"RGDH9951150024083150237",
-"RGDH9951150024082850095",
-"RGDH9951150024082850223",
-"RGDH9951150024083050077",
-"RGDH9951150024082950118",
-"RGDH9951150024082850056",
-"RGDH9951150024082550177",
-"RGDH9951150024083050358",
-"RGDH9951150024080750193",
-"RGDH9951150024082150011",
-"RGDH9951150024082950167",
-"RGDH9951150024083050201",
-"RGDH9951150024082850048",
-"RGDH9951150024083150161",
-"RGDH9951150024083050300",
-"RGDH9951150024083150016",
-"RGDH9951150024083150101",
-"RGDH9951150024082850207",
-"RGDH9951150024083150144",
-"RGDH9951150024082750300",
-"RGDH9951150024083050230",
-"RGDH9951150024080750211",
-"RGDH9951150024082750379",
-"RGDH9951150024082850252",
-"RGDH9951150024083050204",
-"RGDH9951150024080750176",
-"RGDH9951150024080750190",
-"RGDH9951150024083150027",
-"RGDH9951150024082850308",
-"RGDH9951150024082850070",
-"RGDH9951150024082950068",
-"RGDH9951150024083050002",
-"RGDH9951150024083050130",
-"RGDH9951150024083050060",
-"RGDH9951150024082950282",
-"RGDH9951150024082950320",
-"RGDH9951150024082850312",
-"RGDH9951150024083150040",
-"RGDH9951150024080750202",
-"RGDH9951150024083150216",
-"RGDH9951150024082750156",
-"RGDH9951150024082850224",
-"RGDH9951150024083150238",
-"RGDH9951150024083050327",
-"RGDH9951150024082950090",
-"RGDH9951150024083150131",
-"RGDH9951150024080750187",
-"RGDH9951150024082350072",
-"RGDH9951150024080750177",
-"RGDH9951150024082950010",
-"RGDH9951150024082850263",
-"RGDH9951150024082850087",
-"RGDH9951150024083050234",
-"RGDH9951150024083150098",
-"RGDH9951150024083050166",
-"RGDH9951150024082850339",
-"RGDH9951150024083150157",
-"RGDH9951150024080150214",
-"RGDH9951150024083150149",
-"RGDH9951150024083150264",
-"RGDH9951150024080750200",
-"RGDH9951150024083150116",
-"RGDH9951150024082950188",
-"RGDH9951150024083050148",
-"RGDH9951150024082750338",
-"RGDH9951150024082950268",
-"RGDH9951150024083050337",
-"RGDH9951150024082250263",
-"RGDH9951150024080750196",
-"RGDH9951150024080750192",
-"RGDH9951150024082850272",
-"RGDH9951150024082650290",
-"RGDH9951150024083150117",
-"RGDH9951150024080750207",
-"RGDH9951150024080750201",
-"RGDH9951150024080750188",
-"RGDH9951150024083050011",
-"RGDH9951150024083050067",
-"RGDH9951150024083050193",
-"RGDH9951150024082850242",
-"RGDH9951150024082950121",
-"RGDH9951150024080750222",
-"RGDH9951150024082950280",
-"RGDH9951150024080750205",
-"RGDH9951150024083050334",
-"RGDH9951150024083150235",
-"RGDH9951150024082950156",
-"RGDH9951150024080750220",
-"RGDH9951150024082750209",
-"RGDH9951150024083150046",
-"RGDH9951150024080750214",
-"RGDH9951150024082850116",
-"RGDH9951150024083050082",
-"RGDH9951150024083150118",
-"RGDH9951150024082950338",
-"RGDH9951150024082950359",
-"RGDH9951150024083150156",
-"RGDH9951150024083150072",
-"RGDH9951150024082150001",
-"RGDH9951150024082850186",
-"RGDH9951150024082750377",
-"RGDH9951150024083150166",
-"RGDH9951150024083050255",
-"RGDH9951150024083050190",
-"RGDH9951150024080750175",
-"RGDH9951150024083050184",
-"RGDH9951150024083050021",
-"RGDH9951150024082850317",
-"RGDH9951150024082950146",
-"RGDH9951150024083150172",
-"RGDH9951150024083050354",
-"RGDH9951150024082850305",
-"RGDH9951150024082850351",
-"RGDH9951150024082950340",
-"RGDH9951150024083150273",
-"RGDH9951150024080750208",
-"RGDH9951150024083150053",
-"RGDH9951150024082850291",
-"RGDH9951150024082950104",
-"RGDH9951150024082850247",
-"RGDH9951150024082850320",
-"RGDH9951150024083050285",
-"RGDH9951150024082850011",
-"RGDH9951150024083050174",
-"RGDH9951150024083150212",
-"RGDH9951150024083050323",
-"RGDH9951150024080750224",
-"RGDH9951150024083150244",
-"RGDH9951150024080750199",
-"RGDH9951150024082850269",
-"RGDH9951150024082850053",
-"RGDH9951150024082950342",
-"RGDH9951150024082950046",
-"RGDH9951150024082950194"
         };
         _logger.LogWarning($"推送数据共:{provinceNos.Count}");
 

+ 2 - 2
src/Hotline.Api/Controllers/WebPortalController.cs

@@ -1055,8 +1055,8 @@ namespace Hotline.Api.Controllers
                     data.Source = ESource.APP;
                     break;
                 case "3":
-                    data.SourceChannel = "微信";
-                    data.SourceChannelCode = "WX";
+                    data.SourceChannel = "微信小程序";
+                    data.SourceChannelCode = "XCX";
                     data.Source = ESource.WeChat;
                     break;
                 case "9"://宜宾人社专用

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

@@ -0,0 +1,97 @@
+using Hotline.Settings;
+using Hotline.Share.Tools;
+using Hotline.Tools;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Newtonsoft.Json;
+using System.Text;
+
+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 resultContext = await next();
+
+            if (resultContext.Exception != null)
+            {
+                StringBuilder sbException = new StringBuilder();
+                Exception exception = resultContext.Exception;
+                sbException.AppendLine(exception.Message);
+                if (exception.InnerException != null)
+                    sbException.AppendLine(exception.InnerException.Message);
+
+                sbException.AppendLine(LogTool.GetSubString(resultContext.Exception.StackTrace, 4000));
+                systemLog.ExecuteResult = sbException.ToString();
+                systemLog.Status = 0;
+            }
+            else
+            {
+                systemLog.Status = 1;
+                ObjectResult result = context.Result as ObjectResult;
+                if (result != null)
+                {
+                    systemLog.ExecuteResult = JsonConvert.SerializeObject(result.Value);
+                }
+            }
+
+            await repository.AddAsyncNoException(systemLog);
+        }
+    }
+
+    private void GetMethod(ActionExecutingContext context, SystemLog systemLog)
+    {
+        var request = context.HttpContext.Request;
+        try
+        {
+            if (request != null)
+            {
+                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 && context.ActionArguments.Values.FirstOrDefault() != null)
+                    {
+                        systemLog.ExecuteParam = context.ActionArguments.Values.FirstOrDefault();
+                        systemLog.ExecuteParam = systemLog.ExecuteParam.ToJson().FromJson<dynamic>();
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            _logger.LogError(e, "GetMethod 异常");
+        }
+    }
+}

+ 27 - 27
src/Hotline.Api/Middleware/HeaderMiddleware.cs

@@ -1,34 +1,34 @@
-using Microsoft.AspNetCore.Mvc;
+//using Microsoft.AspNetCore.Mvc;
 
-namespace Hotline.Api.Middleware;
+//namespace Hotline.Api.Middleware;
 
-/// <summary>
-/// 为特定的返回结果添加 头部信息 的中间件
-/// </summary>
-public class HeaderMiddleware
-{
-    private readonly RequestDelegate _next;
+///// <summary>
+///// 为特定的返回结果添加 头部信息 的中间件
+///// </summary>
+//public class HeaderMiddleware
+//{
+//    private readonly RequestDelegate _next;
 
-    public HeaderMiddleware(RequestDelegate next)
-    {
-        _next = next;
-    }
+//    public HeaderMiddleware(RequestDelegate next)
+//    {
+//        _next = next;
+//    }
 
-    public async Task InvokeAsync(HttpContext context)
-    {
-        var originalBodyStream = context.Response.Body;
-        using var responseBody = new MemoryStream();
-        context.Response.Body = responseBody;
+//    public async Task InvokeAsync(HttpContext context)
+//    {
+//        var originalBodyStream = context.Response.Body;
+//        using var responseBody = new MemoryStream();
+//        context.Response.Body = responseBody;
 
-        await _next(context);
+//        await _next(context);
 
-        // 为返回类型是 FileStreamResult 的请求添加 头部信息
-        if (context.Response.StatusCode == 200 && context.Response is FileStreamResult)
-        {
-            context.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
-        }
+//        // 为返回类型是 FileStreamResult 的请求添加 头部信息
+//        if (context.Response.StatusCode == 200 && context.Response is FileStreamResult)
+//        {
+//            context.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
+//        }
 
-        context.Response.Body.Seek(0, SeekOrigin.Begin);
-        await responseBody.CopyToAsync(originalBodyStream);
-    }
-}
+//        context.Response.Body.Seek(0, SeekOrigin.Begin);
+//        await responseBody.CopyToAsync(originalBodyStream);
+//    }
+//}

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

@@ -1,5 +1,4 @@
-using System.IO.Compression;
-using System.Reflection;
+using System.Reflection;
 using FluentValidation;
 using FluentValidation.AspNetCore;
 using Hotline.Api.Realtimes;
@@ -26,26 +25,18 @@ using Hotline.Application.CallCenter.Calls;
 using Hotline.Application.CallCenter;
 using Hotline.Authentications;
 using Hotline.CallCenter.Calls;
-using Swashbuckle.AspNetCore.SwaggerUI;
 using Hotline.Configurations;
 using Hotline.DI;
-using Hotline.Share.Tools;
 using Hotline.Settings.TimeLimitDomain.ExpireTimeSupplier;
-using Hotline.Api.Middleware;
 using XF.Domain.Authentications;
 using Hotline.XingTang;
-using Hotline.Logger;
 using HotPot.Mvc.Filters;
-using Microsoft.AspNetCore.ResponseCompression;
 using Hotline.WeChat;
 using Hotline.Snapshot.Interfaces;
 using TianQue.Sdk;
-using Hotline.Application.Snapshot;
 using Hotline.Orders;
 using XF.Domain.Repository.Events;
 using Hotline.Orders.DatabaseEventHandler;
-using Hotline.Snapshot;
-using Hotline.WeChat;
 using Hotline.Ai.XingTang;
 using Hotline.Pdf;
 

+ 2 - 2
src/Hotline.Application.Contracts/Validators/Order/AddOrderDtoValidator.cs

@@ -102,9 +102,9 @@ public class AddOrderDtoValidator : AbstractValidator<AddOrderDto>
         #endregion
 
         #region 投诉对象信息
-        RuleFor(d => d.OrderExtension.EnterpriseName).MaxLengthWithChineseChar(80).When(d => d.OrderExtension != null).WithMessage("企业名称最多80字符");
+        RuleFor(d => d.OrderExtension.EnterpriseName).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("企业名称最多50字符");
         RuleFor(d => d.OrderExtension.UnifiedSocialCreditCode).MaxLengthWithChineseChar(30).When(d => d.OrderExtension != null).WithMessage("统一社会信用代码最多30字符");
-        RuleFor(d => d.OrderExtension.RegisterAddress).MaxLengthWithChineseChar(400).When(d => d.OrderExtension != null).WithMessage("企业注册地址最多400字符");
+        RuleFor(d => d.OrderExtension.RegisterAddress).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("企业注册地址最多50字符");
         RuleFor(d => d.OrderExtension.RegisterNumber).MaxLengthWithChineseChar(50).When(d => d.OrderExtension != null).WithMessage("注册号最多50字符");
         RuleFor(d => d.OrderExtension.EnterpriseContact).MaxLengthWithChineseChar(70).When(d => d.OrderExtension != null).WithMessage("联系人最多70字符");
         RuleFor(d => d.OrderExtension.MarketTypeCode).MaxLengthWithChineseChar(64).When(d => d.OrderExtension != null).WithMessage("市场主体类型代码最多64字符");

+ 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)
         {

+ 1 - 1
src/Hotline.Application/Caselibrary/CaseApplication.cs

@@ -173,7 +173,7 @@ namespace Hotline.Application.Caselibrary
                 throw UserFriendlyException.SameMessage("存在子级分类!");
 
             //查询是否有案例分类
-            var checkKnowledge = await _caseListRepository.CountAsync(p => p.CaseTypes.Any(t => t.Id == Id), cancellationToken);
+            var checkKnowledge = await _caseListRepository.CountAsync(p => p.CaseTypes.Any(t => t.Id == Id && t.IsDeleted == false), cancellationToken);
             if (checkKnowledge > 0)
                 throw UserFriendlyException.SameMessage("分类存在案例!");
 

+ 2 - 2
src/Hotline.Application/Jobs/XingTangTelOperationSyncJob.cs

@@ -90,7 +90,7 @@ namespace Hotline.Application.Jobs
 
                     }
                     await _telOperationRepository.AddRangeAsync(operations, context.CancellationToken);
-                    _logger.LogInformation($"旧方法同步分机操作记录成功,数量:{operations.Count}");
+                    //_logger.LogInformation($"旧方法同步分机操作记录成功,数量:{operations.Count}");
                 }
                 else
                 {
@@ -108,7 +108,7 @@ namespace Hotline.Application.Jobs
                         operations.Add(operation);
                     }
                     await _telOperationRepository.AddRangeAsync(operations, context.CancellationToken);
-                    _logger.LogInformation($"新方法同步分机操作记录成功,数量:{operations.Count}");
+                    //_logger.LogInformation($"新方法同步分机操作记录成功,数量:{operations.Count}");
                 }
             }
             catch (Exception e)

+ 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);
 

+ 1 - 1
src/Hotline.Application/Planlibrary/PlanApplication.cs

@@ -164,7 +164,7 @@ namespace Hotline.Application.Planlibrary
                 throw UserFriendlyException.SameMessage("存在子级分类!");
 
             //查询是否有预案分类
-            var checkKnowledge = await _planListRepository.CountAsync(p => p.PlanTypes.Any(t => t.Id == Id), cancellationToken);
+            var checkKnowledge = await _planListRepository.CountAsync(p => p.PlanTypes.Any(t => t.Id == Id && t.IsDeleted == false), cancellationToken);
             if (checkKnowledge > 0)
                 throw UserFriendlyException.SameMessage("分类存在预案!");
 

+ 7 - 3
src/Hotline.Repository.SqlSugar/Orders/OrderRepository.cs

@@ -74,7 +74,7 @@ namespace Hotline.Repository.SqlSugar.Orders
             //弥补AddNav方法没有自动指派到创建人
             order.AssignToCreator(_dataPermissionFilterBuilder.DataPermissionManager);
 
-            await AddNav(order).Include(d => d.OrderExtension).Include(d=>d.OrderTags).ExecuteCommandAsync();
+            await AddNav(order).Include(d => d.OrderExtension).Include(d => d.OrderTags).ExecuteCommandAsync();
 
             return order.Id;
         }
@@ -1802,7 +1802,7 @@ namespace Hotline.Repository.SqlSugar.Orders
                 //      x => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(x.OrgProcessingResults, "Key")))
                 //.WhereIF(dto.OrgHandledAttitude != null && dto.OrgHandledAttitude.Any(),
                 //     x => dto.OrgHandledAttitude.Contains(SqlFunc.JsonField(x.OrgHandledAttitude, "Key")))
-                .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults),dto.AttitudeType == EAttitudeType.ProcessingResult ?x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults  :x => SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == dto.OrgProcessingResults)
+                .WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), dto.AttitudeType == EAttitudeType.ProcessingResult ? x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults : x => SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == dto.OrgProcessingResults)
                 .WhereIF(!string.IsNullOrEmpty(dto.VisitUser), x => x.OrderVisit.Employee.Name.Contains(dto.VisitUser))
                 .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.OrderVisit.Order.No == dto.No)
                 .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.OrderVisit.Order.Title.Contains(dto.Title))
@@ -1819,6 +1819,8 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, x => x.OrderVisit.Order.Source == ESource.ProvinceStraight)
                 .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, x => x.OrderVisit.Order.Source != ESource.ProvinceStraight)
                 .WhereIF(!string.IsNullOrEmpty(dto.ContentRetrieval), x => x.VisitContent.Contains(dto.ContentRetrieval!))
+                .WhereIF(!string.IsNullOrEmpty(dto.LevelOneOrg), x => x.OrderVisit.Order.OrgLevelOneName.Contains(dto.LevelOneOrg))//一级部门名称
+                .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), x => x.OrderVisit.Order.ActualHandleOrgName.Contains(dto.ActualHandleOrgName))// 接办部门
                 .Select(x => new OrgVisitDetailListResp()
                 {
                     Id = x.Id,
@@ -1886,6 +1888,7 @@ namespace Hotline.Repository.SqlSugar.Orders
                 .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == true, x => x.OrderVisit.Order.Source == ESource.ProvinceStraight)
                 .WhereIF(dto.IsProvinceOrder.HasValue && dto.IsProvinceOrder == false, x => x.OrderVisit.Order.Source != ESource.ProvinceStraight)
                 .WhereIF(!string.IsNullOrEmpty(dto.ContentRetrieval), x => x.VisitContent.Contains(dto.ContentRetrieval!))
+                .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), x => x.OrderVisit.Order.ActualHandleOrgName.Contains(dto.ActualHandleOrgName!))
                 .Select(x => new OrgVisitDetailListResp
                 {
                     Id = x.Id,
@@ -1908,7 +1911,8 @@ namespace Hotline.Repository.SqlSugar.Orders
                     FileOpinion = x.OrderVisit.Order.FileOpinion,
                     FiledTime = x.OrderVisit.Order.FiledTime,
                     VisitOrgName = x.VisitOrgName,
-                    IsProvinceOrder = x.OrderVisit.Order.Source == ESource.ProvinceStraight ? true : false
+                    IsProvinceOrder = x.OrderVisit.Order.Source == ESource.ProvinceStraight ? true : false,
+                    ActualHandleOrgName = x.OrderVisit.Order.ActualHandleOrgName
                 }).MergeTable()
                 .OrderByIF(string.IsNullOrEmpty(dto.SortField), x => x.VisitTime, OrderByType.Desc)
                 .OrderByIF(dto is { SortField: "creationTime", SortRule: 0 }, x => x.CreationTime, OrderByType.Asc) //受理时间升序

+ 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();
+    }
+}

+ 55 - 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,57 @@ 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 AddAsyncNoException(SystemLog entity)
+    {
+        try
+        {
+            await AddAsync(entity);
+        }
+        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;
+        }
+    }
 }

+ 5 - 0
src/Hotline.Share/Dtos/Bi/BiOrderDto.cs

@@ -71,6 +71,11 @@ namespace Hotline.Share.Dtos.Bi
         /// 内容检索(回访内容)
         /// </summary>
         public string? ContentRetrieval { get; set; }
+
+        /// <summary>
+        /// 接办部门
+        /// </summary>
+        public string? ActualHandleOrgName { get; set; }
     }
 
     public record HighFrequencyCallStatisticsRequest : PagedRequest

+ 4 - 3
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -92,11 +92,12 @@ namespace Hotline.Share.Dtos.Order
         /// <summary>
         /// 工单标签(自贡)
         /// </summary>
-        public string? OrderTag  => OrderTags != null && OrderTags.Any()? string.Join(",", OrderTags.Select(x => x.DicDataName)) : string.Empty; 
+        public string? OrderTag => OrderTags != null && OrderTags.Any() ? string.Join(",", OrderTags.Select(x => x.DicDataName)) : string.Empty;
 
-        public string? OrderTagCode => OrderTags != null && OrderTags.Any() ? string.Join(",", OrderTags.Select(x => x.DicDataValue)) : string.Empty; 
+        // public string? OrderTagCode => OrderTags != null && OrderTags.Any() ? string.Join(",", OrderTags.Select(x => x.DicDataValue)) : string.Empty; 
+        public string? OrderTagCode { get; set; }
 
-		public List<SystemDicDataOutDto>? OrderTags { get; set; }
+        public List<SystemDicDataOutDto>? OrderTags { get; set; }
 
         #region 流程信息
 

+ 1 - 1
src/Hotline.Share/Dtos/WebPortal/WebFlowAcceptDto.cs

@@ -81,7 +81,7 @@ namespace Hotline.Share.Dtos.WebPortal
         public string Content { get; set; }
 
         /// <summary>
-        /// 来源方式 APP来源为2,微信来源为3,宜宾人社来源9,
+        /// 来源方式 APP来源为2,微信小程序来源为3,宜宾人社来源9,
         /// </summary>
         public string? FromID { get; set; }
 

+ 1 - 1
src/Hotline.Share/Hotline.Share.csproj

@@ -7,7 +7,7 @@
     <GenerateDocumentationFile>True</GenerateDocumentationFile>
     <NoWarn>$(NoWarn);1591;8618;</NoWarn>
     <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
-    <Version>1.0.117</Version>
+    <Version>1.0.118</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 6 - 1
src/Hotline.Share/Requests/PagedKeywordRequest.cs

@@ -556,6 +556,11 @@ public record OrgVisitDetailListReq : PagedKeywordRequest
     /// </summary>
     public string? LevelOneOrg { get; set; }
 
+    /// <summary>
+    /// 接办部门
+    /// </summary>
+    public string? ActualHandleOrgName { get; set; }
+
     /// <summary>
     /// 部门分类
     /// </summary>
@@ -564,7 +569,7 @@ public record OrgVisitDetailListReq : PagedKeywordRequest
     /// <summary>
     /// 部门办件结果
     /// </summary>
-    public string? OrgProcessingResults { get; set; } 
+    public string? OrgProcessingResults { get; set; }
 
     /// <summary>
     /// 回访人

+ 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>
     /// 收到用户回复的短信后回填回访信息

+ 2 - 4
src/Hotline/Orders/OrderDomainService.cs

@@ -760,8 +760,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
         return valid;
     }
     #endregion
-
-
+    
     #region 即将超期和超期短信
 
     /// <summary>
@@ -817,8 +816,7 @@ public class OrderDomainService : IOrderDomainService, IScopeDependency
     }
 
     #endregion
-
-
+    
     #region private
 
     private async Task<Order> GetOrderByFlowIdAsync(string workflowId, CancellationToken cancellationToken)

+ 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);
+}

+ 8 - 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,10 @@ 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);
+
+    Task AddAsyncNoException(SystemLog entity);
 }

+ 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>