1799 lines
80 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AutoMapper;
using Castle.DynamicProxy.Generators;
using Google.Protobuf.Collections;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using VOL.Business.IRepositories;
using VOL.Business.IServices.Norm;
using VOL.Business.IServices.School;
using VOL.Core.CacheManager;
using VOL.Core.Extensions;
using VOL.Core.Extensions.AutofacManager;
using VOL.Core.ManageUser;
using VOL.Core.Utilities;
using VOL.Entity.DomainModels;
using VOL.Entity.DomainModels.Business.People;
using VOL.Entity.Enum;
using VOL.Model;
using VOL.Model.Ai;
using VOL.Model.IOT.Request;
using VOL.Model.School.Request;
using VOL.Model.School.Response;
using VOL.System.IRepositories;
using VOL.System.Repositories;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
namespace VOL.Business.Services.School
{
public class S_StudentService : IS_StudentService, IDependency
{
#region 初始化
private readonly IMapper _mapper;
private readonly ICacheService _cacheService;
private readonly ICacheQueryService _cacheQueryService;
private readonly IotDataSyncService _iotDataSyncService;
private readonly IS_StudentRepository _studentRepository;
private readonly IS_GradeRepository _gradeRepository;
private readonly IS_ClassRepository _classRepository;
private readonly IN_SportsTestResultRepository _sportsTestResultRepository;
private readonly IN_SportsTestCategoryRepository _sportsTestCategoryRepository;
[ActivatorUtilitiesConstructor]
public S_StudentService(IMapper mapper,
ICacheService cacheService,
ICacheQueryService cacheQueryService,
IS_StudentRepository studentRepository,
IS_GradeRepository gradeRepository,
IS_ClassRepository classRepository,
IN_SportsTestResultRepository sportsTestResultRepository,
IN_SportsTestCategoryRepository sportsTestCategoryRepository,
IotDataSyncService iotDataSyncService)
{
_mapper = mapper;
_cacheService = cacheService;
_cacheQueryService = cacheQueryService;
_studentRepository = studentRepository;
_gradeRepository = gradeRepository;
_classRepository = classRepository;
_sportsTestResultRepository = sportsTestResultRepository;
_sportsTestCategoryRepository = sportsTestCategoryRepository;
_iotDataSyncService = iotDataSyncService;
}
#endregion
public async Task<PageDataDto<StudentPageListModel>> GetStudentPageList(StudentPageListParam paramDto)
{
var res = new PageDataDto<StudentPageListModel>();
var classIds = new List<int>();
var isTeacher = (UserContext.Current.RoleId == 3);
if (isTeacher)
{
var teacher = await _studentRepository.DbContext.Set<S_Teacher>().FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo);
var teacherClassList = await (
from at in _studentRepository.DbContext.Set<S_ClassAssocTeacher>()
join c in _studentRepository.DbContext.Set<S_Class>() on at.ClassId equals c.Id
where at.TeacherId == teacher.Id
select c).ToListAsync();
classIds = teacherClassList.Select(c => c.Id).Distinct().ToList();
}
var query = from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id into sc
from c in sc.DefaultIfEmpty()
where s.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && (!isTeacher || classIds.Contains(c.Id))
select new StudentPageListModel()
{
Id = s.Id,
Age = s.Age,
ClassId = s.ClassId,
ClassName = c.ClassName,
GradeName = c.GradeName,
HeartRateFrontNo = s.HeartRateFrontNo,
HeartRateId = s.HeartRateId,
Photo = s.Photo,
RopeSkipQRCode = s.RopeSkipQRCode,
SchoolRollNo = s.SchoolRollNo,
Sex = s.Sex,
StudentName = s.StudentName,
StudentNo = s.StudentNo,
OrderNo = s.OrderNo,
StudentStatus = s.StudentStatus
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentName))
{
query = query.Where(x => x.StudentName.Contains(paramDto.StudentName));
}
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
{
query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo));
}
if (paramDto.Sex > 0)
{
query = query.Where(x => x.Sex == paramDto.Sex);
}
if (paramDto.ClassId > 0)
{
query = query.Where(x => x.ClassId == paramDto.ClassId);
}
if (paramDto.StudentStatus > 0)
{
query = query.Where(x => x.StudentStatus == paramDto.StudentStatus);
}
res.Total = await query.CountAsync();
var list = await query
.OrderBy(x => x.Id)
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToListAsync();
res.Datas = list;
return res;
}
public async Task<List<StudentPageListModel>> GetStudentList(StudentExportParam paramDto)
{
var classIds = new List<int>();
var isTeacher = (UserContext.Current.RoleId == 3);
if (isTeacher)
{
var teacher = await _studentRepository.DbContext.Set<S_Teacher>().FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo);
var teacherClassList = await (
from at in _studentRepository.DbContext.Set<S_ClassAssocTeacher>()
join c in _studentRepository.DbContext.Set<S_Class>() on at.ClassId equals c.Id
where at.TeacherId == teacher.Id
select c).ToListAsync();
classIds = teacherClassList.Select(c => c.Id).Distinct().ToList();
}
var query = from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id
where c.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && s.StudentStatus == StudentStatus.Normal && (!isTeacher || classIds.Contains(c.Id))
select new StudentPageListModel()
{
Id = s.Id,
Age = s.Age,
ClassId = s.ClassId,
ClassName = c.ClassName,
GradeName = c.GradeName,
HeartRateFrontNo = s.HeartRateFrontNo,
HeartRateId = s.HeartRateId,
Photo = s.Photo,
RopeSkipQRCode = s.RopeSkipQRCode,
SchoolRollNo = s.SchoolRollNo,
Sex = s.Sex,
StudentName = s.StudentName,
StudentNo = s.StudentNo,
StudentStatus = s.StudentStatus
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentName))
{
query = query.Where(x => x.StudentName.Contains(paramDto.StudentName));
}
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
{
query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo));
}
if (paramDto.Sex > 0)
{
query = query.Where(x => x.Sex == paramDto.Sex);
}
if (paramDto.ClassId > 0)
{
query = query.Where(x => x.ClassId == paramDto.ClassId);
}
if (paramDto.StudentStatus > 0)
{
query = query.Where(x => x.StudentStatus == paramDto.StudentStatus);
}
var list = await query.OrderBy(x => x.Id).ToListAsync();
return list;
}
/// <summary>
/// 获取场馆学员
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<List<StudentPageListModel>> GetStudentListBySportsHall(StudentExportParam paramDto)
{
var query = from s in _studentRepository.DbContext.Set<S_Student>()
where s.SchoolCode.Equals(UserContext.Current.TenantId) && s.StudentStatus == StudentStatus.Normal && s.StudentStatus == StudentStatus.Normal
select new StudentPageListModel()
{
Id = s.Id,
Age = s.Age,
ClassId = s.ClassId,
ClassName = s.ClassName,
GradeName = "运动馆",
HeartRateFrontNo = s.HeartRateFrontNo,
HeartRateId = s.HeartRateId,
Photo = s.Photo,
RopeSkipQRCode = s.RopeSkipQRCode,
SchoolRollNo = s.SchoolRollNo,
Sex = s.Sex,
StudentName = s.StudentName,
StudentNo = s.StudentNo,
StudentStatus = s.StudentStatus,
OrderNo = s.OrderNo,
};
if (!string.IsNullOrWhiteSpace(paramDto.StudentName))
{
query = query.Where(x => x.StudentName.Contains(paramDto.StudentName));
}
if (!string.IsNullOrWhiteSpace(paramDto.StudentNo))
{
query = query.Where(x => x.StudentNo.Contains(paramDto.StudentNo));
}
if (paramDto.Sex > 0)
{
query = query.Where(x => x.Sex == paramDto.Sex);
}
if (paramDto.ClassId > 0)
{
query = query.Where(x => x.ClassId == paramDto.ClassId);
}
if (paramDto.StudentStatus > 0)
{
query = query.Where(x => x.StudentStatus == paramDto.StudentStatus);
}
var list = await query.OrderBy(x => x.OrderNo).ToListAsync();
return list;
}
public async Task AddStudent(AddStudentParam paramDto)
{
var stuExists = await _studentRepository.ExistsAsync(x => x.StudentNo == paramDto.StudentNo);
if (stuExists)
throw new Exception($"学号{paramDto.StudentNo}的学生已存在!");
var studentEntity = _mapper.Map<S_Student>(paramDto);
studentEntity.StudentStatus = StudentStatus.Normal;
studentEntity.SchoolCode = UserContext.Current.TenantId;
studentEntity.Creator = UserContext.Current.UserId;
studentEntity.CreateDate = DateTime.Now;
await _studentRepository.AddAsync(studentEntity);
await _studentRepository.SaveChangesAsync();
//调用回调函数,同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Add.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(new List<S_Student>() { studentEntity })
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Add.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(new List<S_Student>() { studentEntity })
});
}
public async Task ModifyStudent(AddStudentParam paramDto)
{
if (paramDto == null || string.IsNullOrEmpty(paramDto.StudentNo))
{
throw new ArgumentNullException("学生学号必填");
}
var student = await _studentRepository.FindAsyncFirst(x => x.Id == paramDto.Id);
if (student == null)
throw new ArgumentNullException("未找到要更新的数据");
var stuExists = await _studentRepository.ExistsAsync(x => x.StudentNo == paramDto.StudentNo && x.Id != student.Id);
if (stuExists)
throw new Exception($"学号{paramDto.StudentNo}的学生已存在!");
student.StudentName = paramDto.StudentName; // 学生名称
student.ClassId = paramDto.ClassId; // 班级
student.Sex = paramDto.Sex; // 性别
student.Age = paramDto.Age; // 年龄
student.Birthday = paramDto.Birthday; // 出生日期
student.IDCard = paramDto.IDCard; // 身份证
student.Photo = paramDto.Photo; // 学生照片
student.HeartRateFrontNo = paramDto.HeartRateFrontNo; // 心率设备正面编号
student.HeartRateId = paramDto.HeartRateId; // 心率设备ID
student.RunChipNo = paramDto.RunChipNo; // 中长跑芯片编号
student.RopeSkipNo = paramDto.RopeSkipQRCode; // 跳绳编号
student.OrderNo = 1;
student.Creator = UserContext.Current.UserId;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = DateTime.Now;
student.StudentStatus = StudentStatus.Normal;
_studentRepository.Update(student);
await _studentRepository.SaveChangesAsync();
//调用回调函数,同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Update.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(student)
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Update.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(student)
});
}
public async Task UpdateStudentStatus(UpdateStudentStatusParam paramDto)
{
var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == paramDto.StudentNo);
if (student == null)
throw new ArgumentNullException("未找到要更新的数据");
student.StudentStatus = paramDto.Status;
_studentRepository.Update(student);
await _studentRepository.SaveChangesAsync();
//调用回调函数,同步数据到IOT
_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Update.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(student)
}));
}
public async Task UpdateStudentPwd(UpdateStudentPwdParam paramDto)
{
var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == paramDto.StudentNo);
if (student == null)
throw new ArgumentNullException("未找到要更新的数据");
student.AppletPwd = paramDto.StudentNo;
_studentRepository.Update(student);
await _studentRepository.SaveChangesAsync();
//调用回调函数,同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Update.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(student)
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Update.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(student)
});
}
public async Task<int> BatchDeleteStudents(List<int> ids)
{
if (UserContext.Current.RoleId != 1 && UserContext.Current.RoleId != 2)
throw new Exception("仅管理员可以批量删除学生");
var studentIds = ids?.Where(x => x > 0).Distinct().ToList() ?? new List<int>();
if (studentIds.Count == 0)
throw new ArgumentException("请至少选择一名学生");
var students = await _studentRepository.FindAsIQueryable(x =>
x.SchoolCode == UserContext.Current.TenantId && studentIds.Contains(x.Id))
.ToListAsync();
if (students.Count != studentIds.Count)
throw new Exception("部分学生不存在或不属于当前学校,请刷新列表后重新选择");
var activeBatchId = await _studentRepository.DbContext.Set<S_StudentPromotionBatch>()
.Where(x => x.SchoolCode == UserContext.Current.TenantId && x.Status == 1)
.OrderByDescending(x => x.Id)
.Select(x => (int?)x.Id)
.FirstOrDefaultAsync();
if (activeBatchId.HasValue)
{
var promotionStudentCount = await _studentRepository.DbContext.Set<S_StudentPromotionDetail>()
.CountAsync(x => x.BatchId == activeBatchId.Value && studentIds.Contains(x.StudentId));
if (promotionStudentCount > 0)
throw new Exception($"所选学生中有{promotionStudentCount}人属于当前可撤回的升学记录,请先撤回升学后再删除");
}
_studentRepository.DbContext.RemoveRange(students);
await _studentRepository.SaveChangesAsync();
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Delete.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(students)
});
return students.Count;
}
public async Task ChangeClasses(string studentNo, int classId)
{
var student = await _studentRepository.FindAsyncFirst(x => x.StudentNo == studentNo);
if (student == null)
throw new ArgumentNullException("未找到更新的数据");
var classModel = await _classRepository.FindAsyncFirst(x => x.Id == student.ClassId);
var sportsTestResults = await _sportsTestResultRepository.FindAsIQueryable(x => x.ClassId == student.ClassId && x.StudentNo == studentNo).ToListAsync();
using (var transaction = _studentRepository.DbContext.Database.BeginTransaction())
{
try
{
student.ClassId = classId;
student.ClassName = classModel.ClassName;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = DateTime.Now;
_studentRepository.Update(student);
foreach (var item in sportsTestResults)
{
item.ClassId = classId;
item.ClassName = classModel.ClassName;
item.Modifier = UserContext.Current.UserId;
item.ModifyDate = DateTime.Now;
}
_sportsTestResultRepository.UpdateRange(sportsTestResults);
await _studentRepository.SaveChangesAsync();
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 发生错误,回滚事务
transaction.Rollback();
throw new Exception("操作失败");
}
}
}
public async Task ImportStudents(IFormFile file)
{
if (file == null || file.Length <= 0)
throw new Exception("操作失败");
var tenantId = UserContext.Current.TenantId;
var dataObjects = new List<ImportStudentParam>();
using (var fileStream = file.OpenReadStream())
{
dataObjects = Tool.ConvertExcelToList<ImportStudentParam>(fileStream);
}
var greadNames = dataObjects.Select(x => x.GradeName).Distinct().ToList();
var greadList = await _gradeRepository.FindAsync(x => greadNames.Contains(x.GradeName));
var greadIds = greadList.Select(x => x.Id).ToList();
var classList = await _classRepository.FindAsync(x => greadIds.Contains(x.GradeId) && x.SchoolCode == tenantId);
var groupedStudentNos = dataObjects.GroupBy(x => x.StudentNo);
foreach (var group in groupedStudentNos)
{
if (!string.IsNullOrWhiteSpace(group.Key) && group.Count() > 1)
{
throw new Exception($"存在重复的学号:{group.Key},请检查后重新操作");
}
}
var stuNos = groupedStudentNos.Select(g => g.Key).ToList();
var stuList = await _studentRepository.FindAsync(x => x.SchoolCode == tenantId && stuNos.Contains(x.StudentNo));
var entitys = new List<S_Student>();
foreach (var data in dataObjects)
{
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("学号不能为空");
}
var stuExists = stuList.Exists(x => x.StudentNo == data.StudentNo);
if (stuExists)
throw new Exception($"学号{data.StudentNo}的学生已存在!");
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("姓名不能为空");
}
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("性别不能为空");
}
if (string.IsNullOrWhiteSpace(data.StudentNo))
{
throw new Exception("年龄不能为空");
}
var greadModel = greadList.Where(x => x.GradeName.Equals(data.GradeName)).FirstOrDefault();
if (greadModel == null)
throw new Exception("未找到年级信息");
var classModel = classList.Where(x => x.GradeId == greadModel.Id && x.ClassName == data.ClassName).FirstOrDefault();
if (classModel == null)
throw new Exception("未找到班级信息");
var studentEntity = new S_Student()
{
SchoolCode = tenantId,
StudentName = data.StudentName,
StudentNo = data.StudentNo,
ClassId = classModel.Id,
Age = data.Age,
IDCard = data.IDCard,
Birthday = data.Birthday,
HeartRateFrontNo = data.HeartRateFrontNo,
HeartRateId = data.HeartRateId,
RopeSkipQRCode = data.RopeSkipQRCode,
Sex = data.Sex == "男" ? SexType.Male : SexType.Female,
RunChipNo = data.RunChipNo,
StudentStatus = StudentStatus.Normal,
Creator = UserContext.Current.UserId,
CreateDate = DateTime.Now
};
entitys.Add(studentEntity);
}
await _studentRepository.AddRangeAsync(entitys);
await _studentRepository.SaveChangesAsync();
//调用回调函数,同步数据到IOT
//_ = Task.Run(() => _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
//{
// EventType = EventType.Add.GetDisplayName(),
// DataType = IOTDataSyncType.Student.GetDisplayName(),
// Json = JsonConvert.SerializeObject(entitys)
//}));
await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
{
EventType = EventType.Add.GetDisplayName(),
DataType = IOTDataSyncType.Student.GetDisplayName(),
Json = JsonConvert.SerializeObject(entitys)
});
}
/// <summary>
/// 上传学生头像
/// </summary>
public async Task<string> UploadPhoto(IFormFile file, int gradeId, int classId, string studentNo, string studentName)
{
//var student = _studentRepository.FindFirst(c => c.StudentNo == studentNo);
//if (student == null)
//{
// throw new Exception("学生未找到");
//}
// 设置照片最大允许大小 (5MB)
const long MaxFileSize = 2 * 1024 * 1024;
// 检查文件大小是否超过限制
if (file.Length > MaxFileSize)
{
throw new Exception("上传的照片文件大小不能超过2MB");
}
var url = ALiYunOss.Upload(file, $"Upload/{UserContext.Current.TenantId}/Photo/Student/{gradeId}/{classId}/", studentNo);
var faceEntityWithRequest = new FaceEntityWithDto()
{
EntityId = studentNo,
SchoolCode = UserContext.Current.TenantId,
IsStudent = true
};
await ALiYunFace.DeleteFaceEntityWith(faceEntityWithRequest);
var faceEntityWithReuslt = await ALiYunFace.FaceEntityWith(faceEntityWithRequest);
if (!faceEntityWithReuslt)
{
throw new Exception("上传失败");
}
var faceWithReuslt = await ALiYunFace.FaceWith(new FaceWithDto()
{
SchoolCode = UserContext.Current.TenantId,
Name = studentName,
IsStudent = true,
EntityId = studentNo,
ImageUrl = url
});
if (!faceWithReuslt)
{
throw new Exception("上传失败");
}
return url;
}
public async Task BatchUploadPhoto(IFormFile zipFile)
{
// 解压缩 zip 文件
var uploadPath = Path.Combine("Upload", "Student");
Directory.CreateDirectory(uploadPath);
var zipFilePath = Path.Combine(uploadPath, zipFile.FileName);
using (var fileStream = new FileStream(zipFilePath, FileMode.Create))
{
await zipFile.CopyToAsync(fileStream);
}
// 解压后的目录路径
var extractPath = Path.Combine(uploadPath, Path.GetFileNameWithoutExtension(zipFile.FileName));
// 检查并删除已存在的目录
if (Directory.Exists(extractPath))
{
Directory.Delete(extractPath, true);
}
ZipFile.ExtractToDirectory(zipFilePath, extractPath);
// 递归获取所有文件
var files = Directory.GetFiles(extractPath, "*.*", SearchOption.AllDirectories)
.Select(filePath => new FileInfo(filePath))
.ToList();
// 获取学号列表
var studentNos = files.Select(x => Path.GetFileNameWithoutExtension(x.Name)).ToList();
var students = await _studentRepository.FindAsync(x => studentNos.Contains(x.StudentNo) && x.SchoolCode.Equals(UserContext.Current.TenantId));
var classList = await _classRepository.FindAsync(x => x.SchoolCode.Equals(UserContext.Current.TenantId));
var studentEntitys = new List<S_Student>();
foreach (var fileInfo in files)
{
try
{
using (var stream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read))
{
var formFile = new FormFile(stream, 0, stream.Length, fileInfo.FullName, fileInfo.Name);
var stuNo = Path.GetFileNameWithoutExtension(fileInfo.Name);
var student = students.FirstOrDefault(x => x.StudentNo.Equals(stuNo, StringComparison.OrdinalIgnoreCase));
if (student != null)
{
var cla = classList.FirstOrDefault(x => x.Id == student.ClassId);
var url = await UploadPhoto(formFile, cla == null ? 0 : cla.GradeId, student.ClassId, stuNo, student.StudentName);
student.Photo = url;
studentEntitys.Add(student);
}
else
{
//throw new Exception($"未找到学号:{stuNo}");
continue;
}
}
}
catch (Exception)
{
continue;
}
}
_studentRepository.UpdateRange(studentEntitys);
await _studentRepository.SaveChangesAsync();
// 删除临时文件和目录
Directory.Delete(extractPath, true);
File.Delete(zipFilePath);
}
/// <summary>
/// 学生数据统计
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<StudentDataStatsModel> StudentWholeDataStats(StudentDataStatsParam paramDto)
{
var res = new StudentDataStatsModel();
var tenantId = UserContext.Current.TenantId;
if (string.IsNullOrEmpty(paramDto.StudentNo))
{
paramDto.StudentNo = _studentRepository.DbContext.Set<S_Student>().First().StudentNo;
}
var student = await (from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id
where s.StudentNo == paramDto.StudentNo && s.SchoolCode.Equals(UserContext.Current.TenantId)
select new StudentPageListModel()
{
StudentName = s.StudentName,
GradeId = c.GradeId,
ClassId = s.ClassId,
ClassName = c.ClassName,
GradeName = c.GradeName,
Photo = s.Photo,
Age = s.Age,
Sex = s.Sex
}).FirstOrDefaultAsync();
if (student == null)
throw new ArgumentNullException("未找到学生数据");
res.StudentName = student.StudentName;
res.ClassName = student.ClassName;
res.GradeName = student.GradeName;
res.Photo = student.Photo;
res.Sex = student.Sex.Description();
res.Age = student.Age;
var heartRateData = await (from t in _studentRepository.DbContext.Set<I_TrainingData>()
join s in _studentRepository.DbContext.Set<I_TrainRanking>() on t.Id equals s.TrainingDataId
where
t.SchoolCode.Equals(UserContext.Current.TenantId)
&& t.ItemType == (int)TrainingItemType.HeartRate
&& s.StudentNo == paramDto.StudentNo
select new
{
t.Id,
s.HighHeartRate
})
.OrderByDescending(x => x.Id)
.Take(5)
.ToListAsync();
var keys = new[] { "一", "二", "三", "四", "五" };
if (heartRateData.Count == 0)
{
res.HeartRateData = keys.ToDictionary(key => key, key => 0);
}
else
{
res.HeartRateData = heartRateData
.Select((x, index) => new { Key = keys[index], Value = x.HighHeartRate ?? 0 })
.ToDictionary(x => x.Key, x => x.Value);
}
// 从缓存中获取数据
var sportsTestResults = await _cacheQueryService.GetSportsTestDataCacheAsync(x => x.SchoolCode.Equals(tenantId) && x.StudentNo == paramDto.StudentNo, paramDto.Mode);
var categoryList = await (from s in _sportsTestResultRepository.DbContext.Set<S_GradeAssocCategory>()
join n in _sportsTestResultRepository.DbContext.Set<N_SportsTestCategory>()
on s.CategoryValue equals n.CategoryValue
where s.GradeId == student.GradeId
select new
{
n.CategoryValue,
n.CategoryName
}).ToListAsync();
foreach (var category in categoryList)
{
var categoryResults = sportsTestResults.Where(x => x.CategoryValue == category.CategoryValue).ToList();
var maxValue = categoryResults.Any() ? categoryResults.Max(x => x.Score + x.AdditionalScore) : 0f;
var lastValue = categoryResults.OrderByDescending(x => x.ScoreTime).Select(x => x.Score + x.AdditionalScore).FirstOrDefault();
var resultContrast = new ResultContrast
{
MaxValue = maxValue,
LastValue = lastValue
};
res.TestResultCompare.AxisX.Add(category.CategoryName);
res.TestResultCompare.AxisY.Add(resultContrast);
}
return res;
}
/// <summary>
/// 各项目平均成绩
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Dictionary<string, float>> TestResultAvg(StudentCategoryParam paramDto)
{
var sportsTestResults = await _cacheQueryService.GetSportsTestDataCacheAsync(x =>
x.StudentNo == paramDto.StudentNo &&
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
var now = DateTime.Now;
var currentQuarter = (now.Month - 1) / 3 + 1;
var currentYear = now.Year;
// 处理跨年情况
var quarters = new[]
{
new { Label = "本季度", Quarter = currentQuarter, Year = currentYear },
new { Label = "上季度", Quarter = currentQuarter - 1 == 0 ? 4 : currentQuarter - 1, Year = currentQuarter == 1 ? currentYear - 1 : currentYear },
new { Label = "前季度", Quarter = currentQuarter - 2 == 0 ? 4 : currentQuarter - 2 == -1 ? 3 : currentQuarter - 2, Year = currentQuarter <= 2 ? currentYear - 1 : currentYear }
};
return quarters.ToDictionary(
q => q.Label,
q =>
{
var filteredResults = sportsTestResults
.Where(x => x.ScoreTime >= Tool.GetQuarterStartDate(q.Quarter, q.Year) && x.ScoreTime <= Tool.GetQuarterEndDate(q.Quarter, q.Year))
.Select(x => x.Score + x.AdditionalScore)
.ToList();
return filteredResults.Any() ? (float)Math.Round(filteredResults.Average(), 2) : 0;
}
);
}
/// <summary>
/// 各项目成绩对比
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Dictionary<string, float>> TestResultContrast(StudentCategoryParam paramDto)
{
// 获取指定班级和类别的测试结果
var sportsTestResults = await _cacheQueryService.GetSportsTestDataCacheAsync(x =>
x.ClassId == paramDto.ClassId &&
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
// 提取班级所有成绩
var filteredResults = sportsTestResults
.Select(x => x.Score + x.AdditionalScore)
.ToList();
// 提取个人成绩
var personalScore = sportsTestResults
.Where(x => x.StudentNo == paramDto.StudentNo)
.Select(x => x.Score + x.AdditionalScore)
.FirstOrDefault();
var quarters = new[]
{
new { Label = "个人成绩", Value = personalScore },
new { Label = "班级平均分", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Average(),2) : 0f },
new { Label = "班级中位数", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Median(),2) : 0f },
new { Label = "班级最高分", Value = filteredResults.Any() ? (float)Math.Round(filteredResults.Max(),2) : 0f }
};
return quarters.ToDictionary(q => q.Label, q => q.Value);
}
/// <summary>
/// 成绩趋势
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<VariousSportsProportion> ResultTrends(StudentResultTrendsParam paramDto)
{
var sportsTestResults = await _cacheQueryService.GetSportsTestDataCacheAsync(x =>
x.StudentNo == paramDto.StudentNo &&
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
var result = new VariousSportsProportion();
DateTime currentDate = DateTime.Now;
switch (paramDto.CycleTime)
{
case CycleTimeEnum.InThePastWeek:
// 近一周:按天展示
var pastWeekResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddDays(-7))
.GroupBy(x => x.ScoreTime.Date);
foreach (var group in pastWeekResults)
{
result.AxisX.Add(group.Key.ToString("yyyy-MM-dd"));
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
case CycleTimeEnum.InThePastTwoWeeks:
// 近两周:按天展示
var pastTwoWeeksResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddDays(-14))
.GroupBy(x => x.ScoreTime.Date);
foreach (var group in pastTwoWeeksResults)
{
result.AxisX.Add(group.Key.ToString("yyyy-MM-dd"));
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
case CycleTimeEnum.InThePastMonth:
// 近一月:每5天一个阶段展示
var pastMonthResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddDays(-30))
.GroupBy(x => (currentDate - x.ScoreTime).Days / 5);
foreach (var group in pastMonthResults)
{
var minDate = group.Min(r => r.ScoreTime).ToString("yyyy-MM-dd");
var maxDate = group.Max(r => r.ScoreTime).ToString("yyyy-MM-dd");
result.AxisX.Add($"{minDate} - {maxDate}");
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
case CycleTimeEnum.InThePastYear:
// 近一年:按月展示
var pastYearResults = sportsTestResults
.Where(x => x.ScoreTime >= currentDate.AddYears(-1))
.GroupBy(x => new { x.ScoreTime.Year, x.ScoreTime.Month });
foreach (var group in pastYearResults)
{
result.AxisX.Add($"{group.Key.Year}-{group.Key.Month:D2}");
var score = (group.Sum(x => x.Score) + group.Sum(x => x.AdditionalScore)) / (group.Select(x => x.StudentNo).Distinct().Count());
result.AxisY.Add((float)Math.Round(score));
}
break;
default:
// 默认处理,如果没有匹配的情况
break;
}
return result;
}
/// <summary>
/// 训练记录
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<PageDataDto<StudentTrainingRecordsModel>> TrainingRecords(StudentTrainingRecordsParam paramDto)
{
var res = new PageDataDto<StudentTrainingRecordsModel>();
var tenantId = UserContext.Current.TenantId;
if (string.IsNullOrEmpty(paramDto.StudentNo))
{
paramDto.StudentNo = _studentRepository.DbContext.Set<S_Student>().First(s => s.SchoolCode == tenantId).StudentNo;
}
// 查询 I_TrainingData 表
var iotQuery = from student in _studentRepository.DbContext.Set<I_TrainRanking>()
join trainingData in _studentRepository.DbContext.Set<I_TrainingData>()
on student.TrainingDataId equals trainingData.Id
where trainingData.IsDisplay && trainingData.SchoolCode == tenantId
select new StudentTrainingRecordsModel
{
StudentNo = student.StudentNo,
// 训练模式
TrainType = trainingData.ModelType ?? 0,
//项目模式
ModelName = trainingData.ModelName,
CategoryValue = (TrainingItemType)trainingData.ItemType,
// 成绩
Result = (float?)trainingData.ClassNumAvg,
ResultLevel = trainingData.ClassScore,
// 训练时间
ScoreTime = trainingData.InsertTime,
};
// 查询 Ai_SportsTestData 表
var aiQuery = from trainingData in _studentRepository.DbContext.Set<Ai_SportsTestData>()
where trainingData.IsDisplay && trainingData.SchoolCode == tenantId
select new StudentTrainingRecordsModel
{
StudentNo = trainingData.StudentNo,
TrainType = (int)trainingData.ModeType,
ModelName = trainingData.ModelName,
CategoryValue = (TrainingItemType)trainingData.CategoryValue,
Result = (float?)trainingData.Value,
ResultLevel = (int?)trainingData.RankEnum,
ScoreTime = trainingData.ScoreTime
};
// 合并两个查询结果
var combinedQuery = iotQuery.Concat(aiQuery);
// 根据学号过滤
if (!string.IsNullOrEmpty(paramDto.StudentNo))
{
combinedQuery = combinedQuery.Where(x => x.StudentNo == paramDto.StudentNo);
}
// 根据开始时间过滤
if (paramDto.StartTime.HasValue)
{
combinedQuery = combinedQuery.Where(x => x.ScoreTime >= paramDto.StartTime);
}
// 根据结束时间过滤
if (paramDto.EndTime.HasValue)
{
combinedQuery = combinedQuery.Where(x => x.ScoreTime <= paramDto.EndTime);
}
// 根据项目类型过滤
if (paramDto.CategoryValue > 0)
{
combinedQuery = combinedQuery.Where(x => x.CategoryValue == (TrainingItemType)paramDto.CategoryValue);
}
// 根据成绩等级过滤
if (paramDto.Rank > 0)
{
combinedQuery = combinedQuery.Where(x => x.ResultLevel == (int)paramDto.Rank);
}
// 获取总数
res.Total = await combinedQuery.CountAsync();
// 分页和排序
var list = await combinedQuery
.OrderBy(c => c.ScoreTime) // 按训练时间排序
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToListAsync();
foreach (var item in list)
{
// 项目
item.CategoryName = item.CategoryValue.GetDisplayName();
var grade = ((AchievementRank)(item.ResultLevel.HasValue && item.ResultLevel.Value > 0 ? item.ResultLevel.Value : 4));
var gradeName = grade.GetDescription();
// 等级
item.Grade = gradeName;
var trainName = ((Ai_ModeEnum)(item.TrainType)).GetDescription();
// 训练模式
item.TrainName = trainName;
}
res.Datas = list;
return res;
}
/// <summary>
/// 体测记录
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<PageDataDto<StudentTestRecordsModel>> TestRecords(StudentTrainingRecordsParam paramDto)
{
var res = new PageDataDto<StudentTestRecordsModel>();
var tenantId = UserContext.Current.TenantId;
if (string.IsNullOrEmpty(paramDto.StudentNo))
{
paramDto.StudentNo = this._studentRepository.DbContext.Set<S_Student>().First().StudentNo;
}
// 初始化基础查询条件
Expression<Func<SportsTestValueModel, bool>> query = (x => x.SchoolCode == tenantId);
if (!string.IsNullOrEmpty(paramDto.StudentNo))
{
query = query.And(x => x.StudentNo == paramDto.StudentNo);
}
if (paramDto.StartTime.HasValue)
{
query = query.And(x => x.ScoreTime >= paramDto.StartTime);
}
if (paramDto.EndTime.HasValue)
{
query = query.And(x => x.ScoreTime <= paramDto.StartTime);
}
if (paramDto.CategoryValue > 0)
{
query = query.And(x => x.CategoryValue == paramDto.CategoryValue);
}
if (paramDto.Rank > 0)
{
query = query.And(x => x.RankEnum == paramDto.Rank);
}
var sportsTestResults = await _cacheQueryService.GetSportsTestDataCacheAsync(query.Compile(), paramDto.TrainType);
// 获取总数
res.Total = sportsTestResults.Count();
// 分页和排序
res.Datas = sportsTestResults
.OrderBy(c => c.ScoreTime) // 按训练时间排序
.Select(c => new StudentTestRecordsModel
{
TrainName = c.ModeType.GetDescription(),
CategoryValue = (SportsTestItemType)c.CategoryValue,
CategoryName = ((SportsTestItemType)c.CategoryValue).GetDisplayName(),
Result = c.Value,
Score = (c.Score + c.AdditionalScore),
Rank = c.Rank,
ScoreTime = c.ScoreTime
})
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToList();
return res;
}
// 构建课堂模式查询
private IQueryable<StudentTrainingRecordsModel> BuildClassRoomModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from td in _studentRepository.DbContext.Set<I_TrainingData>()
join tr in _studentRepository.DbContext.Set<I_TrainRanking>() on td.Id equals tr.TrainingDataId into ranking
from r in ranking.DefaultIfEmpty()
where td.SchoolCode == tenantId && r.StudentNo == studentNo
select new StudentTrainingRecordsModel
{
TrainName = "课堂模式",
ModelName = td.ModelName,
CategoryValue = (TrainingItemType)td.ItemType,
CategoryName = ((TrainingItemType)td.ItemType).GetDescription(),
Result = r.JumpValue,
ResultLevel = r.ResultLevel,
ScoreTime = td.InsertTime
};
return ApplyFilters(query, paramDto);
}
// 构建自由模式查询
private IQueryable<StudentTrainingRecordsModel> BuildFreeModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from a in _studentRepository.DbContext.Set<Ai_TrainingData>()
join n in _studentRepository.DbContext.Set<N_SportsTestValue>() on a.StartTime equals n.ScoreTime
where a.SchoolCode == tenantId && a.StudentNo == studentNo
select new StudentTrainingRecordsModel
{
TrainName = "自由模式",
ScoreTime = a.StartTime,
CategoryValue = a.CategoryValue,
CategoryName = ((TrainingItemType)Convert.ToInt32(a.CategoryValue)).GetDescription(),
ModelName = "/",
Grade = n.Rank
};
return ApplyFilters(query, paramDto);
}
// 通用过滤逻辑
private IQueryable<StudentTrainingRecordsModel> ApplyFilters(IQueryable<StudentTrainingRecordsModel> query, StudentTrainingRecordsParam paramDto)
{
return query
.WhereIF(paramDto.StartTime.HasValue, x => x.ScoreTime >= paramDto.StartTime)
.WhereIF(paramDto.EndTime.HasValue, x => x.ScoreTime <= paramDto.EndTime)
.WhereIF(paramDto.CategoryValue > 0, x => x.CategoryValue == (TrainingItemType)paramDto.CategoryValue)
.WhereIF(paramDto.Rank > 0, x => x.ResultLevel == (int)paramDto.Rank);
}
// 构建体测课堂模式查询
private IQueryable<StudentTestRecordsModel> BuildTestRoomModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from a in _studentRepository.DbContext.Set<I_SportsTestData>()
join r in _studentRepository.DbContext.Set<N_SportsTestValue>() on a.ScoreTime equals r.ScoreTime
where a.SchoolCode == tenantId && r.StudentNo == studentNo
select new StudentTestRecordsModel
{
TrainName = "课堂模式",
CategoryValue = (SportsTestItemType)r.CategoryValue,
CategoryName = ((SportsTestItemType)r.CategoryValue).GetDescription(),
Result = r.Value,
Rank = r.Rank,
Score = r.Score,
ScoreTime = r.ScoreTime
};
return TestApplyFilters(query, paramDto);
}
private IQueryable<StudentTestRecordsModel> BuildTestFreeModeQuery(string tenantId, string studentNo, StudentTrainingRecordsParam paramDto)
{
var query = from a in _studentRepository.DbContext.Set<Ai_TrainingData>()
join n in _studentRepository.DbContext.Set<N_SportsTestValue>() on a.StartTime equals n.ScoreTime
where a.SchoolCode == tenantId && a.StudentNo == studentNo
select new StudentTestRecordsModel
{
TrainName = "自由模式",
ScoreTime = a.StartTime,
CategoryValue = (SportsTestItemType)a.CategoryValue,
CategoryName = ((TrainingItemType)Convert.ToInt32(a.CategoryValue)).GetDescription(),
Rank = n.Rank,
Score = n.Score,
Result = n.Value
};
return TestApplyFilters(query, paramDto);
}
// 通用过滤逻辑
private IQueryable<StudentTestRecordsModel> TestApplyFilters(IQueryable<StudentTestRecordsModel> query, StudentTrainingRecordsParam paramDto)
{
return query
.WhereIF(paramDto.StartTime.HasValue, x => x.ScoreTime >= paramDto.StartTime)
.WhereIF(paramDto.EndTime.HasValue, x => x.ScoreTime <= paramDto.EndTime)
.WhereIF(paramDto.CategoryValue > 0, x => x.CategoryValue == (SportsTestItemType)paramDto.CategoryValue)
.WhereIF(paramDto.Rank > 0, x => x.Rank == paramDto.RankStr);
}
/// <summary>
/// 学生名单列表
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<List<StudentNamePageListModel>> GetStudentNamePageList(StudentNamePageListParam paramDto)
{
// 查询学生信息
var students = await _studentRepository.FindAsIQueryable(x => x.ClassId == paramDto.ClassId)
.Select(x => new StudentNamePageListModel()
{
StudentNo = x.StudentNo,
StudentName = x.StudentName,
Photo = x.Photo
})
.ToListAsync();
// 查询两个数据源的成绩并合并
var combinedResults = await _sportsTestResultRepository.FindAsIQueryable(x =>
x.ClassId == paramDto.ClassId &&
x.SchoolCode.Equals(UserContext.Current.TenantId)
)
.Select(x => new { x.StudentNo, x.Score })
.Concat(
_sportsTestResultRepository.DbContext.Set<Ai_SportsTestData>().Where(x =>
x.ClassId == paramDto.ClassId &&
x.SchoolCode.Equals(UserContext.Current.TenantId)
)
.Select(x => new { x.StudentNo, x.Score })
)
.ToListAsync();
// 使用字典来存储平均成绩,便于后续查找
var averageScoresDict = combinedResults
.GroupBy(x => x.StudentNo)
.ToDictionary(g => g.Key, g => g.Average(x => x.Score));
// 遍历学生信息,为每个学生分配 Rank
students.ForEach(student =>
{
if (averageScoresDict.TryGetValue(student.StudentNo, out var averageScore))
{
student.Rank = averageScore.GetRank(); // 有平均成绩的情况
}
else
{
student.Rank = 0.GetRank(); // 没有成绩时默认处理
}
});
return students;
}
/// <summary>
/// 更换班级
/// </summary>
/// <param name="studentNoList"></param>
/// <param name="classId"></param>
/// <returns></returns>
public async Task ReplaceClasses(List<string> studentNoList, int classId)
{
var students = await _studentRepository.FindAsync(x => studentNoList.Contains(x.StudentNo));
if (students == null || students.Count == 0)
throw new ArgumentNullException("未找到更新的数据");
foreach (var student in students)
{
student.ClassId = classId;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = DateTime.Now;
}
_studentRepository.UpdateRange(students);
await _studentRepository.SaveChangesAsync();
}
public async Task<StudentPromotionStatusModel> GetPromotionStatus()
{
var school = await GetPromotionSchool();
var academicYear = GetAcademicYear(DateTime.Now);
var latestBatch = await _studentRepository.DbContext.Set<S_StudentPromotionBatch>()
.Where(x => x.SchoolCode == school.SchoolCode)
.OrderByDescending(x => x.Id)
.FirstOrDefaultAsync();
var currentBatchExists = await _studentRepository.DbContext.Set<S_StudentPromotionBatch>()
.AnyAsync(x => x.SchoolCode == school.SchoolCode &&
x.AcademicYear == academicYear &&
x.Status == 1);
var gradeIds = new List<int>();
string gradeConfigurationReason = null;
try
{
gradeIds = await GetPromotionGradeIds(school);
var existingGradeCount = await _studentRepository.DbContext.Set<S_Grade>()
.CountAsync(x => gradeIds.Contains(x.Id));
if (existingGradeCount != gradeIds.Count)
gradeConfigurationReason = $"年级基础数据不完整,应存在{gradeIds.Count}个年级,实际存在{existingGradeCount}个";
}
catch (InvalidOperationException ex)
{
gradeConfigurationReason = ex.Message;
}
var studentCount = 0;
if (string.IsNullOrWhiteSpace(gradeConfigurationReason))
{
studentCount = await _studentRepository.FindAsIQueryable(x =>
x.SchoolCode == school.SchoolCode &&
x.StudentStatus == StudentStatus.Normal)
.Join(_studentRepository.DbContext.Set<S_Class>().Where(x => gradeIds.Contains(x.GradeId)),
student => student.ClassId,
schoolClass => schoolClass.Id,
(student, schoolClass) => student.Id)
.CountAsync();
}
var cannotPromoteReason = currentBatchExists
? $"{academicYear}学年已经执行过一键升学,如需重新操作请先撤回升学"
: gradeConfigurationReason ?? (studentCount == 0 ? "当前学校没有可升学的在读学生" : null);
var cannotUndoReason = latestBatch == null
? "当前学校没有升学记录"
: latestBatch.Status != 1 ? "最近一次升学已经撤回,没有可撤回的升学记录" : null;
return new StudentPromotionStatusModel
{
SchoolNature = latestBatch?.SchoolNatureName ?? await GetSchoolNatureName(school.SchoolNatureId),
AcademicYear = academicYear,
CanPromote = string.IsNullOrWhiteSpace(cannotPromoteReason),
CannotPromoteReason = cannotPromoteReason,
CanUndo = latestBatch?.Status == 1,
CannotUndoReason = cannotUndoReason,
StudentCount = studentCount,
PromotedCount = latestBatch?.PromotedCount ?? 0,
GraduatedCount = latestBatch?.GraduatedCount ?? 0,
PromotionTime = latestBatch?.CreateDate
};
}
public async Task<StudentPromotionStatusModel> PromoteStudents()
{
var school = await GetPromotionSchool();
var academicYear = GetAcademicYear(DateTime.Now);
var gradeIds = await GetPromotionGradeIds(school);
var natureName = await GetSchoolNatureName(school.SchoolNatureId);
var hasPromoted = await _studentRepository.DbContext.Set<S_StudentPromotionBatch>()
.AnyAsync(x => x.SchoolCode == school.SchoolCode &&
x.AcademicYear == academicYear &&
x.Status == 1);
if (hasPromoted)
throw new Exception($"{academicYear}学年已经执行过升学,请勿重复操作");
var grades = await _studentRepository.DbContext.Set<S_Grade>()
.Where(x => gradeIds.Contains(x.Id))
.OrderBy(x => x.Id)
.ToListAsync();
var classes = await _studentRepository.DbContext.Set<S_Class>()
.Where(x => x.SchoolCode == school.SchoolCode && gradeIds.Contains(x.GradeId))
.ToListAsync();
var classIds = classes.Select(x => x.Id).ToList();
var students = await _studentRepository.FindAsIQueryable(x =>
x.SchoolCode == school.SchoolCode &&
x.StudentStatus == StudentStatus.Normal &&
classIds.Contains(x.ClassId))
.ToListAsync();
if (students.Count == 0)
throw new Exception("当前学校没有可升学的在读学生");
var classById = classes.ToDictionary(x => x.Id);
var gradeIndex = grades.Select((grade, index) => new { grade.Id, Index = index })
.ToDictionary(x => x.Id, x => x.Index);
var targetClasses = new Dictionary<int, S_Class>();
var classesToCreate = new List<S_Class>();
var classLookup = classes
.GroupBy(x => $"{x.GradeId}|{GetClassKey(x.ClassName, x.GradeName).ToUpperInvariant()}")
.ToDictionary(x => x.Key, x => x.First());
var now = DateTime.Now;
foreach (var sourceClassId in classes.Select(x => x.Id).Distinct())
{
var sourceClass = classById[sourceClassId];
var sourceGradeIndex = gradeIndex[sourceClass.GradeId];
if (sourceGradeIndex == grades.Count - 1)
continue;
var targetGrade = grades[sourceGradeIndex + 1];
var classKey = GetClassKey(sourceClass.ClassName, sourceClass.GradeName);
var lookupKey = $"{targetGrade.Id}|{classKey.ToUpperInvariant()}";
if (!classLookup.TryGetValue(lookupKey, out var targetClass))
{
var sourceClassName = (sourceClass.ClassName ?? string.Empty).Trim();
var sourceGradeName = (sourceClass.GradeName ?? string.Empty).Trim();
var targetClassName = !string.IsNullOrEmpty(sourceGradeName) &&
sourceClassName.StartsWith(sourceGradeName, StringComparison.OrdinalIgnoreCase)
? $"{targetGrade.GradeName}{classKey}"
: sourceClassName;
targetClass = new S_Class
{
SchoolCode = school.SchoolCode,
GradeId = targetGrade.Id,
GradeName = targetGrade.GradeName,
ClassName = targetClassName,
Creator = UserContext.Current.UserId,
CreateDate = now
};
classesToCreate.Add(targetClass);
classLookup[lookupKey] = targetClass;
}
targetClasses[sourceClassId] = targetClass;
}
var highestGradeId = grades.Last().Id;
var teacherAssociations = await _studentRepository.DbContext.Set<S_ClassAssocTeacher>()
.Where(x => classIds.Contains(x.ClassId))
.ToListAsync();
var batch = new S_StudentPromotionBatch
{
SchoolCode = school.SchoolCode,
SchoolNatureId = school.SchoolNatureId,
SchoolNatureName = natureName,
AcademicYear = academicYear,
Status = 1,
PromotedCount = students.Count(x => classById[x.ClassId].GradeId != highestGradeId),
GraduatedCount = students.Count(x => classById[x.ClassId].GradeId == highestGradeId),
OperatorId = UserContext.Current.UserId,
CreateDate = now
};
using (var transaction = await _studentRepository.DbContext.Database.BeginTransactionAsync())
{
try
{
await _studentRepository.DbContext.Set<S_StudentPromotionBatch>().AddAsync(batch);
await _studentRepository.SaveChangesAsync();
if (classesToCreate.Count > 0)
{
classesToCreate.ForEach(x => x.Remarks = $"一键升学自动创建,批次Id:{batch.Id}");
await _studentRepository.DbContext.Set<S_Class>().AddRangeAsync(classesToCreate);
await _studentRepository.SaveChangesAsync();
}
var teacherSnapshots = teacherAssociations.Select(association =>
{
var sourceClass = classById[association.ClassId];
var isGraduating = sourceClass.GradeId == highestGradeId;
return new TeacherPromotionSnapshotItem
{
AssociationId = association.Id,
TeacherId = association.TeacherId,
OriginalClassId = association.ClassId,
TargetClassId = isGraduating ? 0 : targetClasses[sourceClass.Id].Id,
Creator = association.Creator,
CreateDate = association.CreateDate,
Modifier = association.Modifier,
ModifyDate = association.ModifyDate
};
}).ToList();
batch.TeacherSnapshot = JsonConvert.SerializeObject(teacherSnapshots);
_studentRepository.DbContext.Set<S_StudentPromotionBatch>().Update(batch);
foreach (var association in teacherAssociations)
{
var sourceClass = classById[association.ClassId];
if (sourceClass.GradeId == highestGradeId)
{
_studentRepository.DbContext.Set<S_ClassAssocTeacher>().Remove(association);
continue;
}
association.ClassId = targetClasses[sourceClass.Id].Id;
association.Modifier = UserContext.Current.UserId;
association.ModifyDate = now;
_studentRepository.DbContext.Set<S_ClassAssocTeacher>().Update(association);
}
var details = new List<S_StudentPromotionDetail>();
foreach (var student in students)
{
var sourceClass = classById[student.ClassId];
var isGraduating = sourceClass.GradeId == highestGradeId;
var targetClass = isGraduating ? sourceClass : targetClasses[sourceClass.Id];
var targetStatus = isGraduating ? StudentStatus.Grad : student.StudentStatus;
details.Add(new S_StudentPromotionDetail
{
BatchId = batch.Id,
StudentId = student.Id,
OriginalClassId = student.ClassId,
OriginalClassName = student.ClassName,
OriginalStatus = student.StudentStatus,
OriginalAge = student.Age,
TargetClassId = targetClass.Id,
TargetClassName = targetClass.ClassName,
TargetStatus = targetStatus,
TargetAge = student.Age + 1
});
student.ClassId = targetClass.Id;
student.ClassName = targetClass.ClassName;
student.StudentStatus = targetStatus;
student.Age += 1;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = now;
}
await _studentRepository.DbContext.Set<S_StudentPromotionDetail>().AddRangeAsync(details);
_studentRepository.UpdateRange(students);
await _studentRepository.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
return await GetPromotionStatus();
}
public async Task<StudentPromotionStatusModel> UndoPromotion()
{
var school = await GetPromotionSchool();
var batch = await _studentRepository.DbContext.Set<S_StudentPromotionBatch>()
.Where(x => x.SchoolCode == school.SchoolCode)
.OrderByDescending(x => x.Id)
.FirstOrDefaultAsync();
if (batch == null || batch.Status != 1)
throw new Exception("当前学校没有可撤回的升学记录");
var details = await _studentRepository.DbContext.Set<S_StudentPromotionDetail>()
.Where(x => x.BatchId == batch.Id)
.ToListAsync();
var studentIds = details.Select(x => x.StudentId).ToList();
var students = await _studentRepository.FindAsIQueryable(x => studentIds.Contains(x.Id)).ToListAsync();
var studentById = students.ToDictionary(x => x.Id);
var teacherSnapshots = string.IsNullOrWhiteSpace(batch.TeacherSnapshot)
? new List<TeacherPromotionSnapshotItem>()
: JsonConvert.DeserializeObject<List<TeacherPromotionSnapshotItem>>(batch.TeacherSnapshot)
?? new List<TeacherPromotionSnapshotItem>();
var shiftedAssociationIds = teacherSnapshots
.Where(x => x.TargetClassId > 0)
.Select(x => x.AssociationId)
.ToList();
var currentTeacherAssociations = await _studentRepository.DbContext.Set<S_ClassAssocTeacher>()
.Where(x => shiftedAssociationIds.Contains(x.Id))
.ToListAsync();
var currentTeacherAssociationById = currentTeacherAssociations.ToDictionary(x => x.Id);
var changedCount = details.Count(detail =>
!studentById.TryGetValue(detail.StudentId, out var student) ||
student.ClassId != detail.TargetClassId ||
student.StudentStatus != detail.TargetStatus ||
student.Age != detail.TargetAge);
if (changedCount > 0)
throw new Exception($"升学后已有{changedCount}名学生的班级、状态或年龄被修改,无法自动撤回");
var changedTeacherCount = teacherSnapshots.Count(snapshot =>
snapshot.TargetClassId > 0 &&
(!currentTeacherAssociationById.TryGetValue(snapshot.AssociationId, out var association) ||
association.TeacherId != snapshot.TeacherId ||
association.ClassId != snapshot.TargetClassId));
if (changedTeacherCount > 0)
throw new Exception($"升学后已有{changedTeacherCount}条教师班级关系被修改,无法自动撤回");
var now = DateTime.Now;
using (var transaction = await _studentRepository.DbContext.Database.BeginTransactionAsync())
{
try
{
foreach (var detail in details)
{
var student = studentById[detail.StudentId];
student.ClassId = detail.OriginalClassId;
student.ClassName = detail.OriginalClassName;
student.StudentStatus = detail.OriginalStatus;
student.Age = detail.OriginalAge;
student.Modifier = UserContext.Current.UserId;
student.ModifyDate = now;
}
foreach (var snapshot in teacherSnapshots)
{
if (snapshot.TargetClassId > 0)
{
var association = currentTeacherAssociationById[snapshot.AssociationId];
association.ClassId = snapshot.OriginalClassId;
association.Modifier = snapshot.Modifier;
association.ModifyDate = snapshot.ModifyDate;
_studentRepository.DbContext.Set<S_ClassAssocTeacher>().Update(association);
}
else
{
await _studentRepository.DbContext.Set<S_ClassAssocTeacher>().AddAsync(new S_ClassAssocTeacher
{
ClassId = snapshot.OriginalClassId,
TeacherId = snapshot.TeacherId,
Creator = snapshot.Creator,
CreateDate = snapshot.CreateDate,
Modifier = snapshot.Modifier,
ModifyDate = snapshot.ModifyDate
});
}
}
batch.Status = 2;
batch.RevokeOperatorId = UserContext.Current.UserId;
batch.RevokeDate = now;
_studentRepository.UpdateRange(students);
_studentRepository.DbContext.Set<S_StudentPromotionBatch>().Update(batch);
await _studentRepository.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
return await GetPromotionStatus();
}
private async Task<S_School> GetPromotionSchool()
{
if (UserContext.Current.RoleId != 2)
throw new Exception("仅学校管理员可以执行一键升学");
var school = await _studentRepository.DbContext.Set<S_School>()
.FirstOrDefaultAsync(x => x.SchoolCode == UserContext.Current.TenantId && x.SchoolType == SchoolType.School);
if (school == null)
throw new Exception("未找到当前学校信息");
return school;
}
private async Task<List<int>> GetPromotionGradeIds(S_School school)
{
var natureGradeIds = await _studentRepository.DbContext.Set<S_SchoolNatureAssocGrade>()
.Where(x => x.NatureId == school.SchoolNatureId)
.OrderBy(x => x.GradeId)
.Select(x => x.GradeId)
.Distinct()
.ToListAsync();
if (natureGradeIds.Count == 0)
throw new InvalidOperationException("当前学校性质未配置年级,无法执行一键升学");
var schoolGradeIds = await _studentRepository.DbContext.Set<S_SchoolAssocGrade>()
.Where(x => x.SchoolCode == school.SchoolCode)
.Select(x => x.GradeId)
.Distinct()
.ToListAsync();
var gradeIds = natureGradeIds.Where(schoolGradeIds.Contains).ToList();
if (gradeIds.Count != natureGradeIds.Count)
throw new InvalidOperationException($"当前学校年级配置不完整,应配置{natureGradeIds.Count}个年级,实际配置{gradeIds.Count}个");
return gradeIds;
}
private async Task<string> GetSchoolNatureName(int natureId)
{
return await _studentRepository.DbContext.Set<S_SchoolNature>()
.Where(x => x.Id == natureId)
.Select(x => x.NatureName)
.FirstOrDefaultAsync() ?? "未知";
}
private static string GetAcademicYear(DateTime date)
{
var startYear = date.Month >= 7 ? date.Year : date.Year - 1;
return $"{startYear}-{startYear + 1}";
}
private static string GetClassKey(string className, string gradeName)
{
var value = (className ?? string.Empty).Trim();
var grade = (gradeName ?? string.Empty).Trim();
if (!string.IsNullOrEmpty(grade) && value.StartsWith(grade, StringComparison.OrdinalIgnoreCase))
value = value.Substring(grade.Length).Trim();
return value;
}
private class TeacherPromotionSnapshotItem
{
public int AssociationId { get; set; }
public int TeacherId { get; set; }
public int OriginalClassId { get; set; }
public int TargetClassId { get; set; }
public int Creator { get; set; }
public DateTime? CreateDate { get; set; }
public int Modifier { get; set; }
public DateTime? ModifyDate { get; set; }
}
}
}