Procházet zdrojové kódy

merge confict d -> r

xf před 11 měsíci
rodič
revize
29998c01da
37 změnil soubory, kde provedl 2969 přidání a 527 odebrání
  1. 2 2
      src/Hotline.Ai.Jths/AiJthsStartupExtensions.cs
  2. 45 1
      src/Hotline.Ai.Jths/AiVisitConfig.cs
  3. 13 3
      src/Hotline.Ai.Jths/AiVisitService.cs
  4. 103 28
      src/Hotline.Api/Controllers/AiController.cs
  5. 245 245
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  6. 36 19
      src/Hotline.Api/Controllers/OrderController.cs
  7. 919 0
      src/Hotline.Api/Controllers/WebPortalController.cs
  8. 11 1
      src/Hotline.Api/StartupExtensions.cs
  9. 16 11
      src/Hotline.Api/config/appsettings.Development.json
  10. 2 2
      src/Hotline.Api/config/appsettings.json
  11. 12 6
      src/Hotline.Application/FlowEngine/IWorkflowApplication.cs
  12. 58 49
      src/Hotline.Application/FlowEngine/WorkflowApplication.cs
  13. 2 2
      src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs
  14. 104 0
      src/Hotline.Application/Mappers/WebPortalMapperConfigs.cs
  15. 9 0
      src/Hotline.Application/Orders/IOrderApplication.cs
  16. 81 5
      src/Hotline.Application/Orders/OrderApplication.cs
  17. 10 8
      src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs
  18. 39 121
      src/Hotline.Application/Subscribers/DatasharingSubscriber.cs
  19. 7 0
      src/Hotline.Share/Dtos/Ai/AiDto.cs
  20. 14 1
      src/Hotline.Share/Dtos/Order/OrderVisitDto.cs
  21. 1 1
      src/Hotline.Share/Dtos/Users/UserDto.cs
  22. 277 0
      src/Hotline.Share/Dtos/WebPortal/ArticleDetailsDto.cs
  23. 357 0
      src/Hotline.Share/Dtos/WebPortal/GetOrderCodePwd.cs
  24. 75 0
      src/Hotline.Share/Dtos/WebPortal/UserModelDto.cs
  25. 51 0
      src/Hotline.Share/Dtos/WebPortal/WaitVisitListDataDto.cs
  26. 55 0
      src/Hotline.Share/Dtos/WebPortal/WebPortalDeResponse.cs
  27. 15 0
      src/Hotline.Share/Dtos/WebPortal/WebPortalSendSmsModelDto.cs
  28. 16 0
      src/Hotline.Share/Dtos/WebPortal/WriteLettersSendSmsDto.cs
  29. 7 1
      src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs
  30. 4 4
      src/Hotline/FlowEngine/Workflows/Workflow.cs
  31. 37 1
      src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs
  32. 5 0
      src/Hotline/Orders/OrderVisit.cs
  33. 14 16
      src/Hotline/Orders/OrderVisitDetail.cs
  34. 132 0
      src/Hotline/WebPortal/WebFlowAccept.cs
  35. 95 0
      src/Hotline/WebPortal/WebUserAuth.cs
  36. 95 0
      src/Hotline/WebPortal/WebUserRegister.cs
  37. 5 0
      src/XF.Domain/Constants/SettingConstants.cs

+ 2 - 2
src/Hotline.Ai.Jths/AiJthsStartupExtensions.cs

@@ -18,9 +18,9 @@ namespace Hotline.Ai.Jths
 			return services;
 		}
 
-		public static IServiceCollection AddAiVisitService(this IServiceCollection services,string baseUrl,string appkey,string serviceversion,string sceneuid,string ruleuid,string visitFromNameKey,string visitFromGenderKey,string visitCreationTimeKey,string visitOrderTitleKey)
+		public static IServiceCollection AddAiVisitService(this IServiceCollection services,string baseUrl,string appkey,string serviceversion,string sceneuid,string ruleuid,string visitFromNameKey,string visitFromGenderKey,string visitCreationTimeKey,string visitOrderTitleKey,string visitIsCallOrderKey)
 		{
-			services.AddSingleton<IAiVisitService, AiVisitService>(_ => new AiVisitService(baseUrl, appkey, serviceversion, sceneuid, ruleuid,visitFromNameKey,visitFromGenderKey,visitCreationTimeKey,visitOrderTitleKey));
+			services.AddSingleton<IAiVisitService, AiVisitService>(_ => new AiVisitService(baseUrl, appkey, serviceversion, sceneuid, ruleuid,visitFromNameKey,visitFromGenderKey,visitCreationTimeKey,visitOrderTitleKey,visitIsCallOrderKey));
 			return services;
 		}
 	}

+ 45 - 1
src/Hotline.Ai.Jths/AiVisitConfig.cs

@@ -9,18 +9,62 @@ namespace Hotline.Ai.Jths
         public string SceneUid { get; set; }
         public string RuleUid { get;set; }
 
+        /// <summary>
+        /// 来电名称
+        /// </summary>
         public string VisitFromNameKey { get; set; }
 
+
+        /// <summary>
+        /// 来电性别
+        /// </summary>
         public string VisitFromGenderKey { get; set; }
 
+        /// <summary>
+        /// 来电时间
+        /// </summary>
         public string VisitCreationTimeKey { get; set; }
 
+        /// <summary>
+        /// 工单标题
+        /// </summary>
         public string VisitOrderTitleKey { get; set; }
 
+        /// <summary>
+        /// 是否来电工单
+        /// </summary>
+        public string VisitIsCallOrder { get; set; }
+
+        /// <summary>
+        /// 是否联系
+        /// </summary>
+        public string QuestionIdZone { get; set; }
+
+
+        /// <summary>
+        /// 是否解决
+        /// </summary>
         public string QuestionIdOne { get; set; }
 
+        /// <summary>
+        /// 办件结果满意度
+        /// </summary>
         public string QuestionIdTwo { get; set;}
 
-        public string VisitContentId { get; set; }
+        /// <summary>
+        /// 坐席是否满意
+        /// </summary>
+        public string QuestionIdThree { get; set; }
+
+        /// <summary>
+        /// 办件结果不满意原因
+        /// </summary>
+        public string VisitContentIdOne { get; set; }
+
+        /// <summary>
+        /// 坐席不满意原因
+        /// </summary>
+        public string VisitContentIdTwo { get; set;}
+
     }
 }

+ 13 - 3
src/Hotline.Ai.Jths/AiVisitService.cs

@@ -27,8 +27,9 @@ namespace Hotline.Ai.Jths
         private readonly string _visitFromGenderKey;
         private readonly string _visitCreationTimeKey;
         private readonly string _visitOrderTitleKey;
+        private readonly string _visitIsCallOrderKey;
 
-        public AiVisitService(string baseUrl, string appkey, string serviceversion, string sceneuid, string ruleuid,string visitFromNameKey,string visitFromGenderKey,string visitCreationTimeKey,string visitOrderTitleKey)
+        public AiVisitService(string baseUrl, string appkey, string serviceversion, string sceneuid, string ruleuid,string visitFromNameKey,string visitFromGenderKey,string visitCreationTimeKey,string visitOrderTitleKey,string visitIsCallOrderKey)
         {
             _baseUrl = baseUrl;
             _appkey = appkey;
@@ -39,6 +40,7 @@ namespace Hotline.Ai.Jths
             _visitFromGenderKey = visitFromGenderKey;
             _visitCreationTimeKey = visitCreationTimeKey;
             _visitOrderTitleKey = visitOrderTitleKey;
+            _visitIsCallOrderKey = visitIsCallOrderKey;
             var options = new RestClientOptions(_baseUrl);
             _client = new RestClient(options);
         }
@@ -68,20 +70,29 @@ namespace Hotline.Ai.Jths
                 {
                     if (!string.IsNullOrEmpty(item.Order.FromName))
                     {
+                        //来电名称
                         //开发环境 :OC_SCENE_VAR_FIELD11
                         //生产环境:OC_SCENE_VAR_FIELD12
                         taskData.VariableList.Add(new Variable() { Code = _visitFromNameKey, Value = item.Order.FromName });
                     }
+                    //来电性别
                     //开发环境: OC_SCENE_VAR_FIELD14
                     //生产环境:OC_SCENE_VAR_FIELD14
                     taskData.VariableList.Add(new Variable() { Code = _visitFromGenderKey, Value = item.Order.FromGender == EGender.Female ? "女士" : "先生" });
                 }
+                //来电时间
                 //开发环境:OC_SCENE_VAR_FIELD17
                 //生产环境:OC_SCENE_VAR_FIELD19
                 taskData.VariableList.Add(new Variable() { Code = _visitCreationTimeKey, Value = item.Order.CreationTime.ToString("yyyy年MM月dd日hh点mm分") });
+                //工单标题
                 //开发环境:OC_SCENE_VAR_FIELD18
                 //生产环境:OC_SCENE_VAR_FIELD20
                 taskData.VariableList.Add(new Variable() { Code = _visitOrderTitleKey, Value = item.Order.Title });
+
+                //是否来电工单
+                //开发环境:
+                //生产环境:
+                taskData.VariableList.Add(new Variable() { Code = _visitIsCallOrderKey, Value = item.Order.SourceChannelCode == "RGDH" ? "income" : "outbound" });
                 taskDataList.Add(taskData);
             }
             requestData.TaskDataList = taskDataList;
@@ -101,7 +112,6 @@ namespace Hotline.Ai.Jths
                     else
                     {
                         aiOrderVisit.AiOrderVisitDetails[i].AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.LoseEfficacy;
-                        //处理回访状态TODO
                     }
                 }
             }
@@ -113,7 +123,7 @@ namespace Hotline.Ai.Jths
         {
             var dto = new AiVisitServiceQueryRequest() { BatchUid = batchId, TaskUid = taskId,PageNo=1, PageSize=100 };
             var response = await ExecuteAsync<AiVisitServiceQueryRequest, AiVisitServiceQueryResponse>(_baseUrl + "/edas/task/status/real", Method.Get, dto, cancellationToken);
-            if (response!=null && response.Result.Result.Count>0)
+            if (response!=null && response.Result!=null && response.Result.Result.Count>0)
             {
                 return new AiVisitQueryData() { Uid = response.Result.Result[0].Uid, Status = response.Result.Result[0].Status };
             }

+ 103 - 28
src/Hotline.Api/Controllers/AiController.cs

@@ -20,9 +20,11 @@ using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
+using SqlSugar;
 using System.Threading;
 using XF.Domain.Authentications;
 using XF.Domain.Constants;
+using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers
@@ -124,70 +126,123 @@ namespace Hotline.Api.Controllers
                     var aiOrderVisitDetail = aiOrderVisit.AiOrderVisitDetails.FirstOrDefault(x => x.TaskUid == dto.TaskUid);
                     if (aiOrderVisitDetail != null)
                     {
+                        var callRecord = dto.CallRecordList.OrderBy(x => x.CallStartTime).LastOrDefault();
                         // 回访结果(ReturnVisit)[1成功、0不涉及、-1失败]
-                        var callRecord = dto.CallRecordList.OrderByDescending(x => x.CallNo).FirstOrDefault(x => x.ReturnVisit == 1);
+                       
                         if (callRecord != null) //有结果的任务
                         {
                             aiOrderVisitDetail.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.Ended; //更新AI子表
                             aiOrderVisitDetail.AiVisitTime = DateTime.Now;
                             aiOrderVisit.VisitedCount++;
                             //处理结果
-                            var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id);
+                            var visitDetail = _orderVisitDetailRepository.Queryable().Where(x => x.VisitId == aiOrderVisitDetail.OrderVisit.Id).ToList();
                             //先处理子表
-                            //先处理坐席(因没有坐席回访,所以默认满意)
-                            var seatDetail = visitDetail.First(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Seat);
-                            if (seatDetail != null)
-                            {
-                                seatDetail.VoiceEvaluate = Share.Enums.Order.EVoiceEvaluate.Satisfied;
-                                seatDetail.SeatEvaluate = Share.Enums.Order.ESeatEvaluate.Satisfied;
-                            }
-                            await _orderVisitDetailRepository.UpdateAsync(seatDetail, HttpContext.RequestAborted);
+
                             //处理部门
                             var orgDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Org).ToList();
                             //过滤结果
                             var orgProcessingResults = new Kv();
-                            var orgHandledAttitude = new Kv();
+                            //var orgHandledAttitude = new Kv();
+                            ESeatEvaluate? seatEvaluate = null;
                             var visitContent = "";
+                            var seatVisitContent = "";
+                            var volveConent = "";
+                            bool? isSolve =  null;
+                            bool? isContact = null;
+                            //通话录音
+                            var recordUrl = callRecord.RecordUrl;
                             foreach (var item in callRecord.QuestionnaireResult)
                             {
-                                //服务过程满意度
+                                ////服务过程满意度
+                                //if (item.QuestionId == _options.Value.QuestionIdOne)
+                                //{
+                                //    if (item.QuestionResult == "满意")
+                                //    {
+                                //        orgHandledAttitude = new Kv() { Key="4", Value="满意" };
+                                //    }
+                                //    else
+                                //    {
+                                //        orgHandledAttitude = new Kv() { Key = "2", Value = "不满意" };
+
+                                //    }
+                                //}
+                                //是否联系
+                                if (item.QuestionId == _options.Value.QuestionIdZone)
+                                {
+                                    if (item.QuestionResult == "有联系")
+                                    {
+                                        isContact = true;
+                                    }
+                                    else if(item.QuestionResult == "没有联系")
+                                    {
+                                        isContact = false;
+                                    }
+                                }
+                                //是否解决
                                 if (item.QuestionId == _options.Value.QuestionIdOne)
                                 {
-                                    if (item.QuestionResult == "满意")
+                                    if (item.QuestionResult == "得到解决")
                                     {
-                                        orgHandledAttitude = new Kv() { Key="4", Value="满意" };
+                                        isSolve = true;
                                     }
-                                    else
+                                    else if(item.QuestionResult == "未得到解决")
                                     {
-                                        orgHandledAttitude = new Kv() { Key = "2", Value = "不满意" };
-                                      
+                                        isSolve = false;
                                     }
                                 }
                                 //办件结果满意度
                                 if (item.QuestionId == _options.Value.QuestionIdTwo)
                                 {
-                                    if (item.QuestionResult == "满意")
+                                    if (item.QuestionResult == "办件结果满意")
                                     {
                                         orgProcessingResults = new Kv() { Key = "4", Value = "满意" };
                                     }
-                                    else
+                                    else if(item.QuestionResult == "办件结果不满意")
                                     {
                                         orgProcessingResults = new Kv() { Key = "2", Value = "不满意" };
-                                        visitContent = callRecord.SceneVariable[_options.Value.VisitContentId];
+                                        visitContent = callRecord.SceneVariable[_options.Value.VisitContentIdOne];
                                     }
                                 }
                                 else
                                 {
                                     aiOrderVisitDetail.OrderVisit.VisitState = Share.Enums.Order.EVisitState.WaitForVisit;
                                 }
+
+                                //坐席是否满意
+                                if (item.QuestionId == _options.Value.QuestionIdThree)
+                                {
+                                    if (item.QuestionResult == "满意接电坐席")
+                                    {
+                                        seatEvaluate = ESeatEvaluate.Satisfied;
+                                    }
+                                    else if(item.QuestionResult == "不满意接电坐席")
+                                    {
+                                        seatEvaluate = ESeatEvaluate.NoSatisfied;
+                                        seatVisitContent = callRecord.SceneVariable[_options.Value.VisitContentIdTwo];
+                                    }
+                                }
                             }
+
+                            //先处理坐席(因没有坐席回访,所以默认满意)
+                            var seatDetail = visitDetail.Where(x => x.VisitTarget == Share.Enums.Order.EVisitTarget.Seat).ToList();
+                            seatDetail.ForEach(x =>
+                            {
+                                x.VoiceEvaluate = Share.Enums.Order.EVoiceEvaluate.Satisfied;
+                                x.SeatEvaluate = seatEvaluate;
+                                x.VisitContent = seatVisitContent;
+                            });
+                           
+                            await _orderVisitDetailRepository.UpdateRangeAsync(seatDetail, HttpContext.RequestAborted);
+
                             //处理结果
                             orgDetail.ForEach(x =>
                             {
-                                x.OrgHandledAttitude = orgHandledAttitude;
+                                //x.OrgHandledAttitude = orgHandledAttitude;
                                 x.OrgProcessingResults = orgProcessingResults;
                                 x.VisitContent = visitContent;
-                                if (orgProcessingResults.Value == "不满意")
+                                x.Volved = isSolve;
+                                x.IsContact = isContact;
+                                if (orgProcessingResults.Value == "不满意" || (string.IsNullOrEmpty(orgProcessingResults.Key) && seatEvaluate!=null && isSolve!=null && isContact!=null))
                                 {
                                     //x.OrgNoSatisfiedReason = new List<Kv>() { new Kv() { Key = "7", Value = "未回复" } };
                                     //TODO 记录不满意原因到内容中供人工回访甄别选择不满意原因
@@ -212,6 +267,9 @@ namespace Hotline.Api.Controllers
                             {
                                 aiOrderVisitDetail.OrderVisit.NowEvaluate = orgProcessingResults;
                             }
+                            //处理是否回访完成TODO
+
+
                             await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted);
 
                             //处理Order表
@@ -230,7 +288,7 @@ namespace Hotline.Api.Controllers
                                     VisitType = aiOrderVisitDetail.OrderVisit.VisitType,
                                     VisitName = aiOrderVisitDetail.OrderVisit.CreatorName,
                                     VisitTime = aiOrderVisitDetail.OrderVisit.VisitTime,
-                                    VisitRemark = string.IsNullOrEmpty(visitContent)?aiOrderVisitDetail.OrderVisit.NowEvaluate?.Value: visitContent,
+                                    VisitRemark = string.IsNullOrEmpty(visitContent) ? aiOrderVisitDetail.OrderVisit.NowEvaluate?.Value : visitContent,
                                     AreaCode = aiOrderVisitDetail.OrderVisit.Order.AreaCode!,
                                     SubjectResultSatifyCode = orgProcessingResults.Key,
                                     FirstSatisfactionCode = aiOrderVisitDetail.OrderVisit.Order.FirstVisitResultCode!,
@@ -261,6 +319,8 @@ namespace Hotline.Api.Controllers
                             aiOrderVisitDetail.IsSuccess = false;
                             await _orderVisitRepository.UpdateAsync(aiOrderVisitDetail.OrderVisit, HttpContext.RequestAborted);
                         }
+                        //var callRecord = dto.CallRecordList.OrderByDescending(x => x.CallNo).FirstOrDefault(x => x.ReturnVisit == 1);
+                       
                         
                         await _aiOrderVisitDetailRepository.UpdateAsync(aiOrderVisitDetail, HttpContext.RequestAborted);
                         if ((aiOrderVisit.VisitedFailCount+aiOrderVisit.VisitedCount)== aiOrderVisit.HasVisitCount)
@@ -414,6 +474,12 @@ namespace Hotline.Api.Controllers
         [HttpPost("aivisit/add-aivisit")]
         public async Task AddAiVisit([FromBody]AddAiVisitDto dto)
         {
+            //验证是否有重复电话
+            if(dto.AiOrderVisitDetails.Distinct().Count() != dto.AiOrderVisitDetails.Count)
+            {
+                throw UserFriendlyException.SameMessage("任务中存在重复外呼号码,请检查后重新提交");
+            }
+
             var model = _mapper.Map<AiOrderVisit>(dto);
 
             var detaillist = _mapper.Map<List<AiOrderVisitDetail>>(dto.AiOrderVisitDetails);
@@ -432,11 +498,7 @@ namespace Hotline.Api.Controllers
                 x.AiOrderVisitState = Share.Enums.Ai.EAiOrderVisitState.InProgress;
             });
             await _aiOrderVisitDetailRepository.AddRangeAsync(detaillist, HttpContext.RequestAborted);
-            //修改回访主表
-             await _orderVisitRepository.Updateable()
-                .SetColumns(x => x.IsCanAiVisit == false)
-                .SetColumns(x=> x.VisitState == EVisitState.Visiting)
-                .Where(x=> detaillist.Select(s=>s.OrderVisitId).Contains(x.Id)).ExecuteCommandAsync(HttpContext.RequestAborted);
+            
             //推送任务
             //准备原始数据
             var pushModel = await _aiOrderVisitRepository.Queryable()
@@ -445,6 +507,19 @@ namespace Hotline.Api.Controllers
                 .FirstAsync(x => x.Id == id);
             
             var newModel = await _aiVisitService.CreateAiOrderVisitTask(pushModel, HttpContext.RequestAborted);
+
+            if (!string.IsNullOrEmpty(newModel.BatchUid))
+            {
+                //修改回访主表
+                await _orderVisitRepository.Updateable()
+                   .SetColumns(x => x.IsCanAiVisit == false)
+                   .SetColumns(x => x.VisitState == EVisitState.Visiting)
+                   .Where(x => detaillist.Select(s => s.OrderVisitId).Contains(x.Id)).ExecuteCommandAsync(HttpContext.RequestAborted);
+            }
+            else
+            {
+                newModel.TaskState = Share.Enums.Ai.EAiOrderVisitTaskState.Ended;
+            }
             await _aiOrderVisitRepository.UpdateAsync(newModel, HttpContext.RequestAborted);
             await _aiOrderVisitDetailRepository.UpdateRangeAsync(newModel.AiOrderVisitDetails, HttpContext.RequestAborted);
         }

