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

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

tangjiang 1 долоо хоног өмнө
parent
commit
de50f1880e
20 өөрчлөгдсөн 787 нэмэгдсэн , 47 устгасан
  1. 95 0
      src/Hotline.Api/Controllers/Exam/OfflineExamAnalysisController.cs
  2. 1 1
      src/Hotline.Api/Controllers/Exam/QuestionController.cs
  3. 3 1
      src/Hotline.Api/Controllers/Exam/UserExamController.cs
  4. 14 0
      src/Hotline.Application/Exam/Constants/ApiRoutes/OfflineExamAnalysisApiRoute.cs
  5. 6 0
      src/Hotline.Application/Exam/Constants/Messages/ExamSystemConstants.cs
  6. 28 0
      src/Hotline.Application/Exam/Interface/ExamManages/IOfflineExamAnalysisService.cs
  7. 31 0
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/OfflineExamQueryExtensions.cs
  8. 148 0
      src/Hotline.Application/Exam/Service/ExamManages/OfflineExamAnalysisService.cs
  9. 19 13
      src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayApplication.cs
  10. 48 32
      src/Hotline.Application/OrderApp/OrderSendBackAuditApplication.cs
  11. 20 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IOfflineExamAnalysisRepository.cs
  12. 22 0
      src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/OfflineExamAnalysisRepository.cs
  13. 63 0
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/OfflineExamAnalysisValidator.cs
  14. 75 0
      src/Hotline.Share/Dtos/ExamManages/OfflineExamAnalysisDto.cs
  15. 1 0
      src/Hotline.Share/Dtos/Order/OrderDelay/DelayWithStepId.cs
  16. 20 0
      src/Hotline.Share/Requests/Exam/OfflineExamAnalysisPagedRequest.cs
  17. 10 0
      src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisPageViewResponse.cs
  18. 52 0
      src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisViewResponse.cs
  19. 69 0
      src/Hotline/Exams/ExamManages/ExamOfflineExamAnalysis.cs
  20. 62 0
      src/Hotline/Exams/ExamManages/OfflineExamAnalysisExcel.cs

+ 95 - 0
src/Hotline.Api/Controllers/Exam/OfflineExamAnalysisController.cs

@@ -0,0 +1,95 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Share.ViewResponses.Exam;
+using Exam.Share.ViewResponses.Question;
+using Hotline.Application.Exam.Constants.ApiRoutes;
+using Hotline.Application.Exam.Interface.ExamManages;
+using Hotline.Application.Exam.Service.ExamManages;
+using Hotline.Application.ExportExcel;
+using Hotline.Exams.ExamManages;
+using Hotline.Exams.Questions;
+using Hotline.Share.Dtos.Order;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.Requests.Question;
+using Hotline.Share.ViewResponses.Exam;
+using Hotline.Tools;
+using MapsterMapper;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Exam
+{
+    public class OfflineExamAnalysisController : BaseController
+    {
+        private readonly IOfflineExamAnalysisService _offineExamAnalysisService;
+        private readonly IMapper _mapper;
+        private readonly IExportApplication _exportApplication;
+
+        public OfflineExamAnalysisController(IOfflineExamAnalysisService offineExamAnalysisService,IMapper mapper, IExportApplication exportApplication)
+        {
+            _offineExamAnalysisService = offineExamAnalysisService;
+            this._mapper = mapper;
+            this._exportApplication = exportApplication;
+        }
+
+        /// <summary>
+        /// 获取线下考试分页列表
+        /// </summary>
+        /// <param name="offlineExamAnalysisPagedRequest"></param>
+        /// <returns></returns>
+        [HttpPost(OfflineExamAnalysisApiRoute.GetPagedList)]
+        public async Task<PageViewResponse<OfflineExamAnalysisViewResponse>> GetPagedList([FromBody] OfflineExamAnalysisPagedRequest offlineExamAnalysisPagedRequest)
+        {
+            var offlineExamAnalysisPageViewResponse = await _offineExamAnalysisService.GetPagedListAsync(offlineExamAnalysisPagedRequest);            
+
+            return offlineExamAnalysisPageViewResponse;
+        }
+
+        /// <summary>
+        /// 导入Excel
+        /// </summary>
+        /// <returns></returns>
+        [HttpPost(OfflineExamAnalysisApiRoute.ImportExcel)]
+        public async Task ImportExcel(IFormFile file)
+        {
+            await _offineExamAnalysisService.ImportExcel(file, HttpContext.RequestAborted);
+        }
+
+        /// <summary>
+        /// 导出线下考试成绩
+        /// </summary>
+        /// <param name="exportExcelDto"></param>
+        /// <returns></returns>
+        [HttpPost(OfflineExamAnalysisApiRoute.ExportOfflineExamAnalysis)]
+        public async Task<FileStreamResult> ExportOfflineExamAnalysis([FromBody] ExportExcelDto<OfflineExamAnalysisPagedRequest> exportExcelDto)
+        {
+            if (exportExcelDto.IsExportAll)
+            {
+                exportExcelDto.QueryDto.IsPaged = false;
+            }
+            var result = await _offineExamAnalysisService.GetPagedListAsync(exportExcelDto.QueryDto);
+
+            dynamic? dynamicClass = DynamicClassHelper.CreateDynamicClass<OfflineExamAnalysisViewResponse>(exportExcelDto.ColumnInfos ?? new List<ColumnInfo>());
+
+            var dtos = result.Items
+                .Select(stu => _mapper.Map(stu, typeof(OfflineExamAnalysisViewResponse), dynamicClass))
+                .Cast<object>()
+                .ToList();
+
+            var stream = ExcelHelper.CreateStream(dtos);
+
+            return ExcelStreamResult(stream, "导出线下考试成绩");
+
+        }
+
+        /// <summary>
+        /// 下载Excel模版
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet(OfflineExamAnalysisApiRoute.Download)]
+        public FileStreamResult Download()
+        {
+            var list = new List<OfflineExamAnalysisExcel>();
+            return _exportApplication.ExportData(list, "线下考试成绩统计表.xlsx");
+        }
+    }
+}

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

