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 /// /// 获取年级列表 /// /// public async Task> GetGradeList() { var tenantId = UserContext.Current.TenantId; var teacherClassIds = new List(); var gradeIds = new List(); 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() join c in _teacherRepository.DbContext.Set() 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() join a in _gradeRepository.DbContext.Set() 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().Where(x => x.SchoolCode == tenantId && (!isTeacher || teacherClassIds.Contains(x.Id))).ToListAsync(); var studentList = await _gradeRepository.DbContext.Set().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; } /// /// 获取所有年级名称 /// /// public async Task> GetGradeNames() { var tenantId = UserContext.Current.TenantId; var teacherClassIds = new List(); var gradeIds = new List(); 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() join c in _teacherRepository.DbContext.Set() 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() join a in _gradeRepository.DbContext.Set() 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().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; } /// /// 获取所有性质年级名称 /// /// public async Task> GetNatureGradeNames() { var grades = await ( from g in _gradeRepository.DbContext.Set() join a in _gradeRepository.DbContext.Set() on g.Id equals a.GradeId join snag in _gradeRepository.DbContext.Set() on g.Id equals snag.GradeId into snagGroup from snag in snagGroup.DefaultIfEmpty() join sn in _gradeRepository.DbContext.Set() on snag.NatureId equals sn.Id into snGroup from sn in snGroup.DefaultIfEmpty() join c in _gradeRepository.DbContext.Set() 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 GradeWholeDataStats(GradeDataStatsParam paramDto) { var res = new GradeDataStatsModel(); var tenantId = UserContext.Current.TenantId; var baseQuery = await ( from c in _gradeRepository.DbContext.Set() join s in _gradeRepository.DbContext.Set() 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() 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; } /// /// 获取年级关联的测试项目 /// /// /// public async Task> GetCategoryList(GradeDataStatsParam paramDto) { var res = await ( from g in _gradeRepository.DbContext.Set() join s in _gradeRepository.DbContext.Set() on g.CategoryValue equals s.CategoryValue where g.GradeId == paramDto.GradeId select new CategoryModel { CategoryId = g.CategoryValue, CategoryName = s.CategoryName, }).ToListAsync(); return res; } /// /// 各体测项目等级占比 /// /// /// /// public async Task> 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(); } if (paramDto.CategoryValue == (int)SportsTestItemType.BMI) { var newResult = new Dictionary(); 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; } /// /// 成绩趋势 /// /// /// /// public async Task 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; } } }