+ 245 - 245
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -53,7 +53,7 @@ namespace Hotline.Api.Controllers.Bi
         private readonly IRepository<OrderScreen> _orderScreenRepository;
         private readonly IOrderSecondaryHandlingApplication _orderSecondaryHandlingApplication;
 
-		public BiOrderController(
+        public BiOrderController(
             IOrderRepository orderRepository,
             IRepository<Hotspot> hotspotTypeRepository,
             ISystemDicDataCacheManager sysDicDataCacheManager,
@@ -74,7 +74,7 @@ namespace Hotline.Api.Controllers.Bi
             IRepository<OrderScreen> orderScreenRepository,
             IRepository<WorkflowStepHandler> workflowStepHandleRepository,
             IOrderSecondaryHandlingApplication orderSecondaryHandlingApplication
-			)
+            )
         {
             _orderRepository = orderRepository;
             _hotspotTypeRepository = hotspotTypeRepository;
@@ -97,7 +97,7 @@ namespace Hotline.Api.Controllers.Bi
             _workflowStepHandleRepository = workflowStepHandleRepository;
             _orderSecondaryHandlingApplication = orderSecondaryHandlingApplication;
 
-		}
+        }
 
         /// <summary>
         /// 部门超期统计明细
@@ -1656,7 +1656,7 @@ namespace Hotline.Api.Controllers.Bi
                  PublishedOpen = 0,
                  PublishedNoOpen = 0,
                  YBOverdue = 0,
-                 ZBOverdue = SqlFunc.AggregateSum(SqlFunc.IIF(d.Status < EWorkflowStepStatus.Handled && DateTime.Now >= d.StepExpiredTime, 1, 0)),// 0,
+                 ZBOverdue = SqlFunc.AggregateSum(SqlFunc.IIF(d.CountersignPosition == ECountersignPosition.None && d.Status < EWorkflowStepStatus.Handled && DateTime.Now >= d.StepExpiredTime, 1, 0)),// 0,
                  CompleteOnTime = 0,
                  HQYBOverdue = SqlFunc.AggregateSum(SqlFunc.IIF(d.CountersignPosition > ECountersignPosition.None && d.Status >= EWorkflowStepStatus.Handled && d.HandleTime > d.StepExpiredTime, 1, 0)),
                  HQZBOverdue = SqlFunc.AggregateSum(SqlFunc.IIF(d.CountersignPosition > ECountersignPosition.None && d.Status < EWorkflowStepStatus.Handled && DateTime.Now >= d.StepExpiredTime, 1, 0)),
@@ -2801,7 +2801,7 @@ namespace Hotline.Api.Controllers.Bi
                 .LeftJoin<WorkflowTrace>((a, wt) => a.Id == wt.WorkflowId)
                 .LeftJoin<Workflow>((a, wt, wf) => wt.WorkflowId == wf.Id)
                 .LeftJoin<WorkflowStepHandler>((a, wt, wf, wsh) => wt.StepId == wsh.WorkflowStepId && wsh.CreationTime == a.CreationTime)
-				.InnerJoin<SchedulingUser>((a, wt, wf, wsh, su) => wsh.UserId == su.UserId)
+                .InnerJoin<SchedulingUser>((a, wt, wf, wsh, su) => wsh.UserId == su.UserId)
                 .WhereIF(!string.IsNullOrEmpty(dto.UserName), ((a, wt, wf, wsh, su) => su.UserName == dto.UserName))
                 .GroupBy((a, wt, wf, wsh, su) => new { su.UserId, su.UserName })
                 .Select((a, wt, wf, wsh, su) => new BiOrderSendVo
@@ -2820,7 +2820,7 @@ namespace Hotline.Api.Controllers.Bi
                        {
                            UserId = t1.UserId,
                            UserName = t1.UserName,
-                           SendOrderNum = t1.SendOrderNum ,
+                           SendOrderNum = t1.SendOrderNum,
                            NoSendOrderNum = t1.NoSendOrderNum,
                            ReSendOrderNum = t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault(),
                            ChainRate = t1_t2.Select(x => x.ReSendOrderNum).FirstOrDefault() > 0 ?
@@ -2886,257 +2886,257 @@ namespace Hotline.Api.Controllers.Bi
         }
 
 
-		/// <summary>
-		/// 二次办理统计
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		[HttpGet("secondary_handling_report")]
+        /// <summary>
+        /// 二次办理统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("secondary_handling_report")]
         public async Task<List<SecondaryHandlingVo>> SecondaryHandlingReport([FromQuery] QuerySecondaryHandlingRequest dto)
         {
-	        var query = _orderSecondaryHandlingApplication.SecondaryHandlingReport(dto, HttpContext.RequestAborted);
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingReport(dto, HttpContext.RequestAborted);
             return await query.ToListAsync();
-		}
-		/// <summary>
-		/// 二次办理统计导出
-		/// </summary>
-		/// <returns></returns>
-		[HttpPost("secondary_handling_report/_export")]
+        }
+        /// <summary>
+        /// 二次办理统计导出
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("secondary_handling_report/_export")]
         public async Task<FileStreamResult> SecondaryHandlingReportExport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
         {
-	        var query = _orderSecondaryHandlingApplication.SecondaryHandlingReport(dto.QueryDto, HttpContext.RequestAborted);
-	        List<SecondaryHandlingVo> secondaryHandling;
-			secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
-	        dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
-	        var dtos = secondaryHandling
-				.Select(stu => _mapper.Map(stu, typeof(SecondaryHandlingVo), dynamicClass))
-		        .Cast<object>()
-		        .ToList();
-
-	        var stream = ExcelHelper.CreateStream(dtos);
-
-	        return ExcelStreamResult(stream, "二次办理统计数据");
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingReport(dto.QueryDto, HttpContext.RequestAborted);
+            List<SecondaryHandlingVo> secondaryHandling;
+            secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+            var dtos = secondaryHandling
+                .Select(stu => _mapper.Map(stu, typeof(SecondaryHandlingVo), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "二次办理统计数据");
+        }
+
+        /// <summary>
+        /// 二次办理明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("secondary_handling_detail_report")]
+        public async Task<PagedDto<OrderSecondaryHandlingDto>> SecondaryHandlingDetailReport([FromQuery] QuerySecondaryHandlingRequest dto)
+        {
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingDetailReport(dto, HttpContext.RequestAborted);
+            var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<OrderSecondaryHandlingDto>(total, _mapper.Map<IReadOnlyList<OrderSecondaryHandlingDto>>(items));
+        }
+        /// <summary>
+        /// 二次办理明细导出
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("secondary_handling_detail_report/_export")]
+        public async Task<FileStreamResult> SecondaryHandlingDetailReportExport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
+        {
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingDetailReport(dto.QueryDto, HttpContext.RequestAborted);
+            List<OrderSecondaryHandling> secondaryHandling;
+            if (dto.IsExportAll)
+            {
+                secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
+            }
+            else
+            {
+                var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+                secondaryHandling = items;
+            }
+
+            var secondaryHandlingDtos = _mapper.Map<ICollection<OrderSecondaryHandlingDto>>(secondaryHandling);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+            var dtos = secondaryHandlingDtos
+                .Select(stu => _mapper.Map(stu, typeof(OrderSecondaryHandlingDto), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "二次办理列表数据");
         }
 
-		/// <summary>
-		/// 二次办理明细
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		[HttpGet("secondary_handling_detail_report")]
-		public async Task<PagedDto<OrderSecondaryHandlingDto>> SecondaryHandlingDetailReport([FromQuery] QuerySecondaryHandlingRequest dto)
-		{
-			var query = _orderSecondaryHandlingApplication.SecondaryHandlingDetailReport(dto, HttpContext.RequestAborted);
-			var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
-
-			return new PagedDto<OrderSecondaryHandlingDto>(total, _mapper.Map<IReadOnlyList<OrderSecondaryHandlingDto>>(items));
-		}
-		/// <summary>
-		/// 二次办理明细导出
-		/// </summary>
-		/// <returns></returns>
-		[HttpPost("secondary_handling_detail_report/_export")]
-		public async Task<FileStreamResult> SecondaryHandlingDetailReportExport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
-		{
-			var query = _orderSecondaryHandlingApplication.SecondaryHandlingDetailReport(dto.QueryDto, HttpContext.RequestAborted);
-			List<OrderSecondaryHandling> secondaryHandling;
-			if (dto.IsExportAll)
-			{
-				secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
-			}
-			else
-			{
-				var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
-				secondaryHandling = items;
-			}
-
-			var secondaryHandlingDtos = _mapper.Map<ICollection<OrderSecondaryHandlingDto>>(secondaryHandling);
-
-			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
-
-			var dtos = secondaryHandlingDtos
-				.Select(stu => _mapper.Map(stu, typeof(OrderSecondaryHandlingDto), dynamicClass))
-				.Cast<object>()
-				.ToList();
-
-			var stream = ExcelHelper.CreateStream(dtos);
-
-			return ExcelStreamResult(stream, "二次办理列表数据");
-		}
-
-
-		/// <summary>
-		/// 二次办理满意度统计
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		[HttpGet("secondary_handling_satisfaction_report")]
-		public async Task<List<SecondaryHandlingSatisfactionVo>> SecondaryHandlingSatisfactionReport([FromQuery] QuerySecondaryHandlingRequest dto)
-		{
-			var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionReport(dto, HttpContext.RequestAborted);
-			var list = await query.ToListAsync();
+
+        /// <summary>
+        /// 二次办理满意度统计
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("secondary_handling_satisfaction_report")]
+        public async Task<List<SecondaryHandlingSatisfactionVo>> SecondaryHandlingSatisfactionReport([FromQuery] QuerySecondaryHandlingRequest dto)
+        {
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionReport(dto, HttpContext.RequestAborted);
+            var list = await query.ToListAsync();
             //总计
             var total = new SecondaryHandlingSatisfactionVo
             {
-                OrgId ="0",
-                OrgName ="总计",
-	            TotalSumCount = list.Select(x => x.TotalSumCount).Sum(),
-	            VerySatisfiedCount = list.Select(x => x.VerySatisfiedCount).Sum(),
-	            SatisfiedCount = list.Select(x => x.SatisfiedCount).Sum(),
-	            RegardedAsSatisfiedCount = list.Select(x => x.RegardedAsSatisfiedCount).Sum(),
-	            DefaultSatisfiedCount = list.Select(x => x.DefaultSatisfiedCount).Sum(),
-	            NoSatisfiedCount = list.Select(x => x.NoSatisfiedCount).Sum(),
-	            NoEvaluateCount = list.Select(x => x.NoEvaluateCount).Sum(),
-	            NoPutThroughCount = list.Select(x => x.NoPutThroughCount).Sum()
+                OrgId = "0",
+                OrgName = "总计",
+                TotalSumCount = list.Select(x => x.TotalSumCount).Sum(),
+                VerySatisfiedCount = list.Select(x => x.VerySatisfiedCount).Sum(),
+                SatisfiedCount = list.Select(x => x.SatisfiedCount).Sum(),
+                RegardedAsSatisfiedCount = list.Select(x => x.RegardedAsSatisfiedCount).Sum(),
+                DefaultSatisfiedCount = list.Select(x => x.DefaultSatisfiedCount).Sum(),
+                NoSatisfiedCount = list.Select(x => x.NoSatisfiedCount).Sum(),
+                NoEvaluateCount = list.Select(x => x.NoEvaluateCount).Sum(),
+                NoPutThroughCount = list.Select(x => x.NoPutThroughCount).Sum()
             };
             list.Add(total);
             //区县合计
             var countyList = list.Where(x => x.OrgType == EOrgType.County).ToList();
-			var countyTotal = new SecondaryHandlingSatisfactionVo
-			{
-				OrgId = "0",
-				OrgName = "区县合计",
-				TotalSumCount = countyList.Select(x => x.TotalSumCount).Sum(),
-				VerySatisfiedCount = countyList.Select(x => x.VerySatisfiedCount).Sum(),
-				SatisfiedCount = countyList.Select(x => x.SatisfiedCount).Sum(),
-				RegardedAsSatisfiedCount = countyList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
-				DefaultSatisfiedCount = countyList.Select(x => x.DefaultSatisfiedCount).Sum(),
-				NoSatisfiedCount = countyList.Select(x => x.NoSatisfiedCount).Sum(),
-				NoEvaluateCount = countyList.Select(x => x.NoEvaluateCount).Sum(),
-				NoPutThroughCount = countyList.Select(x => x.NoPutThroughCount).Sum()
-			};
-			list.Add(countyTotal);
-			//市直合计
-			var cityList = list.Where(x => x.OrgType == EOrgType.City).ToList();
-			var cityTotal = new SecondaryHandlingSatisfactionVo
-			{
-				OrgId = "0",
-				OrgName = "市直合计",
-				TotalSumCount = cityList.Select(x => x.TotalSumCount).Sum(),
-				VerySatisfiedCount = cityList.Select(x => x.VerySatisfiedCount).Sum(),
-				SatisfiedCount = cityList.Select(x => x.SatisfiedCount).Sum(),
-				RegardedAsSatisfiedCount = cityList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
-				DefaultSatisfiedCount = cityList.Select(x => x.DefaultSatisfiedCount).Sum(),
-				NoSatisfiedCount = cityList.Select(x => x.NoSatisfiedCount).Sum(),
-				NoEvaluateCount = cityList.Select(x => x.NoEvaluateCount).Sum(),
-				NoPutThroughCount = cityList.Select(x => x.NoPutThroughCount).Sum()
-			};
-			list.Add(cityTotal);
-			return list;
-		}
-		/// <summary>
-		/// 二次办理满意度统计导出
-		/// </summary>
-		/// <returns></returns>
-		[HttpPost("secondary_handling_satisfaction_report/_export")]
-		public async Task<FileStreamResult> SecondaryHandlingSatisfactionReportExport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
-		{
-			var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionReport(dto.QueryDto, HttpContext.RequestAborted);
-			List<SecondaryHandlingSatisfactionVo> secondaryHandling;
-			secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
-			//总计
-			var total = new SecondaryHandlingSatisfactionVo
-			{
-				OrgId = "0",
-				OrgName = "总计",
-				TotalSumCount = secondaryHandling.Select(x => x.TotalSumCount).Sum(),
-				VerySatisfiedCount = secondaryHandling.Select(x => x.VerySatisfiedCount).Sum(),
-				SatisfiedCount = secondaryHandling.Select(x => x.SatisfiedCount).Sum(),
-				RegardedAsSatisfiedCount = secondaryHandling.Select(x => x.RegardedAsSatisfiedCount).Sum(),
-				DefaultSatisfiedCount = secondaryHandling.Select(x => x.DefaultSatisfiedCount).Sum(),
-				NoSatisfiedCount = secondaryHandling.Select(x => x.NoSatisfiedCount).Sum(),
-				NoEvaluateCount = secondaryHandling.Select(x => x.NoEvaluateCount).Sum(),
-				NoPutThroughCount = secondaryHandling.Select(x => x.NoPutThroughCount).Sum()
-			};
-			secondaryHandling.Add(total);
-			//区县合计
-			var countyList = secondaryHandling.Where(x => x.OrgType == EOrgType.County).ToList();
-			var countyTotal = new SecondaryHandlingSatisfactionVo
-			{
-				OrgId = "0",
-				OrgName = "区县合计",
-				TotalSumCount = countyList.Select(x => x.TotalSumCount).Sum(),
-				VerySatisfiedCount = countyList.Select(x => x.VerySatisfiedCount).Sum(),
-				SatisfiedCount = countyList.Select(x => x.SatisfiedCount).Sum(),
-				RegardedAsSatisfiedCount = countyList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
-				DefaultSatisfiedCount = countyList.Select(x => x.DefaultSatisfiedCount).Sum(),
-				NoSatisfiedCount = countyList.Select(x => x.NoSatisfiedCount).Sum(),
-				NoEvaluateCount = countyList.Select(x => x.NoEvaluateCount).Sum(),
-				NoPutThroughCount = countyList.Select(x => x.NoPutThroughCount).Sum()
-			};
-			secondaryHandling.Add(countyTotal);
-			//市直合计
-			var cityList = secondaryHandling.Where(x => x.OrgType == EOrgType.City).ToList();
-			var cityTotal = new SecondaryHandlingSatisfactionVo
-			{
-				OrgId = "0",
-				OrgName = "市直合计",
-				TotalSumCount = cityList.Select(x => x.TotalSumCount).Sum(),
-				VerySatisfiedCount = cityList.Select(x => x.VerySatisfiedCount).Sum(),
-				SatisfiedCount = cityList.Select(x => x.SatisfiedCount).Sum(),
-				RegardedAsSatisfiedCount = cityList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
-				DefaultSatisfiedCount = cityList.Select(x => x.DefaultSatisfiedCount).Sum(),
-				NoSatisfiedCount = cityList.Select(x => x.NoSatisfiedCount).Sum(),
-				NoEvaluateCount = cityList.Select(x => x.NoEvaluateCount).Sum(),
-				NoPutThroughCount = cityList.Select(x => x.NoPutThroughCount).Sum()
-			};
-			secondaryHandling.Add(cityTotal);
-
-			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
-			var dtos = secondaryHandling
-				.Select(stu => _mapper.Map(stu, typeof(SecondaryHandlingSatisfactionVo), dynamicClass))
-				.Cast<object>()
-				.ToList();
-
-			var stream = ExcelHelper.CreateStream(dtos);
-
-			return ExcelStreamResult(stream, "二次办理满意度统计数据");
-		}
-
-		/// <summary>
-		/// 二次办理满意度明细
-		/// </summary>
-		/// <param name="dto"></param>
-		/// <returns></returns>
-		[HttpGet("secondary_handling_satisfaction_detail_report")]
-		public async Task<PagedDto<OrderSecondaryHandlingDto>> SecondaryHandlingSatisfactionDetailReport([FromQuery] QuerySecondaryHandlingRequest dto)
-		{
-			var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionDetailReport(dto, HttpContext.RequestAborted);
-			var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
-
-			return new PagedDto<OrderSecondaryHandlingDto>(total, _mapper.Map<IReadOnlyList<OrderSecondaryHandlingDto>>(items));
-		}
-		/// <summary>
-		/// 二次办理满意度明细导出
-		/// </summary>
-		/// <returns></returns>
-		[HttpPost("secondary_handling_satisfaction_detail_report/_export")]
-		public async Task<FileStreamResult> SecondaryHandlingSatisfactionDetailReport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
-		{
-			var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionDetailReport(dto.QueryDto, HttpContext.RequestAborted);
-			List<OrderSecondaryHandling> secondaryHandling;
-			if (dto.IsExportAll)
-			{
-				secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
-			}
-			else
-			{
-				var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
-				secondaryHandling = items;
-			}
-
-			var secondaryHandlingDtos = _mapper.Map<ICollection<OrderSecondaryHandlingDto>>(secondaryHandling);
-
-			dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
-
-			var dtos = secondaryHandlingDtos
-				.Select(stu => _mapper.Map(stu, typeof(OrderSecondaryHandlingDto), dynamicClass))
-				.Cast<object>()
-				.ToList();
-
-			var stream = ExcelHelper.CreateStream(dtos);
-
-			return ExcelStreamResult(stream, "二次办理满意度列表数据");
-		}
-	}
+            var countyTotal = new SecondaryHandlingSatisfactionVo
+            {
+                OrgId = "0",
+                OrgName = "区县合计",
+                TotalSumCount = countyList.Select(x => x.TotalSumCount).Sum(),
+                VerySatisfiedCount = countyList.Select(x => x.VerySatisfiedCount).Sum(),
+                SatisfiedCount = countyList.Select(x => x.SatisfiedCount).Sum(),
+                RegardedAsSatisfiedCount = countyList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
+                DefaultSatisfiedCount = countyList.Select(x => x.DefaultSatisfiedCount).Sum(),
+                NoSatisfiedCount = countyList.Select(x => x.NoSatisfiedCount).Sum(),
+                NoEvaluateCount = countyList.Select(x => x.NoEvaluateCount).Sum(),
+                NoPutThroughCount = countyList.Select(x => x.NoPutThroughCount).Sum()
+            };
+            list.Add(countyTotal);
+            //市直合计
+            var cityList = list.Where(x => x.OrgType == EOrgType.City).ToList();
+            var cityTotal = new SecondaryHandlingSatisfactionVo
+            {
+                OrgId = "0",
+                OrgName = "市直合计",
+                TotalSumCount = cityList.Select(x => x.TotalSumCount).Sum(),
+                VerySatisfiedCount = cityList.Select(x => x.VerySatisfiedCount).Sum(),
+                SatisfiedCount = cityList.Select(x => x.SatisfiedCount).Sum(),
+                RegardedAsSatisfiedCount = cityList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
+                DefaultSatisfiedCount = cityList.Select(x => x.DefaultSatisfiedCount).Sum(),
+                NoSatisfiedCount = cityList.Select(x => x.NoSatisfiedCount).Sum(),
+                NoEvaluateCount = cityList.Select(x => x.NoEvaluateCount).Sum(),
+                NoPutThroughCount = cityList.Select(x => x.NoPutThroughCount).Sum()
+            };
+            list.Add(cityTotal);
+            return list;
+        }
+        /// <summary>
+        /// 二次办理满意度统计导出
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("secondary_handling_satisfaction_report/_export")]
+        public async Task<FileStreamResult> SecondaryHandlingSatisfactionReportExport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
+        {
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionReport(dto.QueryDto, HttpContext.RequestAborted);
+            List<SecondaryHandlingSatisfactionVo> secondaryHandling;
+            secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
+            //总计
+            var total = new SecondaryHandlingSatisfactionVo
+            {
+                OrgId = "0",
+                OrgName = "总计",
+                TotalSumCount = secondaryHandling.Select(x => x.TotalSumCount).Sum(),
+                VerySatisfiedCount = secondaryHandling.Select(x => x.VerySatisfiedCount).Sum(),
+                SatisfiedCount = secondaryHandling.Select(x => x.SatisfiedCount).Sum(),
+                RegardedAsSatisfiedCount = secondaryHandling.Select(x => x.RegardedAsSatisfiedCount).Sum(),
+                DefaultSatisfiedCount = secondaryHandling.Select(x => x.DefaultSatisfiedCount).Sum(),
+                NoSatisfiedCount = secondaryHandling.Select(x => x.NoSatisfiedCount).Sum(),
+                NoEvaluateCount = secondaryHandling.Select(x => x.NoEvaluateCount).Sum(),
+                NoPutThroughCount = secondaryHandling.Select(x => x.NoPutThroughCount).Sum()
+            };
+            secondaryHandling.Add(total);
+            //区县合计
+            var countyList = secondaryHandling.Where(x => x.OrgType == EOrgType.County).ToList();
+            var countyTotal = new SecondaryHandlingSatisfactionVo
+            {
+                OrgId = "0",
+                OrgName = "区县合计",
+                TotalSumCount = countyList.Select(x => x.TotalSumCount).Sum(),
+                VerySatisfiedCount = countyList.Select(x => x.VerySatisfiedCount).Sum(),
+                SatisfiedCount = countyList.Select(x => x.SatisfiedCount).Sum(),
+                RegardedAsSatisfiedCount = countyList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
+                DefaultSatisfiedCount = countyList.Select(x => x.DefaultSatisfiedCount).Sum(),
+                NoSatisfiedCount = countyList.Select(x => x.NoSatisfiedCount).Sum(),
+                NoEvaluateCount = countyList.Select(x => x.NoEvaluateCount).Sum(),
+                NoPutThroughCount = countyList.Select(x => x.NoPutThroughCount).Sum()
+            };
+            secondaryHandling.Add(countyTotal);
+            //市直合计
+            var cityList = secondaryHandling.Where(x => x.OrgType == EOrgType.City).ToList();
+            var cityTotal = new SecondaryHandlingSatisfactionVo
+            {
+                OrgId = "0",
+                OrgName = "市直合计",
+                TotalSumCount = cityList.Select(x => x.TotalSumCount).Sum(),
+                VerySatisfiedCount = cityList.Select(x => x.VerySatisfiedCount).Sum(),
+                SatisfiedCount = cityList.Select(x => x.SatisfiedCount).Sum(),
+                RegardedAsSatisfiedCount = cityList.Select(x => x.RegardedAsSatisfiedCount).Sum(),
+                DefaultSatisfiedCount = cityList.Select(x => x.DefaultSatisfiedCount).Sum(),
+                NoSatisfiedCount = cityList.Select(x => x.NoSatisfiedCount).Sum(),
+                NoEvaluateCount = cityList.Select(x => x.NoEvaluateCount).Sum(),
+                NoPutThroughCount = cityList.Select(x => x.NoPutThroughCount).Sum()
+            };
+            secondaryHandling.Add(cityTotal);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+            var dtos = secondaryHandling
+                .Select(stu => _mapper.Map(stu, typeof(SecondaryHandlingSatisfactionVo), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "二次办理满意度统计数据");
+        }
+
+        /// <summary>
+        /// 二次办理满意度明细
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("secondary_handling_satisfaction_detail_report")]
+        public async Task<PagedDto<OrderSecondaryHandlingDto>> SecondaryHandlingSatisfactionDetailReport([FromQuery] QuerySecondaryHandlingRequest dto)
+        {
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionDetailReport(dto, HttpContext.RequestAborted);
+            var (total, items) = await query.ToPagedListAsync(dto, HttpContext.RequestAborted);
+
+            return new PagedDto<OrderSecondaryHandlingDto>(total, _mapper.Map<IReadOnlyList<OrderSecondaryHandlingDto>>(items));
+        }
+        /// <summary>
+        /// 二次办理满意度明细导出
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("secondary_handling_satisfaction_detail_report/_export")]
+        public async Task<FileStreamResult> SecondaryHandlingSatisfactionDetailReport([FromBody] ExportExcelDto<QuerySecondaryHandlingRequest> dto)
+        {
+            var query = _orderSecondaryHandlingApplication.SecondaryHandlingSatisfactionDetailReport(dto.QueryDto, HttpContext.RequestAborted);
+            List<OrderSecondaryHandling> secondaryHandling;
+            if (dto.IsExportAll)
+            {
+                secondaryHandling = await query.ToListAsync(HttpContext.RequestAborted);
+            }
+            else
+            {
+                var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+                secondaryHandling = items;
+            }
+
+            var secondaryHandlingDtos = _mapper.Map<ICollection<OrderSecondaryHandlingDto>>(secondaryHandling);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass(dto.ColumnInfos);
+
+            var dtos = secondaryHandlingDtos
+                .Select(stu => _mapper.Map(stu, typeof(OrderSecondaryHandlingDto), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "二次办理满意度列表数据");
+        }
+    }
 }