@@ -130,7 +130,7 @@ namespace Hotline.Api.Controllers.Exam
         public FileStreamResult Download()
         {
             var list = new List<QuestionExcel>();
-            return _exportApplication.ExportData(list, "Template.xlsx");
+            return _exportApplication.ExportData(list, "题库.xlsx");
         }
     }
 }

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

@@ -40,7 +40,9 @@ namespace Hotline.Api.Controllers.Exam
         {
             await _examManageService.UpdateExamStatus(new EntityQueryRequest(), HttpContext.RequestAborted);
 
-            return await _userExamService.GetPagedListAsync(userExamPagedRequest) as UserExamResultPageViewResponse;
+            var userExamResultPageViewResponse = await _userExamService.GetPagedListAsync(userExamPagedRequest);
+
+            return userExamResultPageViewResponse as UserExamResultPageViewResponse;
         }
 
         /// <summary>

+ 14 - 0
src/Hotline.Application/Exam/Constants/ApiRoutes/OfflineExamAnalysisApiRoute.cs

@@ -0,0 +1,14 @@
+using Hotline.Application.Exam.Core.Constants;
+
+namespace Hotline.Application.Exam.Constants.ApiRoutes
+{
+    public class OfflineExamAnalysisApiRoute:ApiRoute
+    {
+        public const string ImportExcel = "ImportExcel";
+
+        public const string Download = "Download";
+
+        public const string ExportOfflineExamAnalysis = "ExportOfflineExamAnalysis";
+
+    }
+}

+ 6 - 0
src/Hotline.Application/Exam/Constants/Messages/ExamSystemConstants.cs

@@ -6,4 +6,10 @@
 
         public static string[] ColumnNames = ["题型","试题标签","难度级别","正式可用","模拟可用", "题干", "选项A", "选项B", "选项C", "选项D", "选项E", "选项F", "选项G", "选项H", "选项I", "选项J", "答案"];
     }
+
+    public class OfflineExamSystemConstants
+    {
+        public static string[] ColumnNames = ["参考人员", "部门名称", "考试标题", "开始时间", "结束时间", "考试总分", "合格分数", "考试分数"];
+
+    }
 }

+ 28 - 0
src/Hotline.Application/Exam/Interface/ExamManages/IOfflineExamAnalysisService.cs

@@ -0,0 +1,28 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Exams.Questions;
+using Hotline.Repository.SqlSugar.Exam.Interface;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.Interface.ExamManages
+{
+    public interface IOfflineExamAnalysisService : IQueryService<OfflineExamAnalysisViewResponse, OfflineExamAnalysisDto, OfflineExamAnalysisPagedRequest>, IApiService<AddOfflineExamAnalysisDto, UpdateOfflineExamAnalysisDto, ExamOfflineExamAnalysis>
+    {
+
+        /// <summary>
+        /// 导入Excel
+        /// </summary>
+        /// <param name="file"></param>
+        /// <param name="requestAborted"></param>
+        /// <returns></returns>
+        Task ImportExcel(IFormFile file, CancellationToken requestAborted);
+    }
+}

+ 31 - 0
src/Hotline.Application/Exam/QueryExtensions/ExamManages/OfflineExamQueryExtensions.cs

