Explorar o código

Merge branch 'test' into lib/test

libin hai 2 semanas
pai
achega
e3510ebf17
Modificáronse 68 ficheiros con 1481 adicións e 396 borrados
  1. 75 4
      src/Hotline.Api/Controllers/Bi/BiOrderController.cs
  2. 2 0
      src/Hotline.Api/Controllers/Exam/ExamManageController.cs
  3. 2 2
      src/Hotline.Api/Controllers/Exam/QuestionController.cs
  4. 18 1
      src/Hotline.Api/Controllers/Exam/UserExamController.cs
  5. 12 2
      src/Hotline.Api/Controllers/IPPbxController.cs
  6. 48 0
      src/Hotline.Api/Controllers/Job/ScheduledTasksController.cs
  7. 19 2
      src/Hotline.Api/Controllers/OrderApi/OrderDelayController.cs
  8. 24 0
      src/Hotline.Api/Controllers/OrderApi/OrderVisitController.cs
  9. 13 101
      src/Hotline.Api/Controllers/OrderController.cs
  10. 17 5
      src/Hotline.Api/Controllers/OrgController.cs
  11. 1 0
      src/Hotline.Api/Controllers/TestController.cs
  12. 14 0
      src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs
  13. 12 1
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/ExamQuestionQueryExtensions.cs
  14. 1 1
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/UserExamQueryExtensions.cs
  15. 4 4
      src/Hotline.Application/Exam/Service/ExamManages/ExamManageService.cs
  16. 273 58
      src/Hotline.Application/Exam/Service/ExamManages/UserExamService.cs
  17. 4 2
      src/Hotline.Application/Exam/Service/Practices/PracticeService.cs
  18. 23 17
      src/Hotline.Application/Exam/Service/Trains/TrainRecordService.cs
  19. 39 9
      src/Hotline.Application/Jobs/ApptaskJob.cs
  20. 82 0
      src/Hotline.Application/OrderApp/Handlers/OrderDelayHandler/OrderDelayBatchReviewTaskCompetedHandler.cs
  21. 14 0
      src/Hotline.Application/OrderApp/IOrderApplication.cs
  22. 155 43
      src/Hotline.Application/OrderApp/OrderApplication.cs
  23. 8 0
      src/Hotline.Application/OrderApp/OrderDelayApp/IOrderDelayApplication.cs
  24. 28 20
      src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayApplication.cs
  25. 0 20
      src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayAuditTaskExecutor.cs
  26. 38 0
      src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayReviewTaskExecutor.cs
  27. 16 0
      src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayReviewWithSessionRequest.cs
  28. 7 0
      src/Hotline.Application/OrderApp/OrderVisitApp/IOrderVisitApplication.cs
  29. 48 1
      src/Hotline.Application/OrderApp/OrderVisitApp/OrderVisitApplication.cs
  30. 8 39
      src/Hotline.Application/OrderApp/OrderVisitApp/VoiceVisitTaskExecutor.cs
  31. 16 0
      src/Hotline.Application/OrderApp/OrderVisitApp/VoiceVisitWithSessionRequest.cs
  32. 6 0
      src/Hotline.Share/Dtos/BatchTask/ApptaskProgressDto.cs
  33. 51 0
      src/Hotline.Share/Dtos/ExamManages/ExamQuestionDto.cs
  34. 12 0
      src/Hotline.Share/Dtos/ExamManages/GradingExamQuestionDto.cs
  35. 3 0
      src/Hotline.Share/Dtos/Order/OrderBiDto.cs
  36. 15 0
      src/Hotline.Share/Dtos/Order/OrderDelay/BatchOrderDelayReviewRequest.cs
  37. 7 0
      src/Hotline.Share/Dtos/Order/OrderDelay/DelayWithStepId.cs
  38. 0 20
      src/Hotline.Share/Dtos/Order/OrderDelay/OrderDelayReviewRequest.cs
  39. 5 0
      src/Hotline.Share/Dtos/Order/OrderDto.cs
  40. 14 0
      src/Hotline.Share/Dtos/Order/OrderVisit/BatchVoiceVisitRequest.cs
  41. 7 0
      src/Hotline.Share/Dtos/Order/OrderVisit/VoiceVisitRequest.cs
  42. 24 2
      src/Hotline.Share/Dtos/Order/PublishedDto.cs
  43. 20 5
      src/Hotline.Share/Dtos/Order/QueryOrderDto.cs
  44. 13 0
      src/Hotline.Share/Dtos/Questions/QuestionAnswerDto.cs
  45. 9 0
      src/Hotline.Share/Dtos/Questions/QuestionOptionsDto.cs
  46. 1 1
      src/Hotline.Share/Dtos/Questions/QuestionSourcewareDto.cs
  47. 2 2
      src/Hotline.Share/Enums/BatchTask/ETaskType.cs
  48. 6 1
      src/Hotline.Share/Enums/Exams/EExamStatus.cs
  49. 6 0
      src/Hotline.Share/Enums/Order/EDelayState.cs
  50. 1 1
      src/Hotline.Share/Hotline.Share.csproj
  51. 3 3
      src/Hotline.Share/Requests/Exam/ExamQuestionRequest.cs
  52. 23 0
      src/Hotline.Share/Requests/PagedKeywordRequest.cs
  53. 12 0
      src/Hotline.Share/ViewResponses/Exam/UserExamResultViewResponse.cs
  54. 13 1
      src/Hotline/Authentications/SessionContextProvider.cs
  55. 36 11
      src/Hotline/BatchTask/ApptaskDomainService.cs
  56. 10 1
      src/Hotline/BatchTask/IApptaskDomainService.cs
  57. 15 0
      src/Hotline/BatchTask/IApptaskExecutor.cs
  58. 14 0
      src/Hotline/BatchTask/IApptaskRequest.cs
  59. 10 0
      src/Hotline/BatchTask/Notifications/ApptaskCompletedNotify.cs
  60. 20 0
      src/Hotline/BatchTask/Notifications/ApptaskFailNotify.cs
  61. 15 0
      src/Hotline/BatchTask/Notifications/ApptaskSuccessNotify.cs
  62. 9 1
      src/Hotline/Orders/IOrderVisitDomainService.cs
  63. 10 1
      src/Hotline/Orders/Order.cs
  64. 11 0
      src/Hotline/Orders/OrderPublish.cs
  65. 31 12
      src/Hotline/Orders/OrderVisitDomainService.cs
  66. 21 0
      src/Hotline/Validators/Order/OrderVisit/BatchVoiceVisitRequestValidator.cs
  67. 2 2
      src/XF.Domain/Authentications/ISessionContextProvider.cs
  68. 3 0
      test/Hotline.Tests/Domain/OrderVisitDomainServiceTest.cs

+ 75 - 4
src/Hotline.Api/Controllers/Bi/BiOrderController.cs

@@ -610,11 +610,29 @@ namespace Hotline.Api.Controllers.Bi
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpGet("centre_data_list")]
-        public async Task<List<OrderBiCentreDataListVo>> CentreDataList([FromQuery] ReportPagedRequest dto)
+        public async Task<TotalList<OrderBiCentreDataListVo>> CentreDataList([FromQuery] ReportPagedRequest dto)
         {
-            var query = _orderApplication.CentreDataList(dto);
+            ISugarQueryable<OrderBiCentreDataListVo> query;
+            if (_appOptions.Value.IsZiGong)
+                query = _orderApplication.CentreDataListZG(dto);
+            else
+                query = _orderApplication.CentreDataList(dto);
+
             var list = await query.Where(x => (x.CentreArchive + x.CentreCareOf + x.NoCentreCareOf + x.Invalid + x.Repeat) != 0).ToListAsync(HttpContext.RequestAborted);
-            return list;
+
+            var total = new OrderBiCentreDataListVo
+            {
+                UserName = "合计",
+                UserId = "",
+                CentreArchive = list.Select(s => s.CentreArchive).Sum(),
+                CentreCareOf = list.Select(s => s.CentreCareOf).Sum(),
+                NoCentreCareOf = list.Select(s => s.NoCentreCareOf).Sum(),
+                Invalid = list.Select(s => s.Invalid).Sum(),
+                Repeat = list.Select(s => s.Repeat).Sum(),
+                Subtotal = list.Select(s => s.Subtotal).Sum()
+            };
+
+            return new TotalList<OrderBiCentreDataListVo>(list, total);
         }
 
         /// <summary>
@@ -626,7 +644,13 @@ namespace Hotline.Api.Controllers.Bi
         [LogFilterAlpha("导出日志")]
         public async Task<FileStreamResult> CentreDataListExport([FromBody] ExportExcelDto<ReportPagedRequest> dto)
         {
-            var query = _orderApplication.CentreDataList(dto.QueryDto);
+            ISugarQueryable<OrderBiCentreDataListVo> query;
+            if (_appOptions.Value.IsZiGong)
+                query = _orderApplication.CentreDataListZG(dto.QueryDto);
+            else
+                query = _orderApplication.CentreDataList(dto.QueryDto);
+
+            //var query = _orderApplication.CentreDataList(dto.QueryDto);
             List<OrderBiCentreDataListVo> data = await query.Where(x => (x.CentreArchive + x.CentreCareOf + x.NoCentreCareOf + x.Invalid + x.Repeat) != 0).ToListAsync(HttpContext.RequestAborted);
             data.Add(new OrderBiCentreDataListVo
             {
@@ -648,6 +672,53 @@ namespace Hotline.Api.Controllers.Bi
             return ExcelStreamResult(stream, "话务员办件统计");
         }
 
+        /// <summary>
+        /// 话务员办件统计明细列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("centre_data_list_detail")]
+        public async Task<PagedDto<OrderDto>> CentreDataListDetailExport([FromQuery] CentreDataListDetailRequest dto)
+        {
+            var (total, items) = await _orderApplication.CentreDataListDetailZG(dto).ToPagedListAsync(dto, HttpContext.RequestAborted);
+            return new PagedDto<OrderDto>(total, _mapper.Map<List<OrderDto>>(items));
+        }
+
+        /// <summary>
+        /// 话务员办件统计明细列表--导出
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("centre_data_list_detail_export")]
+        [LogFilterAlpha("导出日志")]
+        public async Task<FileStreamResult> CentreDataListDetailExport([FromBody] ExportExcelDto<CentreDataListDetailRequest> dto)
+        {
+            var query = _orderApplication.CentreDataListDetailZG(dto.QueryDto);
+            List<Order> orders;
+            if (dto.IsExportAll)
+            {
+                orders = await query.ToListAsync(HttpContext.RequestAborted);
+            }
+            else
+            {
+                var (_, items) = await query.ToPagedListAsync(dto.QueryDto, HttpContext.RequestAborted);
+                orders = items;
+            }
+
+            var orderDtos = _mapper.Map<ICollection<OrderDto>>(orders);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass<OrderDto>(dto.ColumnInfos);
+
+            var dtos = orderDtos
+                .Select(stu => _mapper.Map(stu, typeof(OrderDto), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "话务员办件统计明细列表");
+        }
+
         /// <summary>
         /// 工单业务量统计
         /// </summary>

+ 2 - 0
src/Hotline.Api/Controllers/Exam/ExamManageController.cs

@@ -59,6 +59,8 @@ namespace Hotline.Api.Controllers.Exam
         [HttpPost(ExamManageApiRoute.GetPagedList)]
         public async Task<ExamManagePageViewResponse> GetPagedList([FromBody] ExamManagePagedRequest questionPagedRequest)
         {
+            await _examManageService.UpdateExamStatus(new EntityQueryRequest(), HttpContext.RequestAborted);
+
             var questionPageViewResponse = await _examManageService.GetPagedListAsync(questionPagedRequest);
 
             return questionPageViewResponse as ExamManagePageViewResponse;

+ 2 - 2
src/Hotline.Api/Controllers/Exam/QuestionController.cs

@@ -117,9 +117,9 @@ namespace Hotline.Api.Controllers.Exam
         /// </summary>
         /// <returns></returns>
         [HttpPost(ExamManageApiRoute.ImportExcel)]
-        public async Task ImportExcel(IFormFile files)
+        public async Task ImportExcel(IFormFile file)
         {
-            await _questionService.ImportExcel(files, HttpContext.RequestAborted);
+            await _questionService.ImportExcel(file, HttpContext.RequestAborted);
         }
 
         /// <summary>

+ 18 - 1
src/Hotline.Api/Controllers/Exam/UserExamController.cs

@@ -10,16 +10,19 @@ using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using System.ComponentModel;
 using Hotline.Application.Exam.Interface.ExamManages;
+using Exam.Infrastructure.Data.Entity;
 
 namespace Hotline.Api.Controllers.Exam
 {
     public class UserExamController : BaseController
     {
         private readonly IUserExamService _userExamService;
+        private readonly IExamManageService _examManageService;
 
-        public UserExamController(IUserExamService userExamService)
+        public UserExamController(IUserExamService userExamService,IExamManageService examManageService)
         {
             this._userExamService = userExamService;
+            this._examManageService = examManageService;
         }
 
         /// <summary>
@@ -30,6 +33,8 @@ namespace Hotline.Api.Controllers.Exam
         [HttpPost(UserExamApiRoute.GetPagedList)]
         public async Task<UserExamResultPageViewResponse> GetPagedList([FromBody] UserExamPagedRequest userExamPagedRequest)
         {
+            await _examManageService.UpdateExamStatus(new EntityQueryRequest(), HttpContext.RequestAborted);
+
             return await _userExamService.GetPagedListAsync(userExamPagedRequest) as UserExamResultPageViewResponse;
         }
 
@@ -199,5 +204,17 @@ namespace Hotline.Api.Controllers.Exam
 
             return await _userExamService.GetUserExamResults(userExamResultReportPagedRequest); 
         }
+
+        /// <summary>
+        /// 查看考试试题
+        /// </summary>
+        /// <param name="id"></param>
+        /// <param name="questionId"></param>
+        /// <returns></returns>
+        [HttpGet(UserExamApiRoute.View)]
+        public async Task<ViewExamQuestionDto> View([FromQuery] string id,string questionId)
+        {
+            return await _userExamService.View(id, questionId);
+        }
     }
 }

+ 12 - 2
src/Hotline.Api/Controllers/IPPbxController.cs

@@ -74,6 +74,7 @@ namespace Hotline.Api.Controllers
         private readonly IRepository<TelOperation> _telOperationRepository;
         private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
         private readonly IRepository<VoiceEvaluationButton> _voiceEvaluationButtonRepository;
+        private readonly IOrderVisitDomainService _orderVisitDomainService;
 
         public IPPbxController(IMapper mapper, IUserDomainService userDomainService,
             ISessionContext sessionContext, IRepository<TrCallRecord> trCallRecordRepository,
@@ -94,7 +95,8 @@ namespace Hotline.Api.Controllers
             ICallTelClient callTelClient,
             IRepository<TelOperation> telOperationRepository,
            IOptionsSnapshot<AppConfiguration> appOptions,
-           IRepository<VoiceEvaluationButton> voiceEvaluationButtonRepository)
+           IRepository<VoiceEvaluationButton> voiceEvaluationButtonRepository,
+           IOrderVisitDomainService orderVisitDomainService)
         {
             _mapper = mapper;
             _userDomainService = userDomainService;
@@ -125,6 +127,7 @@ namespace Hotline.Api.Controllers
             _telOperationRepository = telOperationRepository;
             _appOptions = appOptions;
             _voiceEvaluationButtonRepository = voiceEvaluationButtonRepository;
+            _orderVisitDomainService = orderVisitDomainService;
         }
 
         #region 添添呼
@@ -708,7 +711,13 @@ namespace Hotline.Api.Controllers
         {
             var data = _mapper.Map<VoiceEvaluationButton>(dto);
             await _voiceEvaluationButtonRepository.AddAsync(data, HttpContext.RequestAborted);
-
+            try
+            {
+                await _orderVisitDomainService.UpdateVoiceReplyAsync(data);
+            }
+            catch (Exception)
+            {
+            }
         }
 
         #endregion
@@ -920,6 +929,7 @@ namespace Hotline.Api.Controllers
                 .WhereIF(string.IsNullOrEmpty(dto.UserName) == false, x => x.UserName.Contains(dto.UserName))
                 .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
                 .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
+                .WhereIF(_appOptions.Value.IsLuZhou,x=>x.ActionType != EActionType.CallEndArrange)
                 .OrderByDescending(x => x.CreationTime)
                 .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
             return new PagedDto<TelActionListRep>(total, _mapper.Map<IReadOnlyList<TelActionListRep>>(items));

+ 48 - 0
src/Hotline.Api/Controllers/Job/ScheduledTasksController.cs

@@ -0,0 +1,48 @@
+using Hotline.CallCenter.Calls;
+using Hotline.Orders;
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Job
+{
+    public class ScheduledTasksController : BaseController
+    {
+        private readonly IOrderVisitRepository _orderVisitRepository;
+        private readonly ILogger<ScheduledTasksController> _logger;
+        private readonly IMediator _mediator;
+        private readonly IOrderVisitDomainService _orderVisitDomainService;
+
+        public ScheduledTasksController(IOrderVisitRepository orderVisitRepository,
+            ILogger<ScheduledTasksController> logger,
+            IMediator mediator,
+            IOrderVisitDomainService orderVisitDomainService)
+        {
+            _orderVisitRepository = orderVisitRepository;
+            _logger = logger;
+            _mediator = mediator;
+            _orderVisitDomainService = orderVisitDomainService;
+        }
+
+        /// <summary>
+        /// 每天晚上凌晨处理前一天语音回访没处理的数据
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost("voice-evaluation-visit-job")]
+        [AllowAnonymous]
+        public async Task VoiceEvaluationVisitJob()
+        {
+            var list = await _orderVisitRepository.Queryable()
+                .Where(p => p.VisitState == Share.Enums.Order.EVisitState.AiVisiting)
+                .ToListAsync();
+            if (list != null && list.Any())
+            {
+                foreach (var item in list)
+                {
+                    item.VisitState = Share.Enums.Order.EVisitState.WaitForVisit;
+                    await _orderVisitRepository.UpdateAsync(item, HttpContext.RequestAborted);
+                }
+            }
+        }
+    }
+}