+ 36 - 19
src/Hotline.Api/Controllers/OrderController.cs

@@ -679,7 +679,7 @@ public class OrderController : BaseController
             SeatEvaluate = seatEvaluate,
             RecordingFileUrl = recordingFileUrl,
             RecordingBaseAddress = recordingBaseAddress,
-            RecordingAbsolutePath = recordingAbsolutePath
+            RecordingAbsolutePath = recordingAbsolutePath,
         };
     }
 
@@ -1555,6 +1555,10 @@ public class OrderController : BaseController
         if (screenAny)
             throw UserFriendlyException.SameMessage("该工单已提起甄别申请,正在审批过程中,不能申请");
 
+        var isNoPass = await _orderScreenRepository.AnyAsync(x => x.Status == EScreenStatus.Refuse && x.VisitDetailId == dto.Data.VisitDetailId);
+        if (isNoPass)
+            throw UserFriendlyException.SameMessage("该工单已被拒绝过甄别申请,不能再次申请");
+
         var setting = _systemSettingCacheManager.GetSetting(SettingConstants.ScreenApplyNum);
         int count = await _orderScreenRepository.CountAsync(x =>
             x.OrderId == dto.Data.OrderId && x.Status == EScreenStatus.Refuse && x.VisitDetailId == dto.Data.VisitDetailId);
@@ -3021,9 +3025,13 @@ public class OrderController : BaseController
     {
         var oneSendBack = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.OneOrgSendBack)?.SettingValue[0]);
         var twoSendBack = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.TwoOrgSendBack)?.SettingValue[0]);
-        if (oneSendBack || twoSendBack)
+        var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true, cancellationToken: HttpContext.RequestAborted);
+		var order = await _orderRepository
+	        .Queryable()
+	        .Includes(d => d.Workflow)
+	        .FirstAsync(d => d.Id == workflow.ExternalId);
+		if (oneSendBack || twoSendBack)
         {
-            var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true, cancellationToken: HttpContext.RequestAborted);
             var (currentStep, prevStep, isOrgToCenter, isSecondToFirstOrgLevel) = await _workflowApplication.GetPreviousInformationAsync(
                 dto.WorkflowId, _sessionContext.RequiredUserId, _sessionContext.RequiredOrgId, HttpContext.RequestAborted);
             var sendBack = await _orderSendBackAuditRepository.Queryable().Where(x => x.OrderId == workflow.ExternalId && x.State == ESendBackAuditState.Apply).AnyAsync();
@@ -3034,10 +3042,7 @@ public class OrderController : BaseController
                 .AnyAsync();
             if (specialAny) throw UserFriendlyException.SameMessage("工单已存在待审批特提信息!");
 
-            var order = await _orderRepository
-                .Queryable()
-                .Includes(d => d.Workflow)
-                .FirstAsync(d => d.Id == workflow.ExternalId);
+            
             if (order.Workflow.IsInCountersign) throw UserFriendlyException.SameMessage("工单会签中,无法进行退回!");
             if ((oneSendBack && isOrgToCenter) || (twoSendBack && isSecondToFirstOrgLevel))
             {
@@ -3059,15 +3064,21 @@ public class OrderController : BaseController
             }
             else
             {
-                await _workflowApplication.PreviousAsync(dto, HttpContext.RequestAborted);
-                //发送短信TODO
-            }
+               var flowDirection =  await _workflowApplication.PreviousAsync(dto, HttpContext.RequestAborted);
+               var processType = flowDirection  == EFlowDirection.OrgToCenter || flowDirection == EFlowDirection.CenterToCenter ? EProcessType.Zhiban : EProcessType.Jiaoban;
+               await _orderRepository.Updateable().SetColumns(o => new Order() {  ProcessType = processType })
+	               .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
+				//发送短信TODO
+			}
         }
         else
         {
-            await _workflowApplication.PreviousAsync(dto, HttpContext.RequestAborted);
-            //发送短信TODO
-        }
+			var flowDirection = await _workflowApplication.PreviousAsync(dto, HttpContext.RequestAborted);
+            var processType = flowDirection == EFlowDirection.OrgToCenter || flowDirection == EFlowDirection.CenterToCenter ? EProcessType.Zhiban : EProcessType.Jiaoban;
+            await _orderRepository.Updateable().SetColumns(o => new Order() { ProcessType = processType })
+	            .Where(o => o.Id == order.Id).ExecuteCommandAsync(HttpContext.RequestAborted);
+			//发送短信TODO
+		}
     }
 
     /// <summary>
@@ -3096,10 +3107,13 @@ public class OrderController : BaseController
         //执行退回
         if (sendBack.State == ESendBackAuditState.End)
         {
-            await _workflowApplication.PreviousAsync(sendBack.SendBackData, sendBack.WorkflowUserId, HttpContext.RequestAborted);
-            //发送短信TODO
+			var flowDirection = await _workflowApplication.PreviousAsync(sendBack.SendBackData, sendBack.WorkflowUserId, HttpContext.RequestAborted);
+			var processType = flowDirection == EFlowDirection.OrgToCenter || flowDirection == EFlowDirection.CenterToCenter ? EProcessType.Zhiban : EProcessType.Jiaoban;
+			await _orderRepository.Updateable().SetColumns(o => new Order() { ProcessType = processType })
+				.Where(o => o.Id == sendBack.OrderId).ExecuteCommandAsync(HttpContext.RequestAborted);
+			//发送短信TODO
 
-        }
+		}
         await _orderSendBackAuditRepository.UpdateAsync(sendBack, HttpContext.RequestAborted);
     }
 
@@ -3129,10 +3143,13 @@ public class OrderController : BaseController
             //执行退回
             if (sendBack.State == ESendBackAuditState.End)
             {
-                await _workflowApplication.PreviousAsync(sendBack.SendBackData, sendBack.WorkflowUserId, HttpContext.RequestAborted);
-                //发送短信TODO
+	            var flowDirection = await _workflowApplication.PreviousAsync(sendBack.SendBackData, sendBack.WorkflowUserId, HttpContext.RequestAborted);
+                var processType = flowDirection == EFlowDirection.OrgToCenter || flowDirection == EFlowDirection.CenterToCenter ? EProcessType.Zhiban : EProcessType.Jiaoban;
+                await _orderRepository.Updateable().SetColumns(o => new Order() { ProcessType = processType })
+	                .Where(o => o.Id == sendBack.OrderId).ExecuteCommandAsync(HttpContext.RequestAborted);
+				//发送短信TODO
 
-            }
+			}
             await _orderSendBackAuditRepository.UpdateAsync(sendBack, HttpContext.RequestAborted);
         }
 

+ 919 - 0
src/Hotline.Api/Controllers/WebPortalController.cs

