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