+ 19 - 2
src/Hotline.Api/Controllers/OrderApi/OrderDelayController.cs

@@ -34,12 +34,29 @@ public class OrderDelayController : BaseController
         return list;
     }
 
+    /// <summary>
+    /// 延期待审批总量
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpGet("waited/count")]
+    public Task<int> WaitedCount([FromQuery] DelayListDto dto) =>
+        _orderDelayApplication.QueryWaited(dto)
+            .CountAsync(HttpContext.RequestAborted);
+
     /// <summary>
     /// 延期审核
     /// </summary>
     [HttpPost("review")]
-    public Task ReviewAsync([FromBody]OrderDelayReviewRequest request) =>
+    public Task Review([FromBody]OrderDelayReviewRequest request) =>
         _orderDelayApplication.ReviewAsync(request, HttpContext.RequestAborted);
 
-
+    /// <summary>
+    /// 延期批量审核
+    /// </summary>
+    /// <param name="request"></param>
+    /// <returns></returns>
+    [HttpPost("batch-review")]
+    public Task BatchReview([FromBody]BatchOrderDelayReviewRequest request) =>
+        _orderDelayApplication.BatchReviewAsync(request, HttpContext.RequestAborted);
 }

+ 24 - 0
src/Hotline.Api/Controllers/OrderApi/OrderVisitController.cs

@@ -0,0 +1,24 @@
+using Hotline.Application.OrderApp.OrderVisitApp;
+using Hotline.BatchTask;
+using Hotline.Share.Dtos.Order.OrderVisit;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.OrderApi;
+
+public class OrderVisitController : BaseController
+{
+    private readonly IOrderVisitApplication _orderVisitApplication;
+
+    public OrderVisitController(IOrderVisitApplication orderVisitApplication)
+    {
+        _orderVisitApplication = orderVisitApplication;
+    }
+
+    /// <summary>
+    /// 批量语音回访
+    /// </summary>
+    /// <returns></returns>
+    [HttpPost("batch-voice")]
+    public Task BatchVoiceVisit([FromBody] BatchVoiceVisitRequest request) => 
+        _orderVisitApplication.AddTaskBatchVoiceVisitAsync(request, HttpContext.RequestAborted);
+}

+ 13 - 101
src/Hotline.Api/Controllers/OrderController.cs

@@ -531,6 +531,13 @@ public class OrderController : BaseController
         if (order.Status != EOrderStatus.Filed)
             throw UserFriendlyException.SameMessage("当前状态无法发布");
 
+
+        if(order.SourceChannelCode=="SZMHD" && order.IsProvince && dto.PublishState && dto.IsOpenReview is null)
+        {
+            throw UserFriendlyException.SameMessage("公开信件必须选择开展保密审查");
+        }
+
+
         var enabled = _systemSettingCacheManager.CancelPublishOrderEnabled;
         if (enabled)
         {
@@ -813,6 +820,8 @@ public class OrderController : BaseController
             OrderTitle = order.Title,
             Content = order.Content,
             ActualOpinion = order.FileOpinion,
+            IsPublic = order.IsPublic,
+            IsProvincePublic = (order.SourceChannelCode == "SZMHD" && order.IsProvince == true) ? true :false,
             NetizenEvaluateType = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.NetizenEvaluateType)
                 .Select(x => new Kv { Key = x.DicDataValue, Value = x.DicDataName }).ToList()
         };
@@ -3485,105 +3494,6 @@ public class OrderController : BaseController
         await _workflowDomainService.NextAsync(dto, cancellationToken: HttpContext.RequestAborted);
     }
 
-    /// <summary>
-    /// 批量审批甄别
-    /// </summary>
-    [HttpPost("screen/batch_audit")]
-    [LogFilter("批量审批甄别")]
-    public async Task<string> BatchAuditScreen([FromBody] BatchScreenNextFlowDto dto)
-    {
-        var result = new StringBuilder();
-        var fail = 0;
-        var success = 0;
-        var workflow = dto.NextWorkflow;
-        foreach (var item in dto.ScreenId)
-        {
-            try
-            {
-                var screen = await _orderScreenRepository.Queryable().Includes(x => x.Order).Where(x => x.Id == item)
-                    .FirstAsync(HttpContext.RequestAborted);
-                workflow.WorkflowId = screen.WorkflowId;
-                var workflowEntuty = await _workflowDomainService.GetWorkflowAsync(workflow.WorkflowId, withDefine: true, withSteps: true,
-                    cancellationToken: HttpContext.RequestAborted);
-                var currentStep = workflowEntuty.Steps.FirstOrDefault(d =>
-                    d.Status == EWorkflowStepStatus.WaitForAccept || d.Status == EWorkflowStepStatus.WaitForHandle);
-
-                NextStepsWithOpinionDto<NextStepOption> next = null;
-
-                try
-                {
-                    next = await _workflowApplication.GetNextStepsAsync(screen.WorkflowId, HttpContext.RequestAborted);
-                }
-                catch (UserFriendlyException e)
-                {
-                    if (e.Message.Contains("未找到对应节点"))
-                    {
-                        result.Append("无权审核:" + screen.No);
-                        fail++;
-                    }
-                    else
-                    {
-                        throw;
-                    }
-                }
-
-                if (next == null) continue;
-
-                if (!screen.Order.IsProvince)
-                {
-                    if (next.Steps.Any(x => x.Value == "省审批"))
-                    {
-                        next.Steps.Remove(next.Steps.First(x => x.Value == "省审批"));
-                    }
-                }
-
-                if (!_sessionContext.OrgIsCenter && currentStep.Name != "中心初审")
-                {
-                    if (next.Steps.Any(x => x.Value == "中心终审"))
-                    {
-                        next.Steps.Remove(next.Steps.First(x => x.Value == "中心终审"));
-                    }
-                }
-
-                workflow.StepId = next.StepId;
-                workflow.ReviewResult = dto.IsPass ? EReviewResult.Approval : EReviewResult.Failed;
-
-                if (workflow.ReviewResult == EReviewResult.Approval)
-                {
-                    var isBatch = next.Steps.Where(x => x.Value == workflow.NextStepName).Any();
-                    if (isBatch)
-                    {
-                        var step = next.Steps.Where(x => x.Value == workflow.NextStepName).FirstOrDefault();
-                        workflow.NextStepCode = step.Key;
-                        workflow.NextStepName = step.Value;
-                    }
-                    else
-                    {
-                        result.Append("无权审核:" + screen.No);
-                        fail++;
-                        continue;
-                    }
-
-                    await _workflowDomainService.NextAsync(workflow, cancellationToken: HttpContext.RequestAborted);
-                }
-                else
-                {
-                    var reject = workflow.Adapt<RejectDto>();
-                    await _workflowApplication.RejectAsync(reject, HttpContext.RequestAborted);
-                }
-
-                success++;
-            }
-            catch (UserFriendlyException e)
-            {
-                result.Append(e.Message);
-                fail++;
-            }
-        }
-
-        return $"总共: {dto.ScreenId.Length}, 成功: {success}, 失败: {fail}, 失败原因: {result.ToString()}";
-    }
-
     /// <summary>
     /// 甄别审批退回(返回前一节点)
     /// </summary>
@@ -6350,8 +6260,10 @@ public class OrderController : BaseController
             IdentityTypeOptions = EnumExts.GetDescriptions<EIdentityType>(),
             OrderTags = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.OrderTag),
             FocusOnEvents = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.FocusOnEvent),
-            PoliticalIdentity = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.PoliticalIdentity)
-        };
+            PoliticalIdentity = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.PoliticalIdentity),
+			SeatEvaluate = EnumExts.GetDescriptions<ESeatEvaluate>(),
+			VisitSatisfaction = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.VisitSatisfaction),
+		};
         return rsp;
     }
 

+ 17 - 5
src/Hotline.Api/Controllers/OrgController.cs

@@ -201,11 +201,23 @@ namespace Hotline.Api.Controllers
             return org;
         }
 
-        /// <summary>
-        /// 获取可用组织架构树形
-        /// </summary>
-        /// <returns></returns>
-        [HttpGet("getcanuseorg")]
+		/// <summary>
+		/// 获取一级部门
+		/// </summary>
+		/// <param name="id"></param>
+		/// <returns></returns>
+		[HttpGet("get_one_org")]
+		public async Task<List<SystemOrganize>> GetOneOrg()
+		{
+			var oneOrgs = await _systemOrganizeRepository.Queryable().Where(x=>x.Level == 1).ToListAsync();
+			return oneOrgs;
+		}
+
+		/// <summary>
+		/// 获取可用组织架构树形
+		/// </summary>
+		/// <returns></returns>
+		[HttpGet("getcanuseorg")]
         public async Task<IReadOnlyList<SystemOrganize>> GetCanUseOrg()
         {
             return await _systemOrganizeRepository.GetCanUseOrg();

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

@@ -79,6 +79,7 @@ using Hotline.Schedulings;
 using Hotline.Share.Dtos.BatchTask;
 using Hotline.Share.Enums.BatchTask;
 using Hotline.Application.OrderApp.OrderVisitApp;
+using Hotline.Share.Dtos.Order.OrderVisit;
 
 namespace Hotline.Api.Controllers;
 

+ 14 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IUserExamService.cs

@@ -117,5 +117,19 @@ namespace Hotline.Application.Exam.Interface.ExamManages
         /// <param name="examUserQueryRequest"></param>
         /// <returns></returns>
         Task<List<ExamUserViewResponse>> GetUserListAsync(ExamUserQueryRequest examUserQueryRequest);
+        
+        /// <summary>
+        /// 查看考试
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        Task<ViewExamQuestionDto> View(string id,string questionId);
+
+        /// <summary>
+        /// 查看考试
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        Task<List<ViewExamQuestionDto>> View(string id);
     }
 }

+ 12 - 1
src/Hotline.Application/Exam/QueryExtensions/ExamManages/ExamQuestionQueryExtensions.cs

@@ -30,7 +30,18 @@ namespace Hotline.Application.Exam.QueryExtensions.ExamManages
 
             expression = ExpressionableUtility.CreateExpression<ExamQuestionBak>()
                 .AndIF(examQuestionRequest.QuestionId.IsNotNullOrEmpty(), x => x.QuestionId == examQuestionRequest.QuestionId)
