using LiteCqrs; using Microsoft.EntityFrameworkCore; using TeleWave.Application.Broadcast; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Common.Models; using TeleWave.Domain.Programming; namespace TeleWave.Application.Programming.Templates.CopyTemplate; public sealed class CopyTemplateCommandHandler(IAppDbContext dbContext) : ICommandHandler> { public async Task> Handle( CopyTemplateCommand command, CancellationToken cancellationToken ) { var target = await dbContext.Channels.FirstOrDefaultAsync( c => c.Id == command.TargetChannelId, cancellationToken ); if (target is null) return Result.Failure(ChannelErrors.NotFound); var source = await dbContext .ScheduleTemplates.AsNoTracking() .Include(t => t.Layers) .ThenInclude(l => l.Slots) .AsSplitQuery() .FirstOrDefaultAsync(t => t.ChannelId == command.SourceChannelId, cancellationToken); if (source is null) return Result.Failure(ChannelErrors.TemplateNotFound); // Прежняя сетка приёмника заменяется целиком: слить две сетки автоматически нельзя, // а «добавить поверх» дало бы кашу из пересекающихся слотов. var existing = await dbContext .ScheduleTemplates.Where(t => t.ChannelId == target.Id) .ToListAsync(cancellationToken); dbContext.ScheduleTemplates.RemoveRange(existing); var copyTemplate = ScheduleTemplate.Create(target.Id, source.Name); copyTemplate.SetFallbackGroup(source.FallbackGroupId); copyTemplate.SetRules(source.RulesJson); // Стыки и заставки общие для всех каналов — копия ссылается на те же, без перевешивания. copyTemplate.SetDefaultJunction(source.DefaultJunctionId); var (layers, slots) = CopyGrid(source, copyTemplate); dbContext.ScheduleTemplates.Add(copyTemplate); target.SetTemplate(copyTemplate.Id); return Result.Success(new CopyTemplateResultDto(layers, slots)); } /// Переносит слои со слотами. Возвращает, сколько слоёв (кроме фонового) и слотов скопировано. private static (int Layers, int Slots) CopyGrid( ScheduleTemplate source, ScheduleTemplate copyTemplate ) { var layers = 0; var slots = 0; foreach (var layer in source.Layers.OrderByDescending(l => l.Priority)) { // Фоновый слой у нового шаблона уже есть — в него переносим слоты, а не заводим второй. var copyLayer = layer.IsBackground ? copyTemplate.Background! : copyTemplate.AddLayer(layer.Name, layer.Priority); copyLayer.Update(layer.Name, layer.Priority, layer.ApplicabilityJson, layer.IsEnabled); if (!layer.IsBackground) layers++; foreach (var slot in layer.Slots) { CopySlot(slot, copyLayer); slots++; } } return (layers, slots); } private static void CopySlot(Slot slot, GridLayer copyLayer) { var copySlot = copyLayer.AddSlot( slot.Title, slot.TargetStart, slot.TargetDurationMinutes, slot.Daypart, slot.SlotKind, slot.Weekday ); copySlot.UpdateTiming( slot.Weekday, slot.TargetStart, slot.TargetDurationMinutes, slot.Daypart, slot.IsAnchor, slot.MaxDriftMinutes, slot.SnapToMinutes ); copySlot.UpdateContent( new SlotContent( slot.Title, slot.SlotKind, slot.GroupId, slot.StrategyJson, slot.RepeatSourceJson, slot.BlockMode, slot.BlockValue, slot.OverflowPolicy, slot.JunctionBetweenId, slot.JunctionAfterId ) ); } }