Forráskód Böngészése

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

xf 1 hete
szülő
commit
a57efa20ea
21 módosított fájl, 908 hozzáadás és 147 törlés
  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. 126 2
      src/Hotline.Api/Controllers/OrderApi/OrderComplementController.cs
  5. 3 111
      src/Hotline.Api/Controllers/OrderController.cs
  6. 14 0
      src/Hotline.Application/Exam/Constants/ApiRoutes/OfflineExamAnalysisApiRoute.cs
  7. 6 0
      src/Hotline.Application/Exam/Constants/Messages/ExamSystemConstants.cs
  8. 28 0
      src/Hotline.Application/Exam/Interface/ExamManages/IOfflineExamAnalysisService.cs
  9. 31 0
      src/Hotline.Application/Exam/QueryExtensions/ExamManages/OfflineExamQueryExtensions.cs
  10. 148 0
      src/Hotline.Application/Exam/Service/ExamManages/OfflineExamAnalysisService.cs
  11. 48 32
      src/Hotline.Application/OrderApp/OrderSendBackAuditApplication.cs
  12. 20 0
      src/Hotline.Repository.SqlSugar/Exam/Interfaces/ExamManages/IOfflineExamAnalysisRepository.cs
  13. 22 0
      src/Hotline.Repository.SqlSugar/Exam/Repositories/ExamManages/OfflineExamAnalysisRepository.cs
  14. 63 0
      src/Hotline.Repository.SqlSugar/Exam/Validators/ExamManages/OfflineExamAnalysisValidator.cs
  15. 75 0
      src/Hotline.Share/Dtos/ExamManages/OfflineExamAnalysisDto.cs
  16. 12 0
      src/Hotline.Share/Dtos/Order/ComplementOrderDto.cs
  17. 20 0
      src/Hotline.Share/Requests/Exam/OfflineExamAnalysisPagedRequest.cs
  18. 10 0
      src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisPageViewResponse.cs
  19. 52 0
      src/Hotline.Share/ViewResponses/Exam/OfflineExamAnalysisViewResponse.cs
  20. 69 0
      src/Hotline/Exams/ExamManages/ExamOfflineExamAnalysis.cs
  21. 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>

+ 126 - 2
src/Hotline.Api/Controllers/OrderApi/OrderComplementController.cs

@@ -1,16 +1,39 @@
-using Hotline.Orders;
+using Hotline.Article;
+using Hotline.FlowEngine.Workflows;
+using Hotline.Orders;
+using Hotline.Share.Dtos.Article;
 using Hotline.Share.Dtos.Order;
+using Hotline.Share.Enums.Article;
+using Hotline.Users;
 using Microsoft.AspNetCore.Mvc;