@@ -0,0 +1,31 @@
+using Hotline.Application.Exam.Core.Utilities;
+using Hotline.Exams.ExamManages;
+using Hotline.Share.Requests.Exam;
+using JiebaNet.Segmenter.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Application.Exam.QueryExtensions.ExamManages
+{
+    public static class OfflineExamQueryExtensions
+    {
+        public static Expression<Func<ExamOfflineExamAnalysis, bool>> GetExpression(this OfflineExamAnalysisPagedRequest offlineExamAnalysisPagedRequest)
+        {
+            Expression<Func<ExamOfflineExamAnalysis, bool>> expression = m => m.Id != null;
+
+            expression = ExpressionableUtility.CreateExpression<ExamOfflineExamAnalysis>()
+            .AndIF(offlineExamAnalysisPagedRequest.StartTime.IsNotNull(), x => x.StartTime >= offlineExamAnalysisPagedRequest.StartTime)
+            .AndIF(offlineExamAnalysisPagedRequest.EndTime.IsNotNull(), x => x.StartTime <= offlineExamAnalysisPagedRequest.EndTime)
+            .AndIF(offlineExamAnalysisPagedRequest.Keyword.IsNotEmpty(), x => x.ExamName.Contains(offlineExamAnalysisPagedRequest.Keyword))
+            .AndIF(offlineExamAnalysisPagedRequest.MinScore.IsNotNull(), x => x.Score > offlineExamAnalysisPagedRequest.MinScore)
+            .AndIF(offlineExamAnalysisPagedRequest.MaxScore.IsNotNull(), x => x.Score < offlineExamAnalysisPagedRequest.MaxScore)
+            .ToExpression();
+
+            return expression;
+        }
+    }
+}

+ 148 - 0
src/Hotline.Application/Exam/Service/ExamManages/OfflineExamAnalysisService.cs

@@ -0,0 +1,148 @@
+using DocumentFormat.OpenXml.Drawing;
+using Exam.Infrastructure.Data.Entity;
+using FluentValidation;
+using Hotline.Application.Exam.Constants.Messages;
+using Hotline.Application.Exam.Interface.ExamManages;
+using Hotline.Application.Exam.QueryExtensions.ExamManages;
+using Hotline.Exams.ExamManages;
+using Hotline.Exams.Questions;
+using Hotline.Repository.SqlSugar;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interface;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.Questions;
+using Hotline.Repository.SqlSugar.Exam.Service;
+using Hotline.Share.Dtos.ExamManages;
+using Hotline.Share.Dtos.TestPapers;
+using Hotline.Share.Enums.Exams;
+using Hotline.Share.Requests.Exam;
+using Hotline.Share.ViewResponses.Exam;
+using Hotline.Tools;
+using JiebaNet.Segmenter.Common;
+using MapsterMapper;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+
+namespace Hotline.Application.Exam.Service.ExamManages
+{
+    public class OfflineExamAnalysisService : ApiService<ExamOfflineExamAnalysis, AddOfflineExamAnalysisDto, UpdateOfflineExamAnalysisDto, HotlineDbContext>, IOfflineExamAnalysisService, IScopeDependency
+    {
+        private readonly IOfflineExamAnalysisRepository _repository;
+        private readonly ISessionContext _sessionContext;
+        private readonly IDataPermissionFilterBuilder _dataPermissionFilterBuilder;
+        private readonly IServiceProvider _serviceProvider;
+        private readonly IMapper _mapper;
+
+        public OfflineExamAnalysisService(IOfflineExamAnalysisRepository repository,
+             ISessionContext sessionContext,
+            IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider,
+            IMapper mapper) : base(repository, mapper, sessionContext)
+        {
+            this._repository = repository;
+            this._sessionContext = sessionContext;
+            this._dataPermissionFilterBuilder = dataPermissionFilterBuilder;
+            this._serviceProvider = serviceProvider;
+            this._mapper = mapper;
+        }
+
+        #region public method
+        public Task<OfflineExamAnalysisDto> GetAsync(EntityQueryRequest entityQueryRequest)
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task<(int, List<OfflineExamAnalysisViewResponse>)> GetListAsync(OfflineExamAnalysisPagedRequest queryRequest)
+        {
+            throw new NotImplementedException();
+        }
+
+        public async Task<PageViewResponse<OfflineExamAnalysisViewResponse>> GetPagedListAsync(OfflineExamAnalysisPagedRequest queryRequest)
+        {
+            var expression = queryRequest.GetExpression();
+            var offlineExamAnalysisTable = _repository.Queryable().Where(expression);
+
+            var queryable = offlineExamAnalysisTable.Select(x => new OfflineExamAnalysisViewResponse
+            {
+                CutoffScore = x.CutoffScore,
+                ExamName = x.ExamName,
+                OrgName = x.DepartmentName,
+                Id = x.Id,
+                Score = x.Score,
+                TotalScore = x.TotalScore,
+                UserName = x.UserName
+            }).OrderByPropertyNameIF(!string.IsNullOrEmpty(queryRequest.SortField), queryRequest.SortField, (OrderByType)(queryRequest.SortRule ?? 0));
+
+            var total = await offlineExamAnalysisTable.CountAsync();
+            var items = queryRequest.IsPaged ? await queryable.ToPageListAsync(queryRequest.PageIndex, queryRequest.PageSize) : await queryable.ToListAsync();
+
+            var result = new PageViewResponse<OfflineExamAnalysisViewResponse>
+            {
+                Items = items,
+                Pagination = new Pagination(queryRequest.PageIndex, queryRequest.PageSize, total)
+            };
+
+            return result;
+        }
+
+        public async Task ImportExcel(IFormFile file, CancellationToken cancellationToken)
+        {
+            var stream = file.OpenReadStream();
+            var contents = ExcelHelper.Read(stream, true);
+
+            var offlineExamAnalysisDtos = new List<AddOfflineExamAnalysisDto>();
+
+            contents.ForEach(async item =>
+            {
+                var value = (item as ExpandoObject).GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[0]);
+
+                if (value == null)
+                    return;
+
+                var offlineExamAnalysisDto = BuildOfflineExamAnalysis(item as ExpandoObject);
+
+                if (offlineExamAnalysisDto != null)
+                    offlineExamAnalysisDtos.Add(offlineExamAnalysisDto);
+            });
+
+            await base.AddAsync(offlineExamAnalysisDtos, cancellationToken);
+
+            stream.Close();
+        }
+        #endregion
+
+        #region private method
+        private OfflineExamAnalysisDto BuildOfflineExamAnalysis(ExpandoObject? item)
+        {
+            var totalScore = 0;
+            var cutoffScore = 0;
+            var score = 0;
+
+            var offlineExamAnalysisDto = new OfflineExamAnalysisDto
+            {
+                UserName = item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[0]) != null ? item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[0]).ToString() : string.Empty,
+                DepartmentName= item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[1]) != null ? item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[1]).ToString() : string.Empty,
+                ExamName = item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[2]) != null ? item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[2]).ToString() : string.Empty,
+                StartTime = DateTime.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[3])?.ToString(),out DateTime startTime) ? startTime : DateTime.MinValue,
+                EndTime = item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[4])!=null? (DateTime.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[4])?.ToString(),out DateTime dateTime) ? dateTime : null):null,
+                TotalScore = int.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[5])?.ToString(),out totalScore)?totalScore:0,
+                CutoffScore = int.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[6])?.ToString(),out cutoffScore)? cutoffScore:0,
+                Score = int.TryParse(item.GetValueOrDefault(OfflineExamSystemConstants.ColumnNames[7])?.ToString(), out score) ? score : 0,
+
+            };
+
+            return offlineExamAnalysisDto; 
+        } 
+        #endregion
+    }
+}