-                .AndIF(examQuestionRequest.ExamId.IsNotNullOrEmpty(), x => x.ExamId == examQuestionRequest.ExamId)
+                .ToExpression();
+
+            return expression;
+        }
+
+        public static Expression<Func<ExamUserExam, bool>> GetUserExamExpression(this ExamQuestionRequest examQuestionRequest)
+        {
+            Expression<Func<ExamUserExam, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<ExamUserExam>()
+                .AndIF(examQuestionRequest.UserExamId.IsNotNullOrEmpty(), x => x.Id == examQuestionRequest.UserExamId
+                )
                 .ToExpression();
 
             return expression;

+ 1 - 1
src/Hotline.Application/Exam/QueryExtensions/ExamManages/UserExamQueryExtensions.cs

@@ -78,7 +78,7 @@ namespace Hotline.Application.Exam.QueryExtensions.ExamManages
             Expression<Func<ExamUserExam, bool>> expression = m => m.Id != null;
 
             expression = ExpressionableUtility.CreateExpression<ExamUserExam>()
-                .And(x=>x.ExamStatus == Share.Enums.Exams.EExamStatus.NoStart)
+                .And(x=>x.ExamStatus == Share.Enums.Exams.EExamStatus.Absent)
             .ToExpression();
 
             return expression;

+ 4 - 4
src/Hotline.Application/Exam/Service/ExamManages/ExamManageService.cs

@@ -332,7 +332,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
         {
             List<ExamManage> examManages = await UpdateExamManageStatus(entityQueryRequest, requestAborted);
 
-            //await UdpateUserExam(examManages, requestAborted);
+            await UdpateUserExam(examManages, requestAborted);
 
         }
         #endregion
@@ -604,7 +604,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
             var expression = queryRequest.GetExpression();
             var questionTable = _repository.Queryable().Where(expression);
 
-            var queryable = questionTable.Select(e => new ExamManageViewResponse
+            var queryable = questionTable.OrderBy(e => e.CreationTime).Select(e => new ExamManageViewResponse
             {
                 Id = e.Id,
                 Code = e.Code,
@@ -696,7 +696,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
 
             userExams.ForEach(item =>
             {
-                item.ExamStatus = Share.Enums.Exams.EExamStatus.Complete;
+                item.ExamStatus = item.ExamStatus== Share.Enums.Exams.EExamStatus.NoStart? Share.Enums.Exams.EExamStatus.Absent: item.ExamStatus;
             });
 
             userExams.ToUpdate(_sessionContext);
@@ -708,7 +708,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
         {
             var queryable = _repository.Queryable()
                 .WhereIF(entityQueryRequest != null && entityQueryRequest.Ids != null, x => entityQueryRequest.Ids.Contains(x.Id))
-                .Where(x => x.EndTime < DateTime.Now);
+                .Where(x => x.EndTime < DateTime.Now && x.ExamStatus != Share.Enums.Exams.EExamStatus.Complete);
             var examManages = await queryable.ToListAsync();
 
             examManages.ForEach(item =>

+ 273 - 58
src/Hotline.Application/Exam/Service/ExamManages/UserExamService.cs

@@ -43,6 +43,11 @@ using Hotline.Share.Enums.Exams;
 using DocumentFormat.OpenXml.Wordprocessing;
 using Hotline.Repository.SqlSugar.Exam.Repositories.ExamManages;
 using Hotline.Exams.Questions;
+using Hotline.Exams.Sourcewares;
+using Hotline.Share.Dtos.Questions;
+using DocumentFormat.OpenXml.Office2010.Excel;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
 
 namespace Hotline.Application.Exam.Service.ExamManages
 {
@@ -91,18 +96,22 @@ namespace Hotline.Application.Exam.Service.ExamManages
         public async Task<ExamQuestionDto> GetExamQuestionDto(ExamQuestionRequest examQuestionRequest)
         {
             var expression = examQuestionRequest.GetExpression();
-            var question = await new ExamRepository<Exams.ExamManages.ExamQuestionBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(expression).FirstAsync();
-
+            var userUserExpression = examQuestionRequest.GetUserExamExpression();
+            //var question = await new ExamRepository<Exams.ExamManages.ExamQuestionBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(expression).FirstAsync();
+            var querable = new ExamRepository<Exams.ExamManages.ExamQuestionBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(expression);
+            var userExamTable = new ExamRepository<Exams.ExamManages.ExamUserExam>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable().Where(userUserExpression);
+            querable = querable.InnerJoin(userExamTable, (q, u) => q.ExamId == u.ExamId).Select((q, u) => q);
+            var question = await querable.FirstAsync();
             if (question != null)
             {
                 var examQuestionDto = _mapper.Map<ExamQuestionDto>(question);
 
-                var questionScore = await new ExamRepository<ExamQuestionScoreBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).GetAsync(x=>x.QuestionType == question.QuestionType && x.ExamManageId == examQuestionRequest.ExamId);
+                var questionScore = await new ExamRepository<ExamQuestionScoreBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).GetAsync(x => x.QuestionType == question.QuestionType && x.ExamManageId == question.ExamId);
 
                 if (questionScore != null)
                 {
                     examQuestionDto.Score = questionScore.Score;
-                }               
+                }
 
                 if (examQuestionDto.QuestionType.CheckSelectType())
                 {
@@ -151,7 +160,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
             var userExamItemRepository = new ExamRepository<ExamUserExamItem>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
 
             var userExamItemTable = userExamItemRepository.Queryable().Where(i => i.QuestionId == examQuestionRequest.QuestionId);
-            var userExamTable = _repository.Queryable().Where(u => u.UserId == _sessionContext.UserId && u.ExamId == examQuestionRequest.ExamId);
+            var userExamTable = _repository.Queryable().Where(u => u.UserId == _sessionContext.UserId && u.Id == examQuestionRequest.UserExamId);
             var examAnswerTable = examAnswerRepository.Queryable();
 
             var examAnswers = await examAnswerTable.InnerJoin(userExamItemTable, (e, i) => e.UserExamItemId == i.Id)
@@ -352,7 +361,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
                 IsJoin = false
             };
 
-            if (userExam.StartTime == null || userExam.ExamStatus == EExamStatus.Complete)
+            if (userExam.ExamStatus == EExamStatus.NoStart || userExam.ExamStatus == EExamStatus.Complete)
                 userExam.StartTime = DateTime.Now;
 
             var startExamViewResponse = await CheckExamValid(userExam, cancellationToken);
@@ -374,7 +383,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
 
             }
             // TODO: 删除之前选项和答案
-            else if(userExam.ExamStatus == EExamStatus.Complete)
+            else if (userExam.ExamStatus == EExamStatus.Complete)
             {
                 await ReExam(userExam, cancellationToken);
             }
@@ -611,6 +620,35 @@ namespace Hotline.Application.Exam.Service.ExamManages
         public async Task<List<GradingExamQuestionDto>> GetGradingExamQuestion(GradingExamRequest gradingExamRequest)
         {
             var expression = gradingExamRequest.GetExpression();
+
+            ISugarQueryable<GradingExamQuestionTempDto> queryable = GetViewExamQuestionTempDtos(expression);
+
+            queryable =
+            queryable.MergeTable().Where((q) => !(q.QuestionType == EQuestionType.Single || q.QuestionType == EQuestionType.Multi || q.QuestionType == EQuestionType.Judge));
+
+            var queryResult = await queryable.ToListAsync();
+
+            var gradingExamQuestionDtos = queryResult.GroupBy(x => new
+            {
+                Id = x.Id,
+                QuestionType = x.QuestionType
+            }).Select(g => new GradingExamQuestionDto
+            {
+                Answer = g.FirstOrDefault().Answer,
+                QuestionType = g.Key.QuestionType,
+                Id = g.Key.Id,
+                QuestionScore = g.FirstOrDefault().QuestionScore,
+                Score = g.FirstOrDefault().Score,
+                Title = g.FirstOrDefault().Title,
+                CorrectAnswer = g.Key.QuestionType.CheckSelectType() ? string.Join(",", g.Where(i => i.IsAnswer).Select(n => n.Label).Distinct()) : g.FirstOrDefault()?.CorrectAnswer
+            }).ToList();
+
+            return gradingExamQuestionDtos;
+        }
+
+        private ISugarQueryable<GradingExamQuestionTempDto> GetViewExamQuestionTempDtos(Expression<Func<ExamUserExam, bool>> expression)
+        {
+
             var userExamTable = _repository.Queryable().Where(expression);
 
             var questionScoreRepository = new ExamRepository<ExamQuestionScoreBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
@@ -623,14 +661,13 @@ namespace Hotline.Application.Exam.Service.ExamManages
             var testPaperItemAnswerTable = new ExamRepository<ExamQuestionAnswerBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
             var questionScoreTable = questionScoreRepository.Queryable();
 
-            var queryable = userExamTable.InnerJoin(userExamItemTable, (e, i) => e.Id == i.UserExamId)
-                .InnerJoin(questionTable, (e, i, q) => i.QuestionId == q.QuestionId)
+            return userExamTable.InnerJoin(userExamItemTable, (e, i) => e.Id == i.UserExamId)
+                .InnerJoin(questionTable, (e, i, q) => i.QuestionId == q.QuestionId && q.ExamId == e.ExamId)
                 .LeftJoin(userExamItemOptionTable, (e, i, q, o) => i.Id == o.UserExamItemId)
                 .LeftJoin(quesitonOptionTable, (e, i, q, o, qo) => o.QuestionOptionId == qo.Id)
                 .LeftJoin(examAnswerTable, (e, i, q, o, qo, a) => i.Id == a.UserExamItemId)
                 .LeftJoin(testPaperItemAnswerTable, (e, i, q, o, qo, a, ta) => ta.QuestionId == q.QuestionId)
                 .InnerJoin(questionScoreTable, (e, i, q, o, qo, a, ta, s) => q.QuestionType == s.QuestionType && e.ExamId == s.ExamManageId)
-            .Where((e, i, q, o, qo, a, ta, s) => !(q.QuestionType == EQuestionType.Single || q.QuestionType == EQuestionType.Multi || q.QuestionType == EQuestionType.Judge))
             .Select(
             (e, i, q, o, qo, a, ta, s) => new GradingExamQuestionTempDto
             {
@@ -640,6 +677,8 @@ namespace Hotline.Application.Exam.Service.ExamManages
                 Answer = a.Id != null ? a.Answer : string.Empty,
                 Title = q.Title,
                 QuestionOptionId = o.Id,
+                QuestionId = q.QuestionId,
+                ExamQuestionId = q.Id,
                 UserExamItemId = i.Id,
                 Content = qo.Content,
                 Label = qo.Label,
@@ -649,28 +688,8 @@ namespace Hotline.Application.Exam.Service.ExamManages
                 Score = i.Score
             }
                 );
-
-            var queryResult = await queryable.ToListAsync();
-
-            var gradingExamQuestionDtos = queryResult.GroupBy(x => new
-            {
-                Id = x.Id,
-                QuestionType = x.QuestionType
-            }).Select(g => new GradingExamQuestionDto
-            {
-                Answer = g.FirstOrDefault().Answer,
-                QuestionType = g.Key.QuestionType,
-                Id = g.Key.Id,
-                QuestionScore = g.FirstOrDefault().QuestionScore,
-                Score = g.FirstOrDefault().Score,
-                Title = g.FirstOrDefault().Title,
-                CorrectAnswer = g.Key.QuestionType.CheckSelectType() ? string.Join(",", g.Where(i => i.IsAnswer).Select(n => n.Label).Distinct()) : g.FirstOrDefault()?.CorrectAnswer
-            }).ToList();
-
-            return gradingExamQuestionDtos;
         }
 
-
         public async Task<GradingExamQuestionDto> ViewGradingExamQuestion(ViewGradingExamRequest viewGradingExamRequest)
         {
             var gradingExtamItemDto = _mapper.Map<ViewGradingExamRequest, GradingExamItemDto>(viewGradingExamRequest);
@@ -681,7 +700,8 @@ namespace Hotline.Application.Exam.Service.ExamManages
 
         public async Task<UnExamUserPageViewResponse> GetUnExamUsers(UnExamUserReportPagedRequest unExamUserReportPagedRequest)
         {
-            unExamUserReportPagedRequest.ResolveEndTime();
+            if (unExamUserReportPagedRequest.EndTime == null)
+                unExamUserReportPagedRequest.ResolveEndTime();
 
             var examManageRepository = new ExamRepository<ExamManage>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
             var userRepository = new ExamRepository<User>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
@@ -699,7 +719,9 @@ namespace Hotline.Application.Exam.Service.ExamManages
                     ExamName = e.Name,
                     OrgName = u.Organization.Name,
                     UserName = u.Name
-                });
+                })
+                .MergeTable()
+                .OrderByPropertyNameIF(!string.IsNullOrEmpty(unExamUserReportPagedRequest.SortField), unExamUserReportPagedRequest.SortField, (OrderByType)(unExamUserReportPagedRequest.SortRule ?? 0));
 
             var total = await queryResult.CountAsync();
             var items = await queryResult.ToPageListAsync(unExamUserReportPagedRequest.PageIndex, unExamUserReportPagedRequest.PageSize);
@@ -714,7 +736,8 @@ namespace Hotline.Application.Exam.Service.ExamManages
 
         public async Task<UserExamResultPageViewResponse> GetUserExamResults(UserExamResultReportPagedRequest userExamResultReportPagedRequest)
         {
-            userExamResultReportPagedRequest.ResolveEndTime();
+            if (userExamResultReportPagedRequest.EndTime == null)
+                userExamResultReportPagedRequest.ResolveEndTime();
 
             var examManageRepository = new ExamRepository<ExamManage>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
             var userRepository = new ExamRepository<User>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
@@ -727,18 +750,20 @@ namespace Hotline.Application.Exam.Service.ExamManages
 
             var queryResult = userExamTable.InnerJoin(examManageTable, (ue, e) => ue.ExamId == e.Id)
                 .InnerJoin(userTable, (ue, e, u) => ue.UserId == u.Id)
-                .Select((ue, e, u) => new UserExamResultViewResponse
-                {
-                    ExamName = e.Name,
-                    OrgName = u.Organization.Name,
-                    UserName = u.Name,
-                    TotalScore = e.TotalScore,
-                    CutoffScore = e.CutoffScore,
-                    Score = ue.Score ?? 0,
-
-                })
+               .Select((ue, e, u) => new UserExamResultViewResponse
+               {
+                   ExamName = e.Name,
+                   OrgName = u.Organization.Name,
+                   UserName = u.Name,
+                   TotalScore = e.TotalScore,
+                   CutoffScore = e.CutoffScore,
+                   Score = ue.Score ?? 0,
+
+               })
                 .MergeTable()
-                .OrderByDescending(x => x.Score);
+               .OrderByPropertyNameIF(!string.IsNullOrEmpty(userExamResultReportPagedRequest.SortField), userExamResultReportPagedRequest.SortField, (OrderByType)(userExamResultReportPagedRequest.SortRule ?? 0))
+                .OrderByDescending(x => x.Score)
+      ;
 
             var total = await queryResult.CountAsync();
             var items = await queryResult.ToPageListAsync(userExamResultReportPagedRequest.PageIndex, userExamResultReportPagedRequest.PageSize);
@@ -826,6 +851,7 @@ namespace Hotline.Application.Exam.Service.ExamManages
                         QuestionId = u.QuestionId,
                         QuestionType = q.QuestionType,
                         UserExamId = u.UserExamId,
+                        Id = u.Id
                     }).Distinct().ToListAsync();
                 await CalcuteExamItemScore(_userExamItemRepository, userExamItemsInCheck, cancellationToken);
 
@@ -890,31 +916,24 @@ namespace Hotline.Application.Exam.Service.ExamManages
             var userExamIds = addUserExamItemDtos.Select(x => x.UserExamId).ToList();
             var questionTypes = addUserExamItemDtos.Select(x => x.QuestionType).ToList();
 
-            var testPaperItemOptionsRepository = new ExamRepository<ExamQuestionOptionsBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
+            List<ExamQuestionOptionsBak> examQuestionOptions = await GetQuestionOptionBaks(questionIds, userExamIds, true).ToListAsync();
+
             var userExamItemOptionRepository = new ExamRepository<ExamUserExamItemOptions>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
-            var examManageRepository = new ExamRepository<ExamManage>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
-            var testPaperItemRepository = new ExamRepository<Exams.ExamManages.ExamQuestionBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
             var examQuestionScoreRepository = new ExamRepository<ExamQuestionScoreBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
-            var testPaperOptionsTable = testPaperItemOptionsRepository.Queryable().Where(x => questionIds.Contains(x.QuestionId) && x.IsAnswer);
-            var testPaperItemTable = testPaperItemRepository.Queryable().Where(x => x.QuestionType == EQuestionType.Single || x.QuestionType == EQuestionType.Multi || x.QuestionType == EQuestionType.Judge);
             var userExamTable = _repository.Queryable().Where(x => userExamIds.Contains(x.Id));
-            var examManageTable = examManageRepository.Queryable();
-            var testPaperOptionIds = await testPaperOptionsTable.InnerJoin(testPaperItemTable, (t, i) => t.ExamQuestionId == i.Id)
-                .InnerJoin(examManageTable, (t, i, e) => i.ExamId == e.Id)
-                .InnerJoin(userExamTable, (t, i, e, u) => e.Id == u.ExamId)
-                .Select((t, i, e, u) => t.Id).ToListAsync();
 
             var userExamItems = await userExamItemRepository.Queryable().Where(x => userExamIds.Contains(x.UserExamId)).ToListAsync();
             var userExamItemIds = userExamItems.Select(x => x.Id).ToList();
             var userExamItemOptions = await userExamItemOptionRepository.Queryable().Where(x => userExamItemIds.Contains(x.UserExamItemId)).ToListAsync();
             var examQuesiontScores = await examQuestionScoreRepository.Queryable().Where(x => questionTypes.Contains(x.QuestionType))
-                .InnerJoin(userExamTable, (e, u) => e.Id == u.ExamId)
+                .InnerJoin(userExamTable, (e, u) => e.ExamManageId == u.ExamId)
                 .Select((e, u) => e).ToListAsync();
 
 
             foreach (var addUserExamItemDto in addUserExamItemDtos)
             {
-                var isCorrect = userExamItemOptions.Select(x => x.QuestionOptionId).OrderBy(x => x).SequenceEqual(testPaperOptionIds.OrderBy(x => x));
+                var examQuestionOptionIds = examQuestionOptions.Where(x => x.QuestionId == addUserExamItemDto.QuestionId).Select(x => x.Id).ToList();
+                var isCorrect = userExamItemOptions.Where(x => x.UserExamItemId == addUserExamItemDto.Id).Select(x => x.QuestionOptionId).OrderBy(x => x).SequenceEqual(examQuestionOptionIds.OrderBy(x => x));
                 var userExamItem = userExamItems.FirstOrDefault(x => x.QuestionId == addUserExamItemDto.QuestionId);
                 if (userExamItem != null)
                 {
@@ -928,6 +947,32 @@ namespace Hotline.Application.Exam.Service.ExamManages
             await userExamItemRepository.UpdateWithValidateAsync(userExamItems, cancellationToken);
         }
 
+        private ISugarQueryable<ExamQuestionOptionsBak> GetQuestionOptionBaks(List<string> questionIds, List<string> userExamIds, bool isFilterAnswer)
+        {
+            var questionRepository = new ExamRepository<Exams.ExamManages.ExamQuestionBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
+            var questionTable = questionRepository.Queryable().Where(x => x.QuestionType == EQuestionType.Single || x.QuestionType == EQuestionType.Multi || x.QuestionType == EQuestionType.Judge);
+            var userExamTable = _repository.Queryable().Where(x => userExamIds.Contains(x.Id));
+            var examManageRepository = new ExamRepository<ExamManage>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
+            var examManageTable = examManageRepository.Queryable();
+            var examQuestionOptionsRepository = new ExamRepository<ExamQuestionOptionsBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider);
+            var examQuestionOptionsTable = examQuestionOptionsRepository.Queryable()
+                .Where(x => questionIds.Contains(x.QuestionId))
+                .WhereIF(isFilterAnswer, x => x.IsAnswer);
+            var examQuestionOptions = examQuestionOptionsTable.InnerJoin(questionTable, (t, i) => t.ExamQuestionId == i.Id)
+                .InnerJoin(examManageTable, (t, i, e) => i.ExamId == e.Id)
+                .InnerJoin(userExamTable, (t, i, e, u) => e.Id == u.ExamId)
+                .Select((t, i, e, u) => new ExamQuestionOptionsBak
+                {
+                    Id = t.Id,
+                    Content = t.Content,
+                    IsAnswer = t.IsAnswer,
+                    Label = t.Label,
+                    ExamQuestionId = t.ExamQuestionId,
+                    QuestionId = t.QuestionId
+                });
+            return examQuestionOptions;
+        }
+
         private async Task CalcuteTotalScore(IUserExamItemRepository userExamItemRepository, string userExamId, CancellationToken cancellationToken)
         {
             var userExam = await _repository.GetAsync(x => x.Id == userExamId);
@@ -1299,17 +1344,187 @@ namespace Hotline.Application.Exam.Service.ExamManages
                 Score = u.Score ?? 0,
                 Status = u.Status,
                 SortIndex = u.SortIndex,
-                ExamStatus = u.ExamStatus,
+                ExamStatus = u.ExamStatus == EExamStatus.Absent? EExamStatus.Complete: u.ExamStatus,
                 IsSuccess = u.IsCheck ? u.IsSuccess : null,
                 EndTime = e.EndTime,
                 StartTime = e.StartTime,
                 TimeSpan = e.TimeSpan,
                 ExamType = e.ExamType,
                 ExamId = e.Id,
-                IsCheck = u.IsCheck
+                IsCheck = u.IsCheck,
+                IsReExam = u.IsReExam,
+                CanReExam = SqlFunc.Subqueryable<ExamUserExamItem>().Where(x => x.UserExamId == u.Id).Count() < e.Count
             });
             return queryable;
         }
