2025-06-06 16:55:14 +08:00

570 lines
25 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 DlibDotNet;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using VOL.Business.IServices;
using VOL.Business.IServices.Norm;
using VOL.Business.IServices.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.DomainModels.Business.People;
using VOL.Entity.Enum;
using VOL.Model;
using VOL.Model.Ai;
using VOL.Model.Norm.Response;
using VOL.Model.School.Request;
using VOL.Model.School.Response;
using VOL.System.IRepositories;
using VOL.System.Repositories;
namespace VOL.Business.Services.School
{
public class S_GradeService : IS_GradeService, IDependency
{
#region
private readonly IMapper _mapper;
private readonly ICacheService _cacheService;
private readonly ICacheQueryService _cacheQueryService;
private readonly IS_GradeRepository _gradeRepository;
private readonly IN_SportsTestResultRepository _sportsTestResultRepository;
private readonly IN_SportsTestCategoryRepository _sportsTestCategoryRepository;
private readonly IS_ClassRepository _classRepository;
private readonly IS_TeacherRepository _teacherRepository;
[ActivatorUtilitiesConstructor]
public S_GradeService(IMapper mapper,
ICacheService cacheService,
ICacheQueryService cacheQueryService,
IS_GradeRepository gradeRepository,
IN_SportsTestResultRepository sportsTestResultRepository,
IN_SportsTestCategoryRepository sportsTestCategoryRepository,
IS_ClassRepository classRepository,
IS_TeacherRepository teacherRepository)
{
_mapper = mapper;
_cacheService = cacheService;
_gradeRepository = gradeRepository;
_cacheQueryService = cacheQueryService;
_sportsTestResultRepository = sportsTestResultRepository;
_sportsTestCategoryRepository = sportsTestCategoryRepository;
_classRepository = classRepository;
_teacherRepository = teacherRepository;
}
#endregion
/// <summary>
/// 获取年级列表
/// </summary>
/// <returns></returns>
public async Task<List<GradeListModel>> GetGradeList()
{
var tenantId = UserContext.Current.TenantId;
var teacherClassIds = new List<int>();
var gradeIds = new List<int>();
var isTeacher = (UserContext.Current.RoleId == 3);
if (isTeacher)
{
var teacher = await _teacherRepository.FindAsyncFirst(x => x.SchoolCode == tenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo);
var teacherClassList = await (
from at in _teacherRepository.DbContext.Set<S_ClassAssocTeacher>()
join c in _teacherRepository.DbContext.Set<S_Class>() on at.ClassId equals c.Id
where at.TeacherId == teacher.Id
select c).ToListAsync();
gradeIds = teacherClassList.Select(c => c.GradeId).Distinct().ToList();
teacherClassIds = teacherClassList.Select(c => c.Id).Distinct().ToList();
}
var list = await (
from g in _gradeRepository.DbContext.Set<S_Grade>()
join a in _gradeRepository.DbContext.Set<S_SchoolAssocGrade>() on g.Id equals a.GradeId
where a.SchoolCode == tenantId && (!isTeacher || gradeIds.Contains(g.Id))
select new GradeListModel()
{
Id = g.Id,
GradeName = g.GradeName,
})
.OrderBy(x => x.Id)
.ToListAsync();
var classList = await _gradeRepository.DbContext.Set<S_Class>().Where(x => x.SchoolCode == tenantId && (!isTeacher || teacherClassIds.Contains(x.Id))).ToListAsync();
var studentList = await _gradeRepository.DbContext.Set<S_Student>().Where(x => x.SchoolCode == tenantId).ToListAsync();
foreach (var item in list)
{
var classIds = classList.Where(x => x.GradeId == item.Id).Select(x => x.Id).ToList();
var studentCount = studentList.Where(x => classIds.Contains(x.ClassId)).Count();
item.ClassCount = classIds.Count;
item.StudentCount = studentCount;
}
return list;
}
/// <summary>
/// 获取所有年级名称
/// </summary>
/// <returns></returns>
public async Task<List<GradeNameModel>> GetGradeNames()
{
var tenantId = UserContext.Current.TenantId;
var teacherClassIds = new List<int>();
var gradeIds = new List<int>();
var isTeacher = (UserContext.Current.RoleId == 3);
if (isTeacher)
{
var teacher = await _teacherRepository.FindAsyncFirst(x => x.SchoolCode == tenantId && x.TeacherPhoneNo == UserContext.Current.UserInfo.PhoneNo);
var teacherClassList = await (
from at in _teacherRepository.DbContext.Set<S_ClassAssocTeacher>()
join c in _teacherRepository.DbContext.Set<S_Class>() on at.ClassId equals c.Id
where at.TeacherId == teacher.Id
select c).ToListAsync();
gradeIds = teacherClassList.Select(c => c.GradeId).Distinct().ToList();
teacherClassIds = teacherClassList.Select(c => c.Id).Distinct().ToList();
}
var list = await (
from g in _gradeRepository.DbContext.Set<S_Grade>()
join a in _gradeRepository.DbContext.Set<S_SchoolAssocGrade>() on g.Id equals a.GradeId
where a.SchoolCode == tenantId && (!isTeacher || gradeIds.Contains(g.Id))
select new GradeNameModel()
{
Id = g.Id,
GradeName = g.GradeName,
})
.OrderBy(x => x.Id)
.ToListAsync();
var classList = await _gradeRepository.DbContext.Set<S_Class>().Where(x => x.SchoolCode == tenantId && (!isTeacher || teacherClassIds.Contains(x.Id))).ToListAsync();
foreach (var item in list)
{
var classIds = classList.Where(x => x.GradeId == item.Id).Select(x => x.Id).ToList();
item.Class = classList.Where(x => x.GradeId == item.Id).Select(x => new ClassNameModel()
{
Id = x.Id,
ClassName = x.ClassName
}).ToList();
}
return list;
}
/// <summary>
/// 获取所有性质年级名称
/// </summary>
/// <returns></returns>
public async Task<List<NatureGradeNameModel>> GetNatureGradeNames()
{
var grades = await (
from g in _gradeRepository.DbContext.Set<S_Grade>()
join a in _gradeRepository.DbContext.Set<S_SchoolAssocGrade>() on g.Id equals a.GradeId
join snag in _gradeRepository.DbContext.Set<S_SchoolNatureAssocGrade>() on g.Id equals snag.GradeId into snagGroup
from snag in snagGroup.DefaultIfEmpty()
join sn in _gradeRepository.DbContext.Set<S_SchoolNature>() on snag.NatureId equals sn.Id into snGroup
from sn in snGroup.DefaultIfEmpty()
join c in _gradeRepository.DbContext.Set<S_Class>() on g.Id equals c.GradeId into classGroup
from c in classGroup.DefaultIfEmpty()
where g.SchoolCode.Equals(UserContext.Current.TenantId)
group new { g, c, sn } by new { sn.Id, sn.NatureName } into natureGroup
select new NatureGradeNameModel
{
Id = natureGroup.Key.Id,
NatureName = natureGroup.Key.NatureName ?? string.Empty,
GradeList = natureGroup
.Where(g => g.g != null)
.GroupBy(g => new { g.g.Id, g.g.GradeName })
.Select(gradeGroup => new GradeNameModel
{
Id = gradeGroup.Key.Id,
GradeName = gradeGroup.Key.GradeName,
Class = gradeGroup.Where(g => g.c != null).Select(g => new ClassNameModel
{
Id = g.c.Id,
ClassName = g.c.ClassName
}).ToList()
}).ToList()
}).ToListAsync();
return grades;
}
public async Task<GradeDataStatsModel> GradeWholeDataStats(GradeDataStatsParam paramDto)
{
var res = new GradeDataStatsModel();
var tenantId = UserContext.Current.TenantId;
var baseQuery = await (
from c in _gradeRepository.DbContext.Set<S_Class>()
join s in _gradeRepository.DbContext.Set<S_Student>() on c.Id equals s.ClassId into studentGroup
from s in studentGroup.DefaultIfEmpty()
where (!paramDto.GradeId.HasValue || paramDto.GradeId <= 0 || c.GradeId == paramDto.GradeId) && c.SchoolCode == tenantId
select new
{
c.Id,
c.ClassName,
c.GradeId,
c.GradeName,
StudentId = (int?)s.Id
}).ToListAsync();
var gradeModel = baseQuery
.GroupBy(x => x.GradeId)
.Select(g => new
{
GradeId = g.Key,
ClassCount = g.Select(x => x.Id).Distinct().Count(),
StudentCount = g.Where(x => x.StudentId != null).Select(x => x.StudentId).Distinct().Count(),
ClassList = g
.GroupBy(x => x.Id)
.Select(cg => new
{
Id = cg.Key,
ClassName = cg.First().ClassName,
GradeId = cg.First().GradeId,
GradeName = cg.First().GradeName,
StudentCount = cg.Where(x => x.StudentId != null).Select(x => x.StudentId).Distinct().Count()
})
.Distinct()
.ToList()
}).ToList();
if (gradeModel.Count == 0)
return res;
res.ClassCount = gradeModel.SelectMany(x => x.ClassList).Count();
res.StudentCount = gradeModel.Sum(x => x.StudentCount);
// 从缓存中获取数据
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x =>
x.SchoolCode.Equals(tenantId) &&
(!paramDto.GradeId.HasValue || paramDto.GradeId <= 0 || x.GradeId == paramDto.GradeId)
, paramDto.Mode);
var monitorList = sportsTestResults
.GroupBy(x => new { x.StudentNo })
.Select(g => new
{
g.Key.StudentNo,
g.First().Sex,
g.First().ClassId,
g.First().ClassName,
g.First().GradeId,
g.First().GradeName,
g.First().TeacherId,
g.First().TeacherName,
g.First().ClassRoomRecordId,
Score = (g.Sum(x => x.Score) + g.Sum(x => x.AdditionalScore)) / (g.Select(x => x.CategoryEnum).Distinct().Count())
})
.Select(x => new
{
x.StudentNo,
x.Sex,
x.ClassId,
x.ClassName,
x.GradeId,
x.GradeName,
x.TeacherId,
x.TeacherName,
x.ClassRoomRecordId,
x.Score,
Rank = x.Score.GetRank()
}).ToList();
// 计算总人数
float totalCount = monitorList.Count();
res.ExcellentRate = totalCount > 0 ? Math.Round((monitorList.Count(x => x.Rank == "优秀") / totalCount) * 100) : 0;
res.FineRate = totalCount > 0 ? Math.Round((monitorList.Count(x => x.Rank == "良好") / totalCount) * 100) : 0;
res.PassRate = totalCount > 0 ? Math.Round((monitorList.Count(x => x.Rank == "及格") / totalCount) * 100) : 0;
res.FailRate = totalCount > 0 ? Math.Round((monitorList.Count(x => x.Rank == "不及格") / totalCount) * 100) : 0;
if (res != null)
{
// 计算并调整最后一个百分比确保总和为100%
double sum = (res.ExcellentRate + res.FineRate + res.PassRate + res.FailRate);
double adjustment = 100 - sum;
res.FailRate += (totalCount > 0 ? adjustment : 0);
}
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 }
};
res.TestResultAvg = 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)
.ToList();
return filteredResults.Any() ? filteredResults.Average() : 0;
}
);
float CalculatePassRate(int passed, int total) => total > 0 ? (float)Math.Truncate((double)passed / total * 100) / 100 : 0;
var teacherIds = sportsTestResults.Select(x => x.TeacherId).Distinct().ToList();
var teacherList = await _teacherRepository.FindAsync(x => teacherIds.Contains(x.Id));
var classList = gradeModel.SelectMany(x => x.ClassList).ToList();
var classTeachingTimes = await (
from r in _gradeRepository.DbContext.Set<Ai_ClassRoomRecord>()
where classList.Select(c => c.Id).Contains(r.ClassId)
group r by r.ClassId into g
select new
{
ClassId = g.Key,
TeachingTimes = g.Count()
}
).ToDictionaryAsync(x => x.ClassId, x => x.TeachingTimes);
foreach (var c in classList)
{
// 从班级中获取学生人数
var studentCount = gradeModel
.SelectMany(g => g.ClassList)
.FirstOrDefault(cl => cl.Id == c.Id)?
.StudentCount ?? 0;
// 从字典中获取当前班级的教学次数
var accumulatedTeachingTimes = classTeachingTimes.TryGetValue(c.Id, out var teachingTimes) ? teachingTimes : 0;
var classResults = monitorList.Where(x => x.ClassId == c.Id).ToList();
var totalFemale = classResults.Count(x => x.Sex == SexType.Female);
var totalMale = classResults.Count(x => x.Sex == SexType.Male);
var totalPassedFemale = classResults.Count(x => x.Sex == SexType.Female && x.Rank != "不及格");
var totalPassedMale = classResults.Count(x => x.Sex == SexType.Male && x.Rank != "不及格");
var totalPassedOverall = classResults.Count(x => x.Rank != "不及格");
var totalClassResults = classResults.Count();
var sexAndOverall = new SexAndOverall
{
FemaleValue = CalculatePassRate(totalPassedFemale, totalFemale),
MaleValue = CalculatePassRate(totalPassedMale, totalMale),
OverallValue = CalculatePassRate(totalPassedOverall, totalClassResults)
};
res.TestResultPassRate.AxisX.Add($"{c.GradeName}{c.ClassName}");
res.TestResultPassRate.AxisY.Add(sexAndOverall);
var classResult = classResults.FirstOrDefault();
var teacher = teacherList.FirstOrDefault(x => x.Id == classResult?.TeacherId);
res.GradeDetailsList.Add(new GradeDetails()
{
ClassId = c.Id,
GradeAndClassName = $"{c.GradeName}-{c.ClassName}",
Count = studentCount,
TeacherName = teacher?.TeacherName,
AccumulatedTeachingTimes = accumulatedTeachingTimes,
PassRate = CalculatePassRate(totalPassedOverall, totalClassResults)
});
}
return res;
}
/// <summary>
/// 获取年级关联的测试项目
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
public async Task<List<CategoryModel>> GetCategoryList(GradeDataStatsParam paramDto)
{
var res = await (
from g in _gradeRepository.DbContext.Set<S_GradeAssocCategory>()
join s in _gradeRepository.DbContext.Set<N_SportsTestCategory>() on g.CategoryValue equals s.CategoryValue
where g.GradeId == paramDto.GradeId
select new CategoryModel
{
CategoryId = g.CategoryValue,
CategoryName = s.CategoryName,
}).ToListAsync();
return res;
}
/// <summary>
/// 各体测项目等级占比
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<Dictionary<string, float>> CategoryRankRatio(CategoryParam paramDto)
{
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x =>
x.SchoolCode.Equals(UserContext.Current.TenantId) &&
(paramDto.GradeId <= 0 || x.GradeId == paramDto.GradeId) &&
x.CategoryValue == paramDto.CategoryValue
, paramDto.Mode);
var result = sportsTestResults
.Where(x => x.CategoryValue == paramDto.CategoryValue && !string.IsNullOrWhiteSpace(x.Rank))
.GroupBy(x => x.Rank)
.ToDictionary(g => g.Key, g => (float)g.Count());
int totalCount = sportsTestResults.Count();
if (totalCount == 0)
{
return new Dictionary<string, float>();
}
if (paramDto.CategoryValue == (int)SportsTestItemType.BMI)
{
var newResult = new Dictionary<string, float>();
foreach (var kvp in result)
{
string newRank = kvp.Key switch
{
"正常" => "优秀",
"超重" => "及格",
"偏瘦" => "良好",
"肥胖" => "不及格",
_ => kvp.Key
};
if (newResult.ContainsKey(newRank))
{
newResult[newRank] += kvp.Value;
}
else
{
newResult[newRank] = kvp.Value;
}
}
result = newResult;
}
// 计算比例
foreach (var key in result.Keys.ToList())
{
result[key] = (float)Math.Round(result[key] / totalCount, 2);
}
if (result != null)
{
// 计算并调整最后一个百分比确保总和为100%
var sum = result.Values.Sum(c => c);
var adjustment = 1 - sum;
var lastKey = result.Keys.Last();
result[lastKey] += adjustment;
}
return result;
}
/// <summary>
/// 成绩趋势
/// </summary>
/// <param name="paramDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<VariousSportsProportion> ResultTrends(GradeResultTrendsParam paramDto)
{
// 从缓存中获取数据
var sportsTestResults = await _cacheQueryService.GeSportsTestDataCacheAsync(x =>
(paramDto.GradeId <= 0 || x.GradeId == paramDto.GradeId) &&
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;
}
}
}