@@ -0,0 +1,919 @@
+using DotNetCore.CAP;
+using Hotline.Article;
+using Hotline.Orders;
+using Hotline.Settings.Hotspots;
+using Hotline.Share.Dtos.Push.FWMessage;
+using Hotline.Share.Dtos.Push;
+using Hotline.Share.Dtos.WebPortal;
+using Hotline.Share.Enums.Order;
+using Hotline.WebPortal;
+using MapsterMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using XF.Domain.Filters;
+using XF.Domain.Repository;
+using XF.Domain.Cache;
+using Hotline.Push.Notifies;
+using Hotline.Share.Enums.Push;
+using StackExchange.Redis;
+using MediatR;
+using XF.Domain.Constants;
+using Hotline.Caching.Interfaces;
+using Hotline.Application.Orders;
+using XF.Domain.Authentications;
+using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
+using Hotline.Share.Dtos.Order;
+using Org.BouncyCastle.Ocsp;
+using System.Threading;
+
+namespace Hotline.Api.Controllers
+{
+    public class WebPortalController : BaseController
+    {
+        private readonly IMapper _mapper;
+        private readonly IMediator _mediator;
+        private readonly IRepository<Bulletin> _bulletinRepository;
+        private readonly IRepository<WebUserRegister> _webUserRegisterRepository;
+        private readonly IRepository<WebUserAuth> _webUserAuthRepository;
+        private readonly IRepository<WebFlowAccept> _webFlowAcceptRepository;
+        private readonly ITypedCache<WriteLettersSendSmsDto> _writeLettersSendSms;
+        private readonly IRepository<Hotspot> _hotspotRepository;
+        private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+        private readonly IRepository<OrderPublish> _orderPublishRepository;
+        private readonly IOrderRepository _orderRepository;
+        private readonly IRepository<OrderVisit> _orderVisitRepository;
+        private readonly IOrderApplication _orderApplication;
+        private readonly ISessionContext _sessionContext;
+        private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+
+
+        public WebPortalController(IMapper mapper,
+            IMediator mediator,
+            IRepository<Bulletin> bulletinRepository,
+             IRepository<WebUserRegister> webUserRegisterRepository,
+            IRepository<WebUserAuth> webUserAuthRepository,
+            IRepository<WebFlowAccept> webFlowAcceptRepository,
+           ITypedCache<WriteLettersSendSmsDto> writeLettersSendSms,
+           IRepository<Hotspot> hotspotRepository,
+           ISystemSettingCacheManager systemSettingCacheManager,
+           IRepository<OrderPublish> orderPublishRepository,
+           IOrderRepository orderRepository,
+           IRepository<OrderVisit> orderVisitRepository,
+           IOrderApplication orderApplication,
+           ISessionContext sessionContext,
+            IRepository<OrderVisitDetail> orderVisitDetailRepository
+            )
+        {
+            _mapper = mapper;
+            _mediator = mediator;
+            _bulletinRepository = bulletinRepository;
+            _webUserRegisterRepository = webUserRegisterRepository;
+            _webUserAuthRepository = webUserAuthRepository;
+            _webFlowAcceptRepository = webFlowAcceptRepository;
+            _writeLettersSendSms = writeLettersSendSms;
+            _hotspotRepository = hotspotRepository;
+            _systemSettingCacheManager = systemSettingCacheManager;
+            _orderPublishRepository = orderPublishRepository;
+            _orderRepository = orderRepository;
+            _orderVisitRepository = orderVisitRepository;
+            _orderApplication = orderApplication;
+            _sessionContext = sessionContext;
+            _orderVisitDetailRepository = orderVisitDetailRepository;
+        }
+
+        #region 通知
+        /// <summary>
+        /// 获取列表
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getarticlelist")]
+        public async Task<OpenResponse> GetArticleList([FromBody] QueryArticleListDto dto)
+        {
+            RefAsync<int> total = 0;
+            var items = await _bulletinRepository.Queryable()
+                .Where(p => p.LoseEfficacyTime >= DateTime.Now)
+                .Where(d => SqlFunc.JsonListObjectAny(d.PushRanges, "Key", "2"))
+                 .Where(p => p.BulletinTypeId == dto.NoticeType)
+                 .WhereIF(!string.IsNullOrEmpty(dto.Condition), p => p.Title.Contains(dto.Condition))
+                 .OrderByDescending(p => p.BulletinTime)
+                 .Select(it => new
+                 {
+                     Page = SqlFunc.RowNumber($"{it.BulletinTime} desc "),// SqlFunc.MappingColumn(default(int), "  row_number()  over( order by 'NoticeCreateDate'  ) "),
+                     NoticeID = it.Id,
+                     NoticeContent = it.Content,
+                     NoticeTypeName = it.BulletinTypeName,
+                     NoticeTitle = it.Title,
+                     NoticeBMName = it.SourceOrgName,
+                     NoticeCreateDate = it.BulletinTime,
+                     VideoUrl = "",
+                     WNLT_FullCode = ""
+                 })
+                .ToPageListAsync(dto.PageIndex, dto.PageSize, total);
+
+            //计算总页数
+            int nPageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(total) / dto.PageSize));
+            ArticleListDataDto dataDto = new()
+            {
+                PageCount = nPageCount,
+                data = _mapper.Map<IReadOnlyList<ArticleListDto>>(items)
+            };
+
+            return OpenResponse.Ok(WebPortalDeResponse<ArticleListDataDto>.Success(dataDto));
+        }
+
+        /// <summary>
+        /// 获取通知公告前几条数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getarticlelistbynum")]
+        public async Task<OpenResponse> GetArticleListByNum([FromBody] ArticleIdByNumDto dto)
+        {
+            var items = await _bulletinRepository.Queryable()
+                .Where(p => p.LoseEfficacyTime >= DateTime.Now)
+                .Where(d => SqlFunc.JsonListObjectAny(d.PushRanges, "Key", "2"))
+                 .Where(p => p.BulletinTypeId == dto.BulletinTypeId)
+                 .WhereIF(!string.IsNullOrEmpty(dto.CheckChar), p => p.Content.Contains(dto.CheckChar))
+                 .OrderByDescending(p => p.BulletinTime)
+                 .Select(it => new
+                 {
+                     DataID = it.Id,
+                     it.Title,
+                     CreateDate = it.BulletinTime,
+                     it.Content
+                 })
+                 .Take(dto.Num)
+                .ToListAsync();
+
+            var data = _mapper.Map<IReadOnlyList<DataListTopDto>>(items);
+            return OpenResponse.Ok(WebPortalDeResponse<IReadOnlyList<DataListTopDto>>.Success(data, "成功"));
+        }
+
+        /// <summary>
+        /// 获取详情,修改阅读次数
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getarticledetails")]
+        public async Task<OpenResponse> GetArticleDetails([FromBody] ArticleIdDto dto)
+        {
+            var data = await _bulletinRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
+            ArticleDetailsDto detailsDto = null;
+            if (data != null)
+            {
+                data.ReadedNum = data.ReadedNum++;
+                await _bulletinRepository.UpdateAsync(data, HttpContext.RequestAborted);
+
+                detailsDto = new()
+                {
+                    NoticeID = data.Id,
+                    NoticeTypeName = data.BulletinTypeName,
+                    NoticeTitle = data.Title,
+                    NoticeBMName = data.SourceOrgName,
+                    NoticeCreateDate = data.BulletinTime,
+                    NoticeRCount = data.ReadedNum,
+                    WNED_VideoUrl = "",
+                    NoticeContent = data.Content
+                };
+            }
+            else
+                detailsDto = new();
+            List<ArticleDetailsDto> dataDto = new() { detailsDto };
+            return OpenResponse.Ok(WebPortalDeResponse<IReadOnlyList<ArticleDetailsDto>>.Success(dataDto));
+        }
+
+        /// <summary>
+        /// 上一条和下一条
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getpreviousandnext")]
+        public async Task<OpenResponse> GetPreviousAndNext([FromBody] ArticlePreviousAndNextDto dto)
+        {
+            var sugar = _bulletinRepository.Queryable()
+                .Where(p => p.LoseEfficacyTime >= DateTime.Now)
+                .Where(d => SqlFunc.JsonListObjectAny(d.PushRanges, "Key", "2"))
+                ;
+            if (dto.FullSearch == "1")//全文搜索
+            {
+                sugar.Where(p => p.BulletinTypeId == "1" || p.BulletinTypeId == "5" || p.BulletinTypeId == "6" || p.BulletinTypeId == "7" || p.BulletinTypeId == "3" || p.BulletinTypeId == "4")
+                    .WhereIF(!string.IsNullOrEmpty(dto.Condition), p => p.Content.Contains(dto.Condition) || p.Title.Contains(dto.Condition));
+            }
+            else//指定分类
+            {
+                sugar.WhereIF(!string.IsNullOrEmpty(dto.Condition), p => p.Title.Contains(dto.Condition))
+                    .Where(p => p.BulletinTypeId == dto.NoticeTypeId);
+            }
+
+            var list = await sugar.OrderByDescending(p => p.BulletinTime)
+                .Select(it => new
+                {
+                    Page = SqlFunc.RowNumber($"{it.BulletinTime} desc "),// SqlFunc.MappingColumn(default(int), "  row_number()  over( order by 'NoticeCreateDate'  ) "),
+                    NoticeID = it.Id,
+                    NoticeTitle = it.Title,
+                    NoticeCreateDate = it.BulletinTime
+                })
+                .ToListAsync();
+            //数据为空返回空数据
+            if (list == null || list.Count == 0)
+                return OpenResponse.Ok(WebPortalDeResponse<List<ArticlePreviousAndNextDataDto>>.Success(null));
+            else
+            {
+                var temp = list.Find(p => p.NoticeID == dto.ID);
+                if (temp != null)
+                {
+                    List<ArticlePreviousAndNextDataDto> returnDto = new();
+                    //上一条
+                    var pTemp = list.Find(p => p.Page == temp.Page - 1);
+                    if (pTemp != null)
+
+                        returnDto.Add(new()
+                        {
+                            NoticeID = pTemp.NoticeID,
+                            NoticeTitle = pTemp.NoticeTitle,
+                            pntype = "p"
+                        });
+
+                    //下一条
+                    var nTemp = list.Find(p => p.Page == temp.Page + 1);
+                    if (nTemp != null)
+                        returnDto.Add(new()
+                        {
+                            NoticeID = nTemp.NoticeID,
+                            NoticeTitle = nTemp.NoticeTitle,
+                            pntype = "n"
+                        });
+                    return OpenResponse.Ok(WebPortalDeResponse<List<ArticlePreviousAndNextDataDto>>.Success(returnDto));
+                }
+                else
+                    return OpenResponse.Ok(WebPortalDeResponse<List<ArticlePreviousAndNextDataDto>>.Success(null));
+            }
+        }
+
+        /// <summary>
+        /// 全文检索
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getfulltextsearchlist")]
+        public async Task<OpenResponse> GetFullTextSearchList([FromBody] QueryArticleListDto dto)
+        {
+            if (string.IsNullOrEmpty(dto.Condition))
+                return OpenResponse.Ok(WebPortalDeResponse<FullTextSearchListDataDto>.Success(null));
+
+            RefAsync<int> total = 0;
+            var items = await _bulletinRepository.Queryable()
+                .Where(p => p.LoseEfficacyTime >= DateTime.Now)
+                .Where(d => SqlFunc.JsonListObjectAny(d.PushRanges, "Key", "2"))
+                 .Where(p => p.BulletinTypeId == "1" || p.BulletinTypeId == "5" || p.BulletinTypeId == "6" || p.BulletinTypeId == "7" || p.BulletinTypeId == "3" || p.BulletinTypeId == "4")
+                 .WhereIF(!string.IsNullOrEmpty(dto.Condition), p => p.Title.Contains(dto.Condition))
+                 .OrderByDescending(p => p.BulletinTime)
+                 .Select(it => new
+                 {
+                     Page = SqlFunc.RowNumber($"{it.BulletinTime} desc "),
+                     NoticeID = it.Id,
+                     Content = it.Content,
+                     NoticeTypeID = it.BulletinTypeId,
+                     NoticeTypeName = it.BulletinTypeName,
+                     NoticeTitle = it.Title,
+                     NoticeBMName = it.SourceOrgName,
+                     NoticeCreateDate = it.BulletinTime,
+                     TypeCode = "",
+                 })
+                .ToPageListAsync(dto.PageIndex, dto.PageSize, total);
+
+            //计算总页数
+            int nPageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(total) / dto.PageSize));
+            FullTextSearchListDataDto dataDto = new()
+            {
+                PageCount = nPageCount,
+                data = _mapper.Map<IReadOnlyList<FullTextSearchListDto>>(items)
+            };
+            return OpenResponse.Ok(WebPortalDeResponse<FullTextSearchListDataDto>.Success(dataDto));
+        }
+
+        #endregion
+
+        #region 用户
+        /// <summary>
+        /// 添加统一认证用户数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("adduserauth")]
+        public async Task<OpenResponse> AddUserAuth([FromBody] UserModelDto dto)
+        {
+            bool bResult = false;
+            // 唯一记录ID
+            string id = dto.id;
+            // 注册用户ID      
+            string webUserID = "";
+            // 统一认证用户注册ID
+            string wUAID = "";
+            //查询是否有注册用户
+            var dataUser = await _webUserAuthRepository.GetAsync(p => p.DataId == id, HttpContext.RequestAborted);
+            if (dataUser != null)
+            {
+                webUserID = dataUser.WebUserID;
+                wUAID = dataUser.Id;
+            }
+            //没有注册用户添加数据
+            if (string.IsNullOrEmpty(webUserID))
+            {
+                var userRegister = _mapper.Map<WebUserRegister>(dto);
+                userRegister.RegDate = DateTime.Now;
+                userRegister.LastDate = DateTime.Now;
+                webUserID = await _webUserRegisterRepository.AddAsync(userRegister, HttpContext.RequestAborted);
+            }
+
+            //统一认证数据
+            var userAuth = _mapper.Map<WebUserAuth>(dto);
+            if (!string.IsNullOrEmpty(wUAID))
+            {
+                //修改数据
+                userAuth.Id = wUAID;
+                await _webUserAuthRepository.UpdateAsync(userAuth, HttpContext.RequestAborted);
+                bResult = true;
+            }
+            else
+            {
+                //新增数据
+                userAuth.WebGUID = Guid.NewGuid().ToString();
+                userAuth.WebUserID = webUserID;
+                var addId = await _webUserAuthRepository.AddAsync(userAuth, HttpContext.RequestAborted);
+                if (!string.IsNullOrEmpty(addId))
+                    bResult = true;
+            }
+            //判断是否成功
+            if (bResult)
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Success(webUserID));
+            else
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Failed());
+        }
+
+        /// <summary>
+        /// 用户中心用户写信数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getorderbyuserlist")]
+        [AllowAnonymous]
+        public async Task<OpenResponse> GetOrderByUserList([FromBody] QueryOrderListByUserDto dto)
+        {
+            var dataUser = await _webUserAuthRepository.GetAsync(p => p.DataId == dto.UserId, HttpContext.RequestAborted);
+            if (dataUser != null)
+            {
+                RefAsync<int> total = 0;
+                var items = await _webFlowAcceptRepository.Queryable()
+                   .LeftJoin<Hotline.Orders.Order>((o, or) => o.OrderId == or.Id)
+                   .Where(o => o.WebUserID == dataUser.WebUserID)
+               //重新构建数据
+               .Select((o, or) => new
+               {
+                   FlowID = o.OrderId,
+                   FlowCode = o.Code,
+                   FlowPwd = o.Pwd,
+                   FlowTitle = o.Title,
+                   FlowFromName = or.SourceChannel,
+                   FlowPurTypeName = o.PurTypeName,
+                   ConTypeName = or.HotspotName,
+                   FlowAddDate = o.CreationTime,
+                   RSFlagName = or.Status < EOrderStatus.Filed ? "办理中" : "办理完成",
+                   WebUserID = o.WebUserID
+               })
+           //将结果合并成一个表
+           .MergeTable()
+                  .Where(p => p.WebUserID == dataUser.WebUserID)
+               .ToPageListAsync(dto.PageIndex, dto.PageSize, total);
+
+                //计算总页数
+                int nPageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(total) / dto.PageSize));
+                OrderListReturnDto returnDto = new()
+                {
+                    PageNum = dto.PageIndex,
+                    PageCount = nPageCount,
+                    Data = _mapper.Map<IReadOnlyList<OrderListDto>>(items)
+                };
+
+                return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(returnDto, "成功"));
+            }
+            return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(new OrderListReturnDto(), "成功"));
+
+        }
+        #endregion
+
+        #region 短信、基础设置
+        /// <summary>
+        /// 短信验证码发送
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("writeletterssendsms")]
+        public async Task<OpenResponse> WriteLettersSendSms([FromBody] WebPortalSendSmsModelDto dto)
+        {
+            string keyToken = "SmsUserWriteKey_" + dto.TelNum + "_" + DateTime.Now.ToString("yyyyMMdd");
+
+            var data = await _writeLettersSendSms.GetAsync(keyToken, HttpContext.RequestAborted);
+            if (data != null)//已经发过短信
+            {
+                //是否可以继续发送短信(10次)
+                if (data.SendCount > 10)
+                    //短信发送超过10条
+                    return OpenResponse.Ok(WebPortalDeResponse<string>.Success("-2"));
+
+                // 验证是否在两分钟之内
+                TimeSpan duration = DateTime.Now - data.AddTime; // 计算时间差
+                if ((int)duration.TotalSeconds < 120)
+                    // 距离上次发送时间不足两分钟
+                    return OpenResponse.Ok(WebPortalDeResponse<string>.Success("-4"));
+
+                //修改缓存信息
+                data.SendCount++;
+                data.SmsCode = dto.SmsCode;
+                data.AddTime = DateTime.Now;
+
+            }
+            else
+            {
+                //没有发过短信,直接写入数据
+                data = new WriteLettersSendSmsDto
+                {
+                    AddTime = DateTime.Now,
+                    SmsCode = dto.SmsCode,
+                    MobileNum = dto.TelNum,
+                    SendCount = 1
+                };
+            }
+            //这里发送短信  宜宾12345市民服务热线网站验证码{0},用于写信时验证联系电话。验证码有效期10分钟。
+
+            var messageDto = new Share.Dtos.Push.MessageDto
+            {
+                PushBusiness = EPushBusiness.MsgCode,
+                ExternalId = "",
+                OrderId = "",
+                PushPlatform = EPushPlatform.Web,
+                Remark = "",
+                Name = dto.TelNum,
+                TemplateCode = "1008",
+                Params = new List<string>() { dto.SmsCode },
+                TelNumber = dto.TelNum,
+
+            };
+            await _mediator.Publish(new PushMessageNotify(messageDto), HttpContext.RequestAborted);
+
+            //修改缓存
+            await _writeLettersSendSms.SetAsync(keyToken, data, TimeSpan.FromDays(1), HttpContext.RequestAborted);
+
+            return OpenResponse.Ok(WebPortalDeResponse<string>.Success("1"));
+        }
+
+        /// <summary>
+        /// 验证短信验证码是否正确   正确返回1,错误返回-1
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("checksmscode")]
+        public async Task<OpenResponse> CheckSmsCode([FromBody] WebPortalSendSmsModelDto dto)
+        {
+            string keyToken = "SmsUserWriteKey_" + dto.TelNum + "_" + DateTime.Now.ToString("yyyyMMdd");
+
+            var data = await _writeLettersSendSms.GetAsync(keyToken, HttpContext.RequestAborted);
+            //检测是否存在
+            if (data != null)
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Success(data.SmsCode));
+
+            //不存在
+            return OpenResponse.Ok(WebPortalDeResponse<string>.Success("-1"));
+        }
+
+        /// <summary>
+        /// 获取热点分类的树形
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("gethotspottreelist")]
+        public async Task<OpenResponse> GetHotspotTreeList()
+        {
+            var data = await _hotspotRepository.Queryable().ToTreeAsync(it => it.Children, it => it.ParentId, null);
+            return OpenResponse.Ok(WebPortalDeResponse<List<Hotspot>>.Success(data));
+        }
+
+        /// <summary>
+        /// 系统主题颜色
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getsystemsettingstheme")]
+        public async Task<OpenResponse> GetSystemSettingsTheme()
+        {
+            var WebSystemSettingsTheme = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.WebSystemSettingsTheme).SettingValue[0]);
+
+            if (WebSystemSettingsTheme == false)
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Success(""));
+
+            else
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Success("class=gray2"));
+
+        }
+        #endregion
+
+        #region 办件
+        /// <summary>
+        /// 获取信件前6条数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getorderlistbynum")]
+        [AllowAnonymous]
+        public async Task<OpenResponse> GetOrderListByNum([FromBody] QueryOrderListByNumDto dto)
+        {
+            var items = await _orderPublishRepository.Queryable()
+               .Includes(p => p.Order)
+                .Where(p => p.PublishState == true)
+                .Where(p => p.Order.Id != null)
+                .OrderByDescending(p => p.CreationTime)
+               .Select(p => new
+               {
+                   DataID = p.OrderId,
+                   Title = p.Order.Title,
+                   CreateDate = p.CreationTime
+               })
+                .Take(dto.Num)
+                .ToListAsync();
+            var data = _mapper.Map<IReadOnlyList<DataListTopDto>>(items);
+            return OpenResponse.Ok(WebPortalDeResponse<IReadOnlyList<DataListTopDto>>.Success(data, "成功"));
+        }
+
+        /// <summary>
+        /// 办件摘编列表数据
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("getorderlist")]
+        [AllowAnonymous]
+        public async Task<OpenResponse> GetOrderList([FromBody] QueryOrderListDto dto)
+        {
+            RefAsync<int> total = 0;
+            var items = await _orderPublishRepository.Queryable()
+                 .Includes(d => d.Order)
+                .Where(d => d.PublishState == true && d.Order.Id != null)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowCode), d => d.Order.No == dto.FlowCode)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowName), d => d.Order.Title.Contains(dto.FlowName))
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowSType), d => d.Order.AcceptTypeCode == dto.FlowSType)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowRType), d => d.Order.HotspotId == dto.FlowRType)
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowSDate), d => d.Order.StartTime >= DateTime.Parse(DateTime.Parse(dto.FlowSDate).ToString("yyyy-MM-dd 00:00:00")))//dto.FlowSDate
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowEDate), d => d.Order.StartTime <= DateTime.Parse(DateTime.Parse(dto.FlowEDate).ToString("yyyy-MM-dd 00:00:00")))// dto.FlowEDate
+                .WhereIF(!string.IsNullOrEmpty(dto.FlowFrom), d => d.Order.FromName.Contains(dto.FlowFrom))
+                .OrderByDescending(p => p.CreationTime)
+                .ToPageListAsync(dto.PageIndex, dto.PageSize, total);
+
+            //计算总页数
+            int nPageCount = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(total) / dto.PageSize));
+            OrderListReturnDto returnDto = new()
+            {
+                PageNum = dto.PageIndex,
+                PageCount = nPageCount,
+                Data = _mapper.Map<IReadOnlyList<OrderListDto>>(items)
+            };
+
+            return OpenResponse.Ok(WebPortalDeResponse<OrderListReturnDto>.Success(returnDto, "成功"));
+        }
+
+        /// <summary>
+        /// 办件摘编详情
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getorderdetailbyid")]
+        public async Task<OpenResponse> GetOrderDetailById([FromBody] ArticleIdDto dto)
+        {
+            var data = await _orderRepository.GetAsync(p => p.Id == dto.Id, HttpContext.RequestAborted);
+
+            var orderDetail = _mapper.Map<OrderDetail>(data);
+            if (data != null)
+            {
+                //如果是省工单或者是省政民互动的工单,市民不能进行评价
+                if (data.IsProvince == true || data.SourceChannelCode == "ZMHD")
+                    orderDetail.IsProvinceOrder = "1";
+                else
+                    orderDetail.IsProvinceOrder = "0";
+
+                //获取发布的数据
+                var orderPublish = await _orderPublishRepository.GetAsync(p => p.OrderId == data.Id, HttpContext.RequestAborted);
+                if (orderPublish != null)
+                {
+                    orderDetail.FlowTitle = orderPublish.ArrangeTitle;
+                    orderDetail.FlowContent = orderPublish.ArrangeContent;
+                    orderDetail.FlowResult = orderPublish.ArrangeOpinion;
+                    if (orderPublish.PublishState)
+                        orderDetail.FlowPubFlagName = "公开";
+                }
+
+                //能否进行评价
+                var orderVisit = await _orderVisitRepository.GetAsync(p => p.OrderId == data.Id && p.VisitState != EVisitState.None, HttpContext.RequestAborted);
+                if (orderVisit == null)
+                    orderDetail.VisitType = "0";
+                else
+                {
+                    orderDetail.VisitType = orderVisit.VisitState switch
+                    {
+                        EVisitState.WaitForVisit => "1",
+                        EVisitState.Visited => "2",
+                        EVisitState.Visiting or EVisitState.NoSatisfiedWaitForVisit or EVisitState.None => "0",
+                        _ => "0",
+                    };
+                }
+
+            }
+            List<OrderDetail> dataDto = new() { orderDetail };
+            return OpenResponse.Ok(WebPortalDeResponse<IReadOnlyList<OrderDetail>>.Success(dataDto));
+        }
+
+        /// <summary>
+        /// 根据编号和密码查询信件ID
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getorderid")]
+        public async Task<OpenResponse> GetOrderId([FromBody] GetOrderCodePwd dto)
+        {
+            var data = await _orderRepository.GetAsync(p => p.No == dto.OrderNo && p.Password == dto.Pwd, HttpContext.RequestAborted);
+            return OpenResponse.Ok(WebPortalDeResponse<string>.Success(data?.Id));
+        }
+
+        /// <summary>
+        /// 办件摘编详情
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getorderdetailbyno")]
+        public async Task<OpenResponse> GetOrderDetailByNo([FromBody] GetOrderCodePwd dto)
+        {
+            var data = await _orderRepository.GetAsync(p => p.No == dto.OrderNo && p.Password == dto.Pwd, HttpContext.RequestAborted);
+            if (data != null)
+            {
+                var orderDetail = _mapper.Map<OrderDetail>(data);
+                if (data != null)
+                {
+                    //如果是省工单或者是省政民互动的工单,市民不能进行评价
+                    if (data.IsProvince == true || data.SourceChannelCode == "ZMHD")
+                        orderDetail.IsProvinceOrder = "1";
+                    else
+                        orderDetail.IsProvinceOrder = "0";
+
+                    //获取发布的数据
+                    var orderPublish = await _orderPublishRepository.GetAsync(p => p.OrderId == data.Id, HttpContext.RequestAborted);
+                    if (orderPublish != null)
+                    {
+                        orderDetail.FlowTitle = orderPublish.ArrangeTitle;
+                        orderDetail.FlowContent = orderPublish.ArrangeContent;
+                        orderDetail.FlowResult = orderPublish.ArrangeOpinion;
+                        if (orderPublish.PublishState)
+                            orderDetail.FlowPubFlagName = "公开";
+                    }
+
+                    //能否进行评价
+                    var orderVisit = await _orderVisitRepository.GetAsync(p => p.OrderId == data.Id && p.VisitState != EVisitState.None, HttpContext.RequestAborted);
+                    if (orderVisit == null)
+                        orderDetail.VisitType = "0";
+                    else
+                    {
+                        orderDetail.VisitType = orderVisit.VisitState switch
+                        {
+                            EVisitState.WaitForVisit => "1",
+                            EVisitState.Visited => "2",
+                            EVisitState.Visiting or EVisitState.NoSatisfiedWaitForVisit or EVisitState.None => "0",
+                            _ => "0",
+                        };
+                    }
+
+                }
+                List<OrderDetail> dataDto = new() { orderDetail };
+                return OpenResponse.Ok(WebPortalDeResponse<IReadOnlyList<OrderDetail>>.Success(dataDto));
+            }
+            return OpenResponse.Ok(WebPortalDeResponse<IReadOnlyList<string>>.Failed(code: "0", description: "查询失败"));
+        }
+
+        /// <summary>
+        /// 写信接口
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("orderacceptance")]
+        public async Task<OpenResponse> OrderAcceptance([FromBody] WebFlowAccept dto)
+        {
+            var data = _mapper.Map<Hotline.Share.Dtos.Order.AddOrderDto>(dto);
+            data.SourceChannel = "网站";
+            data.SourceChannelCode = "WZ";
+            data.Source = ESource.WebPortal;
+            if (!string.IsNullOrEmpty(data.LicenceNo))
+            {
+                data.LicenceTypeCode = "10";
+                data.LicenceType = "中华人民共和国居民身份证";
+            }
+            data.ExternalId = Guid.NewGuid().ToString();
+
+            data.IdentityType = EIdentityType.Citizen;
+            data.Transpond = false;
+            data.IsEnforcementOrder = false;
+
+
+            var result = await _orderApplication.ReceiveOrderFromExternalAsync(data, _sessionContext, HttpContext.RequestAborted);
+
+            OrderAcceptanceReturnDto returnDto = new();
+            if (result != null)
+            {
+                returnDto.PWD = result.Password;
+                returnDto.Code = result.No;
+                returnDto.State = "1";
+
+                dto.Pwd = result.Password;
+                dto.Code = result.No;
+                dto.OrderId = result.Id;
+                await _webFlowAcceptRepository.AddAsync(dto, HttpContext.RequestAborted);
+
+            }
+            else
+                returnDto.State = "0";
+
+
+            return OpenResponse.Ok(WebPortalDeResponse<OrderAcceptanceReturnDto>.Success(returnDto));
+        }
+
+        /// <summary>
+        /// 受理类型和热点统计
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getchartdata")]
+        public async Task<OpenResponse> GetChartData()
+        {
+            var startDate = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 00:00:00"));
+            var endDate = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 23:59:59"));
+
+            //数据查询
+            var listType = await _orderRepository.Queryable()
+                .Where(p => p.StartTime >= startDate && p.StartTime <= endDate && p.Status > EOrderStatus.WaitForAccept)
+                .Select(it => new
+                {
+                    it.AcceptType,
+                    it.AcceptTypeCode
+                })
+                .MergeTable()//将查询出来的结果合并成一个新表
+                 .GroupBy(it => new { it.AcceptType, it.AcceptTypeCode })//对新表进行分组
+                 .Select(it => new
+                 {
+                     name = it.AcceptType,
+                     value = SqlFunc.AggregateCount(it.AcceptTypeCode)
+                 })
+                 .ToListAsync();
+
+            //数据查询-查询总数前5的数据
+            var listHot = await _orderRepository.Queryable()
+                .Where(p => p.StartTime >= startDate && p.StartTime <= endDate && p.Status > EOrderStatus.WaitForAccept)
+                .Select(it => new
+                {
+                    it.HotspotId,
+                    it.HotspotName
+                })
+                .MergeTable()//将查询出来的结果合并成一个新表
+                 .GroupBy(it => new { it.HotspotId, it.HotspotName })//对新表进行分组
+                 .Select(it => new
+                 {
+                     typeName = it.HotspotName,
+                     num = SqlFunc.AggregateCount(it.HotspotId)
+                 })
+                 .OrderByDescending(it => it.num)
+                 .Take(5)
+                 .ToListAsync();
+
+            GetChartDataDto dataDto = new()
+            {
+                formCount = _mapper.Map<IReadOnlyList<OrderFormCount>>(listType),
+                hotCount = _mapper.Map<IReadOnlyList<OrderHotCount>>(listHot)
+            };
+
+            return OpenResponse.Ok(WebPortalDeResponse<GetChartDataDto>.Success(dataDto));
+        }
+
+        /// <summary>
+        /// 获取统计数据
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getstatist")]
+        public async Task<OpenResponse> GetStatist()
+        {
+            var startDate = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 00:00:00"));
+            var endDate = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 23:59:59"));
+            GetStatistDto getStatistDto = new()
+            {
+                AllCount = await _orderRepository.Queryable().Where(p => p.Status > EOrderStatus.WaitForAccept).CountAsync(),
+                AllTrandCount = await _orderRepository.Queryable().Where(p => p.Status >= EOrderStatus.Filed).CountAsync(),
+                DayCount = await _orderRepository.Queryable().Where(p => p.StartTime >= startDate && p.StartTime <= endDate && p.Status > EOrderStatus.WaitForAccept).CountAsync(),
+                DayTrandCount = await _orderRepository.Queryable().Where(p => p.ActualHandleTime >= startDate && p.ActualHandleTime <= endDate && p.Status >= EOrderStatus.Filed).CountAsync()
+            };
+
+            return OpenResponse.Ok(WebPortalDeResponse<GetStatistDto>.Success(getStatistDto));
+        }
+        #endregion
+
+        #region 评价
+        /// <summary>
+        /// 获取待评价部门信息
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("getwaitvisitdata")]
+        public async Task<OpenResponse> GetWaitVisitData([FromBody] ArticleIdDto dto)
+        {
+            var dataOrder = await _orderVisitRepository.GetAsync(p => p.OrderId == dto.Id && p.VisitState != EVisitState.None, HttpContext.RequestAborted);
+            if (dataOrder == null)
+                return OpenResponse.Ok(WebPortalDeResponse<List<WaitVisitListDataDto>>.Failed("工单不能评价!"));
+
+            if (dataOrder.VisitState != EVisitState.WaitForVisit)
+                return OpenResponse.Ok(WebPortalDeResponse<List<WaitVisitListDataDto>>.Failed("工单已经评价!"));
+
+            var data = await _orderVisitDetailRepository.Queryable()
+                .Where(p => p.VisitId == dataOrder.Id && p.VisitTarget == EVisitTarget.Org)
+                .ToListAsync();
+
+            var listDat = _mapper.Map<List<WaitVisitListDataDto>>(data);
+            return OpenResponse.Ok(WebPortalDeResponse<List<WaitVisitListDataDto>>.Success(listDat));
+        }
+
+        /// <summary>
+        /// 评价内容
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpPost("receivevisitdata")]
+        public async Task<OpenResponse> ReceiveVisitData([FromBody] OrderVisitListDataDto dto)
+        {
+            if (dto == null || dto.VistListDtos == null || dto.VistListDtos.Count == 0)
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Failed("评价数据不能为空!"));
+
+            var dataOrder = await _orderVisitRepository.GetAsync(p => p.OrderId == dto.OrderId && p.VisitState != EVisitState.None, HttpContext.RequestAborted);
+            if (dataOrder == null)
+                return OpenResponse.Ok(WebPortalDeResponse<string>.Failed("工单已经评价!"));
+
+            //组装数据
+            OrderVisitWebDto visitWebDto = new()
+            {
+                VisitType = Hotline.Share.Enums.Order.EVisitType.WebVisit,
+                VisitTime = DateTime.Now
+            };
+
+            List<OrderVisitDetailWebDto> orderVisitDetails = new();
+
+            #region 部门评价
+            //部门评价
+            //遍历数据
+            foreach (var item in dto.VistListDtos)
+            {
+                Hotline.Share.Dtos.Kv kv = item.SatisfactionCode switch
+                {
+                    "5" => new Hotline.Share.Dtos.Kv { Key = "5", Value = "非常满意" },
+                    "4" => new Hotline.Share.Dtos.Kv { Key = "4", Value = "满意" },
+                    "2" => new Hotline.Share.Dtos.Kv { Key = "2", Value = "不满意" },
+                    _ => new Hotline.Share.Dtos.Kv { Key = "7", Value = "不做评价" },
+                };
+                //组装回访数据
+                visitWebDto.Id = item.VisitId;
+                OrderVisitDetailWebDto detailDto = new()
+                {
+                    Id = item.Id,
+                    VisitTarget = Hotline.Share.Enums.Order.EVisitTarget.Org,
+                    OrgProcessingResults = kv,
+                    OrgNoSatisfiedReason = new List<Hotline.Share.Dtos.Kv>(),
+                    OrgHandledAttitude = kv,
+                    VisitContent = item.VisitContent
+                };
+                orderVisitDetails.Add(detailDto);
+            }
+            #endregion
+
+            visitWebDto.OrderVisitDetailDto = orderVisitDetails;
+
+            //推送数据
+            await _orderApplication.OrderVisitWeb(visitWebDto, HttpContext.RequestAborted);
+            return OpenResponse.Ok(WebPortalDeResponse<string>.Success("1"));
+        }
+        #endregion
+    }
+}

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