+ 19 - 13
src/Hotline.Application/OrderApp/OrderDelayApp/OrderDelayApplication.cs

@@ -181,10 +181,10 @@ public class OrderDelayApplication : IOrderDelayApplication, IScopeDependency
     /// <returns></returns>
     public async Task BatchReviewAsync(BatchOrderDelayReviewRequest request, CancellationToken cancellation)
     {
-        var delayIds = request.DelayWithStepIds.Select(d => d.DelayId).Distinct().ToList();
-        var delays = await _orderDelayRepository.Queryable()
-            .Where(d => delayIds.Contains(d.Id))
-            .ToListAsync(cancellation);
+        //var delayIds = request.DelayWithStepIds.Select(d => d.DelayId).Distinct().ToList();
+        //var delays = await _orderDelayRepository.Queryable()
+        //    .Where(d => delayIds.Contains(d.Id))
+        //    .ToListAsync(cancellation);
 
         var apptaskItems = new List<AddApptaskItemRequest>();
         var req = new OrderDelayReviewWithSessionRequest
@@ -193,12 +193,13 @@ public class OrderDelayApplication : IOrderDelayApplication, IScopeDependency
             IsPass = request.IsPass,
             NextWorkflow = request.NextWorkflow
         };
-        foreach (var delay in delays)
+        foreach (var delay in request.DelayWithStepIds)
         {
-            req.NextWorkflow.StepId = request.DelayWithStepIds.First(d => d.DelayId == delay.Id).StepId;
+            req.NextWorkflow.WorkflowId = delay.WorkflowId;
+            req.NextWorkflow.StepId = delay.StepId;//request.DelayWithStepIds.First(d => d.DelayId == delay.Id).StepId;
             apptaskItems.Add(new AddApptaskItemRequest
             {
-                BusinessId = delay.Id,
+                BusinessId = delay.DelayId,
                 TaskParams = req
             });
         }
