update:一键升学&批量删除学生&swagger
This commit is contained in:
parent
3932c63fad
commit
96369d9095
@ -62,6 +62,13 @@ namespace VOL.Business.IServices.School
|
||||
/// <returns></returns>
|
||||
Task UpdateStudentPwd(UpdateStudentPwdParam paramDto);
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除当前学校学生
|
||||
/// </summary>
|
||||
/// <param name="ids">学生Id</param>
|
||||
/// <returns>实际删除数量</returns>
|
||||
Task<int> BatchDeleteStudents(List<int> ids);
|
||||
|
||||
/// <summary>
|
||||
/// 转班
|
||||
/// </summary>
|
||||
@ -169,5 +176,20 @@ namespace VOL.Business.IServices.School
|
||||
/// <param name="studentNo"></param>
|
||||
/// <returns></returns>
|
||||
Task ReplaceClasses(List<string> studentNoList, int classId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学校一键升学状态
|
||||
/// </summary>
|
||||
Task<StudentPromotionStatusModel> GetPromotionStatus();
|
||||
|
||||
/// <summary>
|
||||
/// 当前学校学生一键升学
|
||||
/// </summary>
|
||||
Task<StudentPromotionStatusModel> PromoteStudents();
|
||||
|
||||
/// <summary>
|
||||
/// 撤回当前学校最近一次升学
|
||||
/// </summary>
|
||||
Task<StudentPromotionStatusModel> UndoPromotion();
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,6 +420,47 @@ namespace VOL.Business.Services.School
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
@ -1324,5 +1365,434 @@ namespace VOL.Business.Services.School
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8294
VOL.Core/Migrations/20260722034330_20260722StudentPromotion.Designer.cs
generated
Normal file
8294
VOL.Core/Migrations/20260722034330_20260722StudentPromotion.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace VOL.Core.Migrations
|
||||
{
|
||||
public partial class _20260722StudentPromotion : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "S_StudentPromotionBatch",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
SchoolCode = table.Column<string>(type: "nvarchar(100)", nullable: true),
|
||||
SchoolNatureId = table.Column<int>(type: "int", nullable: false),
|
||||
SchoolNatureName = table.Column<string>(type: "nvarchar(100)", nullable: true),
|
||||
AcademicYear = table.Column<string>(type: "nvarchar(20)", nullable: true),
|
||||
Status = table.Column<int>(type: "int", nullable: false),
|
||||
PromotedCount = table.Column<int>(type: "int", nullable: false),
|
||||
GraduatedCount = table.Column<int>(type: "int", nullable: false),
|
||||
OperatorId = table.Column<int>(type: "int", nullable: false),
|
||||
CreateDate = table.Column<DateTime>(type: "datetime", nullable: false),
|
||||
RevokeOperatorId = table.Column<int>(type: "int", nullable: true),
|
||||
RevokeDate = table.Column<DateTime>(type: "datetime", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_S_StudentPromotionBatch", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "S_StudentPromotionDetail",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: false),
|
||||
StudentId = table.Column<int>(type: "int", nullable: false),
|
||||
OriginalClassId = table.Column<int>(type: "int", nullable: false),
|
||||
OriginalClassName = table.Column<string>(type: "nvarchar(100)", nullable: true),
|
||||
OriginalStatus = table.Column<int>(type: "int", nullable: false),
|
||||
OriginalAge = table.Column<int>(type: "int", nullable: false),
|
||||
TargetClassId = table.Column<int>(type: "int", nullable: false),
|
||||
TargetClassName = table.Column<string>(type: "nvarchar(100)", nullable: true),
|
||||
TargetStatus = table.Column<int>(type: "int", nullable: false),
|
||||
TargetAge = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_S_StudentPromotionDetail", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_S_StudentPromotionBatch_SchoolCode_AcademicYear",
|
||||
table: "S_StudentPromotionBatch",
|
||||
columns: new[] { "SchoolCode", "AcademicYear" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_S_StudentPromotionDetail_BatchId",
|
||||
table: "S_StudentPromotionDetail",
|
||||
column: "BatchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_S_StudentPromotionDetail_StudentId",
|
||||
table: "S_StudentPromotionDetail",
|
||||
column: "StudentId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "S_StudentPromotionBatch");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "S_StudentPromotionDetail");
|
||||
}
|
||||
}
|
||||
}
|
||||
8297
VOL.Core/Migrations/20260722083736_20260722PromotionTeacherSnapshot.Designer.cs
generated
Normal file
8297
VOL.Core/Migrations/20260722083736_20260722PromotionTeacherSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace VOL.Core.Migrations
|
||||
{
|
||||
public partial class _20260722PromotionTeacherSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "TeacherSnapshot",
|
||||
table: "S_StudentPromotionBatch",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TeacherSnapshot",
|
||||
table: "S_StudentPromotionBatch");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4490,6 +4490,104 @@ namespace VOL.Core.Migrations
|
||||
b.ToTable("S_Student");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VOL.Entity.DomainModels.S_StudentPromotionBatch", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
|
||||
|
||||
b.Property<string>("AcademicYear")
|
||||
.HasColumnType("nvarchar(20)");
|
||||
|
||||
b.Property<DateTime>("CreateDate")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<int>("GraduatedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("OperatorId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("PromotedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime?>("RevokeDate")
|
||||
.HasColumnType("datetime");
|
||||
|
||||
b.Property<int?>("RevokeOperatorId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SchoolCode")
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("SchoolNatureId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SchoolNatureName")
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TeacherSnapshot")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SchoolCode", "AcademicYear");
|
||||
|
||||
b.ToTable("S_StudentPromotionBatch");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VOL.Entity.DomainModels.S_StudentPromotionDetail", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"), 1L, 1);
|
||||
|
||||
b.Property<long>("BatchId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("OriginalAge")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("OriginalClassId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("OriginalClassName")
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("OriginalStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("StudentId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TargetAge")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("TargetClassId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("TargetClassName")
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int>("TargetStatus")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BatchId");
|
||||
|
||||
b.HasIndex("StudentId");
|
||||
|
||||
b.ToTable("S_StudentPromotionDetail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VOL.Entity.DomainModels.S_SubmittedAssignments", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using VOL.Entity.SystemModels;
|
||||
|
||||
namespace VOL.Entity.DomainModels
|
||||
{
|
||||
[Table("S_StudentPromotionBatch")]
|
||||
[Index(nameof(SchoolCode), nameof(AcademicYear))]
|
||||
[Entity(TableCnName = "学生升学批次表", TableName = "S_StudentPromotionBatch")]
|
||||
public class S_StudentPromotionBatch : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public long Id { get; set; }
|
||||
|
||||
[Column(TypeName = "nvarchar(100)")]
|
||||
public string SchoolCode { get; set; }
|
||||
|
||||
public int SchoolNatureId { get; set; }
|
||||
|
||||
[Column(TypeName = "nvarchar(100)")]
|
||||
public string SchoolNatureName { get; set; }
|
||||
|
||||
[Column(TypeName = "nvarchar(20)")]
|
||||
public string AcademicYear { get; set; }
|
||||
|
||||
/// <summary>1: 已升学,2: 已撤回。</summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
public int PromotedCount { get; set; }
|
||||
|
||||
public int GraduatedCount { get; set; }
|
||||
|
||||
[Column(TypeName = "nvarchar(max)")]
|
||||
public string TeacherSnapshot { get; set; }
|
||||
|
||||
public int OperatorId { get; set; }
|
||||
|
||||
[Column(TypeName = "datetime")]
|
||||
public DateTime CreateDate { get; set; }
|
||||
|
||||
public int? RevokeOperatorId { get; set; }
|
||||
|
||||
[Column(TypeName = "datetime")]
|
||||
public DateTime? RevokeDate { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using VOL.Entity.Enum;
|
||||
using VOL.Entity.SystemModels;
|
||||
|
||||
namespace VOL.Entity.DomainModels
|
||||
{
|
||||
[Table("S_StudentPromotionDetail")]
|
||||
[Index(nameof(BatchId))]
|
||||
[Index(nameof(StudentId))]
|
||||
[Entity(TableCnName = "学生升学明细表", TableName = "S_StudentPromotionDetail")]
|
||||
public class S_StudentPromotionDetail : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public long Id { get; set; }
|
||||
|
||||
public long BatchId { get; set; }
|
||||
|
||||
public int StudentId { get; set; }
|
||||
|
||||
public int OriginalClassId { get; set; }
|
||||
|
||||
[Column(TypeName = "nvarchar(100)")]
|
||||
public string OriginalClassName { get; set; }
|
||||
|
||||
public StudentStatus OriginalStatus { get; set; }
|
||||
|
||||
public int OriginalAge { get; set; }
|
||||
|
||||
public int TargetClassId { get; set; }
|
||||
|
||||
[Column(TypeName = "nvarchar(100)")]
|
||||
public string TargetClassName { get; set; }
|
||||
|
||||
public StudentStatus TargetStatus { get; set; }
|
||||
|
||||
public int TargetAge { get; set; }
|
||||
}
|
||||
}
|
||||
27
VOL.Model/School/Response/StudentPromotionStatusModel.cs
Normal file
27
VOL.Model/School/Response/StudentPromotionStatusModel.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace VOL.Model.School.Response
|
||||
{
|
||||
public class StudentPromotionStatusModel
|
||||
{
|
||||
public string SchoolNature { get; set; }
|
||||
|
||||
public string AcademicYear { get; set; }
|
||||
|
||||
public bool CanPromote { get; set; }
|
||||
|
||||
public string CannotPromoteReason { get; set; }
|
||||
|
||||
public bool CanUndo { get; set; }
|
||||
|
||||
public string CannotUndoReason { get; set; }
|
||||
|
||||
public int StudentCount { get; set; }
|
||||
|
||||
public int PromotedCount { get; set; }
|
||||
|
||||
public int GraduatedCount { get; set; }
|
||||
|
||||
public DateTime? PromotionTime { get; set; }
|
||||
}
|
||||
}
|
||||
@ -115,6 +115,17 @@ namespace VOL.WebApi.Controllers.Business
|
||||
return Ok("更新成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除当前学校学生
|
||||
/// </summary>
|
||||
/// <param name="ids">学生Id列表</param>
|
||||
[HttpPost(nameof(BatchDeleteStudents))]
|
||||
public async Task<ActionResult> BatchDeleteStudents([FromBody] List<int> ids)
|
||||
{
|
||||
var deletedCount = await _studentService.BatchDeleteStudents(ids);
|
||||
return Ok($"成功删除{deletedCount}名学生");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转班
|
||||
/// </summary>
|
||||
@ -301,5 +312,32 @@ namespace VOL.WebApi.Controllers.Business
|
||||
await _studentService.ReplaceClasses(studentNoList, classId);
|
||||
return Ok("修改成功");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前学校一键升学状态
|
||||
/// </summary>
|
||||
[HttpGet(nameof(GetPromotionStatus))]
|
||||
public async Task<StudentPromotionStatusModel> GetPromotionStatus()
|
||||
{
|
||||
return await _studentService.GetPromotionStatus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前学校学生一键升学
|
||||
/// </summary>
|
||||
[HttpPost(nameof(PromoteStudents))]
|
||||
public async Task<StudentPromotionStatusModel> PromoteStudents()
|
||||
{
|
||||
return await _studentService.PromoteStudents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 撤回当前学校最近一次升学
|
||||
/// </summary>
|
||||
[HttpPost(nameof(UndoPromotion))]
|
||||
public async Task<StudentPromotionStatusModel> UndoPromotion()
|
||||
{
|
||||
return await _studentService.UndoPromotion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
VOL.WebApi/Download/Template/summaryOfGrades_import_sample.xlsx
Normal file
BIN
VOL.WebApi/Download/Template/summaryOfGrades_import_sample.xlsx
Normal file
Binary file not shown.
@ -318,18 +318,18 @@ namespace VOL.WebApi
|
||||
//配置HttpContext
|
||||
app.UseStaticHttpContext();
|
||||
|
||||
//app.UseSwagger();
|
||||
//app.UseSwaggerUI(c =>
|
||||
//{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
// //2个下拉框选项 选择对应的文档
|
||||
// //c.SwaggerEndpoint("/swagger/v1/swagger.json", "VOL.Core后台Api");
|
||||
// //c.SwaggerEndpoint("/swagger/v2/swagger.json", "测试第三方Api");
|
||||
// c.SwaggerEndpoint("/swagger/v3/swagger.json", "SmartSportsServer-Api");
|
||||
c.SwaggerEndpoint("/swagger/v3/swagger.json", "SmartSportsServer-Api");
|
||||
// //c.SwaggerEndpoint("/swagger/v4/swagger.json", "物联网-Api");
|
||||
// //c.SwaggerEndpoint("/swagger/v5/swagger.json", "Ai-Api");
|
||||
// c.SwaggerEndpoint("/swagger/v6/swagger.json", "AiApp-Api");
|
||||
// c.RoutePrefix = "";
|
||||
//});
|
||||
c.SwaggerEndpoint("/swagger/v6/swagger.json", "AiApp-Api");
|
||||
c.RoutePrefix = "swagger";
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user