+
+        public async Task<List<ViewExamQuestionDto>> View(string id)
+        {
+
+            List<ViewExamQuestionDto> viewExamQuestionDtos = await GetViewExamQuestion(id, null);
+
+            var examQuestionIds = viewExamQuestionDtos.Select(x => x.ExamQuestionId).ToList();
+            var questionIds = viewExamQuestionDtos.Select(x => x.QuestionId).ToList();
+            var userExamIds = new List<string>
+            {
+                id
+            };
+            //List<ViewQuestionAnswerDto> questionAnswers = await GetQuestionAnswers(id, questionIds);
+            List<ViewQuestionOptionDto> questionOptions = await GetQuestionOptions(questionIds, userExamIds);
+            List<QuestionKnowladgeDto> questionKnowladges = await GetQuestionKnowladges(examQuestionIds);
+            List<QuestionSourcewareDto> sourcewares = await GetSourcewares(examQuestionIds);
+
+            ResolveExamQuestions(viewExamQuestionDtos, questionOptions, questionKnowladges, sourcewares);
+
+            return viewExamQuestionDtos;
+
+        }
+
+
+
+        public async Task<ViewExamQuestionDto> View(string id, string questionId)
+        {
+            List<ViewExamQuestionDto> viewExamQuestionDtos = await GetViewExamQuestion(id, questionId);
+
+            var examQuestionIds = viewExamQuestionDtos.Select(x => x.ExamQuestionId).ToList();
+            var questionIds = viewExamQuestionDtos.Select(x => x.QuestionId).ToList();
+            var userExamIds = new List<string>
+            {
+                id
+            };
+            //List<ViewQuestionAnswerDto> questionAnswers = await GetQuestionAnswers(id, questionIds);
+            List<ViewQuestionOptionDto> questionOptions = await GetQuestionOptions(questionIds, userExamIds);
+            List<QuestionKnowladgeDto> questionKnowladges = await GetQuestionKnowladges(examQuestionIds);
+            List<QuestionSourcewareDto> sourcewares = await GetSourcewares(examQuestionIds);
+
+            ResolveExamQuestions(viewExamQuestionDtos, questionOptions, questionKnowladges, sourcewares);
+
+            return viewExamQuestionDtos.FirstOrDefault();
+        }
+
+        private static void ResolveExamQuestions(List<ViewExamQuestionDto> viewExamQuestionDtos, List<ViewQuestionOptionDto> questionOptions, List<QuestionKnowladgeDto> questionKnowladges, List<QuestionSourcewareDto> sourcewares)
+        {
+            viewExamQuestionDtos.ForEach(item =>
+            {
+                item.QuestionKnowladgeDtos = questionKnowladges.Where(x => x.QuestionId == item.ExamQuestionId).ToList();
+                item.QuestionSourcewareDtos = sourcewares.Where(x => x.QuestionId == item.ExamQuestionId).ToList();
+                if (item.QuestionType.CheckSelectType())
+                {
+                    item.QuestionOptions = questionOptions.Where(x => x.QuestionId == item.ExamQuestionId).ToList();
+
+                    item.CorrectAnswer = string.Join(",", item.QuestionOptions.Where(m => m.IsAnswer).Select(m => m.Label).ToArray());
+                }
+
+            });
+        }
+
+        private async Task<List<QuestionSourcewareDto>> GetSourcewares(List<string> questionIds)
+        {
+            var questionSourcewareTable = new ExamRepository<ExamQuestionSourcewareBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+            var sourcewareTable = new ExamRepository<ExamSourceware>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+            var querable = questionSourcewareTable.InnerJoin<ExamQuestionSourceware>((qsb, qs) => qsb.SourcewareId == qs.Id).InnerJoin(sourcewareTable, (qsb, qs, s) => qs.SourcewareId == s.Id).Select((qsb, qs, s) => new QuestionSourcewareDto
+            {
+                Id = qs.Id,
+                Name = s.Name,
+                QuestionId = qsb.ExamQuestionId,
+                SourcewareId = s.Id,
+                AttachmentId = s.AttachmentId
+            }).MergeTable().Where(x => questionIds.Contains(x.QuestionId));
+
+            return await querable.ToListAsync();
+        }
+
+        private async Task<List<QuestionKnowladgeDto>> GetQuestionKnowladges(List<string> questionIds)
+        {
+            var questionKnowladgeTable = new ExamRepository<ExamQuestionKnowladgeBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+
+            return await questionKnowladgeTable.Where(x => questionIds.Contains(x.ExamQuestionId))
+                .InnerJoin<ExamQuestionKnowladge>((kb, k) => kb.KnowladgeId == k.Id).Select((kb, k) => new QuestionKnowladgeDto
+                {
+                    Id = kb.Id,
+                    QuestionId = kb.ExamQuestionId,
+                    KnowladgeId = k.KnowladgeId,
+                    Title = kb.Title
+                }).ToListAsync();
+        }
+
+        private async Task<List<ViewQuestionOptionDto>> GetQuestionOptions(List<string> questionIds, List<string> userExamIds)
+        {
+            var selectQuestionOptions = GetQuestionOptionBaks(questionIds, userExamIds, false).MergeTable();
+
+            var userExamItemOptionTable = _userExamItemOptionRepository.Queryable();
+
+            var querable = selectQuestionOptions
+                .LeftJoin(userExamItemOptionTable, (op, uio) => op.Id == uio.QuestionOptionId)
+                .Select((op, uio) => new ViewQuestionOptionDto
+                {
+                    Content = op.Content,
+                    Id = op.Id,
+                    IsAnswer = op.IsAnswer,
+                    Label = op.Label,
+                    QuestionId = op.ExamQuestionId,
+                    IsSelected = uio.Id != null
+                }).MergeTable().OrderBy(x => x.Label);
+
+            return await querable.Distinct()
+                .ToListAsync();
+        }
+
+        private async Task<List<ViewQuestionAnswerDto>> GetQuestionAnswers(string id, List<string> questionIds)
+        {
+            var userExamTable = _repository.Queryable().Where(x => x.Id == id);
+            var userExamItemTable = _userExamItemRepository.Queryable();
+            var examAnswerTable = _examAnswerRepository.Queryable();
+            var questionTable = new ExamRepository<ExamQuestionBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+            var questionAnswerTable = new ExamRepository<ExamQuestionAnswerBak>(_uow, _dataPermissionFilterBuilder, _serviceProvider).Queryable();
+
+            var querable = examAnswerTable
+                .InnerJoin(userExamItemTable, (a, ui) => ui.Id == a.UserExamItemId && ui.UserExamId == id)
+                .LeftJoin(questionAnswerTable, (a, ui, qab) => qab.QuestionId == ui.QuestionId)
+                .LeftJoin<ExamQuestionAnswer>((a, ui, qab, qa) => qab.QuestionAnswerId == qa.Id)
+                .LeftJoin(questionTable, (a, ui, qab, qa, q) => qab.QuestionId == q.QuestionId && questionIds.Contains(q.Id))
+                .Select((a, ui, qab, qa, q) => new ViewQuestionAnswerDto
+                {
+                    Answer = a.Answer,
+                    CorrectAnswer = qa.Answer != null ? qa.Answer : string.Empty,
+                    QuestionId = q.Id
+                }).MergeTable();
+
+            return await querable.ToListAsync();
+        }
+
+        private async Task<List<ViewExamQuestionDto>> GetViewExamQuestion(string id, string questionId)
+        {
+            var expression = ExpressionableUtility.CreateExpression<ExamUserExam>()
+                .And(x => x.Id == id).ToExpression();
+
+            ISugarQueryable<GradingExamQuestionTempDto> queryable = GetViewExamQuestionTempDtos(expression);
+
+            queryable = queryable.MergeTable().WhereIF(!string.IsNullOrEmpty(questionId), x => x.QuestionId == questionId);
+
+            var queryResult = await queryable.ToListAsync();
+
+            var viewExamQuestion = queryResult.GroupBy(x => new
+            {
+                Id = x.Id,
+                QuestionType = x.QuestionType
+            }).Select(g => new ViewExamQuestionDto
+            {
+                Answer = g.FirstOrDefault().Answer,
+                QuestionType = g.Key.QuestionType,
+                QuestionId = g.FirstOrDefault().QuestionId,
+                ExamQuestionId = g.FirstOrDefault().ExamQuestionId,
+                Id = g.Key.Id,
+                Score = g.FirstOrDefault().QuestionScore,
+                RealScore = g.FirstOrDefault().Score,
+                Title = g.FirstOrDefault().Title,
+                CorrectAnswer = g.Key.QuestionType.CheckSelectType() ? string.Empty : g.FirstOrDefault()?.CorrectAnswer
+            }).ToList();
+
+            return viewExamQuestion;
+        }
         #endregion
 
     }

+ 4 - 2
src/Hotline.Application/Exam/Service/Practices/PracticeService.cs

@@ -670,11 +670,13 @@ namespace Hotline.Application.Exam.Service.Practices
             var tagQuestionTable = tagQuestionRepository.Queryable().Where(expression);
             var questionTable = quesitonRepository.Queryable().Where(questionExpression);
 
-            var taqQuestions = await tagQuestionTable.LeftJoin(questionTable, (t, q) => t.QuestionId == q.Id)
+            var queryable = tagQuestionTable.InnerJoin(questionTable, (t, q) => t.QuestionId == q.Id)
                 .Select((t, q) => new TagQuestionCountViewResponse
                 {
                     TotalCount = SqlFunc.AggregateCount(t.Id)
-                })
+                });
+
+            var taqQuestions = await queryable
                 .ToListAsync();
 
             return taqQuestions.Count() > 0 ? taqQuestions.FirstOrDefault() : new TagQuestionCountViewResponse

+ 23 - 17
src/Hotline.Application/Exam/Service/Trains/TrainRecordService.cs

@@ -32,6 +32,7 @@ using JiebaNet.Segmenter.Common;
 using Hotline.Repository.SqlSugar.Exam.Service;
 using Hotline.Repository.SqlSugar.Exam.Extensions;
 using DocumentFormat.OpenXml.Office2010.Excel;
+using SqlSugar;
 
 namespace Hotline.Application.Exam.Service.Trains
 {
@@ -68,7 +69,7 @@ namespace Hotline.Application.Exam.Service.Trains
         public async Task CompleteTrainRecordAsync(CompleteTrainRecordDto completeTrainRecordDto, CancellationToken cancellationToken)
         {
             var trainRecord = await _repository.GetAsync(x => x.Id == completeTrainRecordDto.Id);
-            trainRecord= _mapper.Map<CompleteTrainRecordDto,ExamTrainRecord>(completeTrainRecordDto, trainRecord);
+            trainRecord = _mapper.Map<CompleteTrainRecordDto, ExamTrainRecord>(completeTrainRecordDto, trainRecord);
             trainRecord.ToUpdate(_sessionContext);
 
             await _repository.UpdateWithValidateAsync(trainRecord, cancellationToken);
@@ -80,7 +81,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
             trainKnowladge.ToUpdate(_sessionContext);
 
-            await _trainKnowladgeRepository.UpdateWithValidateAsync(trainKnowladge,cancellationToken);
+            await _trainKnowladgeRepository.UpdateWithValidateAsync(trainKnowladge, cancellationToken);
 
         }
 
@@ -113,12 +114,12 @@ namespace Hotline.Application.Exam.Service.Trains
             SqlSugar.ISugarQueryable<TrainRecordViewResponse> queryResult = QueryResult(queryRequest);
 
             var total = await queryResult.CountAsync();
-            var items = await queryResult.ToPageListAsync(queryRequest.PageIndex,queryRequest.PageSize);
+            var items = await queryResult.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize);
 
             return new TrainRecordPageViewResponse
             {
                 Items = items,
-                Pagination = new Pagination(queryRequest.PageIndex,queryRequest.PageSize,total)
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
             };
 
         }