@@ -212,12 +213,17 @@ public class OrderDelayApplication : IOrderDelayApplication, IScopeDependency
 
         if (!string.IsNullOrEmpty(taskId))
         {
-            foreach (var orderDelay in delays)
-            {
-                orderDelay.DelayState = EDelayState.BatchProcessing;
-            }
-            await _orderDelayRepository.Updateable(delays)
-                .UpdateColumns(d=>new {d.DelayState})
+            //foreach (var orderDelay in delays)
+            //{
+            //    orderDelay.DelayState = EDelayState.BatchProcessing;
+            //}
+            //await _orderDelayRepository.Updateable(delays)
+            //    .UpdateColumns(d => new { d.DelayState })
+            //    .ExecuteCommandAsync(cancellation);
+            var delayIds = request.DelayWithStepIds.Select(d => d.DelayId).ToList();
+            await _orderDelayRepository.Updateable()
+                .SetColumns(d => d.DelayState == EDelayState.BatchProcessing)
+                .Where(d => delayIds.Contains(d.Id))
                 .ExecuteCommandAsync(cancellation);
         }
     }

+ 48 - 32
src/Hotline.Application/OrderApp/OrderSendBackAuditApplication.cs

@@ -1,7 +1,9 @@
-using Hotline.Orders;
+using Hotline.Configurations;
+using Hotline.Orders;
 using Hotline.SeedData;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Enums.Order;
+using Microsoft.Extensions.Options;
 using SqlSugar;
 using XF.Domain.Authentications;
 using XF.Domain.Dependency;
@@ -13,45 +15,59 @@ namespace Hotline.Application.OrderApp
     {
         private readonly IRepository<OrderSendBackAudit> _orderSendBackAuditRepository;
         private readonly ISessionContext _sessionContext;
-        public OrderSendBackAuditApplication(IRepository<OrderSendBackAudit> orderSendBackAuditRepository, ISessionContext sessionContext)
+        private readonly IOptionsSnapshot<AppConfiguration> _appOptions;
+        public OrderSendBackAuditApplication(
+            IRepository<OrderSendBackAudit> orderSendBackAuditRepository,
+            ISessionContext sessionContext,
+            IOptionsSnapshot<AppConfiguration> appOptions)
         {
             _orderSendBackAuditRepository = orderSendBackAuditRepository;
             _sessionContext = sessionContext;
+            _appOptions = appOptions;
         }
 
         public ISugarQueryable<OrderSendBackAudit> AuditList(SendBackListDto dto)
         {
-            return _orderSendBackAuditRepository.Queryable()
-                .Includes(x => x.Order)
-                .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
-                    x => x.Order.Title.Contains(dto.Keyword!) || x.Order.No.Contains(dto.Keyword!))
-                .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Order.Title.Contains(dto.Title!))
-                .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.Order.No.Contains(dto.No!))
-                .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.IsProvince == true)
-                .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == false, x => x.Order.IsProvince == false)
-                .WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.Order.SourceChannelCode == dto.Channel)    //来源渠道
-                .WhereIF(!string.IsNullOrEmpty(dto.AcceptTypeCode), x => x.Order.AcceptTypeCode == dto.AcceptTypeCode)
-                .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), x => x.Order.HotspotSpliceName != null && x.Order.HotspotSpliceName.Contains(dto.Hotspot)) //热点
-                .WhereIF(!string.IsNullOrEmpty(dto.CenterToOrgHandlerName), x => x.Order.CenterToOrgHandlerName.Contains(dto.CenterToOrgHandlerName))   //最近派单员              
-                .WhereIF(!string.IsNullOrEmpty(dto.SendBackStepName), x => x.SendBackStepName.Contains(dto.SendBackStepName))                           //退回节点                   
-                .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), x => x.Order.AcceptorName == dto.NameOrNo! || x.Order.AcceptorStaffNo == dto.NameOrNo!)   //受理人/坐席
-                .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.Order.OrgLevelOneName.Contains(dto.OrgLevelOneName))                        //一级部门 
-                .WhereIF(!string.IsNullOrEmpty(dto.Content), x => x.Order.Content.Contains(dto.Content))                                                //受理内容 
-                .WhereIF(!string.IsNullOrEmpty(dto.ApplyName), x => x.CreatorName.Contains(dto.ApplyName))                                              //申请人 
-                .WhereIF(!string.IsNullOrEmpty(dto.ApplyOrgName), x => x.ApplyOrgName.Contains(dto.ApplyOrgName))                                       //申请部门 
-                .WhereIF(!string.IsNullOrEmpty(dto.Opinion), x => SqlFunc.JsonField(x.SendBackData, "Opinion").Contains(dto.Opinion))          //申请理由
+            var query = _orderSendBackAuditRepository.Queryable()
+                        .Includes(x => x.Order)
+                        .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
+                            x => x.Order.Title.Contains(dto.Keyword!) || x.Order.No.Contains(dto.Keyword!))
+                        .WhereIF(!string.IsNullOrEmpty(dto.Title), x => x.Order.Title.Contains(dto.Title!))
+                        .WhereIF(!string.IsNullOrEmpty(dto.No), x => x.Order.No.Contains(dto.No!))
+                        .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == true, x => x.Order.IsProvince == true)
+                        .WhereIF(dto.IsProvince.HasValue && dto.IsProvince == false, x => x.Order.IsProvince == false)
+                        .WhereIF(!string.IsNullOrEmpty(dto.Channel), x => x.Order.SourceChannelCode == dto.Channel)    //来源渠道
+                        .WhereIF(!string.IsNullOrEmpty(dto.AcceptTypeCode), x => x.Order.AcceptTypeCode == dto.AcceptTypeCode)
+                        .WhereIF(!string.IsNullOrEmpty(dto.Hotspot), x => x.Order.HotspotSpliceName != null && x.Order.HotspotSpliceName.Contains(dto.Hotspot)) //热点
+                        .WhereIF(!string.IsNullOrEmpty(dto.CenterToOrgHandlerName), x => x.Order.CenterToOrgHandlerName.Contains(dto.CenterToOrgHandlerName))   //最近派单员              
+                        .WhereIF(!string.IsNullOrEmpty(dto.SendBackStepName), x => x.SendBackStepName.Contains(dto.SendBackStepName))                           //退回节点                   
+                        .WhereIF(!string.IsNullOrEmpty(dto.NameOrNo), x => x.Order.AcceptorName == dto.NameOrNo! || x.Order.AcceptorStaffNo == dto.NameOrNo!)   //受理人/坐席
+                        .WhereIF(!string.IsNullOrEmpty(dto.OrgLevelOneName), x => x.Order.OrgLevelOneName.Contains(dto.OrgLevelOneName))                        //一级部门 
+                        .WhereIF(!string.IsNullOrEmpty(dto.Content), x => x.Order.Content.Contains(dto.Content))                                                //受理内容 
+                        .WhereIF(!string.IsNullOrEmpty(dto.ApplyName), x => x.CreatorName.Contains(dto.ApplyName))                                              //申请人 
+                        .WhereIF(!string.IsNullOrEmpty(dto.ApplyOrgName), x => x.ApplyOrgName.Contains(dto.ApplyOrgName))                                       //申请部门 
+                        .WhereIF(!string.IsNullOrEmpty(dto.Opinion), x => SqlFunc.JsonField(x.SendBackData, "Opinion").Contains(dto.Opinion))
+                        .WhereIF(dto.DataScope is 1, x => x.CreatorId == _sessionContext.RequiredUserId)
+                        .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
+                        .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
+                        .WhereIF(dto.AuditState == 1, x => x.State == ESendBackAuditState.Apply)
+                        .WhereIF(dto is { AuditState: 2, State: null }, x => x.State > ESendBackAuditState.Apply)
+                        .WhereIF(dto.AuditState is 2 or 3 && dto.State.HasValue && dto.State != ESendBackAuditState.All, x => x.State == dto.State)
+                        .WhereIF(dto.AuditState == 3 && _sessionContext.RequiredOrgId != OrgSeedData.CenterId, x => x.ApplyOrgId.StartsWith(_sessionContext.OrgId))
+                        .WhereIF(_sessionContext.Roles.Contains("role_sysadmin") == false && dto.AuditState != 3, x => x.SendBackOrgId == _sessionContext.OrgId) // 123 系统管理员;
+                        .WhereIF(dto.ExpiredTimeStart.HasValue, x => x.Order.ExpiredTime >= dto.ExpiredTimeStart)
+                        .WhereIF(dto.ExpiredTimeEnd.HasValue, x => x.Order.ExpiredTime <= dto.ExpiredTimeEnd);
 
