475 lines
20 KiB
C#
475 lines
20 KiB
C#
using Learn.Archives.API.Controllers.Dto;
|
|
using Learn.Archives.API.Expand;
|
|
using Learn.Archives.Core.Common;
|
|
using Learn.Archives.Core.Common.Expand;
|
|
using Learn.Archives.Core.Model;
|
|
using Learn.Archives.Core.Model.Dto;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using MiniExcelLibs;
|
|
using System.Buffers.Text;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Security.Claims;
|
|
using UserCenter.Model;
|
|
using UserCenter.Model.Common;
|
|
using UserCenter.Model.Enum;
|
|
using static System.Net.Mime.MediaTypeNames;
|
|
|
|
namespace Learn.Archives.API.Controllers
|
|
{
|
|
/// <summary>
|
|
/// 年级控制器
|
|
/// </summary>
|
|
public class ExamClassInfoController : BackController<ExamClassInfo>
|
|
{
|
|
readonly Repository<ExamClassInfo> baseService;
|
|
readonly Repository<Exam> examService;
|
|
readonly Repository<School> schoolService;
|
|
readonly Repository<ExamTags> tagService;
|
|
readonly Repository<ExamClassTag> classTagService;
|
|
readonly Repository<ExamUserInfo> examUserInfoService;
|
|
readonly LiveUserInfo userInfo;
|
|
readonly IHttpContextAccessor accessor;
|
|
public ExamClassInfoController(Repository<ExamClassInfo> baseService, LiveUserInfo userInfo,
|
|
IHttpContextAccessor httpContext, Repository<ExamUserInfo> examUserInfoService,
|
|
Repository<Exam> examService, Repository<School> schoolService,
|
|
Repository<ExamTags> tagService, Repository<ExamClassTag> classTagService) : base(baseService)
|
|
{
|
|
this.baseService = baseService;
|
|
this.userInfo = userInfo;
|
|
this.accessor = httpContext;
|
|
this.examUserInfoService = examUserInfoService;
|
|
this.examService = examService;
|
|
this.schoolService = schoolService;
|
|
this.tagService = tagService;
|
|
this.classTagService = classTagService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 导入考试信息
|
|
/// </summary>
|
|
/// <param name="dto"></param>
|
|
/// <returns></returns>
|
|
[HttpPost, ResultIgnore]
|
|
[HttpLogEnable]
|
|
public async Task<IActionResult> Import(ImportExamDto dto)
|
|
{
|
|
|
|
var exam = await examService.GetByIdAsync(dto.eId);
|
|
if (exam == null ) Oh.ModelError("为传入有效的数据");
|
|
var dataList = await accessor.ParsingExcelAsync<ImportExamInfoError>();
|
|
dataList= dataList.Where(s=>!string.IsNullOrEmpty(s.School)).ToArray();
|
|
if (dataList == null || dataList.Count() == 0)
|
|
Oh.ModelError("导入失败:无有效数据");
|
|
|
|
//处理数据
|
|
var errorExcelInfo = new List<ImportExamInfoError>();
|
|
var insertUserInfo = new List<ExamUserInfo>();
|
|
var insertClassInfo = new List<ExamClassInfo>();
|
|
var oldUidAr = await examUserInfoService.AsQueryable()
|
|
.Where(s => s.ExamId == exam.Id)
|
|
.Select(s=>s.UserId)
|
|
.ToArrayAsync();
|
|
var oldUidHash = oldUidAr.ToHashSet();
|
|
var db = schoolService.Context;
|
|
foreach (var schoolArr in dataList.GroupBy(s => s.School))
|
|
{
|
|
var school = await schoolService.GetFirstAsync(s => s.Name == schoolArr.Key);
|
|
if (school == null) Oh.ModelError($"导入失败:学校 [{schoolArr.Key}] 未找到!");
|
|
var gradeInfo = GradeHelper.GetStudentGradeBaseByGrade(schoolArr.First().Grade);
|
|
if (gradeInfo == null) Oh.ModelError($"导入失败:学校 [{schoolArr.Key}] 年级[{schoolArr.First().Grade}]不符合规范!");
|
|
if(exam.GradeLevel != gradeInfo.GradeLevel || exam.GradeYear != gradeInfo.GradeYear)
|
|
Oh.ModelError($"导入失败:导入年级[{schoolArr.First().Grade}] 与 考试年级不匹配!");
|
|
//学校下的所属班级
|
|
var classArr = await db.Queryable<Classes>().Where(c => c.SchoolId == school.Id &&
|
|
c.GradeLevel == gradeInfo.GradeLevel &&
|
|
c.GraduationYear == gradeInfo.GradeYear && !c.DeleteState).ToArrayAsync();
|
|
|
|
var userDic = await db.Queryable<School>()
|
|
.LeftJoin<Classes>((s, c) => c.SchoolId == s.Id)
|
|
.LeftJoin<Position>((s, c, p) => p.ClassId == c.Id)
|
|
.LeftJoin<PositionRelation>((s, c, p, pr) => pr.PositionId == p.Id)
|
|
.LeftJoin<User>((s, c, p, pr, u) => u.Id == pr.UserId)
|
|
.Where((s, c, p, pr, u) =>
|
|
s.Id == school.Id &&
|
|
c.GradeLevel == gradeInfo.GradeLevel &&
|
|
c.GraduationYear == gradeInfo.GradeYear &&
|
|
u.UserType == UserTypeEnum.学生.GetHashCode() &&
|
|
s.Enable && p.Enable && pr.Enable &&
|
|
!p.DeleteState && !c.DeleteState && !u.DeleteState && !s.DeleteState)
|
|
.Select((s, c, p, pr, u) => new
|
|
{
|
|
Name = c.Name + u.RealName,
|
|
u.Id,
|
|
}).ToDictionaryAsync(s => s.Name, s => s.Id);
|
|
//处理学生成绩数据
|
|
var userList = schoolArr.Select(s =>
|
|
{
|
|
var classInfo = classArr
|
|
.FirstOrDefault(x => x.Name == s.Class );
|
|
if (classInfo == null)
|
|
{
|
|
s.Error = "未能匹配班级";
|
|
errorExcelInfo.Add(s);
|
|
return null;
|
|
}
|
|
var grade = GradeHelper.GetStudentGradeBaseByGrade(s.Grade);
|
|
var sub = ImportExamInfoSubjectDic(s);
|
|
var name = s.Class + s.Student;
|
|
if (!userDic.ContainsKey(name))
|
|
{
|
|
s.Error = "未能匹配到年级班级下对应的学生";
|
|
errorExcelInfo.Add(s);
|
|
return null;
|
|
}
|
|
var uid = userDic[name];
|
|
if(oldUidHash!=null &&oldUidHash.Contains((long)uid))
|
|
{
|
|
s.Error = "此学生已经在考试中已经录入过成绩";
|
|
errorExcelInfo.Add(s);
|
|
return null;
|
|
}
|
|
var assignScore = decimal.TryParse(s.赋分后总分.Trim(), out decimal ass)
|
|
? ass
|
|
: 0m;
|
|
return new ExamUserInfo()
|
|
{
|
|
ExamId = exam.Id,
|
|
ExamName = exam.Name,
|
|
GradeYear = grade.GradeYear,
|
|
GradeLevel = grade.GradeLevel,
|
|
TestPaperType = exam.TestPaperType,
|
|
UserId = (long)userDic[name],
|
|
UserName = s.Student,
|
|
ClassId = classInfo.Id,
|
|
SchoolId = classInfo.SchoolId,
|
|
Type = exam.Type,
|
|
SubjectDic = sub,
|
|
AssignScore = assignScore,
|
|
AssignRanking = s.资源校排名,
|
|
};
|
|
}).ToList();
|
|
insertUserInfo.AddRange(userList);
|
|
insertUserInfo = insertUserInfo.Where(s => s != null).ToList();
|
|
};
|
|
|
|
if (errorExcelInfo.Count != 0)
|
|
return File(errorExcelInfo.ExportExcel(), "application/ms-excel", $"错误考试信息{DateTime.Now.ToString("MMddHHmm")}.xlsx");
|
|
|
|
//写入数据库
|
|
await examUserInfoService.AsInsertable(insertUserInfo).ExecuteCommandAsync();
|
|
|
|
await CalculatingTestResults(exam,examUserInfoService,schoolService, tagService);
|
|
|
|
return Ok();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 下载导入成绩模板
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpGet, ResultIgnore, AllowAnonymous]
|
|
public IActionResult DwImportTemplate()
|
|
{
|
|
List<ImportExamInfo> resultList = new List<ImportExamInfo>() { new ImportExamInfo()
|
|
{
|
|
School="例子学校[导入时候请删除本行]",
|
|
Class="测试班级",
|
|
Grade="高2028",
|
|
Student="学生姓名",
|
|
语文 = "得分 例: 80.5",
|
|
化学 = "得分 例: 80.5",
|
|
数学 = "得分 例: 80.5",
|
|
历史 = "得分 例: 80.5",
|
|
地理 = "得分 例: 80.5",
|
|
政治 = "得分 例: 80.5",
|
|
物理 = "得分 例: 80.5",
|
|
生物 = "得分 例: 80.5",
|
|
英语 = "得分 例: 80.5",
|
|
赋分后总分="赋分后总分",
|
|
} };
|
|
return File(resultList.ExportExcel(), "application/ms-excel",
|
|
$"导入成绩模板{DateTime.Now.ToString("MMddHHmm")}.xlsx");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 删除班级考试信息
|
|
/// </summary>
|
|
/// <param name="dto"></param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
public async Task<IActionResult> DeleteExamInfo(RestartEntryDto dto)
|
|
{
|
|
if (dto.ClassId == 0 || dto.ExamId == 0)
|
|
Oh.ModelError("传入了异常参数");
|
|
var exam = examService.GetById(dto.ExamId);
|
|
if (exam is null)
|
|
Oh.ModelError("传入了无效的考试");
|
|
var dCount = await baseService.AsDeleteable().Where(s => s.ClassId == dto.ClassId)
|
|
.ExecuteCommandAsync();
|
|
var dCount1 = await examUserInfoService.AsDeleteable().Where(s => s.ClassId == dto.ClassId)
|
|
.ExecuteCommandAsync();
|
|
return Ok();
|
|
|
|
}
|
|
|
|
public override Task<dynamic> PageList([FromBody] QueryRequestBase model)
|
|
{
|
|
var c = model.Conditions.FirstOrDefault(s => s.FieldName == "Grade");
|
|
if (c != null)
|
|
{
|
|
var gInfo = GradeHelper.GetStudentGradeBaseByGrade(c.FieldValue);
|
|
if (gInfo != null)
|
|
{
|
|
model.Conditions = model.Conditions.Where(s => s != c).ToList();
|
|
model.Conditions.Add(new SqlSugar.ConditionalModel()
|
|
{
|
|
FieldName = "GradeLevel",
|
|
FieldValue = gInfo.GradeLevel,
|
|
});
|
|
model.Conditions.Add(new SqlSugar.ConditionalModel()
|
|
{
|
|
FieldName = "GradeYear",
|
|
FieldValue = gInfo.GradeYear.ToString(),
|
|
CSharpTypeName="int"
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Oh.ModelError($"传入了无法识别的 年级 => {c.FieldValue}");
|
|
}
|
|
}
|
|
return base.PageList(model);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 重新计算考试成绩排名
|
|
/// </summary>
|
|
/// <param name="examId">考试id</param>
|
|
/// <returns></returns>
|
|
[HttpGet]
|
|
public async Task<IActionResult> RecalculateExamRankings(long examId)
|
|
{
|
|
if (examId == 0)
|
|
Oh.ModelError("传入了异常参数");
|
|
var exam = examService.GetById(examId);
|
|
if (exam is null)
|
|
Oh.ModelError("无效的考试");
|
|
await CalculatingTestResults(exam, examUserInfoService, schoolService, tagService);
|
|
return Ok();
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 获取班级考试分段排名
|
|
/// </summary>
|
|
/// <param name="examId">考试id</param>
|
|
/// <returns></returns>
|
|
[HttpGet]
|
|
public async Task<IEnumerable<ExamClassTagReq>> ClassRanking(long examId,long classId)
|
|
{
|
|
if (examId == 0)
|
|
Oh.ModelError("传入异常参数");
|
|
if (! await examService.IsAnyAsync(s => s.Id == examId))
|
|
Oh.ModelError("无效的考试");
|
|
var arr = await classTagService.AsQueryable().Where(s => s.ExamId == examId && s.ClassId == classId)
|
|
.Select<ExamClassTagReq>()
|
|
.ToArrayAsync();
|
|
return arr.OrderBy(s => s.SubjectId);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[NonAction]
|
|
private Dictionary<SubjectEnum, decimal>? ImportExamInfoSubjectDic(ImportExamInfo info)
|
|
{
|
|
var c = new Dictionary<SubjectEnum, string>()
|
|
{
|
|
{ SubjectEnum.语文, info.语文},
|
|
{ SubjectEnum.数学, info.数学},
|
|
{ SubjectEnum.英语, info.英语},
|
|
{ SubjectEnum.物理, info.物理},
|
|
{ SubjectEnum.化学, info.化学},
|
|
{ SubjectEnum.生物, info.生物},
|
|
{ SubjectEnum.政治, info.政治},
|
|
{ SubjectEnum.历史, info.历史},
|
|
{ SubjectEnum.地理, info.地理},
|
|
};
|
|
var res = new Dictionary<SubjectEnum, decimal>();
|
|
foreach (var item in c)
|
|
{
|
|
if(string.IsNullOrWhiteSpace(item.Value))
|
|
res.Add(item.Key, 0m);
|
|
else if (decimal.TryParse(item.Value.Trim(), out decimal r))
|
|
res.Add(item.Key, r);
|
|
else
|
|
res.Add(item.Key, 0m);
|
|
}
|
|
return res;
|
|
}
|
|
[NonAction]
|
|
private static decimal? SubjectScore(ExamUserInfo info, SubjectEnum? s)
|
|
{
|
|
switch (s)
|
|
{
|
|
case null:
|
|
return info.AssignScore;
|
|
default:
|
|
if (info?.SubjectDic is null)
|
|
return -999;
|
|
return info.SubjectDic.ContainsKey(s.Value) ? info.SubjectDic?[s.Value] : -999;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算考试成绩
|
|
/// </summary>
|
|
/// <param name="exam"></param>
|
|
/// <param name="examUserInfoService"></param>
|
|
/// <param name="schoolService"></param>
|
|
/// <returns></returns>
|
|
[NonAction]
|
|
public static async Task CalculatingTestResults(Exam exam, Repository<ExamUserInfo> eUService,
|
|
Repository<School> sService, Repository<ExamTags> tagService)
|
|
{
|
|
var userInfoArr = await eUService.AsQueryable()
|
|
.Where(s => s.ExamId == exam.Id)
|
|
.ToArrayAsync();
|
|
|
|
var insertTotalClassInfo = new List<ExamClassInfo>();
|
|
var insertTotalClassTag = new List<ExamClassTag>();
|
|
var eTagArr = await tagService.AsQueryable().Where(s => s.ExamId == exam.Id)
|
|
.ToArrayAsync();
|
|
var db = sService.Context;
|
|
foreach (var schoolArr in userInfoArr.GroupBy(s => s.SchoolId))
|
|
{
|
|
|
|
var school = await sService.GetFirstAsync(s => s.Id == schoolArr.Key);
|
|
var classArr = await db.Queryable<Classes>()
|
|
.Where(c => c.SchoolId == school.Id &&
|
|
c.GradeLevel == exam.GradeLevel &&
|
|
c.GraduationYear == exam.GradeYear && !c.DeleteState)
|
|
.ToArrayAsync();
|
|
|
|
foreach (var classUserArr in schoolArr.GroupBy(s => s.ClassId))
|
|
{
|
|
var classInfo = classArr.FirstOrDefault(s => s.Id == classUserArr.Key);
|
|
if (classInfo is null)
|
|
continue;
|
|
var eCInfo = new ExamClassInfo()
|
|
{
|
|
ExamId = exam.Id,
|
|
ExamName = exam.Name,
|
|
SchoolId = classInfo.SchoolId,
|
|
SchoolName = school.Name,
|
|
ClassId = classInfo.Id,
|
|
ClassName = classInfo.Name,
|
|
GradeLevel = classInfo.GradeLevel,
|
|
GradeYear = classInfo.GraduationYear,
|
|
PeopleCount = classUserArr.Count(),
|
|
MinScore = int.MaxValue,
|
|
MaxScore = -99,
|
|
BaseSchoolScore = exam.BaseSchoolScore,
|
|
TestPaperType = exam.TestPaperType,
|
|
Type = exam.Type,
|
|
Average1 = exam.BaseSchoolScore,
|
|
};
|
|
insertTotalClassInfo.Add(eCInfo);
|
|
var avgTotal = 0m;
|
|
|
|
//todo 班级分段分析
|
|
var classTagDic = new Dictionary<long, ExamClassTag>();
|
|
foreach (var eUserInfo in classUserArr)
|
|
{
|
|
//上线人数
|
|
foreach (var item in eTagArr)
|
|
{
|
|
var subV = SubjectScore(eUserInfo, item.SubjectId);
|
|
if (!classTagDic.TryGetValue(item.Id, out ExamClassTag? tag))
|
|
{
|
|
tag = new ExamClassTag()
|
|
{
|
|
MaxScore = item.MaxScore,
|
|
MinScore = item.MinScore,
|
|
ClassId = classInfo.Id,
|
|
PeopleCount = eCInfo.PeopleCount,
|
|
ExamId = exam.Id,
|
|
ExamTagId = item.Id,
|
|
Name = item.TagName,
|
|
SubjectId = item.SubjectId
|
|
};
|
|
insertTotalClassTag.Add(tag);
|
|
classTagDic.Add(item.Id, tag);
|
|
}
|
|
//总分分段
|
|
if (subV >= item.MinScore && subV <= item.MaxScore)
|
|
{
|
|
tag.OnLineCount++;
|
|
}
|
|
|
|
}
|
|
var v = eUserInfo.AssignScore;
|
|
//最大小分
|
|
if (v < eCInfo.MinScore)
|
|
eCInfo.MinScore = v;
|
|
if (v > eCInfo.MaxScore)
|
|
eCInfo.MaxScore = v;
|
|
avgTotal += v;//追加得分
|
|
}
|
|
//总分平均分
|
|
eCInfo.Average = avgTotal / eCInfo.PeopleCount;
|
|
}
|
|
}
|
|
|
|
{
|
|
|
|
var i = 0;
|
|
//计算班级上线率排名
|
|
foreach (var examTagArr in insertTotalClassTag.GroupBy(s => s.ExamTagId))
|
|
{
|
|
i = 0;
|
|
foreach (var item in examTagArr.OrderByDescending(s => s.OnLineRate)
|
|
.GroupBy(s => s.OnLineRate))
|
|
{
|
|
foreach (var u in item)
|
|
u.OnLineRanking = ++i;
|
|
}
|
|
}
|
|
|
|
//计算年级平均分排名
|
|
i = 0;
|
|
foreach (var item in insertTotalClassInfo.OrderByDescending(s => s.Average)
|
|
.GroupBy(s => s.Average))
|
|
{
|
|
foreach (var u in item)
|
|
u.AverageRank = ++i;
|
|
}
|
|
}
|
|
//写入数据库
|
|
var baseDB = eUService.Context;
|
|
baseDB.Ado.BeginTran();
|
|
try
|
|
{
|
|
await baseDB.Deleteable<ExamClassInfo>().Where(s => s.ExamId == exam.Id).ExecuteCommandAsync();
|
|
await baseDB.Deleteable<ExamClassTag>().Where(s => s.ExamId == exam.Id).ExecuteCommandAsync();
|
|
await baseDB.Insertable(insertTotalClassInfo).ExecuteCommandAsync();
|
|
await baseDB.Insertable(insertTotalClassTag).ExecuteCommandAsync();
|
|
baseDB.Ado.CommitTran();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
baseDB.Ado.RollbackTran();
|
|
Oh.ModelError($"导入失败:写入数据时候出现了异常 [{ex.Message}] !");
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|