Updated the DeleteGroupCommandHandler to release fallback group references during group deletion, ensuring that groups can be removed even when used as fallback in templates. Adjusted related tests to verify the correct behavior of the deletion process, confirming that fallback references are cleared appropriately. Enhanced localization strings to provide clearer context for fallback group functionality in both English and Russian.
97 lines
4.4 KiB
C#
97 lines
4.4 KiB
C#
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<DeleteGroupCommand, Result>
|
|
{
|
|
public async Task<Result> 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Снимает группу с шаблонов, где она стоит аварийной. Внешнего ключа у этой ссылки нет, и
|
|
/// висячая проявилась бы только в эфире — генератор молча остался бы без замены для пустого
|
|
/// слота. Блокировать удаление ею мы намеренно не будем: слот и врезка — это осознанная
|
|
/// настройка сетки, а аварийную группу проставляет и автосборка, и снять её было нечем.
|
|
///
|
|
/// Ревизия поднимается: конфигурация эфира изменилась, и лента должна пересобраться —
|
|
/// как и от любой другой правки шаблона.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>Кто держит группу: слоты каналов и врезки стыков.</summary>
|
|
private async Task<string> 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)
|
|
);
|
|
}
|
|
}
|