-                .WhereIF(dto.DataScope is 1, x => x.CreatorId == _sessionContext.RequiredUserId)
-                .WhereIF(dto.StartTime.HasValue, x => x.CreationTime >= dto.StartTime)
-                .WhereIF(dto.EndTime.HasValue, x => x.CreationTime <= dto.EndTime)
-                .WhereIF(dto.AuditState == 1, x => x.State == ESendBackAuditState.Apply)
-                .WhereIF(dto is { AuditState: 2, State: null }, x => x.State > ESendBackAuditState.Apply)
-                .WhereIF(dto.AuditState is 2 or 3 && dto.State.HasValue && dto.State != ESendBackAuditState.All, x => x.State == dto.State)
-                .WhereIF(dto.AuditState == 3 && _sessionContext.RequiredOrgId != OrgSeedData.CenterId, x => x.ApplyOrgId.StartsWith(_sessionContext.OrgId))
-                .WhereIF(_sessionContext.Roles.Contains("role_sysadmin") == false && dto.AuditState != 3, x => x.SendBackOrgId == _sessionContext.OrgId) // 123 系统管理员;
-                .WhereIF(dto.ExpiredTimeStart.HasValue, x => x.Order.ExpiredTime >= dto.ExpiredTimeStart)
-                .WhereIF(dto.ExpiredTimeEnd.HasValue, x => x.Order.ExpiredTime <= dto.ExpiredTimeEnd)
-            .OrderByDescending(x => x.CreationTime);
+            if (_appOptions.Value.IsYiBin && dto.AuditState == 1)
+            {
+                query = query.OrderBy(x => x.Order.ExpiredTime);
+            }
+            else
+            {
+                query = query.OrderByDescending(x => x.CreationTime);
+            }
+
+            return query;
         }
     }
 }

