2026-06-08 17:09:28 +08:00

673 lines
28 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 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.Linq;
using System.Text;
using System.Threading.Tasks;
using VOL.Business.IServices;
using VOL.Business.IServices.Norm;
using VOL.Business.IServices.School;
using VOL.Business.IServices.SportsHall;
using VOL.Business.Services.School;
using VOL.Core.CacheManager;
using VOL.Core.Extensions.AutofacManager;
using VOL.Core.ManageUser;
using VOL.Core.Utilities;
using VOL.Entity.DomainModels;
using VOL.Entity.Enum;
using VOL.Model;
using VOL.Model.IOT.Request;
using VOL.Model.School.Request;
using VOL.Model.School.Response;
using VOL.Model.SportsHall;
using VOL.System.IRepositories;
using VOL.System.Repositories;
namespace VOL.Business.Services.SportsHall
{
/// <summary>
/// 运动馆
/// </summary>
public class SportsHallService : ISportsHallService, IDependency
{
#region 初始化
private readonly IS_ClassRepository _classRepository;
private readonly IS_GradeRepository _gradeRepository;
private readonly IS_ClassAssocTeacherRepository _classAssocTeacherRepository;
private readonly IS_StudentRepository _studentRepository;
IN_HealthStandardsRepository _healthStandardsRepository;
IN_SportsTestResultRepository _sportsTestResultRepository;
[ActivatorUtilitiesConstructor]
public SportsHallService(IS_ClassRepository classRepository,
IS_GradeRepository gradeRepository,
IS_ClassAssocTeacherRepository classAssocTeacherRepository,
IS_StudentRepository studentRepository,
IN_HealthStandardsRepository healthStandardsRepository,
IN_SportsTestResultRepository sportsTestResultRepository)
{
_classRepository = classRepository;
_gradeRepository = gradeRepository;
_classAssocTeacherRepository = classAssocTeacherRepository;
_studentRepository = studentRepository;
_healthStandardsRepository = healthStandardsRepository;
_sportsTestResultRepository = sportsTestResultRepository;
}
#endregion
/// <summary>
/// 新增班级
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
public async Task AddClass(AddSportsHallClassParam paramDto)
{
var tenantId = UserContext.Current.TenantId;
var grade = await _gradeRepository.FindAsIQueryable(x => x.GradeName == "运动馆").Select(x => new { x.Id, x.GradeName }).FirstAsync();
var exist = await _classRepository.ExistsAsync(x => x.SchoolCode == tenantId && x.ClassName == paramDto.ClassName && x.GradeId == grade.Id);
if (exist)
throw new Exception("班级名称已存在");
var classEntity = new S_Class()
{
GradeId = grade.Id,
GradeName = grade.GradeName,
ClassName = paramDto.ClassName,
Creator = UserContext.Current.UserId,
SchoolCode = UserContext.Current.TenantId,
CreateDate = DateTime.Now
};
using (var transaction = _classRepository.DbContext.Database.BeginTransaction())
{
try
{
await _classRepository.AddAsync(classEntity);
await _classRepository.SaveChangesAsync();
int classId = classEntity.Id;
var classAssocTeacherList = new List<S_ClassAssocTeacher>();
paramDto.TeacherIds.ForEach(x =>
{
classAssocTeacherList.Add(new S_ClassAssocTeacher()
{
ClassId = classId,
TeacherId = x,
Creator = UserContext.Current.UserId,
CreateDate = DateTime.Now,
Modifier = UserContext.Current.UserId,
ModifyDate = DateTime.Now,
});
});
await _classAssocTeacherRepository.AddRangeAsync(classAssocTeacherList);
var students = await _studentRepository.FindAsync(x => paramDto.studentNoList.Contains(x.StudentNo));
foreach (var student in students)
{
student.ClassId = classId;
int index = paramDto.studentNoList.IndexOf(student.StudentNo);
student.OrderNo = index >= 0 ? index + 1 : 0;
}
_studentRepository.UpdateRange(students);
await _studentRepository.SaveChangesAsync();
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 发生错误,回滚事务
transaction.Rollback();
Console.WriteLine("事务回滚:" + ex.Message);
}
}
}
/// <summary>
/// 更新班级
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
public async Task ModifyClass(ModifySportsHallClassParam paramDto)
{
var tenantId = UserContext.Current.TenantId;
var classModel = await _classRepository.FindAsyncFirst(x => x.Id == paramDto.ClassId);
var grade = await _gradeRepository.FindAsIQueryable(x => x.GradeName == "运动馆").Select(x => new { x.Id, x.GradeName }).FirstAsync();
if (classModel == null)
throw new ArgumentNullException("未找到要更新得数据");
var exist = await _classRepository.ExistsAsync(
x => x.SchoolCode == tenantId
&& x.ClassName == paramDto.ClassName
&& x.GradeId == grade.Id
&& x.Id != paramDto.ClassId);
if (exist)
throw new Exception("班级名称已存在");
classModel.ClassName = paramDto.ClassName;
using (var transaction = _classRepository.DbContext.Database.BeginTransaction())
{
try
{
_classRepository.Update(classModel);
var teacherList = await _classAssocTeacherRepository.FindAsync(x => x.ClassId == paramDto.ClassId);
_classAssocTeacherRepository.DbContext.RemoveRange(teacherList);
var classAssocTeacherList = new List<S_ClassAssocTeacher>();
paramDto.TeacherIds.ForEach(x =>
{
classAssocTeacherList.Add(new S_ClassAssocTeacher()
{
ClassId = paramDto.ClassId,
TeacherId = x,
Creator = UserContext.Current.UserId,
CreateDate = DateTime.Now,
Modifier = UserContext.Current.UserId,
ModifyDate = DateTime.Now,
});
});
await _classAssocTeacherRepository.AddRangeAsync(classAssocTeacherList);
var oldStudents = await _studentRepository.FindAsync(x => x.ClassId == classModel.Id);
var newStudents = await _studentRepository.FindAsync(x => paramDto.studentNoList.Contains(x.StudentNo));
var allStudents = oldStudents.UnionBy(newStudents, s => s.Id).ToList();
foreach (var student in allStudents)
{
if (paramDto.studentNoList.Contains(student.StudentNo))
{
student.ClassId = classModel.Id;
student.OrderNo = paramDto.studentNoList.IndexOf(student.StudentNo) + 1;
}
else
{
student.ClassId = 0;
student.OrderNo = 0;
}
}
_studentRepository.UpdateRange(allStudents);
await _classRepository.SaveChangesAsync();
transaction.Commit();
}
catch (Exception ex)
{
// 发生错误,回滚事务
transaction.Rollback();
Console.WriteLine("事务回滚:" + ex.Message);
}
}
}
/// <summary>
/// 导入运动馆学生体测成绩
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task SportsHallImportStudentsTestData(IFormFile file)
{
if (file == null || file.Length <= 0)
throw new Exception("操作失败");
List<SportsHallImportStudentsTestDataParam> dataObjects;
using (var fileStream = file.OpenReadStream())
{
dataObjects = Tool.ConvertExcelToList<SportsHallImportStudentsTestDataParam>(fileStream);
}
var studentNos = dataObjects.Select(x => x.StudentNo).Distinct().ToList();
// 用一个查询一次性获取所有学生信息
var students = await (from s in _studentRepository.DbContext.Set<S_Student>()
join c in _studentRepository.DbContext.Set<S_Class>() on s.ClassId equals c.Id
join g in _studentRepository.DbContext.Set<S_Grade>() on c.GradeId equals g.Id
where c.SchoolCode.Equals(UserContext.Current.TenantId)
select new
{
s.Id,
s.Age,
s.ClassId,
c.ClassName,
s.StudentName,
GradeId = g.Id,
g.GradeName,
s.Sex,
s.StudentNo
}).ToListAsync();
// 将学生信息存入字典,快速查找
var studentsDict = students.ToDictionary(x => x.StudentNo);
var entityList = new List<N_SportsTestValue>();
foreach (var data in dataObjects)
{
if (!studentsDict.TryGetValue(data.StudentNo, out var student))
{
throw new Exception($"未找到此编号为{data.StudentNo}的学生");
}
var timeNow = data.ScoreTime;
// 为每个学生添加多条体测数据
if (data.BMIValue > 0)
entityList.Add(await AddSportsHallEnumStudentsTestData(data, SportsTestItemType.BMI, student, data.GradeId, timeNow));
//if (data.Sit_And_ReachValue > 0)
entityList.Add(await AddSportsHallEnumStudentsTestData(data, SportsTestItemType.Sit_And_Reach, student, data.GradeId, timeNow));
if (data.VitalCapacityValue > 0)
entityList.Add(await AddSportsHallEnumStudentsTestData(data, SportsTestItemType.VitalCapacity, student, data.GradeId, timeNow));
if (data.OneMinuteJumpRopeValue > 0)
entityList.Add(await AddSportsHallEnumStudentsTestData(data, SportsTestItemType.OneMinuteJumpRope, student, data.GradeId, timeNow));
if (data.One_Minute_Sit_UpValue > 0)
entityList.Add(await AddSportsHallEnumStudentsTestData(data, SportsTestItemType.One_Minute_Sit_Up, student, data.GradeId, timeNow));
if (data.StandingLongJumpValue > 0)
entityList.Add(await AddSportsHallEnumStudentsTestData(data, SportsTestItemType.StandingLongJump, student, data.GradeId, timeNow));
}
// 批量添加数据
await _sportsTestResultRepository.AddRangeAsync(entityList);
// 一次性保存所有修改
await _sportsTestResultRepository.SaveChangesAsync();
}
public async Task<N_SportsTestValue> AddSportsHallEnumStudentsTestData(SportsHallImportStudentsTestDataParam data, SportsTestItemType type, dynamic student, int gradeId, DateTime timeNow)
{
var entity = new N_SportsTestValue
{
TeacherId = 0,
TeacherName = "",
DataSource = DataSource.Im,
ScoreTime = timeNow,
GradeId = gradeId,
ClassId = student.ClassId,
GradeName = student.GradeName,
ClassName = student.ClassName,
SchoolCode = UserContext.Current.TenantId,
Sex = student.Sex,
StudentNo = data.StudentNo,
StudentName = student.StudentName,
Creator = UserContext.Current.UserId,
CreateDate = timeNow,
IsDisplay = false
};
SexType sexType = student.Sex;
string sex = sexType == SexType.Male ? "男" : "女";
string typeStr = type.ToString();
// 根据测试项目类型设置对应的值
if (type == SportsTestItemType.BMI)
{
entity.Value = data.BMIValue;
// 根据测试项目查找标准
var standard = await _healthStandardsRepository.FindFirstAsync(x =>
x.CategoryEnum.Equals(typeStr) &&
x.GradeId == gradeId &&
x.Sex == sexType &&
data.BMIValue >= x.MinValue &&
data.BMIValue < x.MaxValue
);
if (standard != null)
{
entity.Score = standard.Score;
entity.Rank = standard.Rank;
}
}
else if (type == SportsTestItemType.Sit_And_Reach)
{
entity.Value = data.Sit_And_ReachValue;
// 根据测试项目查找标准
var standard = await _healthStandardsRepository.FindFirstAsync(x =>
x.CategoryEnum.Equals(typeStr) &&
x.GradeId == gradeId &&
x.Sex == sexType &&
data.Sit_And_ReachValue >= x.MinValue &&
data.Sit_And_ReachValue < x.MaxValue
);
if (standard != null)
{
entity.Score = standard.Score;
entity.Rank = standard.Rank;
}
}
else if (type == SportsTestItemType.VitalCapacity)
{
entity.Value = data.VitalCapacityValue;
// 根据测试项目查找标准
var standard = await _healthStandardsRepository.FindFirstAsync(x =>
x.CategoryEnum.Equals(typeStr) &&
x.GradeId == gradeId &&
x.Sex == sexType &&
data.VitalCapacityValue >= x.MinValue &&
data.VitalCapacityValue < x.MaxValue
);
if (standard != null)
{
entity.Score = standard.Score;
entity.Rank = standard.Rank;
}
}
else if (type == SportsTestItemType.OneMinuteJumpRope)
{
entity.Value = data.OneMinuteJumpRopeValue;
// 根据测试项目查找标准
var standard = await _healthStandardsRepository.FindFirstAsync(x =>
x.CategoryEnum.Equals(typeStr) &&
x.GradeId == gradeId &&
x.Sex == sexType &&
data.OneMinuteJumpRopeValue >= x.MinValue &&
data.OneMinuteJumpRopeValue < x.MaxValue
);
if (standard != null)
{
entity.Score = standard.Score;
entity.Rank = standard.Rank;
}
}
else if (type == SportsTestItemType.One_Minute_Sit_Up)
{
entity.Value = data.One_Minute_Sit_UpValue;
// 根据测试项目查找标准
var standard = await _healthStandardsRepository.FindFirstAsync(x =>
x.CategoryEnum.Equals(typeStr) &&
x.GradeId == gradeId &&
x.Sex == sexType &&
data.One_Minute_Sit_UpValue >= x.MinValue &&
data.One_Minute_Sit_UpValue < x.MaxValue
);
if (standard != null)
{
entity.Score = standard.Score;
entity.Rank = standard.Rank;
}
}
else if (type == SportsTestItemType.StandingLongJump)
{
entity.Value = data.StandingLongJumpValue;
// 根据测试项目查找标准
var standard = await _healthStandardsRepository.FindFirstAsync(x =>
x.CategoryEnum.Equals(typeStr) &&
x.GradeId == gradeId &&
x.Sex == sexType &&
data.StandingLongJumpValue >= x.MinValue &&
data.StandingLongJumpValue < x.MaxValue
);
if (standard != null)
{
entity.Score = standard.Score;
entity.Rank = standard.Rank;
}
}
entity.CategoryEnum = type.ToString();
entity.CategoryValue = (int)type;
return entity;
}
/// <summary>
/// 运动馆体测成绩分页列表
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<PageDataDto<SportsHallStudentsTestModel>> GetSportsHallStudentsTestPageList(SportsHallStudentsTestParam paramDto)
{
var result = new PageDataDto<SportsHallStudentsTestModel>();
// 基础查询(带租户过滤)
var baseQuery = _sportsTestResultRepository.DbContext.Set<N_SportsTestValue>()
.Where(s => s.SchoolCode == UserContext.Current.TenantId);
// 公共条件过滤(时间、班级、性别、等级)
if (paramDto.StartTime.HasValue)
baseQuery = baseQuery.Where(x => x.CreateDate >= paramDto.StartTime.Value);
if (paramDto.EndTime.HasValue)
baseQuery = baseQuery.Where(x => x.CreateDate <= paramDto.EndTime.Value);
if (paramDto.ClassId.HasValue)
baseQuery = baseQuery.Where(x => x.ClassId == paramDto.ClassId.Value);
if (paramDto.Sex.HasValue)
baseQuery = baseQuery.Where(x => (int)x.Sex == paramDto.Sex);
if (!string.IsNullOrWhiteSpace(paramDto.ResultLevel))
baseQuery = baseQuery.Where(x => x.Rank == paramDto.ResultLevel);
// Step 1: 获取每个学生最新的 ScoreTime(子查询)
var latestScoreTimeQuery = baseQuery
.GroupBy(x => x.StudentNo)
.Select(g => new { StudentNo = g.Key, MaxScoreTime = g.Max(x => x.ScoreTime) });
// Step 2: 关联原表,只保留每个学生最新测试时间的记录
var latestRecordsQuery = from record in baseQuery
join latest in latestScoreTimeQuery
on new { record.StudentNo, record.ScoreTime }
equals new { latest.StudentNo, ScoreTime = latest.MaxScoreTime }
select record;
// Step 3: 获取符合条件的学生总数(去重)
result.Total = await latestRecordsQuery
.Select(x => x.StudentNo)
.Distinct()
.CountAsync();
// Step 4: 分页获取当前页的学生学号列表(先按最新测试时间倒序排)
var pagedStudentNos = await latestRecordsQuery
.OrderByDescending(x => x.ScoreTime) // 按最新测试时间整体倒序
.Select(x => x.StudentNo)
.Distinct()
.Skip((paramDto.PageIndex - 1) * paramDto.PageSize)
.Take(paramDto.PageSize)
.ToListAsync();
if (!pagedStudentNos.Any())
{
result.Datas = new List<SportsHallStudentsTestModel>();
return result;
}
// Step 5: 获取当前页学生的最新测试所有项目记录(包含 pivot 需要的数据)
var pageRecords = await (from record in latestRecordsQuery
where pagedStudentNos.Contains(record.StudentNo)
select new
{
record.Id,
record.StudentNo,
record.StudentName,
record.Sex,
record.ClassName,
record.GradeName,
record.ScoreTime,
record.IsDisplay,
record.CategoryValue,
record.Value
}).ToListAsync();
// Step 6: 在内存中完成 pivot(因为同一学生同一测试时间的项目数量有限,性能可接受)
var grouped = pageRecords
.GroupBy(x => new
{
x.StudentNo,
x.StudentName,
x.Sex,
x.ClassName,
x.GradeName,
x.ScoreTime,
x.IsDisplay
})
.Select(g => new SportsHallStudentsTestModel
{
Id = g.Max(x => x.Id), // 取组内最大 Id(或任意一个,仅作标识)
StudentNo = g.Key.StudentNo,
StudentName = g.Key.StudentName,
SexName = g.Key.Sex.GetDescription(),
GradeAndClass = $"{g.Key.GradeName} {g.Key.ClassName}",
ScoreTime = g.Key.ScoreTime,
IsDisplay = g.Key.IsDisplay,
// 各项指标:根据 CategoryValue 匹配,使用 Max(如果同一个 CategoryValue 有多条,取最大值)
BMIValue = g.Where(x => x.CategoryValue == (int)SportsTestItemType.BMI)
.Max(x => (float?)x.Value) ?? 0,
VitalCapacityValue = g.Where(x => x.CategoryValue == (int)SportsTestItemType.VitalCapacity)
.Max(x => (float?)x.Value) ?? 0,
Sit_And_ReachValue = g.Where(x => x.CategoryValue == (int)SportsTestItemType.Sit_And_Reach)
.Max(x => (float?)x.Value) ?? 0,
OneMinuteJumpRopeValue = g.Where(x => x.CategoryValue == (int)SportsTestItemType.OneMinuteJumpRope)
.Max(x => (int?)x.Value) ?? 0,
One_Minute_Sit_UpValue = g.Where(x => x.CategoryValue == (int)SportsTestItemType.One_Minute_Sit_Up)
.Max(x => (int?)x.Value) ?? 0,
StandingLongJumpValue = g.Where(x => x.CategoryValue == (int)SportsTestItemType.StandingLongJump)
.Max(x => (float?)x.Value) ?? 0
})
.OrderByDescending(x => x.ScoreTime) // 按测试时间倒序
.ToList();
result.Datas = grouped;
return result;
}
/// <summary>
/// 成绩推送
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task ScorePush(SportsHallStudentsTestParam paramDto)
{
var sql = new StringBuilder();
sql.Append("UPDATE N_SportsTestValue SET IsDisplay = 1 WHERE SchoolCode = {0} ");
var parameters = new List<object>
{
UserContext.Current.TenantId
};
if (paramDto.StartTime.HasValue)
{
sql.Append(" AND CreateDate >= {1} ");
parameters.Add(paramDto.StartTime.Value);
}
if (paramDto.EndTime.HasValue)
{
sql.Append(" AND CreateDate <= {2} ");
parameters.Add(paramDto.EndTime.Value);
}
if (paramDto.ClassId > 0)
{
sql.Append(" AND ClassId = {3} ");
parameters.Add(paramDto.ClassId.Value);
}
if (paramDto.Sex > 0)
{
sql.Append(" AND Sex = {4} ");
parameters.Add(paramDto.Sex.Value);
}
if (!string.IsNullOrWhiteSpace(paramDto.ResultLevel))
{
sql.Append(" AND Rank = {5} ");
parameters.Add(paramDto.ResultLevel);
}
await _sportsTestResultRepository.DbContext.Database
.ExecuteSqlRawAsync(sql.ToString(), parameters.ToArray());
}
/// <summary>
/// 成绩修成
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
public async Task ScoreUpdate(SportsHallStudentsTestDataUpdateParam paramDto)
{
// 获取该学生每个项目的最新一条记录
var list = await _sportsTestResultRepository.DbContext.Set<N_SportsTestValue>()
.Where(s => s.SchoolCode == UserContext.Current.TenantId && s.StudentNo == paramDto.StudentNo)
.GroupBy(x => x.CategoryValue)
.Select(g => g.OrderByDescending(x => x.ScoreTime).FirstOrDefault())
.ToListAsync();
if (list == null || !list.Any())
throw new Exception($"未找到要修改得数据");
foreach (var entity in list)
{
switch (entity.CategoryValue)
{
case (int)SportsTestItemType.BMI:
entity.Value = paramDto.BMIValue;
break;
case (int)SportsTestItemType.VitalCapacity:
entity.Value = paramDto.VitalCapacityValue;
break;
case (int)SportsTestItemType.Sit_And_Reach:
entity.Value = paramDto.Sit_And_ReachValue;
break;
case (int)SportsTestItemType.OneMinuteJumpRope:
entity.Value = paramDto.OneMinuteJumpRopeValue;
break;
case (int)SportsTestItemType.One_Minute_Sit_Up:
entity.Value = paramDto.One_Minute_Sit_UpValue;
break;
case (int)SportsTestItemType.StandingLongJump:
entity.Value = paramDto.StandingLongJumpValue;
break;
}
}
_sportsTestResultRepository.UpdateRange(list);
await _sportsTestResultRepository.DbContext.SaveChangesAsync();
}
}
}