@@ -127,7 +128,7 @@ namespace Hotline.Application.Exam.Service.Trains
         {
             await GetTrainPracticeKnowladge(addTrainDto, cancellationToken);
 
-            await AddTrainRecordItem(addTrainDto,cancellationToken);
+            await AddTrainRecordItem(addTrainDto, cancellationToken);
 
             await AddTrainRecordAnswer(addTrainDto, cancellationToken);
 
@@ -138,7 +139,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
         private async Task GetTrainPracticeKnowladge(AddTrainDto addTrainDto, CancellationToken cancellationToken)
         {
-            var trainPracticeKnowladeRepository = new TrainPracticeKnowladgeRepository(_uow,_dataPermissionFilterBuilder,_serviceProvider);
+            var trainPracticeKnowladeRepository = new TrainPracticeKnowladgeRepository(_uow, _dataPermissionFilterBuilder, _serviceProvider);
             var trainPracticeKnowladge = await trainPracticeKnowladeRepository.GetAsync(x => x.TrainPracticeId == addTrainDto.TrainPracticeId);
 
             if (trainPracticeKnowladge != null)
@@ -208,6 +209,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
         public async Task<TrainResultPagedViewResponse> AnalysisTrainResult(TrainResultReportPagedRequest trainResultReportPagedRequest)
         {
+            if(trainResultReportPagedRequest.EndTime == null)
             trainResultReportPagedRequest.ResolveEndTime();
 
             var expression = trainResultReportPagedRequest.GetExpression();
@@ -227,7 +229,7 @@ namespace Hotline.Application.Exam.Service.Trains
             var queryResult = trainRecordTable.InnerJoin(trainPlanTable, (r, p) => r.TrainPlanId == p.Id)
                 .InnerJoin(trainPlanTemplateTable, (r, p, pt) => p.Id == pt.TrainPlanId)
                 .InnerJoin(trainTemplateTable, (r, p, pt, t) => pt.TrainTemplateId == t.Id)
-                .InnerJoin(userTable,(r,p,pt,t,u)=>r.UserId == u.Id)
+                .InnerJoin(userTable, (r, p, pt, t, u) => r.UserId == u.Id)
                 .Select((r, p, pt, t, u) => new TrainResultViewResponse
                 {
                     TrainName = t.Name,
@@ -235,7 +237,9 @@ namespace Hotline.Application.Exam.Service.Trains
                     IsComplete = r.IsComplete,
                     SortIndex = r.SortIndex,
                     Status = r.Status
-                });
+                })
+                .MergeTable()
+               .OrderByPropertyNameIF(!string.IsNullOrEmpty(trainResultReportPagedRequest.SortField), trainResultReportPagedRequest.SortField, (OrderByType)(trainResultReportPagedRequest.SortRule ?? 0));
 
             var total = await queryResult.CountAsync();
 
@@ -250,7 +254,8 @@ namespace Hotline.Application.Exam.Service.Trains
 
         public async Task<TrainResultRateViewResponse> CalcuteAnalysisRate(TrainResultReportPagedRequest trainResultReportPagedRequest)
         {
-            trainResultReportPagedRequest.ResolveEndTime();
+            if (trainResultReportPagedRequest.EndTime == null)
+                trainResultReportPagedRequest.ResolveEndTime();
 
             var expression = trainResultReportPagedRequest.GetExpression();
             var templateExpression = trainResultReportPagedRequest.GetTemplateExpression();
@@ -278,7 +283,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
             var calcuteRateResult = new TrainResultRateViewResponse
             {
-                Complete = await queryResult.CountAsync(x=>x.IsComplete),
+                Complete = await queryResult.CountAsync(x => x.IsComplete),
                 Trainning = await queryResult.CountAsync(x => !x.IsComplete && x.TrainTime > DateTime.Now),
                 UnComplete = await queryResult.CountAsync(x => !x.IsComplete && x.TrainTime <= DateTime.Now),
             };
@@ -369,8 +374,9 @@ namespace Hotline.Application.Exam.Service.Trains
             var knowledgeTable = knowledgeRepository.Queryable();
 
             var queryable = questionTable.InnerJoin(questionKnowladgeTable, (r, p, tpt, tp, ExamPracticeQuestionKnowledge) => tp.QuestionId == ExamPracticeQuestionKnowledge.QuestionId)
-                .InnerJoin(knowledgeTable, (r, p, tpt, tp, ExamPracticeQuestionKnowledge,kl) => ExamPracticeQuestionKnowledge.KnowladgeId == kl.Id)
-                .GroupBy((r, p, tpt, tp, ExamPracticeQuestionKnowledge, kl) => new {
+                .InnerJoin(knowledgeTable, (r, p, tpt, tp, ExamPracticeQuestionKnowledge, kl) => ExamPracticeQuestionKnowledge.KnowladgeId == kl.Id)
+                .GroupBy((r, p, tpt, tp, ExamPracticeQuestionKnowledge, kl) => new
+                {
                     Title = kl.Title,
                     Id = kl.Id
                 })
@@ -382,7 +388,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
             return await queryable.ToListAsync();
         }
-       
+
 
         private async Task AddTrainRecordAnswer(AddTrainDto addTrainDto, CancellationToken cancellationToken)
         {
@@ -407,7 +413,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
             trainRecordAnswers.ToInsert(_sessionContext);
 
-            await _trainRecordAnswerRepository.AddWithValidateAsync(trainRecordAnswers,cancellationToken);
+            await _trainRecordAnswerRepository.AddWithValidateAsync(trainRecordAnswers, cancellationToken);
         }
 
         //private async Task<TrainPracticeDto> GetTrainQuestionOptions(AddTrainDto addTrainDto)
@@ -442,7 +448,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
             var trainRecordTable = _repository.Queryable().Where(x => x.Id == trainPracticeRequest.Id);
             var trainPlanTable = trainPlanRepository.Queryable();
-            var trainPracticeTable = trainPracticeRepostitory.Queryable().WhereIF(!string.IsNullOrEmpty(trainPracticeRequest.TrainPracticeId),x=>x.Id == trainPracticeRequest.TrainPracticeId);
+            var trainPracticeTable = trainPracticeRepostitory.Queryable().WhereIF(!string.IsNullOrEmpty(trainPracticeRequest.TrainPracticeId), x => x.Id == trainPracticeRequest.TrainPracticeId);
             var tranPlanTemplateTable = trainPlanTemplateRepository.Queryable();
 
             var trainPractices = trainPracticeTable.InnerJoin(tranPlanTemplateTable, (tp, tpt) => tp.TrainTemplateId == tpt.TrainTemplateId)
@@ -462,7 +468,7 @@ namespace Hotline.Application.Exam.Service.Trains
 
             var trainPracticeOptions = await trainPracticeOptionsRepository.Queryable()
                 .InnerJoin(trainPractices, (tpo, tp) => tpo.TrainPracticeId == tp.Id)
-                .Where((tpo, tp)=> tp.Id == trainPracticeRequest.TrainPracticeId)
+                .Where((tpo, tp) => tp.Id == trainPracticeRequest.TrainPracticeId)
                 .Select((tpo, tp) => tpo).ToListAsync();
 
             var queryable = trainRecordAnswerRepository.Queryable()
@@ -478,7 +484,7 @@ namespace Hotline.Application.Exam.Service.Trains
             var trainPracticeOptionsDtos = new List<SimpleTrainPracticeOptionsDto>();
 
             trainPracticeOptions.ForEach(x =>
-            {              
+            {
                 var trainPracticeOptionsDto = _mapper.Map<SimpleTrainPracticeOptionsDto>(x);
 
                 trainPracticeOptionsDto.IsSelected = trainRecordOptions.Any(m => m.QuestionOptionId == x.QuestionOptionId);

+ 39 - 9
src/Hotline.Application/Jobs/ApptaskJob.cs

@@ -1,8 +1,12 @@
 using Hotline.BatchTask;
+using Hotline.BatchTask.Notifications;
 using Quartz;
 using Hotline.Share.Enums.BatchTask;
-using Hotline.Share.Dtos.Order;
 using Microsoft.Extensions.DependencyInjection;
+using Hotline.EventBus;
+using Hotline.Share.Dtos.Order.OrderVisit;
+using Hotline.Share.Dtos.Order.OrderDelay;
+using Hotline.Application.OrderApp.OrderDelayApp;
 using Hotline.Application.OrderApp.OrderVisitApp;
 
 namespace Hotline.Application.Jobs
@@ -11,12 +15,16 @@ namespace Hotline.Application.Jobs
     {
         private readonly IApptaskDomainService _apptaskDomainService;
         private readonly IServiceProvider _serviceProvider;
+        private readonly Publisher _publisher;
 
-        public ApptaskJob(IApptaskDomainService apptaskDomainService,
-            IServiceProvider serviceProvider)
+        public ApptaskJob(
+            IApptaskDomainService apptaskDomainService,
+            IServiceProvider serviceProvider,
+            Publisher publisher)
         {
             _apptaskDomainService = apptaskDomainService;
             _serviceProvider = serviceProvider;
+            _publisher = publisher;
         }
 
         public async Task Execute(IJobExecutionContext context)
@@ -24,21 +32,43 @@ namespace Hotline.Application.Jobs
             //Console.WriteLine($"执行ApptaskJob: {DateTime.Now}");
             var task = await _apptaskDomainService.GetWaitingTaskAsync(context.CancellationToken);
             if (task is null) return;
+            ApptaskExecuteResult? result = null;
             switch (task.TaskType)
             {
-                case ETaskType.Delay:
-                    //var delayExecutor = _serviceProvider.GetService<IApptaskExecutor<BatchDelayNextFlowDto>>();
-                    //await _apptaskDomainService.ExecuteAsync(delayExecutor, task, context.CancellationToken);
+                case ETaskType.OrderDelay:
+                    var delayExecutor = _serviceProvider.GetService<IApptaskExecutor<OrderDelayReviewWithSessionRequest>>();
+                    result = await _apptaskDomainService.ExecuteAsync(delayExecutor, task, context.CancellationToken);
                     break;
-                case ETaskType.Screen:
+                case ETaskType.OrderScreen:
                     break;
                 case ETaskType.VoiceVisit:
-                    var vvExecutor = _serviceProvider.GetService<IApptaskExecutor<VoiceVisitRequest>>();
-                    await _apptaskDomainService.ExecuteAsync(vvExecutor, task, context.CancellationToken);
+                    var vvExecutor = _serviceProvider.GetService<IApptaskExecutor<VoiceVisitWithSessionRequest>>();
+                    result = await _apptaskDomainService.ExecuteAsync(vvExecutor, task, context.CancellationToken);
                     break;
                 default:
                     throw new ArgumentOutOfRangeException();
             }
+
+            if (result is not null)
+            {
+                if (result.IsSuccess)
+                {
+                    //todo pub single complete suc
+                    await _publisher.PublishAsync(new ApptaskSuccessNotify(task), PublishStrategy.Async, context.CancellationToken);
+
+                    //todo check if task is all complete
+                    //pub all complete
+                    var isCompleted = await _apptaskDomainService.IsCompletedAsync(task.ApptaskId, context.CancellationToken);
+                    if (isCompleted) 
+                        await _publisher.PublishAsync(new ApptaskCompletedNotify(task), PublishStrategy.Async, context.CancellationToken);
+                }
+                else
+                {
+                    //todo pub single complete fail
+                    await _publisher.PublishAsync(new ApptaskFailNotify(task), PublishStrategy.Async, context.CancellationToken);
+                }
+
+            }
         }
 
         /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>

+ 82 - 0
src/Hotline.Application/OrderApp/Handlers/OrderDelayHandler/OrderDelayBatchReviewTaskCompetedHandler.cs

@@ -0,0 +1,82 @@
+using Hotline.BatchTask.Notifications;
+using Hotline.FlowEngine.Notifications;
+using MediatR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.BatchTask;
+using Hotline.Share.Enums.BatchTask;
+using Hotline.Article;
+using Hotline.Orders;
+using Hotline.Share.Dtos.Article;
+using Hotline.Share.Enums.Article;
+using Hotline.Share.Enums.Order;
+using XF.Domain.Authentications;
+using XF.Domain.Repository;
+
+namespace Hotline.Application.OrderApp.Handlers.OrderDelayHandler;
+
+/// <summary>
+/// 延期批量审批任务执行完成
+/// </summary>
+public class OrderDelayBatchReviewTaskCompetedHandler : INotificationHandler<ApptaskCompletedNotify>
+{
+    private readonly IRepository<Apptask> _apptaskRepository;
+    private readonly IOrderDelayRepository _orderDelayRepository;
+    private readonly ICircularRecordDomainService _circularRecordDomainService;
+
+    public OrderDelayBatchReviewTaskCompetedHandler(
+        IRepository<Apptask> apptaskRepository,
+        IOrderDelayRepository orderDelayRepository,
+        ICircularRecordDomainService circularRecordDomainService)
+    {
+        _apptaskRepository = apptaskRepository;
+        _orderDelayRepository = orderDelayRepository;
+        _circularRecordDomainService = circularRecordDomainService;
+    }
+
+    /// <summary>Handles a notification</summary>
+    /// <param name="notification">The notification</param>
+    /// <param name="cancellationToken">Cancellation token</param>
+    public async Task Handle(ApptaskCompletedNotify notification, CancellationToken cancellationToken)
+    {
+        if (notification.ApptaskItem.TaskType is not ETaskType.OrderDelay) return;
+
+        var apptask = await _apptaskRepository.Queryable()
+            .Includes(d => d.ApptaskItems)
+            .FirstAsync(d => d.Id == notification.ApptaskItem.ApptaskId, cancellationToken);
+        var unSuccessItems = apptask.ApptaskItems
+            .Where(d => d.TaskStatus != ETaskStatus.Succeeded)
+            .ToList();
+        if (unSuccessItems.Count > 0)
+        {
+            // 回滚delay状态为待审批
+            await _orderDelayRepository.Updateable()
+                .SetColumns(d => d.DelayState == EDelayState.Examining)
+                .Where(d => unSuccessItems.Select(x => x.BusinessId).Contains(d.Id))
+                .ExecuteCommandAsync(cancellationToken);
+        }
+
+        await _circularRecordDomainService.AddCircularMessage(new AddCircularDto
+        {
+            Title = $"{apptask.Name} 执行完成",
+            Content = $"总共:{apptask.ApptaskItems.Count}个任务,成功:{apptask.ApptaskItems.Count(d => d.TaskStatus == ETaskStatus.Succeeded)},失败:{apptask.ApptaskItems.Count(d => d.TaskStatus == ETaskStatus.Failed)}",
+            CircularTypeId = "5",
+            CircularTypeName = "系统消息",
+            CircularType = ECircularType.Person,
+            IsMustRead = false,
+            SourceOrgId = apptask.CreatorId,
+            SourceOrgName = apptask.CreatorName,
+            CircularReadGroups = new List<CircularReadGroupDto>
+            {
+                new()
+                {
+                    UserId = apptask.CreatorId,
+                    UserName = apptask.CreatorName
+                }
+            }
+        }, cancellationToken);
+    }
+}

+ 14 - 0
src/Hotline.Application/OrderApp/IOrderApplication.cs

@@ -245,6 +245,20 @@ namespace Hotline.Application.OrderApp
         /// <returns></returns>
         ISugarQueryable<OrderBiCentreDataListVo> CentreDataList(ReportPagedRequest dto);
 
+        /// <summary>
+        /// 话务员办件统计--自贡专用
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<OrderBiCentreDataListVo> CentreDataListZG(ReportPagedRequest dto);
+
+        /// <summary>
+        /// 话务员办件统计明细--自贡专用
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        ISugarQueryable<Order> CentreDataListDetailZG(CentreDataListDetailRequest dto);
+
         /// <summary>
         /// 工单业务量统计
         /// </summary>

+ 155 - 43
src/Hotline.Application/OrderApp/OrderApplication.cs

@@ -46,6 +46,7 @@ using Hotline.Share.Enums.Settings;
 using Hotline.Share.Mq;
 using Hotline.Share.Requests;
 using Hotline.Share.Tools;
+using Hotline.Snapshot.IRepository;
 using Hotline.Statistics;
 using Hotline.Tools;
 using Hotline.Users;
@@ -71,6 +72,7 @@ namespace Hotline.Application.OrderApp;
 
 public class OrderApplication : IOrderApplication, IScopeDependency
 {
+    private readonly IOrderSnapshotRepository _orderSnapshotRepository;
     private readonly IMediator _mediator;
     private readonly IRepository<TranspondCityRawData> _transpondCityRawDataRepository;
     private readonly ISessionContextProvider _sessionContextProvider;
@@ -181,7 +183,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         IRepository<OrderVisitDetailCopy> orderVisitDetailCopyRepository,
         IRepository<OrderDelayAutomatic> orderDelayAutomaticRepository,
         IRepository<Hotline.Orders.ObservationPiece> observationPieceRepository
-        )
+,
+        IOrderSnapshotRepository orderSnapshotRepository)
     {
         _orderDomainService = orderDomainService;
         _workflowDomainService = workflowDomainService;
@@ -236,6 +239,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         _orderVisitDetailCopyRepository = orderVisitDetailCopyRepository;
         _orderDelayAutomaticRepository = orderDelayAutomaticRepository;
         _observationPieceRepository = observationPieceRepository;
+        _orderSnapshotRepository = orderSnapshotRepository;
     }
 
     /// <summary>
@@ -986,8 +990,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 d.Order.SourceChannelCode == "SZMHD" && d.Order.IsProvince == true) //政民互动
             .WhereIF(!string.IsNullOrEmpty(dto.ProvinceChannel) && dto.ProvinceChannel == "3", d => d.Order.Source == ESource.ProvinceStraight &&
                 d.Order.SourceChannelCode == "S12345" && d.Order.IsProvince == true) //省12345
-            .WhereIF(dto.PublishState.HasValue , d=>d.PublishState == dto.PublishState.Value) //发布范围 是否公开
-			.OrderByIF(string.IsNullOrEmpty(dto.SortField), d => d.CreationTime, OrderByType.Desc)
+            .WhereIF(dto.PublishState.HasValue, d => d.PublishState == dto.PublishState.Value) //发布范围 是否公开
+            .OrderByIF(string.IsNullOrEmpty(dto.SortField), d => d.CreationTime, OrderByType.Desc)
             .OrderByIF(dto is { SortRule: 0, SortField: "creationTime" }, d => d.Order.CreationTime, OrderByType.Asc)   //受理时间
             .OrderByIF(dto is { SortRule: 1, SortField: "creationTime" }, d => d.Order.CreationTime, OrderByType.Desc)
             .OrderByIF(dto is { SortRule: 0, SortField: "filedTime" }, d => d.Order.FiledTime, OrderByType.Asc)  //办结时间
@@ -1303,20 +1307,20 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 canUpdateOrderSender);
             //TODO发送短信即将超期
             //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
-            //自动延期订阅
-            var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
-            if (bool.Parse(enabled))
-            {
-                //自动延期催办短信发送
-                await _capPublisher.PublishDelayAsync(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(2),
-                    EventNames.HotlineOrderAutomaticSendSmsDelay, new PublishAutomaticDelayDto() { OrderId = order.Id }, cancellationToken: cancellationToken);
-
-                await _capPublisher.PublishDelayAsync(
-                    expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1),
-                    EventNames.HotlineOrderAutomaticDelay,
-                    new PublishAutomaticDelayDto() { OrderId = order.Id },
-                    cancellationToken: cancellationToken);
-            }
+            ////自动延期订阅
+            //var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+            //if (bool.Parse(enabled))
+            //{
+            //    //自动延期催办短信发送
+            //    await _capPublisher.PublishDelayAsync(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(2),
+            //        EventNames.HotlineOrderAutomaticSendSmsDelay, new PublishAutomaticDelayDto() { OrderId = order.Id }, cancellationToken: cancellationToken);
+
+            //    await _capPublisher.PublishDelayAsync(
+            //        expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1),
+            //        EventNames.HotlineOrderAutomaticDelay,
+            //        new PublishAutomaticDelayDto() { OrderId = order.Id },
+            //        cancellationToken: cancellationToken);
+            //}
         }
         else if (dto.Workflow.FlowDirection is EFlowDirection.CenterToOrg)
         {
@@ -1341,16 +1345,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             //TODO发送短信即将超期
             //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
             //自动延期订阅
-            var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
-            if (bool.Parse(enabled))
-            {
-                //自动延期催办短信发送
-                await _capPublisher.PublishDelayAsync(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(2),
-                    EventNames.HotlineOrderAutomaticSendSmsDelay, new PublishAutomaticDelayDto() { OrderId = order.Id }, cancellationToken: cancellationToken);
+            //var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+            //if (bool.Parse(enabled))
+            //{
+            //    //自动延期催办短信发送
+            //    await _capPublisher.PublishDelayAsync(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(2),
+            //        EventNames.HotlineOrderAutomaticSendSmsDelay, new PublishAutomaticDelayDto() { OrderId = order.Id }, cancellationToken: cancellationToken);
 
-                _capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
-                    new PublishAutomaticDelayDto() { OrderId = order.Id });
-            }
+            //    _capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
+            //        new PublishAutomaticDelayDto() { OrderId = order.Id });
+            //}
         }
         else if (dto.Workflow.FlowDirection is EFlowDirection.CenterToCenter)
         {
@@ -1364,16 +1368,16 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 //TODO发送短信即将超期
                 //_capPublisher.PublishDelay(expiredTimeConfig.NearlyExpiredTime - DateTime.Now, EventNames.HotlineOrderNearlyExpiredTimeSms, new PublishNearlyExpiredTimeSmsDto() { OrderId = order.Id });
                 //自动延期订阅
-                var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
-                if (bool.Parse(enabled))
-                {
-                    //自动延期催办短信发送
-                    await _capPublisher.PublishDelayAsync(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(2),
-                        EventNames.HotlineOrderAutomaticSendSmsDelay, new PublishAutomaticDelayDto() { OrderId = order.Id }, cancellationToken: cancellationToken);
-
-                    _capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
-                        new PublishAutomaticDelayDto() { OrderId = order.Id });
-                }
+                //var enabled = _systemSettingCacheManager.GetSetting(SettingConstants.EnabledAutomaticDelay)?.SettingValue[0];
+                //if (bool.Parse(enabled))
+                //{
+                //    //自动延期催办短信发送
+                //    await _capPublisher.PublishDelayAsync(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(2),
+                //        EventNames.HotlineOrderAutomaticSendSmsDelay, new PublishAutomaticDelayDto() { OrderId = order.Id }, cancellationToken: cancellationToken);
+
+                //    _capPublisher.PublishDelay(expiredTimeConfig.ExpiredTime - DateTime.Now.AddHours(1), EventNames.HotlineOrderAutomaticDelay,
+                //        new PublishAutomaticDelayDto() { OrderId = order.Id });
+                //}
             }
         }
         else if (dto.Workflow.FlowDirection is EFlowDirection.CenterToFile) //中心直接归档,中心意见为实际办理意见
@@ -1764,15 +1768,23 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     {
         var orderVisitList = await _orderVisitRepository.Queryable()
             .Includes(d => d.Order)
-            .Where(d => dto.Ids.Contains(d.Id) && (d.VisitState == EVisitState.WaitForVisit || d.VisitState == EVisitState.NoSatisfiedWaitForVisit ||
-                                                   d.VisitState == EVisitState.SMSUnsatisfied))
+            .Where(d => dto.Ids.Contains(d.Id) && (d.VisitState == EVisitState.WaitForVisit || d.VisitState == EVisitState.NoSatisfiedWaitForVisit || d.VisitState == EVisitState.SMSUnsatisfied))
             .Select(d => new { d.Id, d.Order.SourceChannelCode, d.Order.Contact, d.Order.Password, d.No, d.OrderId, d.Order.Title, d.Order.FromName })
             .ToListAsync(cancellationToken);
 
         foreach (var item in orderVisitList)
         {
             var code = "1013";
-            if (item.SourceChannelCode == "ZGSSP") code = "1012";
+            if (_systemSettingCacheManager.Snapshot && item.SourceChannelCode == "ZGSSP")
+            {
+                await _orderSnapshotRepository.Queryable().Where(m => m.Id == item.OrderId)
+                    .Select(m => m.IndustryName)
+                    .FirstAsync()
+                    .Then(name => 
+                    {
+                        if (name.Trim() == "电气焊作业申报") code = "1012";
+                    });
+            }
             var messageDto = new Share.Dtos.Push.MessageDto
             {
                 PushBusiness = EPushBusiness.VisitSms,
@@ -1875,6 +1887,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                                                                                                                //.WhereIF(!string.IsNullOrEmpty(dto.OrgId), d => d.CurrentHandleOrgId == dto.OrgId)//接办部门
                 .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), d => d.OrgLevelOneName.Contains(dto.OrgLevelOneName)) //一级部门
                 .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgName), d => d.ActualHandleOrgName.Contains(dto.ActualHandleOrgName)) //接办部门(综合查询模糊)
+                .WhereIF(!string.IsNullOrEmpty(dto.ActualHandleOrgCode), d => d.ActualHandleOrgCode.StartsWith(dto.ActualHandleOrgCode)) //接办部门(综合查询模糊)
                 .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), d => d.AcceptorName == dto.NameOrNo! || d.AcceptorStaffNo == dto.NameOrNo!) //受理人/坐席
                 .WhereIF(dto.CreationTimeStart.HasValue, d => d.CreationTime >= dto.CreationTimeStart) //受理时间开始
                 .WhereIF(dto.CreationTimeEnd.HasValue, d => d.CreationTime <= dto.CreationTimeEnd) //受理时间结束
@@ -1933,6 +1946,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 .WhereIF(!string.IsNullOrEmpty(dto.OrderTagCode), d => d.OrderTags.Any(ot => ot.DicDataValue == dto.OrderTagCode)) //工单标签
                 .WhereIF(!string.IsNullOrEmpty(dto.FocusOnEvents),
                     d => d.FocusOnEvents.Contains(dto.FocusOnEvents) && !d.FocusOnEvents.Contains("99")) //!string.IsNullOrEmpty(d.FocusOnEvents) && SqlFunc.SplitIn(d.FocusOnEvents, dto.FocusOnEvents))
+                 .WhereIF(dto.SeatEvaluate.Any(), d => dto.SeatEvaluate.Contains(d.SeatEvaluate.Value)) //话务员评价(话务评价)
+                .WhereIF(dto.OrgProcessingResults.Any(), d => dto.OrgProcessingResults.Contains(SqlFunc.JsonField(d.OrgProcessingResults, "Key")))//部门办件结果
                 .OrderByIF(string.IsNullOrEmpty(dto.SortField), d => d.CreationTime, OrderByType.Desc) //默认排序时间为创建时间
                 .OrderByIF(dto is { SortField: "no", SortRule: 0 }, d => d.No, OrderByType.Asc) //工单编号升序
                 .OrderByIF(dto is { SortField: "no", SortRule: 1 }, d => d.No, OrderByType.Desc) //工单编号降序
