using LiteCqrs; using Microsoft.EntityFrameworkCore; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Common.Models; namespace TeleWave.Application.Programming.Groups.DeleteGroup; public sealed class DeleteGroupCommandHandler(IAppDbContext dbContext) : ICommandHandler { public async Task Handle( DeleteGroupCommand command, CancellationToken cancellationToken ) { var group = await dbContext.Groups.FirstOrDefaultAsync( g => g.Id == command.GroupId, cancellationToken ); if (group is null) return Result.Failure(GroupErrors.NotFound); // Слот и врезка стыка держат группу внешним ключом с Restrict — без этой проверки удаление // упало бы исключением БД вместо управляемой ошибки. var used = await dbContext.Slots.AnyAsync(s => s.GroupId == group.Id, cancellationToken) || await dbContext.JunctionElements.AnyAsync( e => e.GroupId == group.Id, cancellationToken ); // Сам факт занятости и адрес — разные вопросы: адрес собирается по цепочке слой → шаблон → // канал, и если она неполна, имени не будет, а запрещать удаление всё равно надо. if (used) return Result.Failure( GroupErrors.InUse(await UsedByAsync(group.Id, cancellationToken)) ); await ReleaseFallbackAsync(group.Id, cancellationToken); dbContext.Groups.Remove(group); return Result.Success(); } /// /// Снимает группу с шаблонов, где она стоит аварийной. Внешнего ключа у этой ссылки нет, и /// висячая проявилась бы только в эфире — генератор молча остался бы без замены для пустого /// слота. Блокировать удаление ею мы намеренно не будем: слот и врезка — это осознанная /// настройка сетки, а аварийную группу проставляет и автосборка, и снять её было нечем. /// /// Ревизия поднимается: конфигурация эфира изменилась, и лента должна пересобраться — /// как и от любой другой правки шаблона. /// private async Task ReleaseFallbackAsync(Guid groupId, CancellationToken cancellationToken) { var templates = await dbContext .ScheduleTemplates.Where(t => t.FallbackGroupId == groupId) .ToListAsync(cancellationToken); foreach (var template in templates) { template.SetFallbackGroup(null); template.MarkChanged(); } } /// Кто держит группу: слоты каналов и врезки стыков. private async Task UsedByAsync(Guid groupId, CancellationToken cancellationToken) { var slots = await ( from slot in dbContext.Slots join layer in dbContext.GridLayers on slot.LayerId equals layer.Id join template in dbContext.ScheduleTemplates on layer.TemplateId equals template.Id join channel in dbContext.Channels on template.ChannelId equals channel.Id where slot.GroupId == groupId select channel.Name ) .Distinct() .ToListAsync(cancellationToken); var junctions = await ( from element in dbContext.JunctionElements join junction in dbContext.JunctionTemplates on element.JunctionTemplateId equals junction.Id where element.GroupId == groupId select junction.Name ) .Distinct() .ToListAsync(cancellationToken); return UsageText.Join( UsageText.Part("слоты каналов", slots), UsageText.Part("врезки стыков", junctions) ); } }