update:一键升学&批量删除学生&swagger

This commit is contained in:
ltx 2026-07-23 15:50:43 +08:00
parent 3932c63fad
commit 96369d9095
13 changed files with 17451 additions and 8 deletions

View File

@ -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();
}
}

View File

@ -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; }
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -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")

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View 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; }
}
}

View File

@ -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();
}
}
}

View File

@ -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();