@@ -112,7 +112,17 @@ internal static class StartupExtensions
         services.AddYbEnterpriseSdk(enterpriseConfig?.AddressUrl);
   
         var aiVisitConfig = configuration.GetSection("AiVisit").Get<AiVisitConfig>();
-        services.AddAiVisitService(aiVisitConfig.Url, aiVisitConfig.Appkey, aiVisitConfig.ServiceVersion, aiVisitConfig.SceneUid, aiVisitConfig.RuleUid,aiVisitConfig.VisitFromNameKey,aiVisitConfig.VisitFromGenderKey,aiVisitConfig.VisitCreationTimeKey,aiVisitConfig.VisitOrderTitleKey);
+        services.AddAiVisitService(
+            aiVisitConfig.Url, 
+            aiVisitConfig.Appkey, 
+            aiVisitConfig.ServiceVersion, 
+            aiVisitConfig.SceneUid, 
+            aiVisitConfig.RuleUid,
+            aiVisitConfig.VisitFromNameKey,
+            aiVisitConfig.VisitFromGenderKey,
+            aiVisitConfig.VisitCreationTimeKey,
+            aiVisitConfig.VisitOrderTitleKey,
+            aiVisitConfig.VisitIsCallOrder);
 
         //sqlsugar
         services.AddSqlSugar(configuration);

+ 16 - 11
src/Hotline.Api/config/appsettings.Development.json

@@ -25,7 +25,7 @@
     "Host": "110.188.24.182",
     "Port": 50179,
     //"Password": "fengwo22@@",
-    "Database": 3
+    "Database": 5
   },
   "Swagger": true,
   "Cors": {
@@ -205,18 +205,23 @@
   },
   //智能回访
   "AiVisit": {
-    "Url": "http://118.121.201.140:19061",
+    "Url": "http://118.122.73.80:19061",
     "Appkey": "MTAwMDAx",
     "ServiceVersion": "V1.0.0", //接口版本号
-    "SceneUid": "MTAwMDAxM4yRrAE5SsihkQkKuyMwM", //场景ID
-    "RuleUid": "MTAwMDAxSGdDJryoQUgoDeiKlsTEyc", //现有规则ID
-    "VisitFromNameKey": "OC_SCENE_VAR_FIELD11", //来电名称       //开发环境 :OC_SCENE_VAR_FIELD11   //生产环境:OC_SCENE_VAR_FIELD12
-    "VisitFromGenderKey": "OC_SCENE_VAR_FIELD14", //来电性别     //开发环境: OC_SCENE_VAR_FIELD14   //生产环境:OC_SCENE_VAR_FIELD14
-    "VisitCreationTimeKey": "OC_SCENE_VAR_FIELD17", //来电时间   //开发环境:OC_SCENE_VAR_FIELD17    //生产环境:OC_SCENE_VAR_FIELD19
-    "VisitOrderTitleKey": "OC_SCENE_VAR_FIELD18", //工单标题      //开发环境:OC_SCENE_VAR_FIELD18    //生产环境:OC_SCENE_VAR_FIELD20
-    "QuestionIdOne": "MTAwMDAxK1F9Qnu-QXYolr9JoOI8fM", //服务过程满意度 //开发环境:MTAwMDAxK1F9Qnu-QXYolr9JoOI8fM  //生产环境:MTAwMDAxvgT-2PoYRR8pxyL4p8AJsM
-    "QuestionIdTwo": "MTAwMDAxWQWym04VSRwn4dmWKnYEsQ", //办件结果满意度 //开发环境:MTAwMDAxWQWym04VSRwn4dmWKnYEsQ  //生产环境:MTAwMDAxhIyRJzpwTskpsiXPD-uwl0
-    "VisitContentId": "NotSat2" //不满意原因 // 开发环境:NotSat2   //生产环境:NotSat2
+    "SceneUid": "MTAwMDAxVW7cUNRwRegsLGqb0pvXCU", //场景ID
+    "RuleUid": "MTAwMDAxUbQsuOcmS5ApRyHGyWQr7g", //现有规则ID
+    "VisitFromNameKey": "OC_SCENE_VAR_FIELD14", //来电名称       //开发环境 :OC_SCENE_VAR_FIELD14  //生产环境:OC_SCENE_VAR_FIELD12
+    "VisitFromGenderKey": "OC_SCENE_VAR_FIELD13", //来电性别     //开发环境: OC_SCENE_VAR_FIELD13   //生产环境:OC_SCENE_VAR_FIELD14
+    "VisitCreationTimeKey": "OC_SCENE_VAR_FIELD15", //来电时间   //开发环境:OC_SCENE_VAR_FIELD15    //生产环境:OC_SCENE_VAR_FIELD19
+    "VisitOrderTitleKey": "OC_SCENE_VAR_FIELD16", //工单标题      //开发环境:OC_SCENE_VAR_FIELD16    //生产环境:OC_SCENE_VAR_FIELD20
+    "VisitIsCallOrder": "OC_SCENE_VAR_FIELD17", //是否来电工单 //开发环境:OC_SCENE_VAR_FIELD17          //生产环境:
+    //"QuestionIdOne": "MTAwMDAxK1F9Qnu-QXYolr9JoOI8fM", //服务过程满意度 //开发环境:MTAwMDAxK1F9Qnu-QXYolr9JoOI8fM  //生产环境:MTAwMDAxvgT-2PoYRR8pxyL4p8AJsM
+    "QuestionIdZone": "MTAwMDAxvpXEOVcRQR0meGKouw6oXU", //是否联系  //开发环境:MTAwMDAxvpXEOVcRQR0meGKouw6oXU    //测试环境:
+    "QuestionIdOne": "MTAwMDAxP2AJqUCvTU4jsgzvRkADVo", //是否解决 , //开发环境:MTAwMDAxP2AJqUCvTU4jsgzvRkADVo   //生产环境:
+    "QuestionIdTwo": "MTAwMDAx_mLpLD0hSyosIwvgW-byjg", //办件结果满意度 //开发环境:MTAwMDAx_mLpLD0hSyosIwvgW-byjg  //生产环境:MTAwMDAxhIyRJzpwTskpsiXPD-uwl0
+    "QuestionIdThree": "MTAwMDAxos3PXU5TTzEnXMSU8GeApc", //坐席是否满意  //开发环境:  //生产环境:
+    "VisitContentIdOne": "reason1", //办件结果不满意原因 // 开发环境:NotSat2   //生产环境:NotSat2
+    "VisitContentIdTwo": "reason2", //坐席不满意原因  // 开发环境:    //生产环境:
   },
   //智能质检
   "AiQuality": {

+ 2 - 2
src/Hotline.Api/config/appsettings.json

@@ -25,11 +25,11 @@
     "Host": "110.188.24.182",
     "Port": 50179,
     "Password": "fengwo22@@",
-    "Database": 3
+    "Database": 5
   },
   "Swagger": true,
   "Cors": {
-    "Origins": [ "http://localhost:8888", "http://localhost:8113", "http://hotline.12345lm.cn", "http://110.188.24.28:50101", "http://110.188.24.28:50102", "http://110.188.24.28:50301", "http://bs.hotline.12345lm.cn" ]
+    "Origins": [ "http://10.210.252.122:8888", "http://localhost:8888", "http://localhost:8113", "http://hotline.12345lm.cn", "http://110.188.24.28:50101", "http://110.188.24.28:50102", "http://110.188.24.28:50301", "http://bs.hotline.12345lm.cn" ]
   },
   "IdentityConfiguration": {
     "Password": {

+ 12 - 6
src/Hotline.Application/FlowEngine/IWorkflowApplication.cs

@@ -30,12 +30,12 @@ namespace Hotline.Application.FlowEngine
         /// <summary>
         /// 退回(返回前一节点)
         /// </summary>
-        Task PreviousAsync(PreviousWorkflowDto dto, CancellationToken cancellationToken);
+        Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, CancellationToken cancellationToken);
 
         /// <summary>
         /// 工单退回(返回前一节点)
         /// </summary>
-        Task PreviousAsync(PreviousWorkflowDto dto, string userId, CancellationToken cancellationToken);
+        Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, string userId, CancellationToken cancellationToken);
 
         /// <summary>
         /// 撤回至任意节点
@@ -43,10 +43,16 @@ namespace Hotline.Application.FlowEngine
         Task RecallAsync(RecallDto dto, DateTime? expiredTime, CancellationToken cancellationToken);
 
         /// <summary>
-        /// 跳转至结束节点(无视流程模板配置直接跳至结束节点)
+        /// 办理至结束节点(无视流程模板配置,操作人需是当前节点办理对象)
         /// </summary>
-        Task HandleToEndAsync(ISessionContext current, string workflowId, string opinion, List<FileDto> file,
-            EReviewResult? reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default);
+        Task HandleToEndAsync(ISessionContext current, string workflowId, string opinion, List<FileDto> files,
+            EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// 跳转至结束节点(无视流程模板配置以及当前办理对象)
+        /// </summary>
+        Task JumpToEndAsync(ISessionContext current, string workflowId, string opinion, List<FileDto> files,
+            EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default);
 
         ////////
 
@@ -84,7 +90,7 @@ namespace Hotline.Application.FlowEngine
         Task<(WorkflowStep currentStep, WorkflowStep prevStep, bool isOrgToCenter, bool isSecondToFirstOrgLevel)>
             GetPreviousInformationAsync(string workflowId, string operatorId, string operatorOrgId,
                 CancellationToken cancellationToken);
-        
+
         /// <summary>
         /// 开启流程直接归档
         /// </summary>

+ 58 - 49
src/Hotline.Application/FlowEngine/WorkflowApplication.cs

@@ -29,6 +29,7 @@ using XF.Utility.EnumExtensions;
 using Hotline.Share.Dtos.File;
 using Microsoft.Extensions.Logging;
 using System.Text;
+using System.Diagnostics;
 
 namespace Hotline.Application.FlowEngine;
 
@@ -266,27 +267,27 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// <summary>
     /// 退回(返回前一节点)
     /// </summary>
-    public async Task PreviousAsync(PreviousWorkflowDto dto, CancellationToken cancellationToken)
+    public async Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
             withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
         var user = await _userRepository.Queryable()
             .Includes(x => x.Organization)
             .FirstAsync(x => x.Id == _sessionContext.RequiredUserId, cancellationToken);
-        await _workflowDomainService.PreviousAsync(workflow, dto, user, _sessionContext, cancellationToken);
+        return await _workflowDomainService.PreviousAsync(workflow, dto, user, _sessionContext, cancellationToken);
     }
 
     /// <summary>
     /// 工单退回(返回前一节点)
     /// </summary>
-    public async Task PreviousAsync(PreviousWorkflowDto dto, string userId, CancellationToken cancellationToken)
+    public async Task<EFlowDirection> PreviousAsync(PreviousWorkflowDto dto, string userId, CancellationToken cancellationToken)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
             withTraces: true, withCountersigns: true, cancellationToken: cancellationToken);
         var user = await _userRepository.Queryable()
             .Includes(x => x.Organization)
             .FirstAsync(x => x.Id == userId, cancellationToken);
-        await _workflowDomainService.PreviousAsync(workflow, dto, user, _sessionContext, cancellationToken);
+        return await _workflowDomainService.PreviousAsync(workflow, dto, user, _sessionContext, cancellationToken);
     }
 
     /// <summary>
@@ -320,7 +321,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
     /// 无视流程模板配置直接将当前节点办理至结束节点
     /// </summary>
     public async Task HandleToEndAsync(ISessionContext current, string workflowId, string opinion, List<FileDto> files,
-        EReviewResult? reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default)
+        EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default)
     {
         var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withDefine: true, withSteps: true,
             cancellationToken: cancellationToken);
@@ -334,22 +335,62 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
             NextStepName = endStepDefine.Name,
             FlowDirection = EFlowDirection.OrgToFile,
             BusinessType = endStepDefine.BusinessType,
+            ReviewResult = reviewResult,
             Opinion = opinion,
             Files = files
         };
         await NextAsync(dto, current, cancellationToken: cancellationToken);
+    }
+
+    /// <summary>
+    /// 跳转至结束节点(无视流程模板配置以及当前办理对象,直接跳至结束节点)
+    /// </summary>
+    public async Task JumpToEndAsync(ISessionContext current, string workflowId, string opinion, List<FileDto> files,
+        EReviewResult reviewResult = EReviewResult.Unknown, CancellationToken cancellationToken = default)
+    {
+        var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withDefine: true, withSteps: true,
+            withTraces: true, cancellationToken: cancellationToken);
+        var endStepDefine = workflow.WorkflowDefinition.FindEndStepDefine();
+        if (endStepDefine is null)
+            throw new UserFriendlyException("未正确配置结束节点");
 
         //var currentStep = workflow.GetActualStep();
         //if (currentStep is null)
         //    throw new UserFriendlyException("未找到实际办理节点");
 
-        //await _workflowDomainService.EndAsync(workflow,
-        //    new BasicWorkflowDto
-        //    {
-        //        Opinion = opinion,
-        //        Files = files
-        //    },
-        //    endStepDefine, currentStep, reviewResult, isProvince, cancellationToken: cancellationToken);
+        var dto = new BasicWorkflowDto
+        {
+            NextStepCode = endStepDefine.Code,
+            NextStepName = endStepDefine.Name,
+            FlowDirection = EFlowDirection.OrgToFile,
+            BusinessType = endStepDefine.BusinessType,
+            ReviewResult = reviewResult,
+            Opinion = opinion,
+            Files = files
+        };
+
+        var unhandleSteps = workflow.Steps
+            .Where(d => d.Status != EWorkflowStepStatus.Handled).ToList();
+
+        var unhandleTraces = workflow.Traces
+            .Where(d => d.Status != EWorkflowStepStatus.Handled).ToList();
+
+        //todo 结束会签
+
+        foreach (var step in unhandleSteps)
+        {
+            await _workflowDomainService.HandleStepAsync(current, step, workflow, dto, null,
+                null, null, cancellationToken);
+
+            var trace = unhandleTraces.First(d => d.StepId == step.Id);
+            _mapper.Map(dto, trace);
+            _mapper.Map(step, trace);
+        }
+        await _workflowStepRepository.UpdateRangeAsync(unhandleSteps, cancellationToken);
+        await _workflowTraceRepository.UpdateRangeAsync(unhandleTraces, cancellationToken);
+
+        await _workflowDomainService.EndAsync(workflow, dto,
+            endStepDefine, unhandleSteps.First(), current, cancellationToken);
     }
 
     /// <summary>
@@ -597,8 +638,8 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         nextDto.ReviewResult = EReviewResult.Failed;
         nextDto.NextStepCode = endStepDefine.Code;
         nextDto.NextStepName = endStepDefine.Name;
-        nextDto.FlowDirection = _sessionContext.OrgIsCenter 
-            ? EFlowDirection.CenterToFile 
+        nextDto.FlowDirection = _sessionContext.OrgIsCenter
+            ? EFlowDirection.CenterToFile
             : EFlowDirection.OrgToFile;
 
         await NextAsync(nextDto, _sessionContext, cancellationToken: cancellationToken);
@@ -622,7 +663,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
             if (stepDefine.StepType is EStepType.End) continue;
 
-            nextStepOption.FlowDirection = CheckFlowDirection(currentBusinessType, stepDefine.BusinessType);
+            nextStepOption.FlowDirection = _workflowDomainService.GetFlowDirection(currentBusinessType, stepDefine.BusinessType);
             //stepOptions.Add(nextStepOption);
         }
 
@@ -651,7 +692,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
                 //根据汇总对象id找到被汇总节点
                 var summaryTargetStep = workflow.Steps.FirstOrDefault(d =>
                     d.Status == EWorkflowStepStatus.Handled &&
-                    d.StepType == EStepType.Normal && 
+                    d.StepType == EStepType.Normal &&
                     d.Code == stepDefine.SummaryTargetCode &&
                     d.IsOrigin);
                 if (summaryTargetStep is null)
@@ -681,7 +722,7 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
 
             if (stepDefine.StepType is EStepType.End) continue;
 
-            nextStepOption.FlowDirection = CheckFlowDirection(currentStep.BusinessType, stepDefine.BusinessType);
+            nextStepOption.FlowDirection = _workflowDomainService.GetFlowDirection(currentStep.BusinessType, stepDefine.BusinessType);
         }
 
         return stepOptions;
