using Microsoft.EntityFrameworkCore; using TeleWave.Application.Common.Interfaces; using TeleWave.Application.Common.Models; using TeleWave.Domain.Programming; namespace TeleWave.Application.Programming.Templates; /// /// Общая часть создания и правки слота: проверки, которые нельзя доверить валидатору, потому что /// им нужны соседние слоты и справочник групп. /// public sealed class SlotWriter(IAppDbContext dbContext) { /// /// Проверяет вход и применяет его к слоту. = null — проверка перед /// созданием. /// public async Task ApplyAsync( GridLayer layer, Slot? slot, SlotInput input, CancellationToken cancellationToken ) { // Перекрытие внутри слоя запрещено: два слота на одну минуту сделали бы выбор неоднозначным. if ( layer.HasOverlap( input.Weekday, input.TargetStart, input.TargetDurationMinutes, slot?.Id ) ) return Result.Failure(TemplateErrors.SlotsOverlap); if (input.SlotKind == SlotKind.Content) { if (input.GroupId is not { } groupId) return Result.Failure(TemplateErrors.GroupRequired); if (!await dbContext.Groups.AnyAsync(g => g.Id == groupId, cancellationToken)) return Result.Failure(TemplateErrors.GroupNotFound); } if (input.SlotKind == SlotKind.Repeat && input.RepeatSource is null) return Result.Failure(TemplateErrors.RepeatSourceRequired); var target = slot ?? layer.AddSlot( input.Title, input.TargetStart, input.TargetDurationMinutes, input.Daypart, input.SlotKind, input.Weekday ); target.UpdateTiming( input.Weekday, input.TargetStart, input.TargetDurationMinutes, input.Daypart, input.IsAnchor, input.MaxDriftMinutes, input.SnapToMinutes ); target.UpdateContent( new SlotContent( input.Title, input.SlotKind, input.GroupId, input.Strategy?.ToJson(), input.RepeatSource?.ToJson(), input.BlockMode, input.BlockValue, input.OverflowPolicy, input.JunctionBetweenId, input.JunctionAfterId ) ); return Result.Success(); } /// Загружает шаблон целиком по слою — правка слота меняет ревизию всего шаблона. public Task LoadTemplateByLayerAsync( Guid layerId, CancellationToken cancellationToken ) => dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) .AsSplitQuery() .FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken); /// Загружает шаблон по слоту. public Task LoadTemplateBySlotAsync( Guid slotId, CancellationToken cancellationToken ) => dbContext .ScheduleTemplates.Include(t => t.Layers) .ThenInclude(l => l.Slots) .AsSplitQuery() .FirstOrDefaultAsync( t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)), cancellationToken ); }