Add reset template functionality to channel management
ci / build-backend (push) Successful in 1m35s
ci / build-frontend (push) Successful in 53s
ci / tests (push) Successful in 1m36s
ci / sonar (push) Successful in 4m21s

Implemented a new endpoint for resetting the channel template, which clears the grid and removes future scheduled entries while preserving past and current recordings. Updated the frontend to include a reset button in the channel management interface, along with corresponding localization strings in English and Russian. Enhanced integration tests to verify the reset functionality, ensuring that the expected number of slots, layers, and entries are accurately reported after the reset operation.
This commit is contained in:
Leonid Pershin
2026-07-28 14:19:35 +03:00
parent 3908b6e722
commit 6c85db2d60
10 changed files with 268 additions and 1 deletions
@@ -3,6 +3,7 @@ using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Programming.Templates;
using TeleWave.Application.Programming.Templates.CopyTemplate;
using TeleWave.Application.Programming.Templates.CreateTemplate;
using TeleWave.Application.Programming.Templates.Reset;
using TeleWave.Application.Programming.Templates.Validate;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Programming;
@@ -136,6 +137,84 @@ public sealed class TemplateOperationsIntegrationTests(PostgresFixture fixture)
Assert.Single(verify.GridLayers.Where(l => l.TemplateId == template.Id && l.IsBackground));
}
/// <summary>
/// Сброс: сетка снимается целиком, будущий эфир уходит, прошлое и идущая запись остаются.
/// Проверяется на настоящей БД — команда держит транзакцию с advisory-локом канала и сносит
/// ленту через ExecuteDelete, а этого InMemory не умеет.
/// </summary>
[SkippableFact]
public async Task Reset_ClearsGridAndFutureTape_KeepingPastAndCurrentEntry()
{
Skip.IfNot(fixture.Available, "Docker недоступен");
await using var seedDb = fixture.CreateContext();
var (channelId, _, _) = await SeedPairAsync(seedDb);
var now = DateTimeOffset.UtcNow;
var slotId = seedDb
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.Single(t => t.ChannelId == channelId)
.Layers.SelectMany(l => l.Slots)
.Single()
.Id;
// Курсор слота: он обязан уйти вместе со слотом, иначе сериал продолжится с той же серии.
var state = SlotState.Create(slotId);
state.MoveTo(GroupElementKind.Show, Guid.NewGuid(), 3);
seedDb.SlotStates.Add(state);
Entry(seedDb, channelId, now.AddHours(-2), now.AddHours(-1));
Entry(seedDb, channelId, now.AddMinutes(-10), now.AddMinutes(20));
Entry(seedDb, channelId, now.AddHours(1), now.AddHours(2));
await seedDb.SaveChangesAsync();
await using var db = fixture.CreateContext();
var result = await new ResetTemplateCommandHandler(db).Handle(
new ResetTemplateCommand(channelId),
default
);
Assert.True(result.IsSuccess);
Assert.Equal(1, result.Value.Slots);
Assert.Equal(1, result.Value.Layers);
Assert.Equal(1, result.Value.Entries);
await using var verify = fixture.CreateContext();
var template = verify
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.Single(t => t.ChannelId == channelId);
// Фоновый слой остаётся всегда — удалить его нельзя, и без него сетку нечем закрывать.
var layer = Assert.Single(template.Layers);
Assert.True(layer.IsBackground);
Assert.Empty(layer.Slots);
Assert.True(template.HasPendingChanges);
Assert.False(verify.SlotStates.Any(s => s.SlotId == slotId));
var entries = verify.ScheduleEntries.Where(e => e.ChannelId == channelId).ToList();
Assert.Equal(2, entries.Count);
Assert.All(entries, e => Assert.True(e.StartsAtUtc < now));
}
private static void Entry(
AppDbContext db,
Guid channelId,
DateTimeOffset from,
DateTimeOffset to
) =>
db.ScheduleEntries.Add(
ScheduleEntry.FromSlot(
channelId,
Guid.NewGuid(),
ScheduleEntryKind.Program,
from,
to,
new ScheduleEntryOrigin(null, null, null, null, null)
)
);
/// <summary>Два канала: у источника слой со слотом, группой и стыком; у приёмника — пустая сетка.</summary>
private static async Task<(Guid Source, Guid Target, Guid GroupId)> SeedPairAsync(
AppDbContext db