@@ -787,38 +828,6 @@ public class WorkflowApplication : IWorkflowApplication, IScopeDependency
         };
     }
 
-    private EFlowDirection? CheckFlowDirection(EBusinessType sourceStepBusinessType, EBusinessType directionStepBusinessType)
-    {
-        switch (sourceStepBusinessType)
-        {
-            case EBusinessType.Center:
-            case EBusinessType.Send:
-                return directionStepBusinessType switch
-                {
-                    EBusinessType.Center => EFlowDirection.CenterToCenter,
-                    EBusinessType.Send => EFlowDirection.CenterToCenter,
-                    EBusinessType.Department => EFlowDirection.CenterToOrg,
-                    EBusinessType.File => EFlowDirection.CenterToFile,
-                    _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType),
-                        directionStepBusinessType, null)
-                };
-            case EBusinessType.Department:
-                return directionStepBusinessType switch
-                {
-                    EBusinessType.Center => EFlowDirection.OrgToCenter,
-                    EBusinessType.Send => EFlowDirection.OrgToCenter,
-                    EBusinessType.Department => EFlowDirection.OrgToOrg,
-                    EBusinessType.File => EFlowDirection.OrgToFile,
-                    _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType),
-                        directionStepBusinessType, null)
-                };
-            case EBusinessType.File:
-                return null;
-            default:
-                throw new ArgumentOutOfRangeException(nameof(sourceStepBusinessType), sourceStepBusinessType, null);
-        }
-    }
-
     private NextStepOption GetCsEndStepByTargetPrev(List<WorkflowStep> steps, WorkflowStep step)
     {
         var prevStep = steps.FirstOrDefault(d => d.Id == step.PrevStepId);

+ 2 - 2
src/Hotline.Application/Handlers/FlowEngine/WorkflowEndHandler.cs

@@ -141,7 +141,7 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
             case WorkflowModuleConsts.OrderHandle:
                 var order = await _orderDomainService.GetOrderAsync(workflow.ExternalId,
                     withExtension: true, cancellationToken: cancellationToken);
-                order.CheckIfFiled();
+                //order.CheckIfFiled();
                 _mapper.Map(workflow, order);
                 var now = DateTime.Now;
                 var handleDuration = order.StartTime.HasValue
@@ -217,7 +217,7 @@ public class WorkflowEndHandler : INotificationHandler<EndWorkflowNotify>
                         {
                             var screenSatisfy = new Kv() { Key = "-1", Value = "视为满意" };
                             visitDetail.OrgProcessingResults = screenSatisfy;
-                            visitDetail.OrgHandledAttitude = screenSatisfy;
+                            //visitDetail.OrgHandledAttitude = screenSatisfy;
                             await _orderVisitedDetailRepository.UpdateAsync(visitDetail, cancellationToken);
                             // 修改主表当前评价结果
                             await _orderVisitRepository.Updateable().SetColumns(v => new OrderVisit() { NowEvaluate = screenSatisfy }).Where(v => v.Id == visitDetail.VisitId).ExecuteCommandAsync(cancellationToken);

+ 104 - 0
src/Hotline.Application/Mappers/WebPortalMapperConfigs.cs

@@ -0,0 +1,104 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.WebPortal;
+using Hotline.Share.Enums.Order;
+using Hotline.WebPortal;
+using Mapster;
+
+namespace Hotline.Application.Mappers
+{
+    public class WebPortalMapperConfigs : IRegister
+    {
+        public void Register(TypeAdapterConfig config)
+        {
+            #region 门户网站数据
+
+            //办件摘编列表
+            config.ForType<OrderPublish, OrderListDto>()
+               .Ignore(d => d.Source)
+               .Map(d => d.FlowID, x => x.OrderId)
+               .Map(d => d.FlowCode, x => x.Order.No)
+               .Map(d => d.FlowPwd, x => x.Order.Password)
+               .Map(d => d.FlowTitle, x => string.IsNullOrEmpty(x.ArrangeTitle) ? x.Order.Title : x.ArrangeTitle)
+               .Map(d => d.FlowFromName, x => x.Order.SourceChannel)
+               .Map(d => d.FlowPurTypeName, x => x.Order.AcceptType)
+               .Map(d => d.ConTypeName, x => x.Order.HotspotName)
+               .Map(d => d.FlowAddDate, x => x.Order.StartTime)
+               .Map(d => d.PubDate, x => x.CreationTime)
+               .Map(d => d.RSFlagName, x => x.Order.Status < EOrderStatus.Filed ? "办理中" : "办理完成")
+          ;
+
+            //办件摘编详情
+            config.ForType<Order, OrderDetail>()
+            .Map(d => d.FlowID, x => x.Id)
+            .Map(d => d.FlowCode, x => x.No)
+            .Map(d => d.Pwd, x => x.Password)
+             .Map(d => d.FlowTitle, x => x.Title)
+            .Map(d => d.FlowPubFlagName, x => "不公开")
+              .Map(d => d.FlowContent, x => x.Content)
+             .Map(d => d.FlowResult, x => x.ActualOpinion)
+            .Map(d => d.FlowFromName, x => x.SourceChannel)
+            .Map(d => d.FlowPurTypeName, x => x.AcceptType)
+            .Map(d => d.FlowConTypeName, x => x.HotspotName)
+            .Map(d => d.FlowAddDate, x => x.StartTime)
+            .Map(d => d.FlowEndDate, x => x.ActualHandleTime)
+            .Map(d => d.FlowBMID, x => x.ActualHandleOrgCode)
+            .Map(d => d.FlowBMName, x => x.ActualHandleOrgName)
+            .Map(d => d.FlowLKName, x => x.FromName)
+            .Map(d => d.FlowRSFlagName, x => x.Status < EOrderStatus.Filed ? "办理中" : "办理完成")
+       ;
+
+            //注册用户数据
+            config.ForType<UserModelDto, WebUserRegister>()
+            .Map(d => d.WebUserName, x => x.name)
+            .Map(d => d.WebLoginName, x => x.username)
+            .Map(d => d.PhoneNum, x => x.employeeMobile)
+            .Map(d => d.IDCard, x => x.idNumber)
+            .Map(d => d.Address, x => x.address)
+            .Map(d => d.Gender, x => x.sex)
+            ;
+
+            //统一认证数据
+            config.ForType<UserModelDto, WebUserAuth>()
+            .Map(d => d.DataId, x => x.id)
+            .Map(d => d.Type, x => x.type)
+            .Map(d => d.Address, x => x.address)
+            .Map(d => d.Birth, x => x.birth)
+            .Map(d => d.DocumentType, x => x.document_type)
+            .Map(d => d.IdNumber, x => x.idNumber)
+            .Map(d => d.Nationality, x => x.nationality)
+            .Map(d => d.Name, x => x.name)
+            .Map(d => d.Sex, x => x.sex)
+            .Map(d => d.DisplayName, x => x.displayName)
+            .Map(d => d.Username, x => x.username)
+            .Map(d => d.EmployeeMobile, x => x.employeeMobile)
+            ;
+
+            //门户网站写信
+            config.ForType<WebFlowAccept, AddOrderDto>()
+            .Map(d => d.FromName, x => x.LKName)
+            .Map(d => d.FromGender, x => x.Sex == "男" ? EGender.Male : x.Sex == "女" ? EGender.Female : EGender.Unknown)
+            .Map(d => d.IsSecret, x => x.IsSecret == "1" ? true : false)
+            .Map(d => d.LicenceNo, x => x.IDCard)
+            .Map(d => d.Contact, x => x.Mobile)
+            // .Map(d => d.FullAddress, x => x.Address)
+            .Map(d => d.Title, x => x.Title)
+            .Map(d => d.Content, x => x.Content)
+            .Map(d => d.AcceptType, x => x.PurTypeName)
+            .Map(d => d.AcceptTypeCode, x => x.PurTypeID)
+            ;
+
+            //回访部门
+            config.ForType<OrderVisitDetail, WaitVisitListDataDto>()
+            .Map(d => d.Id, x => x.Id)
+            .Map(d => d.VisitId, x => x.VisitId)
+            .Map(d => d.VisitOrgCode, x => x.VisitOrgCode)
+            .Map(d => d.VisitOrgName, x => x.VisitOrgName)
+            .Map(d => d.VisitContent, x => x.VisitContent)
+            .Map(d => d.SatisfactionCode, x => x.OrgProcessingResults != null ? x.OrgProcessingResults.Value : "")
+            ;
+
+            #endregion
+        }
+    }
+}

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

@@ -6,6 +6,7 @@ using System.Text;
 using System.Threading.Tasks;
 using Hotline.Orders;
 using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
@@ -51,6 +52,14 @@ namespace Hotline.Application.Orders
         /// </summary>
         Task UpdateOrderFilesAnonymousAsync(UpdateOrderFilesDto dto, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// 工单回访
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task OrderVisitWeb(OrderVisitWebDto dto, CancellationToken cancellationToken);
+
         #region 工单办理
 
         ISugarQueryable<Order> QueryOrders(QueryOrderDto dto);

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

@@ -1,4 +1,6 @@
-using Hotline.Caching.Interfaces;
+using DotNetCore.CAP;
+using Hotline.Application.Quality;
+using Hotline.Caching.Interfaces;
 using Hotline.File;
 using Hotline.FlowEngine.Workflows;
 using Hotline.Orders;
@@ -6,22 +8,21 @@ using Hotline.Repository.SqlSugar.Extensions;
 using Hotline.Repository.SqlSugar.Ts;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.DataSharing.PusherHotlineDto;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Enums.Order;
+using Hotline.Share.Enums.Quality;
 using Hotline.Share.Enums.Settings;
 using Hotline.Tools;
-using Hotline.Users;
 using MapsterMapper;
-using MediatR;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Constants;
 using XF.Domain.Dependency;
-using XF.Domain.Entities;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 
@@ -39,6 +40,10 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     private readonly IRepositoryTextSearch<OrderTs> _repositoryts;
     private readonly IFileRepository _fileRepository;
     private readonly ISessionContext _sessionContext;
+    private readonly IRepository<OrderVisit> _orderVisitRepository;
+    private readonly IRepository<OrderVisitDetail> _orderVisitDetailRepository;
+    private readonly IQualityApplication _qualityApplication;
+    private readonly ICapPublisher _capPublisher;
 
     public OrderApplication(
         IOrderDomainService orderDomainService,
@@ -50,7 +55,11 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<OrderWord> orderWrodRepository,
         IRepositoryTextSearch<OrderTs> repositoryts,
         IFileRepository fileRepository,
-        ISessionContext sessionContext
+        ISessionContext sessionContext,
+        IRepository<OrderVisit> orderVisitRepository,
+        IRepository<OrderVisitDetail> orderVisitDetailRepository,
+        IQualityApplication qualityApplication,
+        ICapPublisher capPublisher
         )
     {
         _orderDomainService = orderDomainService;
@@ -63,6 +72,10 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _repositoryts = repositoryts;
         _fileRepository = fileRepository;
         _sessionContext = sessionContext;
+        _orderVisitRepository = orderVisitRepository;
+        _orderVisitDetailRepository = orderVisitDetailRepository;
+        _qualityApplication = qualityApplication;
+        _capPublisher = capPublisher;
     }
 
     /// <summary>
@@ -319,6 +332,69 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         }
     }
 
+    /// <summary>
+    /// 工单回访
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task OrderVisitWeb(OrderVisitWebDto dto, CancellationToken cancellationToken)
+    {
+        var visit = await _orderVisitRepository.Queryable()
+            .Includes(x => x.Order)
+            .Includes(x => x.OrderVisitDetails)
+            .FirstAsync(x => x.Id == dto.Id);
+        if (visit != null)
+        {
+            var first = dto.OrderVisitDetailDto.FirstOrDefault(x => x.VisitTarget == EVisitTarget.Org);
+
+            if (first != null)
+            {
+                visit.NowEvaluate = first.OrgProcessingResults;
+                visit.Order.Visited(first.OrgProcessingResults.Key, first.OrgProcessingResults.Value);
+            }
+            visit.VisitState = EVisitState.Visited;
+            visit.VisitTime = dto.VisitTime;
+            visit.VisitType = dto.VisitType;
+
+            for (int i = 0; i < visit.OrderVisitDetails.Count; i++)
+            {
+                var detail = visit.OrderVisitDetails[i];
+                var detaildto = dto.OrderVisitDetailDto.FirstOrDefault(x => x.Id == detail.Id);
+                if (detaildto != null)
+                {
+                    _mapper.Map(detaildto, visit.OrderVisitDetails[i]);
+                }
+            }
+
+            await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
+            await _orderVisitDetailRepository.UpdateRangeAsync(visit.OrderVisitDetails, cancellationToken);
+            await _orderRepository.UpdateAsync(visit.Order, cancellationToken);
+            var orderDto = _mapper.Map<OrderDto>(visit.Order);
+            if (first != null)
+            {
+                //推省上
+                await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
+                        new PublishVisitDto()
+                        {
+                            Order = orderDto,
+                            No = visit.No,
+                            VisitType = visit.VisitType,
+                            VisitName = visit.CreatorName,
+                            VisitTime = visit.VisitTime,
+                            VisitRemark = string.IsNullOrEmpty(first.VisitContent) ? first.OrgProcessingResults?.Value : first.VisitContent,
+                            AreaCode = visit.Order.AreaCode!,
+                            SubjectResultSatifyCode = first.OrgProcessingResults.Key,
+                            FirstSatisfactionCode = visit.Order.FirstVisitResultCode!,
+                            ClientGuid = ""
+                        }, cancellationToken: cancellationToken);
+            }
+            //写入质检
+            await _qualityApplication.AddQualityAsync(EQualitySource.Visit, orderDto.Id, visit.Id,
+                cancellationToken);
+        }
+    }
+
     public ISugarQueryable<Order> QueryOrders(QueryOrderDto dto)
     {
         var isCenter = _sessionContext.OrgIsCenter;

+ 10 - 8
src/Hotline.Application/Orders/OrderSecondaryHandlingApplication.cs

@@ -22,6 +22,7 @@ using XF.Domain.Repository;
 using Hotline.Share.Requests;
 using Hotline.Settings;
 using Novacode;
+using Hotline.Share.Enums.FlowEngine;
 
 namespace Hotline.Application.Orders
 {
@@ -160,7 +161,8 @@ namespace Hotline.Application.Orders
 				};
 				var reTransactNum = order.ReTransactNum is null ? 1 : order.ReTransactNum + 1;
 				var expiredTime = _timeLimitDomainService.CalcEndTime(DateTime.Now, order.AcceptTypeCode);
-				await _orderRepository.Updateable().SetColumns(o => new Order() { ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime, ReTransactNum = reTransactNum })
+				var processType = step.FlowDirection == EFlowDirection.OrgToCenter || step.FlowDirection == EFlowDirection.CenterToCenter ? EProcessType.Zhiban : EProcessType.Jiaoban;
+				await _orderRepository.Updateable().SetColumns(o => new Order() { ExpiredTime = expiredTime.ExpiredTime, NearlyExpiredTime = expiredTime.NearlyExpiredTime, ReTransactNum = reTransactNum, ProcessType = processType })
 					.Where(o => o.Id == order.Id).ExecuteCommandAsync(cancellationToken);
 				var orderDto = _mapper.Map<OrderDto>(order);
 				await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderExpiredTimeUpdate, orderDto, cancellationToken: cancellationToken);
@@ -225,7 +227,7 @@ namespace Hotline.Application.Orders
 				.WhereIF(dto.VisitTime.HasValue && dto.EndVisitTime.HasValue, x => x.OrderVisit.VisitTime == dto.VisitTime && x.OrderVisit.VisitTime <= dto.EndVisitTime)
 				.WhereIF(!string.IsNullOrEmpty(dto.VisitOrgName), x => x.VisitOrgName!.Contains(dto.VisitOrgName!))
 				.WhereIF(!string.IsNullOrEmpty(dto.OrgProcessingResults), x => SqlFunc.JsonField(x.OrgProcessingResults, "Key") == dto.OrgProcessingResults)
-				.WhereIF(!string.IsNullOrEmpty(dto.OrgHandledAttitude), x => SqlFunc.JsonListObjectAny(x.OrgHandledAttitude, "Key", dto.OrgHandledAttitude))
+				//.WhereIF(!string.IsNullOrEmpty(dto.OrgHandledAttitude), x => SqlFunc.JsonListObjectAny(x.OrgHandledAttitude, "Key", dto.OrgHandledAttitude))
 				.WhereIF(!string.IsNullOrEmpty(dto.OrgNoSatisfiedReason), x => SqlFunc.JsonField(x.OrgNoSatisfiedReason, "Key") == dto.OrgNoSatisfiedReason)
 				.Where((x, s) => x.OrderVisit.VisitState != EVisitState.None && x.OrderVisit.IsCanHandle)
 				.Where((x, s) => x.OrderVisit.Order.CounterSignType == null && x.OrderVisit.Order.ActualHandleOrgCode == _sessionContext.OrgId)
@@ -237,9 +239,9 @@ namespace Hotline.Application.Orders
 								  x.OrderVisit.Order.No.Contains(dto.Keyword!))
 					.Where((x, s) => x.VisitTarget == EVisitTarget.Org && x.VisitOrgCode.StartsWith(_sessionContext.OrgId) && (
 						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
-						SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
+						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" 
+						//||SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
+						//SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
 					));
 			}
 			else
@@ -249,9 +251,9 @@ namespace Hotline.Application.Orders
 								  x.OrderVisit.Order.No.Contains(dto.Keyword!))
 					.Where((x, s) => x.VisitTarget == EVisitTarget.Org && (
 						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" ||
-						SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
-						SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
+						SqlFunc.JsonField(x.OrgProcessingResults, "Key") == "2" 
+						//||SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "1" ||
+						//SqlFunc.JsonField(x.OrgHandledAttitude, "Key") == "2"
 					));
 			}
 

+ 39 - 121
src/Hotline.Application/Subscribers/DatasharingSubscriber.cs

@@ -1,5 +1,6 @@
 using DotNetCore.CAP;
 using Hotline.Application.FlowEngine;
+using Hotline.Application.Orders;
 using Hotline.Application.Quality;
 using Hotline.Authentications;
 using Hotline.File;
@@ -50,6 +51,7 @@ namespace Hotline.Application.Subscribers
         private readonly ISystemOrganizeRepository _systemOrganizeRepository;
         private readonly IRepository<TranspondCityRawData> _transpondCityRawDataRepository;
         private readonly IRepository<Workflow> _workflowRepository;
+        private readonly IOrderApplication _orderApplication;
 
         public DataSharingSubscriber(
             IRepository<OrderVisit> orderVisitRepository,
@@ -72,7 +74,8 @@ namespace Hotline.Application.Subscribers
             IRepository<SystemDicData> systemDicDataRepository,
             ISystemOrganizeRepository systemOrganizeRepository,
             IRepository<TranspondCityRawData> transpondCityRawDataRepository,
-            IRepository<Workflow> workflowRepository
+            IRepository<Workflow> workflowRepository,
+            IOrderApplication orderApplication
             )
         {
             _orderSendBackRepository = orderSendBackRepository;
@@ -95,6 +98,7 @@ namespace Hotline.Application.Subscribers
             _systemOrganizeRepository = systemOrganizeRepository;
             _transpondCityRawDataRepository = transpondCityRawDataRepository;
             _workflowRepository = workflowRepository;
+            _orderApplication = orderApplication;
         }
 
         /// <summary>
@@ -113,10 +117,24 @@ namespace Hotline.Application.Subscribers
                 await _orderRepository.UpdateAsync(order, cancellationToken);
                 if (dto.Result is 1)
                 {
-                    if (!string.IsNullOrEmpty(order.WorkflowId))
+                    var current = SessionContextCreator.CreateSessionContext(dto.Source);
+                    if (string.IsNullOrEmpty(order.WorkflowId))
                     {
-                        var current = SessionContextCreator.CreateSessionContext(dto.Source);
-                        await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, "省工单同意退回", null, EReviewResult.Approval, cancellationToken);
+                        var startDto = new StartWorkflowDto
+                        {
+                            DefinitionModuleCode = WorkflowModuleConsts.OrderHandle,
+                            Title = order.Title,
+                            Opinion = dto.Reason ?? "省工单同意退回",
+                        };
+                        await _workflowApplication.StartToEndAsync(startDto, current, order.Id, order.ExpiredTime,
+                            cancellationToken);
+                    }
+                    else
+                    {
+                        //await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, "省工单同意退回", null,
+                        //    EReviewResult.Approval, cancellationToken);
+                        await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Reason ?? "省工单同意退回",
+                            null, cancellationToken: cancellationToken);
                     }
                 }
             }
@@ -146,7 +164,9 @@ namespace Hotline.Application.Subscribers
             }
             else
             {
-                await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, dto.Opinion, null,
+                //await _workflowApplication.HandleToEndAsync(current, order.WorkflowId, dto.Opinion, null,
+                //    cancellationToken: cancellationToken);
+                await _workflowApplication.JumpToEndAsync(current, order.WorkflowId, dto.Opinion, null,
                     cancellationToken: cancellationToken);
             }
         }