+using XF.Domain.Authentications;
+using XF.Domain.Exceptions;
+using XF.Domain.Repository;
 
 namespace Hotline.Api.Controllers.OrderApi;
 
 public class OrderComplementController : BaseController
 {
     private readonly IOrderDomainService _orderDomainService;
+    private readonly ISessionContext _sessionContext;
+    private readonly IRepository<OrderComplement> _orderComplementRepository;
+    private readonly IRepository<WorkflowStep> _workflowStepRepository;
+    private readonly IRepository<User> _userRepository;
+    private readonly ICircularRecordDomainService _circularRecordDomainService;
 
-    public OrderComplementController(IOrderDomainService orderDomainService)
+    public OrderComplementController(IOrderDomainService orderDomainService,
+       ISessionContext sessionContext,
+       IRepository<OrderComplement> orderComplementRepository,
+       IRepository<WorkflowStep> workflowStepRepository,
+       IRepository<User> userRepository,
+       ICircularRecordDomainService circularRecordDomainService)
     {
         _orderDomainService = orderDomainService;
+        _sessionContext = sessionContext;
+        _orderComplementRepository = orderComplementRepository;
+        _workflowStepRepository = workflowStepRepository;
+        _userRepository = userRepository;
+        _circularRecordDomainService = circularRecordDomainService;
     }
     
     /// <summary>
@@ -23,4 +46,105 @@ public class OrderComplementController : BaseController
     {
         return await _orderDomainService.AddOrderComplementAsync(dto, HttpContext.RequestAborted);
     }
+
+    #region 添加补充
+
+    /// <summary>
+    /// 添加补充 _notificationWaitSendRepository
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    [HttpPost("add_order_complement")]
+    public async Task AddOrderComplement([FromBody] AddComplementDto dto)
+    {
+        if (dto == null)
+            throw UserFriendlyException.SameMessage("数据错误!");
+        if (string.IsNullOrEmpty(dto.Opinion))
+            throw UserFriendlyException.SameMessage("补充内容不能为空!");
+        if (dto.Opinion.Length > 2000)
+            throw UserFriendlyException.SameMessage("补充内容限制2000字!");
+        var data = await _orderDomainService.GetOrderAsync(dto.OrderId, cancellationToken: HttpContext.RequestAborted);
+        if (data == null)
+            throw UserFriendlyException.SameMessage("工单查询失败!");
+
+        OrderComplement complement = new OrderComplement()
+        {
+            OrderId = dto.OrderId,
+            Opinion = dto.Opinion,
+            SupplyName = _sessionContext.UserName,
+            SupplyTime = DateTime.Now,
+            No = data.No,
+            IsProComplement = false
+        };
+
+        var id = await _orderComplementRepository.AddAsync(complement, HttpContext.RequestAborted);
+        if (!string.IsNullOrEmpty(id))
+        {
+            #region 处理推送消息
+
+            //获取当前办理节点数据
+            var work = await _workflowStepRepository.GetAsync(p => p.Id == data.ActualHandleStepId, HttpContext.RequestAborted);
+            if (work != null)
+            {
+                //获取办理指定类型
+                var workflowStepHandler = work.GetWorkflowStepHandler();
+                if (workflowStepHandler != null)
+                {
+                    AddCircularDto circularDto = new AddCircularDto()
+                    {
+                        Title = "工单补充",
+                        Content = "工单" + data.No + "有补充内容,请注意查收!",
+                        CircularTypeId = "6",
+                        CircularTypeName = "工单补充",
+                        IsMustRead = true,
+                        SourceOrgId = _sessionContext.RequiredOrgId,
+                        SourceOrgName = _sessionContext.OrgName,
+                        CircularType = ECircularType.Person
+                    };
+
+                    List<CircularReadGroupDto> users = [];
+                    if (!string.IsNullOrEmpty(workflowStepHandler.UserId)) //指定用户
+                    {
+                        users.Add(new CircularReadGroupDto()
+                        {
+                            UserId = workflowStepHandler.UserId,
+                            UserName = workflowStepHandler.Username
+                        });
+                    }
+                    else if (!string.IsNullOrEmpty(workflowStepHandler.RoleId)) //指定角色
+                    {
+                        //查询指定角色下面所有的用户
+                        var userlist = await _userRepository.Queryable().Where(x =>
+                                x.OrgId == workflowStepHandler.OrgId && x.Roles.Any(d => workflowStepHandler.RoleId.Contains(d.Id)))
+                            .Select(d => new CircularReadGroupDto
+                            {
+                                UserId = d.Id,
+                                UserName = d.Name
+                            }).ToListAsync();
+                        users.AddRange(userlist);
+                    }
+                    else if (!string.IsNullOrEmpty(workflowStepHandler.OrgId)) //指定部门
+                    {
+                        users.Add(new CircularReadGroupDto()
+                        {
+                            OrgId = workflowStepHandler.OrgId,
+                            OrgName = workflowStepHandler.OrgName
+                        });
+                        circularDto.CircularType = ECircularType.Org;
+                    }
+
+                    if (users != null && users.Count > 0)
+                    {
+                        circularDto.CircularReadGroups = users;
+                        //调用推送消息通用接口
+                        await _circularRecordDomainService.AddCircularMessage(circularDto, HttpContext.RequestAborted);
+                    }
+                }
+            }
+
+            #endregion
+        }
+    }
+
+    #endregion
 }

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

