Add reset template functionality to channel management
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:
+74
@@ -0,0 +1,74 @@
|
||||
using LiteCqrs;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeleWave.Application.Broadcast;
|
||||
using TeleWave.Application.Common.Interfaces;
|
||||
using TeleWave.Application.Common.Models;
|
||||
|
||||
namespace TeleWave.Application.Programming.Templates.Reset;
|
||||
|
||||
/// <summary>
|
||||
/// Сброс сетки и собранного по ней эфира — «начать канал заново», не пересоздавая его.
|
||||
///
|
||||
/// Прошлое и идущая сейчас запись не трогаются: инвариант общий с генератором — у зрителя нельзя
|
||||
/// вырезать программу из-под носа, а история показов нужна остыванию и потолку повторов.
|
||||
/// Курсоры слотов уходят каскадом вместе со слотами, поэтому сериалы после сброса начинаются
|
||||
/// с начала — это и просят словом «полностью».
|
||||
///
|
||||
/// Снимок последнего применения остаётся: он единственная точка отката, и после сброса «Откатить»
|
||||
/// вернёт сетку. Ленту это не вернёт — её пересоберёт применение.
|
||||
/// </summary>
|
||||
public sealed class ResetTemplateCommandHandler(IAppDbContext dbContext)
|
||||
: ICommandHandler<ResetTemplateCommand, Result<ResetTemplateResultDto>>
|
||||
{
|
||||
public async Task<Result<ResetTemplateResultDto>> Handle(
|
||||
ResetTemplateCommand command,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var template = await dbContext
|
||||
.ScheduleTemplates.Include(t => t.Layers)
|
||||
.ThenInclude(l => l.Slots)
|
||||
.AsSplitQuery()
|
||||
.FirstOrDefaultAsync(t => t.ChannelId == command.ChannelId, cancellationToken);
|
||||
if (template is null)
|
||||
return Result.Failure<ResetTemplateResultDto>(ChannelErrors.TemplateNotFound);
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Удаление ленты идёт мимо change-tracker (ExecuteDelete), снятие слотов — через него:
|
||||
// связывает их в одну операцию только явная транзакция. Лок канала — тот же, что берёт
|
||||
// генератор: фоновый тик не должен дописывать хвост в момент сброса.
|
||||
await using var transaction = await dbContext.BeginTransactionAsync(cancellationToken);
|
||||
await dbContext.AcquireChannelLockAsync(command.ChannelId, cancellationToken);
|
||||
|
||||
var entries = await dbContext
|
||||
.ScheduleEntries.Where(e => e.ChannelId == command.ChannelId && e.StartsAtUtc >= now)
|
||||
.ExecuteDeleteAsync(cancellationToken);
|
||||
|
||||
var slots = 0;
|
||||
var layers = 0;
|
||||
|
||||
foreach (var layer in template.Layers.ToList())
|
||||
{
|
||||
slots += layer.Slots.Count;
|
||||
|
||||
// Нефоновый слой уходит целиком — слоты за ним каскадом. Фоновый удалить нельзя,
|
||||
// поэтому его чистим послотно.
|
||||
if (template.RemoveLayer(layer.Id))
|
||||
{
|
||||
layers++;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var slot in layer.Slots.ToList())
|
||||
layer.RemoveSlot(slot.Id);
|
||||
}
|
||||
|
||||
template.MarkChanged();
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
|
||||
return Result.Success(new ResetTemplateResultDto(slots, layers, entries));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user