@@ -318,7 +338,8 @@ namespace Hotline.Application.Subscribers
                 Kv orgProcessingResults = null;
                 if (!string.IsNullOrEmpty(processingResult))
                 {
-                    var dicData = await _systemDicDataRepository.GetAsync(x => x.DicTypeCode == SysDicTypeConsts.VisitSatisfaction && x.DicDataValue == processingResult, cancellationToken);
+                    var dicData = await _systemDicDataRepository.GetAsync(x => x.DicTypeCode == SysDicTypeConsts.VisitSatisfaction &&
+                                                                               x.DicDataValue == processingResult, cancellationToken);
                     if (dicData != null)
                     {
                         orgProcessingResults = new Kv();
@@ -331,7 +352,7 @@ namespace Hotline.Application.Subscribers
                         orderVisit.VisitState = Share.Enums.Order.EVisitState.Visited;
                         orderVisit.VisitTime = dto.VisitTime;
                         orderVisit.VisitType = dto.VisitType;
-                        orderVisit.IsCanHandle = false;
+                        orderVisit.IsCanHandle = orgProcessingResults.Key == "2";
                         orderVisit.IsCanAiVisit = false;
                         orderVisit.NowEvaluate = orgProcessingResults;
                         await _orderVisitRepository.UpdateAsync(orderVisit, cancellationToken);
@@ -393,113 +414,7 @@ namespace Hotline.Application.Subscribers
         [CapSubscribe(Hotline.Share.Mq.EventNames.SharingOrderVisitAllOther)]
         public async Task OrderVisitWeb(OrderVisitWebDto dto, CancellationToken cancellationToken)
         {
-            var visit = await _orderVisitRepository.Queryable()
-                .Includes(x => x.Order)
-                .Includes(x => x.OrderVisitDetails)
-                .FirstAsync(x => x.Id == dto.Id);
-            if (visit != null)
-            {
-                var first = dto.OrderVisitDetailDto.FirstOrDefault(x => x.VisitTarget == EVisitTarget.Org);
-
-                if (first != null)
-                {
-                    visit.NowEvaluate = first.OrgProcessingResults;
-                    visit.Order.Visited(first.OrgProcessingResults.Key, first.OrgProcessingResults.Value);
-                }
-                visit.VisitState = EVisitState.Visited;
-                visit.VisitTime = dto.VisitTime;
-                visit.VisitType = dto.VisitType;
-
-                for (int i = 0; i < visit.OrderVisitDetails.Count; i++)
-                {
-                    var detail = visit.OrderVisitDetails[i];
-                    var detaildto = dto.OrderVisitDetailDto.FirstOrDefault(x => x.Id == detail.Id);
-                    if (detaildto != null)
-                    {
-                        _mapper.Map(detaildto, visit.OrderVisitDetails[i]);
-                    }
-                }
-
-                await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
-                await _orderVisitDetailRepository.UpdateRangeAsync(visit.OrderVisitDetails, cancellationToken);
-                await _orderRepository.UpdateAsync(visit.Order, cancellationToken);
-                var orderDto = _mapper.Map<OrderDto>(visit.Order);
-                if (first != null)
-                {
-                    //推省上
-                    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisited,
-                            new PublishVisitDto()
-                            {
-                                Order = orderDto,
-                                No = visit.No,
-                                VisitType = visit.VisitType,
-                                VisitName = visit.CreatorName,
-                                VisitTime = visit.VisitTime,
-                                VisitRemark = string.IsNullOrEmpty(first.VisitContent) ? first.OrgProcessingResults?.Value : first.VisitContent,
-                                AreaCode = visit.Order.AreaCode!,
-                                SubjectResultSatifyCode = first.OrgProcessingResults.Key,
-                                FirstSatisfactionCode = visit.Order.FirstVisitResultCode!,
-                                ClientGuid = ""
-                            }, cancellationToken: cancellationToken);
-                }
-                //处理网站通知差评数据
-                //if (visit.Order.Source == ESource.Hotline && visit.OrderVisitDetails.Any(x => x.OrgHandledAttitude?.Key == "1" || x.OrgHandledAttitude?.Key == "2" || x.OrgProcessingResults?.Key == "1" || x.OrgProcessingResults?.Key == "2"))
-                //{
-                //    //处理老数据
-                //    visit.VisitState = EVisitState.None;
-                //    await _orderVisitRepository.UpdateAsync(visit, cancellationToken);
-                //    //推送老数据变更给门户
-                //    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
-                //    {
-                //        Id = visit.Id,
-                //        Order = orderDto,
-                //        OrderVisitDetails = _mapper.Map<List<VisitDetailDto>>(visit.OrderVisitDetails),
-                //        VisitTime = visit.VisitTime,
-                //        VisitType = visit.VisitType,
-                //        VisitState = visit.VisitState,
-                //        PublishTime = visit.PublishTime,
-                //    }, cancellationToken: cancellationToken);
-
-                //    //包含不满意数据,重新生成新的回访
-                //    var newOrderVisit = _mapper.Map<OrderVisit>(visit);
-                //    newOrderVisit.InitId();
-                //    newOrderVisit.VisitState = EVisitState.NoSatisfiedWaitForVisit;
-                //    newOrderVisit.VisitTime = null;
-                //    newOrderVisit.IsCanHandle = false;
-                //    newOrderVisit.IsCanAiVisit = false;
-                //    newOrderVisit.AiVisitCount = 0;
-                //    await _orderVisitRepository.AddAsync(newOrderVisit, cancellationToken);
-                //    var list = _mapper.Map<List<OrderVisitDetail>>(visit.OrderVisitDetails);
-                //    list.ForEach(x =>
-                //    {
-                //        x.VisitId = newOrderVisit.Id;
-                //        x.VoiceEvaluate = null;
-                //        x.VoiceEvaluate = null;
-                //        x.OrgHandledAttitude = null;
-                //        x.OrgNoSatisfiedReason = null;
-                //        x.OrgProcessingResults = null;
-                //        x.VisitContent = "";
-                //    });
-                //    await _orderVisitDetailRepository.AddRangeAsync(list, cancellationToken);
-                //    //推送新数据给门户
-                //    await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.HotlineOrderVisitedWeb, new PublishVisitAllDto()
-                //    {
-                //        Id = newOrderVisit.Id,
-                //        Order = orderDto,
-                //        OrderVisitDetails = _mapper.Map<List<VisitDetailDto>>(list),
-                //        VisitTime = newOrderVisit.VisitTime,
-                //        VisitType = newOrderVisit.VisitType,
-                //        VisitState = newOrderVisit.VisitState,
-                //        PublishTime = newOrderVisit.PublishTime,
-                //    }, cancellationToken: cancellationToken);
-                //}
-                //else
-                //{
-                //写入质检
-                await _qualityApplication.AddQualityAsync(EQualitySource.Visit, orderDto.Id, visit.Id,
-                    cancellationToken);
-                //}
-            }
+            await _orderApplication.OrderVisitWeb(dto, cancellationToken);
         }
 
         /// <summary>
@@ -518,7 +433,10 @@ namespace Hotline.Application.Subscribers
                 var orderDelay = await _orderDelayRepository.GetAsync(x => x.OrderId == order.Id && x.DelayState == EDelayState.Examining, cancellationToken);
                 var current = SessionContextCreator.CreateSessionContext(dto.Source);
                 await _workflowApplication.HandleToEndAsync(current, orderDelay.WorkflowId, dto.Opinion, dto.Files,
-                    dto.IsPass ? Share.Enums.FlowEngine.EReviewResult.Approval : Share.Enums.FlowEngine.EReviewResult.Failed, cancellationToken);
+                    dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed, cancellationToken);
+
+                //await _workflowApplication.JumpToEndAsync(current, orderDelay.WorkflowId, dto.Opinion, dto.Files,
+                //    dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed, cancellationToken);
             }
         }
 
@@ -534,12 +452,12 @@ namespace Hotline.Application.Subscribers
             if (string.IsNullOrEmpty(order.WorkflowId))
                 throw new UserFriendlyException($"该工单未开启流程,orderId: {dto.OrderId}");
 
-            if (dto.Files != null && dto.Files.Any())
-            {
-                order.FileJson = await _fileRepository.AddFileAsync(dto.Files, order.Id, order.WorkflowId,
-                    cancellationToken);
-                await _orderRepository.FileAsync(order, cancellationToken);
-            }
+            //if (dto.Files != null && dto.Files.Any())
+            //{
+            //    order.FileJson = await _fileRepository.AddFileAsync(dto.Files, order.Id, order.WorkflowId,
+            //        cancellationToken);
+            //    await _orderRepository.FileAsync(order, cancellationToken);
+            //}
 
             var current = SessionContextCreator.CreateSessionContext(dto.Source);
             switch (dto.FinishType)

+ 7 - 0
src/Hotline.Share/Dtos/Ai/AiDto.cs

@@ -42,6 +42,13 @@ namespace Hotline.Share.Dtos.Ai
         /// </summary>
         public int CallStatus { get; set; }
 
+        /// <summary>
+        /// 拨通时间
+        /// </summary>
+        public DateTime CallStartTime { get; set; }
+
+        public string? RecordUrl { get; set; }
+
         /// <summary>
         /// 回访结果 (1成功、2:不涉及、3:失败)
         /// </summary>

+ 14 - 1
src/Hotline.Share/Dtos/Order/OrderVisitDto.cs

@@ -119,6 +119,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public bool? Volved { get; set; }
 
+        /// <summary>
+        /// 未解决备注
+        /// </summary>
+        public string? VolveConent { get; set; }
+
         /// <summary>
         /// 回访内容
         /// </summary>
@@ -353,7 +358,10 @@ namespace Hotline.Share.Dtos.Order
 
         public Kv? NowEvaluate { get; set; }
 
-
+        /// <summary>
+        /// 智能回访录音地址
+        /// </summary>
+        public string? RecordUrl { get; set; }
     }
 
     public class OrderVisitDetailDto
@@ -442,6 +450,11 @@ namespace Hotline.Share.Dtos.Order
         /// 回访对象类型 10:话务员 20:部门
         /// </summary>
         public EVisitTarget VisitTarget { get; set; }
+
+        /// <summary>
+        /// 未解决备注
+        /// </summary>
+        public string? VolveConent { get; set; }
     }
 
     public class DistributionVisitRspDto

+ 1 - 1
src/Hotline.Share/Dtos/Users/UserDto.cs

@@ -63,7 +63,7 @@ public record AddUserDto
     /// <summary>
     /// 部门全名
     /// </summary>
-    //public string? FullOrgName { get; set; }
+    public string? FullOrgName { get; set; }
 
     /// <summary>
     /// 用户类型

+ 277 - 0
src/Hotline.Share/Dtos/WebPortal/ArticleDetailsDto.cs