@@ -14,6 +14,7 @@ using Hotline.Caching.Interfaces;
 using Hotline.CallCenter.Calls;
 using Hotline.Configurations;
 using Hotline.ContingencyManagement.Notifies;
+using Hotline.Early;
 using Hotline.EventBus;
 using Hotline.File;
 using Hotline.FlowEngine.Definitions;
@@ -26,7 +27,6 @@ using Hotline.OrderTranspond;
 using Hotline.Push.FWMessage;
 using Hotline.Push.Notifies;
 using Hotline.Repository.SqlSugar.Extensions;
-using Hotline.Repository.SqlSugar.Orders;
 using Hotline.Repository.SqlSugar.Ts;
 using Hotline.SeedData;
 using Hotline.Settings;
@@ -34,7 +34,6 @@ using Hotline.Settings.Hotspots;
 using Hotline.Settings.TimeLimitDomain;
 using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
-using Hotline.Share.Dtos.Article;
 using Hotline.Share.Dtos.CallCenter;
 using Hotline.Share.Dtos.File;
 using Hotline.Share.Dtos.FlowEngine;
@@ -43,11 +42,11 @@ using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Order.Detail;
 using Hotline.Share.Dtos.Order.Handle;
 using Hotline.Share.Dtos.Order.Migration;
+using Hotline.Share.Dtos.Order.OrderDelay;
 using Hotline.Share.Dtos.Order.Publish;
 using Hotline.Share.Dtos.Org;
 using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Dtos.Snapshot;
-using Hotline.Share.Enums.Article;
 using Hotline.Share.Enums.CallCenter;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Order;
@@ -73,19 +72,13 @@ using MiniExcelLibs;
 using SqlSugar;
 using System.Text;
 using System.Text.Json;
-using System.Threading;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
-using XF.Domain.Entities;
 using XF.Domain.Exceptions;
 using XF.Domain.Repository;
 using XF.Utility.EnumExtensions;
 using OrderDto = Hotline.Share.Dtos.Order.OrderDto;
 using UserInfo = Hotline.Share.Dtos.FlowEngine.UserInfo;
-using Hotline.Caching.Services;
-using Hotline.Early;
-using Hotline.Share.Dtos.Order.OrderDelay;
-using MathNet.Numerics.Distributions;
 
 namespace Hotline.Api.Controllers;
 
@@ -196,7 +189,6 @@ public class OrderController : BaseController
         IRepository<OrderVisitDetail> orderVisitedDetailRepository,
         ICapPublisher capPublisher,
         IOrderDelayRepository orderDelayRepository,
-        //ITimeLimitDomainService timeLimitDomainService,
         ISystemSettingCacheManager systemSettingCacheManager,
         IRepository<OrderRedo> orderRedoRepository,
         IRepository<OrderSupervise> orderSuperviseRepository,
@@ -279,7 +271,6 @@ public class OrderController : BaseController
         _orderVisitedDetailRepository = orderVisitedDetailRepository;
         _capPublisher = capPublisher;
         _orderDelayRepository = orderDelayRepository;
-        //_timeLimitDomainService = timeLimitDomainService;
         _systemSettingCacheManager = systemSettingCacheManager;
         _orderRedoRepository = orderRedoRepository;
         _orderSuperviseRepository = orderSuperviseRepository;