@@ -2106,7 +2121,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
 
         query = query
             .Includes(x => x.OrderSupervises.OrderByDescending(d => d.CreationTime).Take(2).ToList())
-            .Includes(x => x.OrderVisits, d => d.OrderVisitDetails.Where(c => c.VisitTarget == EVisitTarget.Org).Take(1).ToList());
+            .Includes(x => x.OrderVisits.Where(q=>q.VisitState == EVisitState.Visited).Take(1).ToList(), d => d.OrderVisitDetails.Where(c => c.VisitTarget == EVisitTarget.Org).Take(1).ToList());
 
         return query;
     }
@@ -3555,6 +3570,104 @@ public class OrderApplication : IOrderApplication, IScopeDependency
         return query;
     }
 
+    /// <summary>
+    /// 话务员办件统计--自贡专用
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<OrderBiCentreDataListVo> CentreDataListZG(ReportPagedRequest dto)
+    {
+        if (!dto.StartTime.HasValue || !dto.EndTime.HasValue) throw UserFriendlyException.SameMessage("请选择时间!");
+
+        var query = _orderRepository.Queryable(false, false, false)
+            .WhereIF(dto.StartTime.HasValue, it => it.CreationTime >= dto.StartTime)
+            .WhereIF(dto.EndTime.HasValue, it => it.CreationTime <= dto.EndTime)
+            .WhereIF(!string.IsNullOrEmpty(dto.Keyword), it => it.SignerName.Contains(dto.Keyword!))
+            .GroupBy(it => new { it.AcceptorId })
+            .Select(it => new OrderBiCentreDataListVo
+            {
+                UserId = it.AcceptorId,
+                CentreArchive = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status >= EOrderStatus.Filed && it.FileOrgIsCenter == true, 1, 0)), //中心归档件
+
+                CentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF((it.Status >= EOrderStatus.Filed && it.FileOrgIsCenter == false) ||
+               (it.ActualHandleStepName != "话务部" && it.Status < EOrderStatus.Filed && it.Status > EOrderStatus.WaitForAccept)
+               , 1, 0)),//转办件(部门归档件+已开启流程且当前节点在非话务部的办理中的工单)
+
+                NoCentreCareOf = SqlFunc.AggregateSum(SqlFunc.IIF(it.ActualHandleStepName == "话务部" &&
+                it.Status < EOrderStatus.Filed && it.Status > EOrderStatus.WaitForAccept, 1, 0)),//待转办信件(当前节点为话务部,已受理未归档的工单)
+                Invalid = 0,
+                Repeat = 0,
+                Subtotal = SqlFunc.AggregateSum(SqlFunc.IIF(it.Status > EOrderStatus.WaitForAccept, 1, 0))//排除待受理工单
+            })
+            .LeftJoin<User>((it, u) => it.UserId == u.Id)
+            .Select((it, u) => new OrderBiCentreDataListVo
+            {
+                UserId = it.UserId,
+                UserName = u.Name,
+                CentreArchive = it.CentreArchive,
+                CentreCareOf = it.CentreCareOf,
+                NoCentreCareOf = it.NoCentreCareOf,
+                Invalid = it.Invalid,
+                Repeat = it.Repeat,
+                Subtotal = it.Subtotal
+            }).MergeTable()
+            ;
+        switch (dto.SortField)
+        {
+            case "centreArchive":
+                query = dto.SortRule is 0 ? query.OrderBy(x => x.CentreArchive) : query.OrderByDescending(x => x.CentreArchive);
+                break;
+            case "centreCareOf":
+                query = dto.SortRule is 0 ? query.OrderBy(x => x.CentreCareOf) : query.OrderByDescending(x => x.CentreCareOf);
+                break;
+            case "noCentreCareOf":
+                query = dto.SortRule is 0 ? query.OrderBy(x => x.NoCentreCareOf) : query.OrderByDescending(x => x.NoCentreCareOf);
+                break;
+            case "subtotal":
+                query = dto.SortRule is 0 ? query.OrderBy(x => x.Subtotal) : query.OrderByDescending(x => x.Subtotal);
+                break;
+        }
+
+        if (string.IsNullOrEmpty(dto.SortField))
+        {
+            query = query.OrderByDescending(x => x.Subtotal);
+        }
+
+        return query;
+    }
+
+    /// <summary>
+    /// 话务员办件统计明细--自贡专用
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public ISugarQueryable<Order> CentreDataListDetailZG(CentreDataListDetailRequest dto)
+    {
+        var query = _orderRepository.Queryable(false, false, false)
+       .WhereIF(dto.StartTime.HasValue, p => p.CreationTime >= dto.StartTime)
+       .WhereIF(dto.EndTime.HasValue, p => p.CreationTime <= dto.EndTime)
+       .WhereIF(!string.IsNullOrEmpty(dto.UserId), p => p.AcceptorId == dto.UserId)
+       .WhereIF(!string.IsNullOrEmpty(dto.No), p => p.No == dto.No)
+       .WhereIF(!string.IsNullOrEmpty(dto.Title), p => p.Title.Contains(dto.Title))
+       ;
+        if (dto.FieldName == "subtotal")//所有信件
+            query = query.Where(p => p.Status > EOrderStatus.WaitForAccept);
+
+        if (dto.FieldName == "centreArchive")//中心归档件
+            query = query.Where(p => p.Status >= EOrderStatus.Filed && p.FileOrgIsCenter == true);
+
+        if (dto.FieldName == "centreCareOf")//转办信件
+            query = query.Where(p => (p.Status >= EOrderStatus.Filed && p.FileOrgIsCenter == false) ||
+               (p.ActualHandleStepName != "话务部" && p.Status < EOrderStatus.Filed && p.Status > EOrderStatus.WaitForAccept));
+
+        if (dto.FieldName == "noCentreCareOf")//待转办信件
+            query = query.Where(p => p.ActualHandleStepName == "话务部" && p.Status < EOrderStatus.Filed && p.Status > EOrderStatus.WaitForAccept);
+
+        query = query.OrderByDescending(p => p.CreationTime);
+
+        return query;
+    }
+
     /// <summary>
     /// 工单业务量统计
     /// </summary>
@@ -6546,7 +6659,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
     {
         var isAdmin = _orderDomainService.IsCheckAdmin();
         var query = _orderDelayRepository.Queryable();
-        if (!isAdmin)
+        if (!isAdmin && _appOptions.Value.IsZiGong == false)
         {
             query.Where(d => SqlFunc.Subqueryable<WorkflowTrace>()
                 .Where(step => step.ExternalId == d.Id &&
@@ -6576,8 +6689,7 @@ public class OrderApplication : IOrderApplication, IScopeDependency
                 // .WhereIF(dto.DataScope is 0, d => (d.AutomaticDelayNum == 0 || d.AutomaticDelayNum == null) && d.DelayState != EDelayState.Withdraw)
                 .WhereIF(dto.DataScope is 0, d => d.DelayState != EDelayState.Withdraw)
                 .WhereIF(dto.DataScope is 0 && !_sessionContext.OrgIsCenter, d => d.CreatorOrgId.StartsWith(_sessionContext.RequiredOrgId))
-                .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-                    d => d.Order.Title.Contains(dto.Keyword!) || d.Order.No.Contains(dto.Keyword!))
+                .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Order.Title.Contains(dto.Keyword!) || d.Order.No.Contains(dto.Keyword!))
                 .WhereIF(dto.IsApply == true, d => d.DelayState != EDelayState.Examining)
                 .WhereIF(dto.IsApply == false, d => d.DelayState == EDelayState.Examining)
                 .WhereIF(dto.DelayState != null, d => d.DelayState == dto.DelayState)

+ 8 - 0
src/Hotline.Application/OrderApp/OrderDelayApp/IOrderDelayApplication.cs

@@ -20,4 +20,12 @@ public interface IOrderDelayApplication
     /// <param name="cancellation"></param>
     /// <returns></returns>
     Task ReviewAsync(OrderDelayReviewRequest request, CancellationToken cancellation);
+
+    /// <summary>
+    /// 延期批量审核
+    /// </summary>
+    /// <param name="request"></param>
+    /// <param name="cancellation"></param>
+    /// <returns></returns>
+    Task BatchReviewAsync(BatchOrderDelayReviewRequest request, CancellationToken cancellation);
 }

+ 28 - 20
src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayApplication.cs

@@ -6,6 +6,7 @@ using Hotline.Share.Enums.FlowEngine;
 using MapsterMapper;
 using Microsoft.AspNetCore.Http;
 using System.Threading;
+using Hotline.Authentications;
 using Hotline.BatchTask;
 using Hotline.Orders;
 using Hotline.Share.Dtos.BatchTask;
@@ -175,7 +176,7 @@ public class OrderDelayApplication : IOrderDelayApplication, IScopeDependency
     }
 
     /// <summary>
-    /// 批量审核
+    /// 延期批量审核
     /// </summary>
     /// <returns></returns>
     public async Task BatchReviewAsync(BatchOrderDelayReviewRequest request, CancellationToken cancellation)
@@ -185,32 +186,39 @@ public class OrderDelayApplication : IOrderDelayApplication, IScopeDependency
             .Where(d => delayIds.Contains(d.Id))
             .ToListAsync(cancellation);
 
-        var apptaskItems = new List<ApptaskItem>();
+        var apptaskItems = new List<AddApptaskItemRequest>();
+        var req = new OrderDelayReviewWithSessionRequest
+        {
+            SessionContext = _sessionContext,
+            IsPass = request.IsPass,
+            NextWorkflow = request.NextWorkflow
+        };
         foreach (var delay in delays)
         {
-            var stepId = request.DelayWithStepIds.First(d => d.DelayId == delay.Id).StepId;
-            request.NextWorkflow.StepId = stepId;
-            apptaskItems.Add(new ApptaskItem
+            req.NextWorkflow.StepId = request.DelayWithStepIds.First(d => d.DelayId == delay.Id).StepId;
+            apptaskItems.Add(new AddApptaskItemRequest
             {
                 BusinessId = delay.Id,
-                TaskType = ETaskType.Delay,
-                TaskParams = System.Text.Json.JsonSerializer.Serialize(request.NextWorkflow)
+                TaskParams = req
             });
         }
 
-        var apptask = new Apptask
+        var taskId = await _apptaskDomainService.AddAsync(new AddApptaskRequest
         {
-            TaskType = ETaskType.Delay,
-            ApptaskItems = apptaskItems,
-        };
-        
-        //
-        //var addApptask = new AddApptaskRequest
-        //{
-            
-        //}
-        
-        
-        //_apptaskDomainService.AddAsync()
+            TaskType = ETaskType.OrderDelay,
+            Priority = 0,
+            ApptaskItems = apptaskItems
+        }, cancellation);
+
+        if (!string.IsNullOrEmpty(taskId))
+        {
+            foreach (var orderDelay in delays)
+            {
+                orderDelay.DelayState = EDelayState.BatchProcessing;
+            }
+            await _orderDelayRepository.Updateable(delays)
+                .UpdateColumns(d=>new {d.DelayState})
+                .ExecuteCommandAsync(cancellation);
+        }
     }
 }

+ 0 - 20
src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayAuditTaskExecutor.cs

@@ -1,20 +0,0 @@
-//using Hotline.BatchTask;
-//using Hotline.Share.Dtos.Order;
-//using XF.Domain.Dependency;
-
-//namespace Hotline.Application.OrderApp.OrderDelayApp;
-
-//public class OrderDelayAuditTaskExecutor : IApptaskExecutor<BatchDelayNextFlowDto>, IScopeDependency
-//{
-//    /// <summary>
-//    /// 执行任务
-//    /// </summary>
-//    /// <param name="request"></param>
-//    /// <param name="cancellation"></param>
-//    /// <returns>是否成功执行</returns>
-//    public async Task<ApptaskExecuteResult> ExecuteAsync(BatchDelayNextFlowDto? request, CancellationToken cancellation)
-//    {
-
-//    }
-
-//}

+ 38 - 0
src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayReviewTaskExecutor.cs

@@ -0,0 +1,38 @@
+using Hotline.BatchTask;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Order.OrderDelay;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.OrderApp.OrderDelayApp;
+
+public class OrderDelayReviewTaskExecutor : IApptaskExecutor<OrderDelayReviewRequest>, IScopeDependency
+{
+    private readonly IOrderDelayApplication _orderDelayApplication;
+
+    public OrderDelayReviewTaskExecutor(IOrderDelayApplication orderDelayApplication)
+    {
+        _orderDelayApplication = orderDelayApplication;
+    }
+
+    /// <summary>
+    /// 执行任务
+    /// </summary>
+    /// <param name="request"></param>
+    /// <param name="cancellation"></param>
+    /// <returns>是否成功执行</returns>
+    public async Task<ApptaskExecuteResult> ExecuteAsync(OrderDelayReviewRequest? request, CancellationToken cancellation)
+    {
+        if (request == null)
+            return ApptaskExecuteResult.Fail("请求参数为空");
+        try
+        {
+            await _orderDelayApplication.ReviewAsync(request, cancellation);
+        }
+        catch (Exception ex)
+        {
+            return ApptaskExecuteResult.Fail(ex.Message);
+        }
+
+        return ApptaskExecuteResult.Success();
+    }
+}

+ 16 - 0
src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayReviewWithSessionRequest.cs

@@ -0,0 +1,16 @@
+using Hotline.Share.Dtos.Order.OrderDelay;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.BatchTask;
+using XF.Domain.Authentications;
+
+namespace Hotline.Application.OrderApp.OrderDelayApp
+{
+    public class OrderDelayReviewWithSessionRequest : OrderDelayReviewRequest, IApptaskRequest
+    {
+        public ISessionContext SessionContext { get; set; }
+    }
+}

+ 7 - 0
src/Hotline.Application/OrderApp/OrderVisitApp/IOrderVisitApplication.cs

@@ -1,4 +1,5 @@
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Order.OrderVisit;
 
 namespace Hotline.Application.OrderApp.OrderVisitApp;
 
@@ -17,4 +18,10 @@ public interface IOrderVisitApplication
     /// <param name="dto"></param>
     /// <returns></returns>
     Task<string> AddOrderVisitRecordAsync(OrderVisitRecordDto dto, CancellationToken cancellationToken = default);
+
+    /// <summary>
+    /// 新增批量语音回访任务
+    /// </summary>
+    /// <returns></returns>
+    Task AddTaskBatchVoiceVisitAsync(BatchVoiceVisitRequest request, CancellationToken cancellation);
 }

+ 48 - 1
src/Hotline.Application/OrderApp/OrderVisitApp/OrderVisitApplication.cs

@@ -1,6 +1,10 @@
-using Hotline.Configurations;
+using Hotline.BatchTask;
+using Hotline.Configurations;
 using Hotline.Orders;
+using Hotline.Share.Dtos.BatchTask;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Dtos.Order.OrderVisit;
+using Hotline.Share.Enums.BatchTask;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Tools;
 using MapsterMapper;
@@ -20,17 +24,20 @@ public class OrderVisitApplication : IOrderVisitApplication, IScopeDependency
 
     private readonly IOrderVisitRepository _orderVisitRepository;
     private readonly IRepository<OrderVisitRecord> _orderVisitRecordRepository;
+    private readonly IApptaskDomainService _apptaskDomainService;
     private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
     private readonly IMapper _mapper;
 
     public OrderVisitApplication(
         IOrderVisitRepository orderVisitRepository,
         IRepository<OrderVisitRecord> orderVisitRecordRepository,
+        IApptaskDomainService apptaskDomainService,
         IOptionsSnapshot<AppConfiguration> appOptions,
         IMapper mapper)
     {
         _orderVisitRepository = orderVisitRepository;
         _orderVisitRecordRepository = orderVisitRecordRepository;
+        _apptaskDomainService = apptaskDomainService;
         _appOptions = appOptions;
         _mapper = mapper;
     }
@@ -99,4 +106,44 @@ public class OrderVisitApplication : IOrderVisitApplication, IScopeDependency
     }
 
     #endregion
+
+    /// <summary>
+    /// 新增批量语音回访任务
+    /// </summary>
+    /// <returns></returns>
+    public async Task AddTaskBatchVoiceVisitAsync(BatchVoiceVisitRequest request, CancellationToken cancellation)
+    {
+        var visits = await _orderVisitRepository.Queryable()
+            .Includes(d => d.Order)
+            .Where(d => request.VisitIds.Contains(d.Id)
+                        && !string.IsNullOrEmpty(d.Order.Contact))
+            .ToListAsync(cancellation);
+
+        var taskId = await _apptaskDomainService.AddAsync(new AddApptaskRequest
+        {
+            TaskType = ETaskType.VoiceVisit,
+            TryLimit = 10,
+            ApptaskItems = visits.Select(d => new AddApptaskItemRequest
+            {
+                BusinessId = d.Id,
+                TaskParams = new VoiceVisitRequest
+                {
+                    VisitId = d.Id,
+                    PhoneNo = d.Order.Contact!
+                }
+            }).ToList()
+        }, cancellation);
+
+        if (!string.IsNullOrEmpty(taskId))
+        {
+            var visitIds = visits.Select(d => d.Id).ToList();
+            await _orderVisitRepository.Updateable()
+                  .SetColumns(d => new OrderVisit
+                  {
+                      VisitState = EVisitState.AiVisiting
+                  })
+                  .Where(d =>  visitIds.Contains(d.Id))
+                  .ExecuteCommandAsync(cancellation);
+        }
+    }
 }

+ 8 - 39
src/Hotline.Application/OrderApp/OrderVisitApp/VoiceVisitTaskExecutor.cs

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
 using Microsoft.Extensions.Options;
 using XF.Domain.Dependency;
 using Hotline.CallCenter.Configs;