@@ -0,0 +1,277 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.WebPortal
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    public class ArticleIdDto
+    {
+        /// <summary>
+        /// 公告ID
+        /// </summary>
+        public string Id { get; set; }
+    }
+
+    public class ArticleIdByNumDto
+    {
+        /// <summary>
+        /// 公告分类
+        /// </summary>
+        public string? BulletinTypeId { get; set; }
+
+        /// <summary>
+        /// 条数
+        /// </summary>
+        public int Num { get; set; }
+
+        /// <summary>
+        /// 是否查询图片
+        /// </summary>
+        public string CheckChar { get; set; }
+    }
+
+    /// <summary>
+    /// 公告上一条下一条
+    /// </summary>
+    public class ArticlePreviousAndNextDto
+    {
+        /// <summary>
+        /// 公告ID
+        /// </summary>
+        public string ID { get; set; }
+
+        /// <summary>
+        /// 公告分类
+        /// </summary>
+        public string? NoticeTypeId { get; set; }
+
+        /// <summary>
+        /// 关键字
+        /// </summary>
+        public string? Condition { get; set; }
+
+        /// <summary>
+        /// 是否全文查找
+        /// </summary>
+        public string? FullSearch { get; set; }
+
+    }
+
+    /// <summary>
+    /// 公告上一条下一条 返回数据
+    /// </summary>
+    public class ArticlePreviousAndNextDataDto
+    {
+        /// <summary>
+        /// 上一条P,下一条N
+        /// </summary>
+        public string pntype { get; set; }
+
+        /// <summary>
+        /// 公告ID
+        /// </summary>
+        public string NoticeID { get; set; }
+
+        /// <summary>
+        /// 公告标题
+        /// </summary>
+        public string NoticeTitle { get; set; }
+    }
+
+    /// <summary>
+    /// 公告详情
+    /// </summary>
+    public class ArticleDetailsDto
+    {
+        /// <summary>
+        /// 公告ID
+        /// </summary>
+        public string NoticeID { get; set; }
+
+        /// <summary>
+        /// 分类名称
+        /// </summary>
+        public string NoticeTypeName { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string NoticeTitle { get; set; }
+
+        /// <summary>
+        /// 发布部门
+        /// </summary>
+        public string NoticeBMName { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime? NoticeCreateDate { get; set; }
+
+        /// <summary>
+        /// 阅读次数
+        /// </summary>
+        public int NoticeRCount { get; set; }
+
+        /// <summary>
+        /// 视频地址
+        /// </summary>
+        public string WNED_VideoUrl { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string NoticeContent { get; set; }
+    }
+
+    /// <summary>
+    /// 公告列表返回数据
+    /// </summary>
+    public class ArticleListDataDto
+    {
+        /// <summary>
+        /// 总页数
+        /// </summary>
+        public int PageCount { get; set; } = 0;
+
+        /// <summary>
+        /// 数据
+        /// </summary>
+        public IReadOnlyList<ArticleListDto> data { get; set; }
+    }
+
+    /// <summary>
+    /// 公告列表
+    /// </summary>
+    public class ArticleListDto
+    {
+        /// <summary>
+        /// 页码
+        /// </summary>
+        public int Page { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string NoticeContent { get; set; }
+
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string NoticeID { get; set; }
+
+        /// <summary>
+        /// 分类名称
+        /// </summary>
+        public string NoticeTypeName { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string NoticeTitle { get; set; }
+
+        /// <summary>
+        /// 部门
+        /// </summary>
+        public string NoticeBMName { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public DateTime? NoticeCreateDate { get; set; }
+
+        /// <summary>
+        /// 视频地址
+        /// </summary>
+        public string VideoUrl { get; set; }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        public string WNLT_FullCode { get; set; }
+    }
+
+    /// <summary>
+    /// 公告列表查询参数
+    /// </summary>
+    public record QueryArticleListDto : PagedRequest
+    {
+        /// <summary>
+        /// 公告分类
+        /// </summary>
+        public string? NoticeType { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string? Condition { get; set; }
+    }
+
+    /// <summary>
+    /// 全文搜索
+    /// </summary>
+    public class FullTextSearchListDto
+    {
+        /// <summary>
+        /// 索引
+        /// </summary>
+        public int Page { get; set; }
+
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string NoticeID { get; set; }
+
+        /// <summary>
+        /// 分类名称ID
+        /// </summary>
+        public string NoticeTypeID { get; set; }
+
+        /// <summary>
+        /// 分类名称
+        /// </summary>
+        public string NoticeTypeName { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string NoticeTitle { get; set; }
+
+        /// <summary>
+        /// 创建部门
+        /// </summary>
+        public string NoticeBMName { get; set; }
+
+        /// <summary>
+        /// 创建时间
+        /// </summary>
+        public string NoticeCreateDate { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        public string TypeCode { get; set; }
+    }
+
+    /// <summary>
+    /// 全文搜索返回参数
+    /// </summary>
+    public class FullTextSearchListDataDto
+    {
+        /// <summary>
+        /// 总页数
+        /// </summary>
+        public int PageCount { get; set; } = 0;
+
+        /// <summary>
+        /// 数据
+        /// </summary>
+        public IReadOnlyList<FullTextSearchListDto> data { get; set; }
+    }
+}

+ 357 - 0
src/Hotline.Share/Dtos/WebPortal/GetOrderCodePwd.cs

@@ -0,0 +1,357 @@
+using Hotline.Share.Requests;
+
+namespace Hotline.Share.Dtos.WebPortal
+{
+    /// <summary>
+    /// 
+    /// </summary>
+    public class GetOrderCodePwd
+    {
+        public string OrderNo { get; set; }
+        public string Pwd { get; set; }
+    }
+
+    /// <summary>
+    /// 获取前几条数据
+    /// </summary>
+    public class QueryOrderListByNumDto
+    {
+        public int Num { get; set; }
+    }
+
+    /// <summary>
+    /// 会员中心查询用户信件
+    /// </summary>
+    public record QueryOrderListByUserDto : PagedRequest
+    {
+        /// <summary>
+        /// 信件编号
+        /// </summary>
+        public string? UserId { get; set; }
+    }
+
+    /// <summary>
+    /// 办件摘编列表查询参数
+    /// </summary>
+    public record QueryOrderListDto : PagedRequest
+    {
+        /// <summary>
+        /// 信件编号
+        /// </summary>
+        public string? FlowCode { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string? FlowName { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public string? FlowSType { get; set; }
+
+        /// <summary>
+        /// 热点类型
+        /// </summary>
+        public string? FlowRType { get; set; }
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        public string? FlowSDate { get; set; }
+
+        /// <summary>
+        /// 结束时间
+        /// </summary>
+        public string? FlowEDate { get; set; }
+
+        /// <summary>
+        /// 来信人
+        /// </summary>
+        public string? FlowFrom { get; set; }
+    }
+
+    /// <summary>
+    /// 办件摘编列表数据
+    /// </summary>
+    public class OrderListDto
+    {
+        /// <summary>
+        /// 信件ID
+        /// </summary>
+        public string FlowID { get; set; }
+
+        /// <summary>
+        /// 信件编号
+        /// </summary>
+        public string FlowCode { get; set; }
+
+        /// <summary>
+        /// 查询密码
+        /// </summary>
+        public string FlowPwd { get; set; }
+
+        /// <summary>
+        /// 信件标题
+        /// </summary>
+        public string FlowTitle { get; set; }
+
+        /// <summary>
+        /// 来源
+        /// </summary>
+        public string FlowFromName { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public string FlowPurTypeName { get; set; }
+
+        /// <summary>
+        /// 热点类型
+        /// </summary>
+        public string ConTypeName { get; set; }
+
+        /// <summary>
+        /// 来信时间
+        /// </summary>
+        public DateTime? FlowAddDate { get; set; }
+
+        /// <summary>
+        /// 发布时间
+        /// </summary>
+        public DateTime? PubDate { get; set; }
+
+        /// <summary>
+        /// 办理状态
+        /// </summary>
+        public string RSFlagName { get; set; }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        public int Source { get; set; } = 1;
+
+    }
+
+    /// <summary>
+    /// 办件摘编列表数据
+    /// </summary>
+    public class OrderListReturnDto
+    {
+        /// <summary>
+        /// 页码
+        /// </summary>
+        public int PageNum { get; set; }
+
+        /// <summary>
+        /// 总页数
+        /// </summary>
+        public int PageCount { get; set; }
+
+        /// <summary>
+        /// 数据对象
+        /// </summary>
+        public IReadOnlyList<OrderListDto> Data { get; set; }
+    }
+
+    /// <summary>
+    /// 信件明细
+    /// </summary>
+    public class OrderDetail
+    {
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string FlowID { get; set; }
+
+        /// <summary>
+        /// 部门id
+        /// </summary>
+        public string FlowBMID { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        public string FlowBMName { get; set; }
+
+        /// <summary>
+        /// 信件编号
+        /// </summary>
+        public string FlowCode { get; set; }
+
+        /// <summary>
+        /// 查询密码
+        /// </summary>
+        public string Pwd { get; set; }
+
+        /// <summary>
+        /// 信件标题
+        /// </summary>
+        public string FlowTitle { get; set; }
+
+        /// <summary>
+        /// 来源
+        /// </summary>
+        public string FlowFromName { get; set; }
+
+        /// <summary>
+        /// 受理类型
+        /// </summary>
+        public string FlowPurTypeName { get; set; }
+
+        /// <summary>
+        /// 热点类型
+        /// </summary>
+        public string FlowConTypeName { get; set; }
+
+        /// <summary>
+        /// 来信时间
+        /// </summary>
+        public DateTime? FlowAddDate { get; set; }
+
+        /// <summary>
+        /// 办结时间
+        /// </summary>
+        public DateTime? FlowEndDate { get; set; }
+
+        /// <summary>
+        /// 来信人
+        /// </summary>
+        public string FlowLKName { get; set; }
+
+        /// <summary>
+        /// 办理状态
+        /// </summary>
+        public string FlowRSFlagName { get; set; }
+
+        /// <summary>
+        /// 是否公开
+        /// </summary>
+        public string FlowPubFlagName { get; set; }
+
+        /// <summary>
+        /// 受理内容
+        /// </summary>
+        public string FlowContent { get; set; }
+
+        /// <summary>
+        /// 办理意见
+        /// </summary>
+        public string FlowResult { get; set; }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        public string PubFlag { get; set; }
+
+        /// <summary>
+        /// 是否是省工单或者政民互动工单 0:否,1:是
+        /// </summary>
+        public string IsProvinceOrder { get; set; }
+
+        /// <summary>
+        /// 是否可以评价 0:不能评价,1:可以评价,2:已评价
+        /// </summary>
+        public string VisitType { get; set; }
+    }
+
+    /// <summary>
+    /// 受理信息返回
+    /// </summary>
+    public class OrderAcceptanceReturnDto
+    {
+        public string State { get; set; }
+
+        public string Code { get; set; }
+
+        public string PWD { get; set; }
+    }
+
+    /// <summary>
+    /// 受理类型统计
+    /// </summary>
+    public class OrderFormCount
+    {
+        /// <summary>
+        /// 名称
+        /// </summary>
+        public string name { get; set; }
+
+        /// <summary>
+        /// 数量
+        /// </summary>
+        public int value { get; set; }
+    }
+
+    /// <summary>
+    /// 热点统计
+    /// </summary>
+    public class OrderHotCount
+    {
+        /// <summary>
+        /// 名称
+        /// </summary>
+        public string typeName { get; set; }
+
+        /// <summary>
+        /// 数量
+        /// </summary>
+        public int num { get; set; }
+    }
+
+    /// <summary>
+    /// 返回数据
+    /// </summary>
+    public class GetChartDataDto
+    {
+        /// <summary>
+        /// 受理类型统计
+        /// </summary>
+        public IReadOnlyList<OrderFormCount> formCount { get; set; }
+
+        /// <summary>
+        /// 热点统计
+        /// </summary>
+        public IReadOnlyList<OrderHotCount> hotCount { get; set; }
+    }
+
+    /// <summary>
+    /// 办件统计
+    /// </summary>
+    public class GetStatistDto
+    {
+        /// <summary>
+        /// 累计接件
+        /// </summary>
+        public int AllCount { get; set; }
+
+        /// <summary>
+        /// 累计已办
+        /// </summary>
+        public int AllTrandCount { get; set; }
+
+        /// <summary>
+        /// 今日接件
+        /// </summary>
+        public int DayCount { get; set; }
+
+        /// <summary>
+        /// 今日已办
+        /// </summary>
+        public int DayTrandCount { get; set; }
+    }
+
+    /// <summary>
+    /// 返回数据
+    /// </summary>
+    public class DataListTopDto
+    {
+        public string DataID { get; set; }
+
+        public string Title { get; set; }
+
+        public DateTime? CreateDate { get; set; }
+
+        public string? Content { get; set; }
+    }
+}

+ 75 - 0
src/Hotline.Share/Dtos/WebPortal/UserModelDto.cs

@@ -0,0 +1,75 @@
+namespace Hotline.Share.Dtos.WebPortal
+{
+    public class UserModelDto
+    {
+        /// <summary>
+        /// 数据获取状态
+        /// true
+        /// false
+        /// </summary>
+        public string success { get; set; }
+
+        /// <summary>
+        /// id 号码
+        /// </summary>
+        public string id { get; set; }
+
+        /// <summary>
+        /// 自然人
+        /// 法人
+        /// </summary>
+        public string type { get; set; }
+
+        /// <summary>
+        /// 地址
+        /// </summary>
+        public string address { get; set; }
+
+        /// <summary>
+        /// 生日
+        /// </summary>
+        public string birth { get; set; }
+
+        /// <summary>
+        /// 文档类型
+        /// </summary>
+        public string document_type { get; set; }
+
+        /// <summary>
+        /// 身份证号码
+        /// </summary>
+        public string idNumber { get; set; }
+
+        /// <summary>
+        /// 未知
+        /// </summary>
+        public string nationality { get; set; }
+
+        /// <summary>
+        /// 姓名
+        /// </summary>
+        public string name { get; set; }
+
+        /// <summary>
+        /// 性别
+        /// 1-男
+        /// 2-女
+        /// </summary>
+        public string sex { get; set; }
+
+        /// <summary>
+        /// 昵称
+        /// </summary>
+        public string displayName { get; set; }
+
+        /// <summary>
+        /// 登陆名
+        /// </summary>
+        public string username { get; set; }
+
+        /// <summary>
+        /// 手机号码
+        /// </summary>
+        public string employeeMobile { get; set; }
+    }
+}

+ 51 - 0
src/Hotline.Share/Dtos/WebPortal/WaitVisitListDataDto.cs

@@ -0,0 +1,51 @@
+namespace Hotline.Share.Dtos.WebPortal
+{
+    public class OrderVisitListDataDto
+    {
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string? OrderId { get; set; }
+
+        public List<WaitVisitListDataDto> VistListDtos { get; set; }
+    }
+
+    public class WaitVisitListDataDto
+    {
+        /// <summary>
+        /// ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 工单ID
+        /// </summary>
+        public string? OrderId { get; set; }
+
+        /// <summary>
+        /// 回访ID
+        /// </summary>
+        public string? VisitId { get; set; }
+
+        /// <summary>
+        /// 回访部门Code
+        /// </summary>
+        public string? VisitOrgCode { get; set; }
+
+        /// <summary>
+        /// 回访部门名称
+        /// </summary>
+        public string? VisitOrgName { get; set; }
+
+        /// <summary>
+        /// 评价内容
+        /// </summary>
+        public string? VisitContent { get; set; }
+
+        /// <summary>
+        /// 满意度
+        /// </summary>
+        public string? SatisfactionCode { get; set; }
+
+    }
+}

+ 55 - 0
src/Hotline.Share/Dtos/WebPortal/WebPortalDeResponse.cs

@@ -0,0 +1,55 @@
+namespace Hotline.Share.Dtos.WebPortal
+{
+    /// <summary>
+    /// 门户数据返回
+    /// </summary>
+    /// <typeparam name="TData"></typeparam>
+    public class WebPortalDeResponse<TData>
+    {
+        /// <summary>
+        /// 状态
+        /// </summary>
+        public string code { get; set; }
+
+        /// <summary>
+        /// 描述
+        /// </summary>
+        public string msg { get; set; }
+
+        /// <summary>
+        /// 数据
+        /// </summary>
+        public TData data { get; set; }
+
+        /// <summary>
+        /// 成功
+        /// </summary>
+        /// <param name="data"></param>
+        /// <param name="description"></param>
+        /// <returns></returns>
+        public static WebPortalDeResponse<TData> Success(TData data, string? description = "")
+        {
+            return new WebPortalDeResponse<TData>
+            {
+                code = "1",
+                data = data,
+                msg = (description ?? "接口调用成功!")
+            };
+        }
+
+        /// <summary>
+        /// 失败
+        /// </summary>
+        /// <param name="code"></param>
+        /// <param name="description"></param>
+        /// <returns></returns>
+        public static WebPortalDeResponse<TData> Failed(string? code = "0", string? description = "")
+        {
+            return new WebPortalDeResponse<TData>
+            {
+                code = code,
+                msg = (description ?? "接口调用失败!")
+            };
+        }
+    }
+}

+ 15 - 0
src/Hotline.Share/Dtos/WebPortal/WebPortalSendSmsModelDto.cs

@@ -0,0 +1,15 @@
+namespace Hotline.Share.Dtos.WebPortal
+{
+    public class WebPortalSendSmsModelDto
+    {
+        /// <summary>
+        /// 号码
+        /// </summary>
+        public string TelNum { get; set; }
+
+        /// <summary>
+        /// 验证码
+        /// </summary>
+        public string SmsCode { get; set; }
+    }
+}

+ 16 - 0
src/Hotline.Share/Dtos/WebPortal/WriteLettersSendSmsDto.cs

@@ -0,0 +1,16 @@
+namespace Hotline.Share.Dtos.WebPortal
+{
+    /// <summary>
+    /// 短信发送缓存设置
+    /// </summary>
+    public class WriteLettersSendSmsDto
+    {
+        public string MobileNum { get; set; }
+
+        public string SmsCode { get; set; }
+
+        public int SendCount { get; set; } = 0;
+
+        public DateTime AddTime { get; set; }
+    }
+}

+ 7 - 1
src/Hotline/FlowEngine/Workflows/IWorkflowDomainService.cs

@@ -56,7 +56,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 退回(返回前一节点)
         /// </summary>
         /// <returns></returns>
-        Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, User operater, ISessionContext current,
+        Task<EFlowDirection> PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, User operater, ISessionContext current,
             CancellationToken cancellationToken);
 
         /// <summary>
@@ -235,5 +235,11 @@ namespace Hotline.FlowEngine.Workflows
         /// </summary>
         /// <returns></returns>
         Task<WorkflowStep> FindLastHandleStepAsync(string workflowId, string orgId, CancellationToken cancellation);
+
+        /// <summary>
+        /// 查询流转方向
+        /// </summary>
+        EFlowDirection GetFlowDirection(EBusinessType sourceStepBusinessType,
+            EBusinessType directionStepBusinessType);
     }
 }

+ 4 - 4
src/Hotline/FlowEngine/Workflows/Workflow.cs

@@ -436,15 +436,15 @@ public partial class Workflow
     /// <summary>
     /// 流程结束
     /// </summary>
-    public void Complete(EReviewResult? reviewResult = EReviewResult.Unknown)
+    public void Complete(EReviewResult reviewResult = EReviewResult.Unknown)
     {
-        if (FlowType is EFlowType.Review && !reviewResult.HasValue)
-            throw new UserFriendlyException("无审核结果");
+        //if (FlowType is EFlowType.Review && !reviewResult.HasValue)
+        //    throw new UserFriendlyException("无审核结果");
 
         Status = EWorkflowStatus.Completed;
         EndTime = DateTime.Now;
         if (FlowType is EFlowType.Review && ReviewResult is EReviewResult.Unknown)
-            ReviewResult = reviewResult.Value;
+            ReviewResult = reviewResult;
 
         ClearHandlers();
 

+ 37 - 1
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -521,7 +521,7 @@ namespace Hotline.FlowEngine.Workflows
         /// 退回(返回前一节点)
         /// </summary>
         /// <returns></returns>
-        public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, User operater,
+        public async Task<EFlowDirection> PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, User operater,
            ISessionContext current, CancellationToken cancellationToken)
         {
             ValidatePermission(workflow, operater.OrgId, operater.Id);
@@ -594,6 +594,8 @@ namespace Hotline.FlowEngine.Workflows
             await _workflowRepository.UpdateAsync(workflow, cancellationToken);
 
             await _mediator.Publish(new PreviousNotify(workflow, newPrevStep, dto, isOrgToCenter), cancellationToken);
+
+            return GetFlowDirection(currentStep.BusinessType, prevStep.BusinessType);
         }
 
         /// <summary>
@@ -773,6 +775,40 @@ namespace Hotline.FlowEngine.Workflows
                 .FirstAsync(cancellation);
         }
 
+        /// <summary>
+        /// 查询流转方向
+        /// </summary>
+        public EFlowDirection GetFlowDirection(EBusinessType sourceStepBusinessType, EBusinessType directionStepBusinessType)
+        {
+            switch (sourceStepBusinessType)
+            {
+                case EBusinessType.Center:
+                case EBusinessType.Send:
+                    return directionStepBusinessType switch
+                    {
+                        EBusinessType.Center => EFlowDirection.CenterToCenter,
+                        EBusinessType.Send => EFlowDirection.CenterToCenter,
+                        EBusinessType.Department => EFlowDirection.CenterToOrg,
+                        EBusinessType.File => EFlowDirection.CenterToFile,
+                        _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType),
+                            directionStepBusinessType, null)
+                    };
+                case EBusinessType.Department:
+                case EBusinessType.File:
+                    return directionStepBusinessType switch
+                    {
+                        EBusinessType.Center => EFlowDirection.OrgToCenter,
+                        EBusinessType.Send => EFlowDirection.OrgToCenter,
+                        EBusinessType.Department => EFlowDirection.OrgToOrg,
+                        EBusinessType.File => EFlowDirection.OrgToFile,
+                        _ => throw new ArgumentOutOfRangeException(nameof(directionStepBusinessType),
+                            directionStepBusinessType, null)
+                    };
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(sourceStepBusinessType), sourceStepBusinessType, null);
+            }
+        }
+
         /// <summary>
         /// 查找当前会签内所有节点(含start,end)
         /// </summary>

+ 5 - 0
src/Hotline/Orders/OrderVisit.cs

@@ -120,6 +120,11 @@ public class OrderVisit : CreationEntity
     [SugarColumn( DefaultValue = "0" )]
     public int AiVisitCount { get; set; }
 
+    /// <summary>
+    /// 智能回访录音地址
+    /// </summary>
+    public string? RecordUrl { get; set; }
+
 
     public void AiVisitTime()
     {

+ 14 - 16
src/Hotline/Orders/OrderVisitDetail.cs

@@ -34,22 +34,20 @@ namespace Hotline.Orders
         /// </summary>
         public ESeatEvaluate? SeatEvaluate { get; set; }
 
-        ///// <summary>
-        ///// 是否联系
-        ///// </summary>
-        //public bool? IsContact { get; set; }
-
-        ///// <summary>
-        ///// 处理结果
-        ///// </summary>
-        //public bool? Volved { get; set; }
-
-        ///// <summary>
-        ///// 未处理内容
-        ///// </summary>
-        //public string? VolveConent { get; set; }
-
-        //public string? RecordUrl { get; set; }
+        /// <summary>
+        /// 是否联系
+        /// </summary>
+        public bool? IsContact { get; set; }
+
+        /// <summary>
+        /// 处理结果
+        /// </summary>
+        public bool? Volved { get; set; }
+
+        /// <summary>
+        /// 未处理内容
+        /// </summary>
+        public string? VolveConent { get; set; }
 
 
         /// <summary>

+ 132 - 0
src/Hotline/WebPortal/WebFlowAccept.cs

@@ -0,0 +1,132 @@
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.WebPortal
+{
+    /// <summary>
+    /// 门户写信
+    /// </summary>
+    public class WebFlowAccept : CreationEntity
+    {
+        /// <summary>
+        /// 门户网站注册用户
+        /// </summary>
+        [SugarColumn(ColumnDescription = "门户网站注册用户")]
+        public string? WebUserID { get; set; }
+
+        /// <summary>
+        /// 工单Id
+        /// </summary>
+        [SugarColumn(ColumnDescription = "工单Id")]
+        public string? OrderId { get; set; }
+
+        /// <summary>
+        /// 姓名
+        /// </summary>
+        [SugarColumn(ColumnDescription = "姓名")]
+        public string LKName { get; set; }
+
+        /// <summary>
+        /// 性别
+        /// </summary>
+        [SugarColumn(ColumnDescription = "性别")]
+        public string? Sex { get; set; }
+
+        /// <summary>
+        /// 是否保密
+        /// </summary>
+        [SugarColumn(ColumnDescription = "是否保密")]
+        public string? IsSecret { get; set; }
+
+        /// <summary>
+        /// 邮箱
+        /// </summary>
+        [SugarColumn(ColumnDescription = "邮箱")]
+        public string? Mail { get; set; }
+
+        /// <summary>
+        /// 身份证
+        /// </summary>
+        [SugarColumn(ColumnDescription = "身份证")]
+        public string? IDCard { get; set; }
+
+        /// <summary>
+        /// 电话
+        /// </summary>
+        [SugarColumn(ColumnDescription = "电话")]
+        public string Mobile { get; set; }
+
+        /// <summary>
+        /// 微博/微信账号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "微博/微信账号")]
+        public string? AccountNum { get; set; }
+
+        /// <summary>
+        /// 地址
+        /// </summary>
+        [SugarColumn(ColumnDescription = "地址")]
+        public string? Address { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        [SugarColumn(ColumnDescription = "标题")]
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 信件内容
+        /// </summary>
+        [SugarColumn(ColumnDescription = "信件内容", ColumnDataType = "varchar(3000)")]
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 来源方式
+        /// </summary>
+        [SugarColumn(ColumnDescription = "来源方式")]
+        public string? FromID { get; set; }
+
+        /// <summary>
+        /// 受理类型ID
+        /// </summary>
+        [SugarColumn(ColumnDescription = "受理类型ID")]
+        public string PurTypeID { get; set; }
+
+        /// <summary>
+        /// 受理类型名称
+        /// </summary>
+        [SugarColumn(ColumnDescription = "受理类型名称")]
+        public string PurTypeName { get; set; }
+
+        /// <summary>
+        /// 热点类型ID
+        /// </summary>
+        [SugarColumn(ColumnDescription = "热点类型ID")]
+        public string? ConTypeID { get; set; }
+
+        /// <summary>
+        /// 信件编号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "信件编号")]
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 查询密码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "查询密码")]
+        public string Pwd { get; set; }
+
+        /// <summary>
+        /// 来信IP
+        /// </summary>
+        [SugarColumn(ColumnDescription = "来信IP")]
+        public string? IP { get; set; }
+
+        /// <summary>
+        /// 微信Id
+        /// </summary>
+        [SugarColumn(ColumnDescription = "微信Id")]
+        public string? WXOpenid { get; set; }
+
+    }
+}

+ 95 - 0
src/Hotline/WebPortal/WebUserAuth.cs

@@ -0,0 +1,95 @@
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.WebPortal
+{
+    public class WebUserAuth : CreationModificationEntity
+    {
+        /// <summary>
+        /// 
+        /// </summary>
+        public string WebGUID { get; set; }
+
+        /// <summary>
+        /// 网站用户ID
+        /// </summary>
+        [SugarColumn(ColumnDescription = "姓名")]
+        public string WebUserID { get; set; }
+
+        /// <summary>
+        /// id 号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "id 号码")]
+        public string? DataId { get; set; }
+
+        /// <summary>
+        /// 自然人
+        /// 法人
+        /// </summary>
+        [SugarColumn(ColumnDescription = "自然人,法人")]
+        public string? Type { get; set; }
+
+        /// <summary>
+        /// 地址
+        /// </summary>
+        [SugarColumn(ColumnDescription = "地址")]
+        public string? Address { get; set; }
+
+        /// <summary>
+        /// 生日
+        /// </summary>
+        [SugarColumn(ColumnDescription = "生日")]
+        public string? Birth { get; set; }
+
+        /// <summary>
+        /// 文档类型
+        /// </summary>
+        [SugarColumn(ColumnDescription = "文档类型")]
+        public string? DocumentType { get; set; }
+
+        /// <summary>
+        /// 身份证号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "身份证号码")]
+        public string? IdNumber { get; set; }
+
+        /// <summary>
+        /// 未知
+        /// </summary>
+        [SugarColumn(ColumnDescription = "未知")]
+        public string? Nationality { get; set; }
+
+        /// <summary>
+        /// 姓名
+        /// </summary>
+        [SugarColumn(ColumnDescription = "姓名")]
+        public string? Name { get; set; }
+
+        /// <summary>
+        /// 性别
+        /// 1-男
+        /// 2-女
+        /// </summary>
+        [SugarColumn(ColumnDescription = " 性别 1-男,2-女")]
+        public string? Sex { get; set; }
+
+        /// <summary>
+        /// 昵称
+        /// </summary>
+        [SugarColumn(ColumnDescription = "昵称")]
+        public string? DisplayName { get; set; }
+
+        /// <summary>
+        /// 登陆名
+        /// </summary>
+        [SugarColumn(ColumnDescription = "登陆名")]
+        public string? Username { get; set; }
+
+        /// <summary>
+        /// 手机号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "手机号码")]
+        public string? EmployeeMobile { get; set; }
+
+    }
+}

+ 95 - 0
src/Hotline/WebPortal/WebUserRegister.cs

@@ -0,0 +1,95 @@
+using SqlSugar;
+using XF.Domain.Repository;
+
+namespace Hotline.WebPortal
+{
+    /// <summary>
+    /// 门户网站注册用户
+    /// </summary>
+    public class WebUserRegister : CreationModificationEntity
+    {
+        /// <summary>
+        /// 姓名
+        /// </summary>
+        [SugarColumn(ColumnDescription = "姓名")]
+        public string WebUserName { get; set; }
+
+        /// <summary>
+        /// 登录名称
+        /// </summary>
+        [SugarColumn(ColumnDescription = "登录名称")]
+        public string WebLoginName { get; set; }
+
+        /// <summary>
+        /// 电话号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "电话号码")]
+        public string? PhoneNum { get; set; }
+
+        /// <summary>
+        /// 登陆密码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "登陆密码")]
+        public string? LoginPwd { get; set; }
+
+        /// <summary>
+        /// 性别
+        /// </summary>
+        [SugarColumn(ColumnDescription = "性别")]
+        public string? Gender { get; set; }
+
+        /// <summary>
+        /// 身份证号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "身份证号码")]
+        public string? IDCard { get; set; }
+
+        /// <summary>
+        /// 区域编号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "区域编号")]
+        public string? AreaID { get; set; }
+
+        /// <summary>
+        /// 的址
+        /// </summary>
+        [SugarColumn(ColumnDescription = "的址")]
+        public string? Address { get; set; }
+
+        /// <summary>
+        /// 邮箱
+        /// </summary>
+        [SugarColumn(ColumnDescription = "邮箱")]
+        public string? Mail { get; set; }
+
+        /// <summary>
+        /// 注册IP
+        /// </summary>
+        [SugarColumn(ColumnDescription = "注册IP")]
+        public string? RegIP { get; set; }
+
+        /// <summary>
+        /// 注册时间
+        /// </summary>
+        [SugarColumn(ColumnDescription = "注册时间")]
+        public DateTime? RegDate { get; set; }
+
+        /// <summary>
+        /// 状态
+        /// </summary>
+        [SugarColumn(ColumnDescription = "状态")]
+        public string? StateFlag { get; set; }
+
+        /// <summary>
+        /// 登录IP
+        /// </summary>
+        [SugarColumn(ColumnDescription = "登录IP")]
+        public string? LastIP { get; set; }
+
+        /// <summary>
+        /// 登录时间
+        /// </summary>
+        [SugarColumn(ColumnDescription = "登录时间")]
+        public DateTime? LastDate { get; set; }
+    }
+}

+ 5 - 0
src/XF.Domain/Constants/SettingConstants.cs

@@ -216,5 +216,10 @@ namespace XF.Domain.Constants
         /// 外呼分机组号
         /// </summary>
         public const string CallOutQueueId = "CallOutQueueId";
+
+        /// <summary>
+        /// 门户网站主题颜色
+        /// </summary>
+        public const string WebSystemSettingsTheme = "WebSystemSettingsTheme";
     }
 }