@@ -1186,7 +1177,7 @@ public class OrderController : BaseController
         {
             Industry = _systemSettingCacheManager.Snapshot ? await _industryRepository
                     .Queryable()
-                    .Select(d => new { d.Id, d.Name, }).ToListAsync() 
+                    .Select(d => new { d.Id, d.Name, }).ToListAsync()
                 : null,
             ChannelOptions = _sysDicDataCacheManager.GetSysDicDataCache(TimeLimitBaseDataConsts.SourceChannel),
             AcceptTypeOptions = _sysDicDataCacheManager.GetSysDicDataCache(SysDicTypeConsts.AcceptType),
@@ -10456,104 +10447,5 @@ public class OrderController : BaseController
 
     #endregion
 
-    #region 添加补充
-
-    /// <summary>
-    /// 添加补充 _notificationWaitSendRepository
-    /// </summary>
-    /// <param name="dto"></param>
-    /// <returns></returns>
-    [HttpPost("add_order_complement")]
-    public async Task AddOrderComplement([FromBody] AddComplementDto dto)
-    {
-        if (dto == null)
-            throw UserFriendlyException.SameMessage("数据错误!");
-        if (string.IsNullOrEmpty(dto.Opinion))
-            throw UserFriendlyException.SameMessage("补充内容不能为空!");
-        if (dto.Opinion.Length > 2000)
-            throw UserFriendlyException.SameMessage("补充内容限制2000字!");
-        var data = await _orderDomainService.GetOrderAsync(dto.OrderId, cancellationToken: HttpContext.RequestAborted);
-        if (data == null)
-            throw UserFriendlyException.SameMessage("工单查询失败!");
-
-        OrderComplement complement = new OrderComplement()
-        {
-            OrderId = dto.OrderId,
-            Opinion = dto.Opinion,
-            SupplyName = _sessionContext.UserName,
-            SupplyTime = DateTime.Now,
-            No = data.No,
-            IsProComplement = false
-        };
 
-        var id = await _orderComplementRepository.AddAsync(complement, HttpContext.RequestAborted);
-        if (!string.IsNullOrEmpty(id))
-        {
-            #region 处理推送消息
-
-            //获取当前办理节点数据
-            var work = await _workflowStepRepository.GetAsync(p => p.Id == data.ActualHandleStepId, HttpContext.RequestAborted);
-            if (work != null)
-            {
-                //获取办理指定类型
-                var workflowStepHandler = work.GetWorkflowStepHandler();
-                if (workflowStepHandler != null)
-                {
-                    AddCircularDto circularDto = new AddCircularDto()
-                    {
-                        Title = "工单补充",
-                        Content = "工单" + data.No + "有补充内容,请注意查收!",
-                        CircularTypeId = "6",
-                        CircularTypeName = "工单补充",
-                        IsMustRead = true,
-                        SourceOrgId = _sessionContext.RequiredOrgId,
-                        SourceOrgName = _sessionContext.OrgName,
-                        CircularType = ECircularType.Person
-                    };
-
-                    List<CircularReadGroupDto> users = [];
-                    if (!string.IsNullOrEmpty(workflowStepHandler.UserId)) //指定用户
-                    {
-                        users.Add(new CircularReadGroupDto()
-                        {
-                            UserId = workflowStepHandler.UserId,
-                            UserName = workflowStepHandler.Username
-                        });
-                    }
-                    else if (!string.IsNullOrEmpty(workflowStepHandler.RoleId)) //指定角色
-                    {
-                        //查询指定角色下面所有的用户
-                        var userlist = await _userRepository.Queryable().Where(x =>
-                                x.OrgId == workflowStepHandler.OrgId && x.Roles.Any(d => workflowStepHandler.RoleId.Contains(d.Id)))
-                            .Select(d => new CircularReadGroupDto
-                            {
-                                UserId = d.Id,
-                                UserName = d.Name
-                            }).ToListAsync();
-                        users.AddRange(userlist);
-                    }
-                    else if (!string.IsNullOrEmpty(workflowStepHandler.OrgId)) //指定部门
-                    {
-                        users.Add(new CircularReadGroupDto()
-                        {
-                            OrgId = workflowStepHandler.OrgId,
-                            OrgName = workflowStepHandler.OrgName
-                        });
-                        circularDto.CircularType = ECircularType.Org;
-                    }
-
-                    if (users != null && users.Count > 0)
-                    {
-                        circularDto.CircularReadGroups = users;
-                        //调用推送消息通用接口
-                        await _circularRecordDomainService.AddCircularMessage(circularDto, HttpContext.RequestAborted);
-                    }
-                }
-            }
-
-            #endregion
-        }
-    }
-
-    #endregion
 }

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

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

+ 12 - 0
src/Hotline.Share/Dtos/Order/ComplementOrderDto.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Dtos.Order
+{
+    public class ComplementOrderDto
+    {
+    }
+}

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