+using Hotline.Share.Dtos.Order.OrderVisit;
 
 namespace Hotline.Application.OrderApp.OrderVisitApp
 {
@@ -33,51 +34,19 @@ namespace Hotline.Application.OrderApp.OrderVisitApp
         {
             Console.WriteLine($"执行vv: {DateTime.Now}");
             if (request == null)
-                return new ApptaskExecuteResult
-                {
-                    IsSuccess = false,
-                    Message = "请求参数为空"
-                };
+                return ApptaskExecuteResult.Fail("请求参数为空");
 
             var baseAddress = _callcenterOptions.Value.XingTang.Address;
             if (string.IsNullOrEmpty(baseAddress))
-                return new ApptaskExecuteResult
-                {
-                    IsSuccess = false,
-                    Message = "未配置请求地址"
-                };
+                return ApptaskExecuteResult.Fail("未配置请求地址");
+
             var client = _httpClientFactory.CreateClient();
             client.BaseAddress = new Uri(baseAddress);
-            var url = $"{baseAddress}/groupcall?content=&called1={request.PhoneNo}&called2=&called3=&called4=&called5=&caller=&customerid={request.VisitId}";
+            var url = $"{baseAddress}/groupcall?content=&called1={request.PhoneNo}&called2=&called3=&called4=&called5=&caller=12345&customerid={request.VisitId}";
             var result = await client.GetAsync(url, cancellation);
-            if (result.IsSuccessStatusCode)
-            {
-                return new ApptaskExecuteResult
-                {
-                    IsSuccess = true,
-                    Message = "成功"
-                };
-            }
-            else
-            {
-                return new ApptaskExecuteResult
-                {
-                    IsSuccess = false,
-                    Message = "请求失败"
-                };
-            }
-
-            return new ApptaskExecuteResult
-            {
-                IsSuccess = true,
-                Message = "成功"
-            };
+            return result.IsSuccessStatusCode
+                ? ApptaskExecuteResult.Success()
+                : ApptaskExecuteResult.Fail("请求呼叫中心外呼失败");
         }
     }
-
-    public class VoiceVisitRequest
-    {
-        public string PhoneNo { get; set; }
-        public string VisitId { get; set; }
-    }
 }

+ 16 - 0
src/Hotline.Application/OrderApp/OrderVisitApp/VoiceVisitWithSessionRequest.cs

@@ -0,0 +1,16 @@
+using Hotline.Share.Dtos.Order.OrderVisit;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.BatchTask;
+using XF.Domain.Authentications;
+
+namespace Hotline.Application.OrderApp.OrderVisitApp
+{
+    public class VoiceVisitWithSessionRequest : VoiceVisitRequest, IApptaskRequest
+    {
+        public ISessionContext SessionContext { get; set; }
+    }
+}

+ 6 - 0
src/Hotline.Share/Dtos/BatchTask/ApptaskProgressDto.cs

@@ -5,9 +5,15 @@
 /// </summary>
 public class ApptaskProgressDto
 {
+    /// <summary>
+    /// 任务名称
+    /// </summary>
+    public string Name { get; set; }
     public int Total { get; set; }
     public int Waiting { get; set; }
     public int Processing { get; set; }
     public int Succeeded { get; set; }
     public int Failed { get; set; }
+    public string? CreatorId { get; set; }
+    public string? CreatorName { get; set; }
 }

+ 51 - 0
src/Hotline.Share/Dtos/ExamManages/ExamQuestionDto.cs

@@ -1,6 +1,8 @@
 using Exam.Infrastructure.Data.Entity;
+using Hotline.Share.Dtos.Questions;
 using Hotline.Share.Enums.Exams;
 using System.ComponentModel;
+using System.Runtime.CompilerServices;
 
 namespace Hotline.Share.Dtos.ExamManages
 {
@@ -40,4 +42,53 @@ namespace Hotline.Share.Dtos.ExamManages
         [Description("答案")]
         public string Answer { get; set; }
     }
+
+    /// <summary>
+    /// 查看考试试题
+    /// </summary>
+    [Description("查看考试试题")]
+    public class ViewExamQuestionDto : ExamQuestionDto
+    {
+        /// <summary>
+        /// 实际得分
+        /// </summary>
+        [Description("实际得分")]
+        public int? RealScore { get; set; }
+
+        /// <summary>
+        /// 试题Id
+        /// </summary>
+        [Description("试题Id")]
+        public string QuestionId { get; set; }
+
+        /// <summary>
+        /// 考试试题Id
+        /// </summary>
+        [Description("考试试题Id")]
+        public string ExamQuestionId { get; set; }
+
+        /// <summary>
+        /// 试题关联知识
+        /// </summary>
+        [Description("试题关联知识")]
+        public List<QuestionKnowladgeDto> QuestionKnowladgeDtos { get; set; }
+
+        /// <summary>
+        /// 试题课件
+        /// </summary>
+        [Description("试题课件")]
+        public List<QuestionSourcewareDto> QuestionSourcewareDtos { get; set; }
+
+        /// <summary>
+        /// 查看试题选项
+        /// </summary>
+        [Description("查看试题选项")]
+        public new List<ViewQuestionOptionDto> QuestionOptions { get; set; }
+
+        /// <summary>
+        /// 参考答案
+        /// </summary>
+        [Description("参考答案")]
+        public string CorrectAnswer { get; set; }
+    }
 }

+ 12 - 0
src/Hotline.Share/Dtos/ExamManages/GradingExamQuestionDto.cs

@@ -109,6 +109,18 @@ namespace Exam.Application.Interface.Exam
         /// </summary>
         [Description("实际分数")]
         public int? Score { get; set; }
+
+        /// <summary>
+        /// 试题Id
+        /// </summary>
+        [Description("试题Id")]
+        public string QuestionId { get; set; }
+
+        /// <summary>
+        /// 考试试题Id
+        /// </summary>
+        [Description("考试试题Id")]
+        public string ExamQuestionId { get; set; }
     }
 
     public class UserExamQuestionDto : IActionRequest

+ 3 - 0
src/Hotline.Share/Dtos/Order/OrderBiDto.cs

@@ -74,6 +74,9 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public int Repeat { get; set; }
 
+        /// <summary>
+        /// 所有信件
+        /// </summary>
         //public int Subtotal => CentreArchive + CentreCareOf + NoCentreCareOf + Invalid + Repeat;
         public int Subtotal { get; set; }
     }

+ 15 - 0
src/Hotline.Share/Dtos/Order/OrderDelay/BatchOrderDelayReviewRequest.cs

@@ -0,0 +1,15 @@
+using Hotline.Share.Dtos.FlowEngine.Workflow;
+
+namespace Hotline.Share.Dtos.Order.OrderDelay;
+
+public class BatchOrderDelayReviewRequest
+{
+    public List<DelayWithStepId> DelayWithStepIds { get; set; }
+
+    /// <summary>
+    /// 是否通过
+    /// </summary>
+    public bool IsPass { get; set; }
+
+    public NextWorkflowDto NextWorkflow { get; set; }
+}

+ 7 - 0
src/Hotline.Share/Dtos/Order/OrderDelay/DelayWithStepId.cs

@@ -0,0 +1,7 @@
+namespace Hotline.Share.Dtos.Order.OrderDelay;
+
+public class DelayWithStepId
+{
+    public string DelayId { get; set; }
+    public string StepId { get; set; }
+}

+ 0 - 20
src/Hotline.Share/Dtos/Order/OrderDelay/OrderDelayReviewRequest.cs

@@ -4,30 +4,10 @@ namespace Hotline.Share.Dtos.Order.OrderDelay;
 
 public class OrderDelayReviewRequest
 {
-    public string DelayId { get; set; }
-
-    /// <summary>
-    /// 是否通过
-    /// </summary>
-    public bool IsPass { get; set; }
-    
-    public NextWorkflowDto NextWorkflow { get; set; }
-}
-
-public class BatchOrderDelayReviewRequest
-{
-    public List<DelayWithStepId> DelayWithStepIds { get; set; }
-
     /// <summary>
     /// 是否通过
     /// </summary>
     public bool IsPass { get; set; }
 
     public NextWorkflowDto NextWorkflow { get; set; }
-}
-
-public class DelayWithStepId
-{
-    public string DelayId { get; set; }
-    public string StepId { get; set; }
 }

+ 5 - 0
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -1469,6 +1469,11 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public string? ReceiveProvinceNo { get; set; }
 
+        /// <summary>
+        /// 省是否公开(省政府网站、省政务服务网、天府通办App)
+        /// </summary>
+        public bool? IsPublic { get; set; }
+
         /// <summary>
         /// 省过期时间(省工单才有)
         /// </summary>

+ 14 - 0
src/Hotline.Share/Dtos/Order/OrderVisit/BatchVoiceVisitRequest.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Order.OrderVisit
+{
+    public class BatchVoiceVisitRequest
+    {
+        public string? Name { get; set; }
+        public List<string> VisitIds { get; set; }
+    }
+}

+ 7 - 0
src/Hotline.Share/Dtos/Order/OrderVisit/VoiceVisitRequest.cs

@@ -0,0 +1,7 @@
+namespace Hotline.Share.Dtos.Order.OrderVisit;
+
+public class VoiceVisitRequest
+{
+    public string PhoneNo { get; set; }
+    public string VisitId { get; set; }
+}

+ 24 - 2
src/Hotline.Share/Dtos/Order/PublishedDto.cs

@@ -184,6 +184,16 @@ public class PublishOrderPageBaseDto
     /// 随手拍工单标签
     /// </summary>
     public IReadOnlyCollection<SystemDicDataOutDto> SnapshotLabel { get; set; }
+
+    /// <summary>
+    /// 诉求人意愿是否公开(省政府网站、省政务服务网、天府通办App)
+    /// </summary>
+    public bool? IsPublic { get; set; }
+
+    /// <summary>
+    /// 省工单是否可以选择意愿是否公开
+    /// </summary>
+    public bool IsProvincePublic { get; set; } = false;
 }
 
 public class PublishPublishOrderDto
@@ -236,7 +246,7 @@ public class PublishPublishOrderDto
     /// </summary>
     public List<FileJson>? FileJsons { get; set; }
     #region 省工单使用
-
+    #region 中国政府网
     /// <summary>
     /// 省是否公开
     /// </summary>
@@ -247,7 +257,7 @@ public class PublishPublishOrderDto
     public string? FeedBackPhone { get; set; }
 
     /// <summary>
-    /// 不公开原因
+    /// 不公开原因(省工单公用)
     /// </summary>
     public string? NoPubReason { get; set; }
 
@@ -280,7 +290,14 @@ public class PublishPublishOrderDto
     /// 答复口径
     /// </summary>
     public string AnswerContent { get; set; }
+    #endregion
 
+    #region  省政府网、省政务服务网、天府通办App
+    /// <summary>
+    /// 是否开展保密审查
+    /// </summary>
+    public bool? IsOpenReview { get; set; }
+    #endregion
     #endregion
 }
 
@@ -366,6 +383,11 @@ public class PublishOrderDto
 
     #endregion
 
+    /// <summary>
+    /// 是否开展保密审查
+    /// </summary>
+    public bool? IsOpenReview { get; set; }
+
     /// <summary>
     /// 是否解决
     /// </summary>

+ 20 - 5
src/Hotline.Share/Dtos/Order/QueryOrderDto.cs

@@ -73,10 +73,15 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
         public string? ActualHandleOrgName { get; set; }
 
-        /// <summary>
-        /// 受理坐席名字或工号(×)
-        /// </summary>
-        public string? NameOrNo { get; set; }
+		/// <summary>
+		/// 接办部门编号(综合查询使用)
+		/// </summary>
+		public string? ActualHandleOrgCode { get; set; }
+
+		/// <summary>
+		/// 受理坐席名字或工号(×)
+		/// </summary>
+		public string? NameOrNo { get; set; }
 
         /// <summary>
         /// 受理时间(工单创建时间)(√)
@@ -245,8 +250,18 @@ namespace Hotline.Share.Dtos.Order
         /// </summary>
 		public bool? IsReTransact { get; set; }
 
+		/// <summary>
+		/// 话务员评价(话务评价)
+		/// </summary>
+		public List<ESeatEvaluate> SeatEvaluate { get; set; } = new();
 
-    }
+		/// <summary>
+		/// 部门办件结果
+		/// </summary>
+		public List<string> OrgProcessingResults { get; set; } = new();
+
+
+	}
 
 
     public enum FiledType

+ 13 - 0
src/Hotline.Share/Dtos/Questions/QuestionAnswerDto.cs

@@ -15,6 +15,19 @@ namespace Hotline.Share.Dtos.Questions
 
     }
 
+    /// <summary>
+    /// 查看试题参考答案
+    /// </summary>
+    [Description("查看试题参考答案")]
+    public class ViewQuestionAnswerDto : QuestionAnswerDto
+    {
+        /// <summary>
+        /// 正确答案
+        /// </summary>
+        [Description("正确答案")]
+        public string CorrectAnswer { get; set; }
+    }
+
     /// <summary>
     /// 试题参考答案
     /// </summary>

+ 9 - 0
src/Hotline.Share/Dtos/Questions/QuestionOptionsDto.cs

@@ -22,6 +22,15 @@ namespace Hotline.Share.Dtos.Questions
         }
     }
 
+    /// <summary>
+    /// 查看试题选项
+    /// </summary>
+    [Description("查看试题选项")]
+    public class ViewQuestionOptionDto : QuestionOptionsDto
+    {
+        public bool IsSelected { get; set; }
+    }
+
     /// <summary>
     /// 试题选项
     /// </summary>

+ 1 - 1
src/Hotline.Share/Dtos/Questions/QuestionSourcewareDto.cs

@@ -12,7 +12,7 @@ namespace Hotline.Share.Dtos.Questions
     [Description("关联课件")]
     public class QuestionSourcewareDto:UpdateQuestionSourcewareDto
     {
-
+        public string AttachmentId { get; set; }
     }
 
     /// <summary>

+ 2 - 2
src/Hotline.Share/Enums/BatchTask/ETaskType.cs

@@ -11,13 +11,13 @@ public enum ETaskType
     /// 延期
     /// </summary>
     [Description("延期任务")]
-    Delay = 1,
+    OrderDelay = 1,
 
     /// <summary>
     /// 甄别
     /// </summary>
     [Description("甄别任务")]
-    Screen = 2,
+    OrderScreen = 2,
 
     /// <summary>
     /// 语音回访

+ 6 - 1
src/Hotline.Share/Enums/Exams/EExamStatus.cs

@@ -18,6 +18,11 @@ namespace Hotline.Share.Enums.Exams
         /// 开始结束
         /// </summary>
         [Description("考试结束")]
-        Complete=2
+        Complete=2,
+        /// <summary>
+        /// 缺考
+        /// </summary>
+        [Description("缺考")]
+        Absent = 3
     }
 }

+ 6 - 0
src/Hotline.Share/Enums/Order/EDelayState.cs

@@ -27,6 +27,12 @@ namespace Hotline.Share.Enums.Order
         /// </summary>
         [Description("撤销")]
         Withdraw = 3,
+
+        /// <summary>
+        /// 批量审批处理中
+        /// </summary>
+        [Description("批量处理中")]
+        BatchProcessing = 9,
     }
 
     public enum EDelayApplyType

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

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

+ 3 - 3
src/Hotline.Share/Requests/Exam/ExamQuestionRequest.cs

@@ -13,9 +13,9 @@ namespace Hotline.Share.Requests.Exam
         public string QuestionId { get; set; }
 
         /// <summary>
-        /// 考试Id
+        /// 用户考试Id
         /// </summary>
-        [Description("考试Id")]
-        public string ExamId { get; set; }
+        [Description("用户考试Id")]
+        public string UserExamId { get; set; }
     }
 }

+ 23 - 0
src/Hotline.Share/Requests/PagedKeywordRequest.cs

@@ -1769,4 +1769,27 @@ public record AreaSubordinateRequest
     /// 线路号
     /// </summary>
     public string? Line { get; set; }
+}
+
+public record CentreDataListDetailRequest : PagedKeywordRequest
+{
+    /// <summary>
+    /// 用户id
+    /// </summary>
+    public string? UserId { get; set; }
+
+    /// <summary>
+    /// 类型
+    /// </summary>
+    public string? FieldName { get; set; }
+
+    /// <summary>
+    /// 编号
+    /// </summary>
+    public string? No {  get; set; }
+
+    /// <summary>
+    /// 标题
+    /// </summary>
+    public string? Title {  get; set; }
 }

+ 12 - 0
src/Hotline.Share/ViewResponses/Exam/UserExamResultViewResponse.cs

@@ -121,6 +121,18 @@ namespace Exam.Share.ViewResponses.Exam
         /// </summary>
         [Description("是否阅卷")]
         public bool IsCheck { get; set; }
+
+        /// <summary>
+        /// 是否重考
+        /// </summary>
+        [Description("是否重考")]
+        public bool? IsReExam { get; set; }
+
+        /// <summary>
+        /// 能否重考
+        /// </summary>
+        [Description("能否重考")]
+        public bool CanReExam { get; set; }
     }
 
     public class GradingResultViewResponse : IViewResponse

+ 13 - 1
src/Hotline/Authentications/SessionContextProvider.cs

@@ -84,7 +84,7 @@ public class SessionContextProvider : ISessionContextProvider, IScopeDependency
         httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(userClaims));
     }
 
-    public void ChangeSession(string userId, string username, string orgId, string orgname, int orgLevel)
+    public void ChangeSession(string userId, string username, string orgId, string orgname, int orgLevel, string orgAreaCode, string orgAreaName)
     {
         var httpContext = _serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
         httpContext ??= new DefaultHttpContext();
@@ -97,10 +97,22 @@ public class SessionContextProvider : ISessionContextProvider, IScopeDependency
             new(AppClaimTypes.DepartmentId, orgId ?? string.Empty),
             new(AppClaimTypes.DepartmentIsCenter, orgId?.IsCenter().ToString() ?? string.Empty),
             new(AppClaimTypes.DepartmentName, orgname ?? string.Empty),
+            new(AppClaimTypes.DepartmentAreaCode, orgAreaCode ?? string.Empty),
+            new(AppClaimTypes.DepartmentAreaName, orgAreaName ?? string.Empty),
             new(AppClaimTypes.DepartmentLevel, orgLevel.ToString() ?? string.Empty),
             new(AppClaimTypes.AreaId, orgId?.GetHigherOrgId() ?? string.Empty),
         ];
         httpContext.User = new ClaimsPrincipal(new ClaimsIdentity(userClaims));
     }
 
