diff --git a/VOL.Business/IServices/SportsHall/ISportsHallService.cs b/VOL.Business/IServices/SportsHall/ISportsHallService.cs
index 288da11..f70e559 100644
--- a/VOL.Business/IServices/SportsHall/ISportsHallService.cs
+++ b/VOL.Business/IServices/SportsHall/ISportsHallService.cs
@@ -27,6 +27,11 @@ namespace VOL.Business.IServices.SportsHall
///
Task ModifyClass(ModifySportsHallClassParam paramDto);
+ ///
+ /// 批量删除当前学校的运动馆空班级
+ ///
+ Task BatchDeleteClasses(List ids);
+
///
/// 导入运动馆学生体测成绩
///
diff --git a/VOL.Business/Services/School/S_StudentService.cs b/VOL.Business/Services/School/S_StudentService.cs
index 3cef4d4..9be2b33 100644
--- a/VOL.Business/Services/School/S_StudentService.cs
+++ b/VOL.Business/Services/School/S_StudentService.cs
@@ -440,6 +440,17 @@ namespace VOL.Business.Services.School
if (UserContext.Current.RoleId != 1 && UserContext.Current.RoleId != 2)
throw new Exception("仅管理员可以批量删除学生");
+ if (UserContext.Current.RoleId != 1)
+ {
+ var menuIds = await _studentRepository.DbContext.Set()
+ .Where(menu => menu.Url == "/studentsManage" && menu.ParentId > 0)
+ .Select(menu => menu.Menu_Id)
+ .ToListAsync();
+ if (!UserContext.Current.Permissions.Any(permission =>
+ menuIds.Contains(permission.Menu_Id) && permission.UserAuthArr.Contains("BatchDelete")))
+ throw new Exception("没有批量删除学生的权限");
+ }
+
var studentIds = ids?.Where(x => x > 0).Distinct().ToList() ?? new List();
if (studentIds.Count == 0)
throw new ArgumentException("请至少选择一名学生");
diff --git a/VOL.Business/Services/SportsHall/SportsHallService.cs b/VOL.Business/Services/SportsHall/SportsHallService.cs
index 2247046..499e104 100644
--- a/VOL.Business/Services/SportsHall/SportsHallService.cs
+++ b/VOL.Business/Services/SportsHall/SportsHallService.cs
@@ -6,6 +6,7 @@ using Newtonsoft.Json;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using System;
using System.Collections.Generic;
+using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -40,6 +41,7 @@ namespace VOL.Business.Services.SportsHall
private readonly IS_GradeRepository _gradeRepository;
private readonly IS_ClassAssocTeacherRepository _classAssocTeacherRepository;
private readonly IS_StudentRepository _studentRepository;
+ private readonly IotDataSyncService _iotDataSyncService;
IN_HealthStandardsRepository _healthStandardsRepository;
IN_SportsTestResultRepository _sportsTestResultRepository;
@@ -48,6 +50,7 @@ namespace VOL.Business.Services.SportsHall
IS_GradeRepository gradeRepository,
IS_ClassAssocTeacherRepository classAssocTeacherRepository,
IS_StudentRepository studentRepository,
+ IotDataSyncService iotDataSyncService,
IN_HealthStandardsRepository healthStandardsRepository,
IN_SportsTestResultRepository sportsTestResultRepository)
{
@@ -55,6 +58,7 @@ namespace VOL.Business.Services.SportsHall
_gradeRepository = gradeRepository;
_classAssocTeacherRepository = classAssocTeacherRepository;
_studentRepository = studentRepository;
+ _iotDataSyncService = iotDataSyncService;
_healthStandardsRepository = healthStandardsRepository;
_sportsTestResultRepository = sportsTestResultRepository;
}
@@ -221,6 +225,68 @@ namespace VOL.Business.Services.SportsHall
}
}
+ public async Task BatchDeleteClasses(List ids)
+ {
+ var user = UserContext.Current;
+ var classIds = ids?.Where(id => id > 0).Distinct().ToList() ?? new List();
+ if (classIds.Count == 0 || classIds.Count > 100)
+ throw new ArgumentException("请选择1至100个班级");
+
+ if (user.RoleId != 1)
+ {
+ var menuIds = await _classRepository.DbContext.Set()
+ .Where(menu => menu.Url == "/sportsHallClass" && menu.ParentId > 0)
+ .Select(menu => menu.Menu_Id)
+ .ToListAsync();
+ if (!user.Permissions.Any(permission =>
+ menuIds.Contains(permission.Menu_Id) && permission.UserAuthArr.Contains("BatchDelete")))
+ throw new Exception("没有批量删除运动馆班级的权限");
+ }
+
+ var gradeId = await _gradeRepository.FindAsIQueryable(grade => grade.GradeName == "运动馆")
+ .Select(grade => grade.Id)
+ .FirstOrDefaultAsync();
+ if (gradeId == 0)
+ throw new Exception("未找到运动馆年级");
+
+ await using var transaction = await _classRepository.DbContext.Database.BeginTransactionAsync(IsolationLevel.Serializable);
+ var classes = await _classRepository.DbContext.Set()
+ .Where(item => item.SchoolCode == user.TenantId && item.GradeId == gradeId && classIds.Contains(item.Id))
+ .ToListAsync();
+ if (classes.Count != classIds.Count)
+ throw new Exception("部分班级不存在或不属于当前学校,请刷新列表后重试");
+
+ var occupiedClassIds = await _classRepository.DbContext.Set()
+ .Where(student => classIds.Contains(student.ClassId))
+ .Select(student => student.ClassId)
+ .Distinct()
+ .ToListAsync();
+ if (occupiedClassIds.Count > 0)
+ {
+ var names = classes.Where(item => occupiedClassIds.Contains(item.Id)).Select(item => item.ClassName);
+ throw new Exception($"班级 {string.Join("、", names)} 仍有学生,请先调整学生后再删除");
+ }
+
+ var teacherAssociations = await _classRepository.DbContext.Set()
+ .Where(item => classIds.Contains(item.ClassId))
+ .ToListAsync();
+ _classRepository.DbContext.RemoveRange(teacherAssociations);
+ _classRepository.DbContext.RemoveRange(classes);
+ await _classRepository.SaveChangesAsync();
+ await transaction.CommitAsync();
+
+ foreach (var item in classes)
+ {
+ await _iotDataSyncService.DataSyncCallBack(new DataSyncCallBackParam
+ {
+ EventType = EventType.Delete.GetDisplayName(),
+ DataType = IOTDataSyncType.Class.GetDisplayName(),
+ Json = JsonConvert.SerializeObject(item)
+ });
+ }
+ return classes.Count;
+ }
+
///
/// 导入运动馆学生体测成绩
///
diff --git a/VOL.WebApi/Controllers/SportsHall/SportsHallController.cs b/VOL.WebApi/Controllers/SportsHall/SportsHallController.cs
index 201697d..d10b35f 100644
--- a/VOL.WebApi/Controllers/SportsHall/SportsHallController.cs
+++ b/VOL.WebApi/Controllers/SportsHall/SportsHallController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using VOL.Business.IServices.School;
using VOL.Business.IServices.SportsHall;
@@ -54,6 +55,16 @@ namespace VOL.WebApi.Controllers.SportsHall
return Ok("更新成功");
}
+ ///
+ /// 批量删除当前学校的运动馆空班级
+ ///
+ [HttpPost(nameof(BatchDeleteClasses))]
+ public async Task BatchDeleteClasses([FromBody] List ids)
+ {
+ var deletedCount = await _sportsHallService.BatchDeleteClasses(ids);
+ return Ok($"成功删除{deletedCount}个班级");
+ }
+
///
/// 导入运动馆学生体测数据
diff --git a/batch-delete-menu-permissions.sql b/batch-delete-menu-permissions.sql
new file mode 100644
index 0000000..eb873d4
--- /dev/null
+++ b/batch-delete-menu-permissions.sql
@@ -0,0 +1,49 @@
+SET XACT_ABORT ON;
+BEGIN TRANSACTION;
+
+DECLARE @menuUrl nvarchar(100);
+DECLARE @menuId int;
+DECLARE menu_cursor CURSOR LOCAL FAST_FORWARD FOR
+ SELECT Url FROM (VALUES (N'/studentsManage'), (N'/sportsHallClass')) AS menus(Url);
+
+OPEN menu_cursor;
+FETCH NEXT FROM menu_cursor INTO @menuUrl;
+WHILE @@FETCH_STATUS = 0
+BEGIN
+ IF (SELECT COUNT(*) FROM Sys_Menu
+ WHERE Url = @menuUrl AND ParentId > 0 AND MenuType = 0 AND Enable IN (1, 2)) <> 1
+ THROW 51001, N'页面菜单不存在或不唯一,请检查菜单配置', 1;
+
+ SELECT @menuId = Menu_Id FROM Sys_Menu
+ WHERE Url = @menuUrl AND ParentId > 0 AND MenuType = 0 AND Enable IN (1, 2);
+
+ IF EXISTS (SELECT 1 FROM Sys_Menu WHERE Menu_Id = @menuId
+ AND NULLIF(LTRIM(RTRIM(Auth)), N'') IS NOT NULL
+ AND (ISJSON(Auth) <> 1 OR LEFT(LTRIM(Auth), 1) <> N'['))
+ THROW 51002, N'菜单权限不是有效 JSON 数组,请先检查原始数据', 1;
+
+ UPDATE Sys_Menu
+ SET Auth = COALESCE(NULLIF(Auth, N''), N'[]')
+ WHERE Menu_Id = @menuId AND (Auth IS NULL OR Auth = N'');
+
+ UPDATE menu
+ SET Auth = JSON_MODIFY(menu.Auth, 'append $',
+ JSON_QUERY(N'{"text":"批量删除","value":"BatchDelete"}'))
+ FROM Sys_Menu AS menu
+ WHERE menu.Menu_Id = @menuId
+ AND NOT EXISTS (SELECT 1 FROM OPENJSON(menu.Auth)
+ WITH (Value nvarchar(100) '$.value') AS action
+ WHERE action.Value = N'BatchDelete');
+
+ FETCH NEXT FROM menu_cursor INTO @menuUrl;
+END;
+CLOSE menu_cursor;
+DEALLOCATE menu_cursor;
+
+COMMIT TRANSACTION;
+
+SELECT MenuName, Url, Auth
+ FROM Sys_Menu
+ WHERE Url IN (N'/studentsManage', N'/sportsHallClass')
+ AND ParentId > 0 AND MenuType = 0;
+GO