+ 20 - 0
src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IOfflineExamAnalysisRepository.cs

@@ -0,0 +1,20 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Interface;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages
+{
+    /// <summary>
+    /// 线下考试统计仓储接口
+    /// </summary>
+    [Description("线下考试统计仓储接口")]
+    public interface IOfflineExamAnalysisRepository : IRepository<ExamOfflineExamAnalysis>, IExamRepository<ExamOfflineExamAnalysis, HotlineDbContext>
+    {
+    }
+}

+ 22 - 0
src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/OfflineExamAnalysisRepository.cs

@@ -0,0 +1,22 @@
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.DataPermissions;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Validators.ExamManages;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Dependency;
+
+namespace Hotline.Repository.SqlSugar.Exam.Repositories.ExamManages
+{
+    public class OfflineExamAnalysisRepository : ExamRepository<ExamOfflineExamAnalysis>, IOfflineExamAnalysisRepository, IScopeDependency
+    {
+        public OfflineExamAnalysisRepository(ISugarUnitOfWork<HotlineDbContext> uow, IDataPermissionFilterBuilder dataPermissionFilterBuilder, IServiceProvider serviceProvider) : base(uow, dataPermissionFilterBuilder, serviceProvider)
+        {
+            Validator = new OfflineExamAnalysisValidator();
+        }
+    }
+}

+ 63 - 0
src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/OfflineExamAnalysisValidator.cs

@@ -0,0 +1,63 @@
+using Exam.Infrastructure.Extensions;
+using Exam.Infrastructure.Validation.Validation;
+using FluentValidation;
+using Hotline.Exams.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Core.Constants;
+using Hotline.Repository.SqlSugar.Exam.Interfaces.ExamManages;
+using Hotline.Repository.SqlSugar.Exam.Validate;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Repository.SqlSugar.Exam.Validators.ExamManages
+{
+    public class OfflineExamAnalysisValidator : BaseValidator<ExamOfflineExamAnalysis>
+    {
+        private readonly IExamManageRepository _examManageRepository;
+
+        public OfflineExamAnalysisValidator()
+        {
+            RuleSet(ValidatorTypeConstants.Create, () =>
+            {
+                BaseValidateRule();
+
+                ValidateRuleWithAdd();
+            });
+
+            RuleSet(ValidatorTypeConstants.Modify, () =>
+            {
+                BaseValidateRule();
+
+                ValidateRuleWithModify();
+            });
+        }
+
+        protected override void BaseValidateRule()
+        {
+            base.BaseValidateRule();
+            RuleFor(m => m.UserName).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.UserName))));
+            RuleFor(m => m.DepartmentName).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.DepartmentName))));
+            RuleFor(m => m.ExamName).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.ExamName))));
+            RuleFor(m => m.TotalScore).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.TotalScore))));
+            RuleFor(m => m.Score).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.Score))));
+            RuleFor(m => m.CutoffScore).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.CutoffScore))));
+            RuleFor(m => m.TotalScore).Must((c, v) => c.Score <= v).WithMessage(x => string.Format(ExamErrorMessage.Greater, x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.Score)),x.GetType().GetDescription(nameof(ExamOfflineExamAnalysis.TotalScore))));
+        }
+
+        protected override void ValidateRuleWithAdd()
+        {
+            base.ValidateRuleWithAdd();
+            RuleFor(m => m.CreationTime).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamAnswer.CreationTime))));
+        }
+
+        protected override void ValidateRuleWithModify()
+        {
+            base.ValidateRuleWithModify();
+            RuleFor(m => m.LastModificationTime).NotEmpty().WithMessage(x => string.Format(ExamErrorMessage.IsRequired, x.GetType().GetDescription(nameof(ExamAnswer.LastModificationTime))));
+
+        }
+
+    }
+}

+ 75 - 0
src/Hotline.Share/Dtos/ExamManages/OfflineExamAnalysisDto.cs