+    public void ChangeSession(ISessionContext sessionContext) =>
+        ChangeSession(
+            sessionContext.UserId,
+            sessionContext.UserName,
+            sessionContext.OrgId,
+            sessionContext.OrgName,
+            sessionContext.OrgLevel,
+            sessionContext.OrgAreaCode,
+            sessionContext.OrgAreaName
+        );
 }

+ 36 - 11
src/Hotline/BatchTask/ApptaskDomainService.cs

@@ -4,10 +4,10 @@ using Hotline.Share.Enums.BatchTask;
 using Hotline.Validators.BatchTask;
 using MapsterMapper;
 using Microsoft.Extensions.Logging;
+using XF.Domain.Authentications;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
-using static Lucene.Net.Util.Fst.Util;
 
 namespace Hotline.BatchTask;
 
@@ -15,17 +15,20 @@ public class ApptaskDomainService : IApptaskDomainService, IScopeDependency
 {
     private readonly IRepository<Apptask> _apptaskRepository;
     private readonly IRepository<ApptaskItem> _apptaskItemRepository;
+    private readonly ISessionContextProvider _sessionContextProvider;
     private readonly IMapper _mapper;
     private readonly ILogger<ApptaskDomainService> _logger;
 
     public ApptaskDomainService(
         IRepository<Apptask> apptaskRepository,
         IRepository<ApptaskItem> apptaskItemRepository,
+        ISessionContextProvider sessionContextProvider,
         IMapper mapper,
         ILogger<ApptaskDomainService> logger)
     {
         _apptaskRepository = apptaskRepository;
         _apptaskItemRepository = apptaskItemRepository;
+        _sessionContextProvider = sessionContextProvider;
         _mapper = mapper;
         _logger = logger;
     }
@@ -40,6 +43,9 @@ public class ApptaskDomainService : IApptaskDomainService, IScopeDependency
         if (!result.IsValid)
             throw new ValidationException(result.Errors.FirstOrDefault()?.ErrorMessage);
 
+        var priority = 9;
+        if (request.Priority.HasValue && request.Priority.Value >= 0 && request.Priority.Value < 9)
+            priority = request.Priority.Value;
         var apptask = new Apptask
         {
             Name = request.Name,
@@ -52,7 +58,7 @@ public class ApptaskDomainService : IApptaskDomainService, IScopeDependency
                 TaskStatus = ETaskStatus.Waiting,
                 TaskParams = d.TaskParams is null ? null : System.Text.Json.JsonSerializer.Serialize(d.TaskParams),
                 TryLimit = request.TryLimit,
-                Priority = request.Priority ?? 9,
+                Priority = priority
             }).ToList()
         };
 
@@ -80,6 +86,21 @@ public class ApptaskDomainService : IApptaskDomainService, IScopeDependency
         return _mapper.Map<ApptaskProgressDto>(apptask);
     }
 
+    /// <summary>
+    /// 查询当前任务是否全部执行完成
+    /// </summary>
+    /// <param name="taskId"></param>
+    /// <param name="cancellation"></param>
+    /// <returns></returns>
+    public async Task<bool> IsCompletedAsync(string taskId, CancellationToken cancellation)
+    {
+        var anyUnCompleted = await _apptaskItemRepository.Queryable()
+            .AnyAsync(d => d.Tries < d.TryLimit
+                           && (d.TaskStatus == ETaskStatus.Waiting || d.TaskStatus == ETaskStatus.Processing)
+                           && d.ApptaskId == taskId, cancellation);
+        return !anyUnCompleted;
+    }
+
     /// <summary>
     /// 终止任务
     /// </summary>
@@ -117,6 +138,7 @@ public class ApptaskDomainService : IApptaskDomainService, IScopeDependency
         var taskItems = await _apptaskItemRepository.Queryable()
             .Where(d => d.Tries < d.TryLimit
                         && (d.TaskStatus == ETaskStatus.Waiting || d.TaskStatus == ETaskStatus.Failed))
+            .OrderBy(d => d.Priority)
             .OrderBy(d => d.CreationTime)
             .Take(10)
             .ToListAsync(cancellation);
@@ -142,28 +164,31 @@ public class ApptaskDomainService : IApptaskDomainService, IScopeDependency
     /// <param name="apptaskItem"></param>
     /// <param name="cancellation"></param>
     /// <returns></returns>
-    public async Task ExecuteAsync<TRequest>(IApptaskExecutor<TRequest> executor, ApptaskItem apptaskItem, CancellationToken cancellation)
+    public async Task<ApptaskExecuteResult> ExecuteAsync<TRequest>(IApptaskExecutor<TRequest> executor, ApptaskItem apptaskItem, CancellationToken cancellation)
+    where TRequest : class, IApptaskRequest
     {
         try
         {
-            TRequest request = default;
-            if (!string.IsNullOrEmpty(apptaskItem.TaskParams))
-            {
-                request = System.Text.Json.JsonSerializer.Deserialize<TRequest>(apptaskItem.TaskParams);
-                if (request is null)
-                    throw new UserFriendlyException("任务参数反序列化异常");
-            }
+            if (string.IsNullOrEmpty(apptaskItem.TaskParams))
+                throw new UserFriendlyException($"无效任务参数, taskItemId: {apptaskItem.Id}");
+
+            var request = System.Text.Json.JsonSerializer.Deserialize<TRequest>(apptaskItem.TaskParams);
+            if (request is null)
+                throw new UserFriendlyException($"任务参数反序列化失败, taskItemId: {apptaskItem.Id}");
+            _sessionContextProvider.ChangeSession(request.SessionContext);
             var result = await executor.ExecuteAsync(request, cancellation);
             apptaskItem.TaskStatus = result.IsSuccess ? ETaskStatus.Succeeded : ETaskStatus.Failed;
             apptaskItem.Message = result.Message;
             apptaskItem.TaskEndTime = DateTime.Now;
+            return ApptaskExecuteResult.Success();
         }
         catch (Exception e)
         {
-            _logger.LogError("批量任务执行异常:{err}", e.Message);
+            _logger.LogError("批量任务执行异常:taskId: {taskItemId}, {err}", apptaskItem.Id, e.Message);
             apptaskItem.TaskStatus = ETaskStatus.Failed;
             apptaskItem.Message = "批量任务执行异常";
             apptaskItem.TaskEndTime = DateTime.Now;
+            return ApptaskExecuteResult.Fail("批量任务执行异常");
         }
         finally
         {

+ 10 - 1
src/Hotline/BatchTask/IApptaskDomainService.cs

@@ -19,6 +19,14 @@ namespace Hotline.BatchTask
         /// </summary>
         /// <returns></returns>
         Task<ApptaskProgressDto> GetProgressAsync(string taskId, CancellationToken cancellation);
+        
+        /// <summary>
+        /// 查询当前任务是否全部完成
+        /// </summary>
+        /// <param name="taskId"></param>
+        /// <param name="cancellation"></param>
+        /// <returns></returns>
+        Task<bool> IsCompletedAsync(string taskId, CancellationToken cancellation);
 
         /// <summary>
         /// 终止任务
@@ -43,6 +51,7 @@ namespace Hotline.BatchTask
         /// <param name="apptaskItem"></param>
         /// <param name="cancellation"></param>
         /// <returns></returns>
-        Task ExecuteAsync<TRequest>(IApptaskExecutor<TRequest> executor, ApptaskItem apptaskItem, CancellationToken cancellation);
+        Task<ApptaskExecuteResult> ExecuteAsync<TRequest>(IApptaskExecutor<TRequest> executor, ApptaskItem apptaskItem, CancellationToken cancellation)
+            where TRequest : class, IApptaskRequest;
     }
 }

+ 15 - 0
src/Hotline/BatchTask/IApptaskExecutor.cs

@@ -16,4 +16,19 @@ public class ApptaskExecuteResult
 {
     public bool IsSuccess { get; set; }
     public string? Message { get; set; }
+
+    public ApptaskExecuteResult()
+    {
+
+    }
+
+    public ApptaskExecuteResult(bool isSuccess, string? message = null)
+    {
+        IsSuccess = isSuccess;
+        Message = message;
+    }
+
+    public static ApptaskExecuteResult Success(string? message = null) => new(true, message ?? "成功");
+
+    public static ApptaskExecuteResult Fail(string message) => new(false, message);
 }

+ 14 - 0
src/Hotline/BatchTask/IApptaskRequest.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+
+namespace Hotline.BatchTask
+{
+    public interface IApptaskRequest
+    {
+        public ISessionContext SessionContext { get; set; }
+    }
+}

+ 10 - 0
src/Hotline/BatchTask/Notifications/ApptaskCompletedNotify.cs

@@ -0,0 +1,10 @@
+using MediatR;
+
+namespace Hotline.BatchTask.Notifications;
+
+public class ApptaskCompletedNotify : ApptaskExecutedNotify, INotification
+{
+    public ApptaskCompletedNotify(ApptaskItem apptaskItem) : base(apptaskItem)
+    {
+    }
+}

+ 20 - 0
src/Hotline/BatchTask/Notifications/ApptaskFailNotify.cs

@@ -0,0 +1,20 @@
+using MediatR;
+
+namespace Hotline.BatchTask.Notifications;
+
+public class ApptaskFailNotify : ApptaskExecutedNotify, INotification
+{
+    public ApptaskFailNotify(ApptaskItem apptaskItem) : base(apptaskItem)
+    {
+    }
+}
+
+public class ApptaskExecutedNotify
+{
+    public ApptaskItem ApptaskItem { get; set; }
+
+    public ApptaskExecutedNotify(ApptaskItem apptaskItem)
+    {
+        ApptaskItem = apptaskItem;
+    }
+}

+ 15 - 0
src/Hotline/BatchTask/Notifications/ApptaskSuccessNotify.cs

@@ -0,0 +1,15 @@
+using MediatR;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.BatchTask.Notifications;
+
+public class ApptaskSuccessNotify : ApptaskExecutedNotify, INotification
+{
+    public ApptaskSuccessNotify(ApptaskItem apptaskItem) : base(apptaskItem)
+    {
+    }
+}

+ 9 - 1
src/Hotline/Orders/IOrderVisitDomainService.cs

@@ -1,9 +1,17 @@
-using Hotline.Settings;
+using Hotline.CallCenter.Calls;
+using Hotline.Settings;
 using Hotline.Share.Dtos.Push;
 
 namespace Hotline.Orders;
 public interface IOrderVisitDomainService
 {
+    /// <summary>
+    /// 语音回访处理
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task UpdateVoiceReplyAsync(VoiceEvaluationButton dto);
+
     /// <summary>
     /// 根据用户短信或电话回复的内容转换成系统中对应的评价
     /// </summary>

+ 10 - 1
src/Hotline/Orders/Order.cs

@@ -915,6 +915,12 @@ namespace Hotline.Orders
         [SugarColumn(ColumnDescription = "省退回结果")]
         public bool? ProvinceSendBack { get; set; }
 
+        /// <summary>
+        /// 诉求人意愿是否公开(省政府网站、省政务服务网、天府通办App)
+        /// </summary>
+        [SugarColumn(ColumnDescription = "诉求人意愿是否公开")]
+        public bool? IsPublic { get; set; }
+
         #endregion
 
         #region 附件冗余
@@ -990,6 +996,7 @@ namespace Hotline.Orders
         public string? CaseProcessType { get; set; }
         #endregion
 
+        #region 工单标签
         /// <summary>
         /// 敏感标签
         /// </summary>
@@ -1117,12 +1124,13 @@ namespace Hotline.Orders
         /// </summary>
         [SugarColumn(ColumnDescription = "话务提醒是否转办")]
         public bool? IsForwarded { get; set; }
+        #endregion
 
         #region 回访信息
         /// <summary>
         /// 话务员评价(话务评价)
         /// </summary>
-		[SugarColumn(ColumnDescription = "话务员评价")]
+        [SugarColumn(ColumnDescription = "话务员评价")]
         public ESeatEvaluate? SeatEvaluate { get; set; }
 
         /// <summary>
@@ -1143,6 +1151,7 @@ namespace Hotline.Orders
         /// </summary>
         [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true, ColumnDescription = "知识库引用")]
         public List<Kv>? KnowledgeQuote { get; set; }
+
     }
 
     public partial class Order

+ 11 - 0
src/Hotline/Orders/OrderPublish.cs

@@ -51,6 +51,7 @@ public class OrderPublish : FullStateEntity
 
     #region 省工单使用字段
 
+    #region 中国政府网
     /// <summary>
     /// 省是否公开
     /// </summary>
@@ -99,6 +100,16 @@ public class OrderPublish : FullStateEntity
     /// </summary>
     [SugarColumn(ColumnDataType = "text", IsNullable = true)]
     public string? AnswerContent { get; set; }
+
+    #endregion
+
+    #region  省政府网站、省政务服务网、天府通办App
+
+    /// <summary>
+    /// 是否开展保密审查
+    /// </summary>
+    public bool? IsOpenReview { get; set; }
+    #endregion
     #endregion
 
     public bool? Resolve { get; set; }

+ 31 - 12
src/Hotline/Orders/OrderVisitDomainService.cs

@@ -1,26 +1,21 @@
 using DotNetCore.CAP;
 using Hotline.Caching.Interfaces;
-using Hotline.Share.Dtos;
+using Hotline.CallCenter.Calls;
+using Hotline.Configurations;
+using Hotline.EventBus;
+using Hotline.Settings;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Push;
 using Hotline.Share.Enums.Order;
 using Hotline.Share.Tools;
 using Mapster;
+using MediatR;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.Text.RegularExpressions;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
-using Hotline.EventBus;
-using Hotline.Orders.Notifications;
-using Hotline.Configurations;
-using Microsoft.Extensions.Options;
-using Hotline.Settings;
-using System.Text.RegularExpressions;
-using System.Threading;
-using Hotline.Caching.Services;
-using Hotline.Push.Notifies;
-using Hotline.Share.Enums.Push;
-using MediatR;
 
 namespace Hotline.Orders;
 public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependency
@@ -67,6 +62,30 @@ public class OrderVisitDomainService : IOrderVisitDomainService, IScopeDependenc
         _mediator = mediator;
     }
 
+    /// <summary>
+    /// 语音回访处理
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task UpdateVoiceReplyAsync(VoiceEvaluationButton dto)
+    {
+        var data = await _orderVisitRepository.GetAsync(p => p.Id == dto.CustomerId);
+        if (data != null)
+        {
+            var replyTxt = "智能电话回访不作评价";
+            if (dto.Content == "1")
+                replyTxt = "智能电话回访满意";
+            else if (dto.Content == "2")
+                replyTxt = "智能电话回访不满意";
+
+            await UpdateSmsReplyAsync(data, replyTxt);
+        }
+        else
+        {
+            _logRepository.Add("语音回访-不处理", dto, "未查询到回访数据", "UpdateVoiceReplyAsync", 0);
+        }
+    }
+
     /// <summary>
     /// 用户回访短信回复更新回访状态
     /// </summary>

+ 21 - 0
src/Hotline/Validators/Order/OrderVisit/BatchVoiceVisitRequestValidator.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using FluentValidation;
+using Hotline.Share.Dtos.Order.OrderVisit;
+
+namespace Hotline.Validators.Order.OrderVisit
+{
+    public class BatchVoiceVisitRequestValidator : AbstractValidator<BatchVoiceVisitRequest>
+    {
+        public BatchVoiceVisitRequestValidator()
+        {
+            RuleFor(d => d.VisitIds)
+                .Cascade(CascadeMode.Stop)
+                .NotEmpty()
+                .WithMessage("请选择待回访数据");
+        }
+    }
+}

+ 2 - 2
src/XF.Domain/Authentications/ISessionContextProvider.cs

@@ -14,8 +14,8 @@ namespace XF.Domain.Authentications
         //public void SetContext(string key);
 
         HttpContext ChangeSessionByUserId(string id, HttpContext httpContext);
-
         Task ChangeSessionByUserIdAsync(string userId, CancellationToken cancellation);
-        void ChangeSession(string userId, string username, string orgId, string orgname, int orgLevel);
+        void ChangeSession(string userId, string username, string orgId, string orgname, int orgLevel, string orgAreaCode, string orgAreaName);
+        void ChangeSession(ISessionContext sessionContext);
     }
 }

+ 3 - 0
test/Hotline.Tests/Domain/OrderVisitDomainServiceTest.cs

@@ -121,6 +121,9 @@ public class OrderVisitDomainServiceTest : TestBase
     [InlineData("都没有办理好", "SMSUnsatisfied", "", "", "都没有办理好", "Published", "YiBin")]
     [InlineData("1", "Visited", "4", "满意", "满意", "Visited", "YiBin")]
     [InlineData("2", "SMSUnsatisfied", "2", "不满意", "不满意", "Published", "YiBin")]
+    [InlineData("智能电话回访满意", "Visited", "4", "满意", "满意", "Visited")]
+    [InlineData("智能电话回访不满意", "Visited", "2", "不满意", "不满意", "Visited")]
+    [InlineData("智能电话回访不作评价", "Visited", "7", "未做评价", "未做评价", "Visited")]
     public async Task UpdateSmsReply_Test(string content, string visitState, string orgResuktKey, string orgResuktValue, string visitContent, string visited = "Visited", string appScope = "ZiGong")
     {
         if (appScope != "ZiGong")