@@ -0,0 +1,75 @@
+using Exam.Infrastructure.Data.Interface;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.ExamManages
+{
+    /// <summary>
+    /// 线下考试统计实体
+    /// </summary>
+    [Description("线下考试统计实体")]
+    public class OfflineExamAnalysisDto : UpdateOfflineExamAnalysisDto
+    {
+        
+    }
+
+    public class AddOfflineExamAnalysisDto : IAddRequest
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [Description("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [Description("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [Description("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [Description("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [Description("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [Description("考试分数")]
+        public int Score { get; set; }
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        [Description("开始时间")]
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 结束时间
+        /// </summary>
+        [Description("结束时间")]
+        public DateTime? EndTime { get; set; }
+    }
+
+    public class UpdateOfflineExamAnalysisDto : AddOfflineExamAnalysisDto,IActionRequest
+    {
+        public string Id { get; set; }
+    }
+}

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

@@ -3,5 +3,6 @@
 public class DelayWithStepId
 {
     public string DelayId { get; set; }
+    public string WorkflowId { get; set; }
     public string StepId { get; set; }
 }

+ 20 - 0
src/Hotline.Share/Requests/Exam/OfflineExamAnalysisPagedRequest.cs

@@ -0,0 +1,20 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Requests.Exam
+{
+    public record OfflineExamAnalysisPagedRequest : AnalysisReportRequest
+    {
+        /// <summary>
+        /// 最小分数
+        /// </summary>
+        [Description("最小分数")]
+        public int? MinScore { get; set; }
+
+        /// <summary>
+        /// 最大分数
+        /// </summary>
+
+        [Description("最大分数")]
+        public int? MaxScore { get; set; }
+    }
+}

+ 10 - 0
src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisPageViewResponse.cs

@@ -0,0 +1,10 @@
+using Exam.Infrastructure.Data.Entity;
+using Exam.Share.ViewResponses.Exam;
+
+namespace Hotline.Share.ViewResponses.Exam
+{
+    public class OfflineExamAnalysisPageViewResponse : PageViewResponse<OfflineExamAnalysisViewResponse>
+    {
+    }
+
+}

+ 52 - 0
src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisViewResponse.cs

@@ -0,0 +1,52 @@
+using Exam.Infrastructure.Data.Interface;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.ViewResponses.Exam
+{
+    public class OfflineExamAnalysisViewResponse : IViewResponse
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [Description("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [Description("部门名称")]
+        public string OrgName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [Description("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [Description("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [Description("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [Description("考试分数")]
+        public int Score { get; set; }
+
+
+        public string Id { get; set ; }
+    }
+}

+ 69 - 0
src/Hotline/Exams/ExamManages/ExamOfflineExamAnalysis.cs

@@ -0,0 +1,69 @@
+using Hotline.Exams.Base;
+using SqlSugar;
+using System.ComponentModel;
+
+namespace Hotline.Exams.ExamManages
+{
+    /// <summary>
+    /// 线下考试统计
+    /// </summary>
+    [Description("线下考试统计")]
+    public class ExamOfflineExamAnalysis: ExamBusinessEntity
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [SugarColumn(ColumnDescription = "参考人员")]
+        [Description("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [SugarColumn(ColumnDescription = "部门名称")]
+        [Description("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试标题")]
+        [Description("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试总分")]
+        [Description("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [SugarColumn(ColumnDescription = "合格分数")]
+        [Description("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试分数")]
+        [Description("考试分数")]
+        public int Score { get; set; }
+
+        /// <summary>
+        /// 考试开始时间
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试开始时间")]
+        [Description("考试开始时间")]
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 考试结束时间
+        /// </summary>
+        [SugarColumn(ColumnDescription = "考试结束时间")]
+        [Description("考试结束时间")]
+        public DateTime? EndTime { get; set; }
+    }
+}

+ 62 - 0
src/Hotline/Exams/ExamManages/OfflineExamAnalysisExcel.cs

@@ -0,0 +1,62 @@
+using MiniExcelLibs.Attributes;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Exams.ExamManages
+{
+    public class OfflineExamAnalysisExcel
+    {
+        /// <summary>
+        /// 参考人员
+        /// </summary>
+        [ExcelColumnName("参考人员")]
+        public string UserName { get; set; }
+
+        /// <summary>
+        /// 部门名称
+        /// </summary>
+        [ExcelColumnName("部门名称")]
+        public string DepartmentName { get; set; }
+
+        /// <summary>
+        /// 考试标题
+        /// </summary>
+        [ExcelColumnName("考试标题")]
+        public string ExamName { get; set; }
+
+        /// <summary>
+        /// 考试开始时间
+        /// </summary>
+        [ExcelColumnName("考试开始时间")]
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        /// 考试结束时间
+        /// </summary>
+        [ExcelColumnName("考试结束时间")]
+        public DateTime? EndTime { get; set; }
+
+        /// <summary>
+        /// 考试总分
+        /// </summary>
+        [ExcelColumnName("考试总分")]
+        public int TotalScore { get; set; }
+
+        /// <summary>
+        /// 合格分数
+        /// </summary>
+        [ExcelColumnName("合格分数")]
+        public int CutoffScore { get; set; }
+
+        /// <summary>
+        /// 考试分数
+        /// </summary>
+        [ExcelColumnName("考试分数")]
+        public int Score { get; set; }
+    }
+}