106 lines
4.9 KiB
C#
106 lines
4.9 KiB
C#
namespace TeleWave.Domain.Programming;
|
|
|
|
/// <summary>
|
|
/// Шаблон сетки канала: слои со слотами плюс аварийные настройки. Один активный шаблон на канал —
|
|
/// сезонность выражается слоями внутри него, а не вторым шаблоном, иначе получились бы два механизма
|
|
/// для одного и того же. На другой канал переносится глубокой копией.
|
|
///
|
|
/// <see cref="Revision"/> растёт при любой правке правил. Эфир при этом не меняется: правка помечает
|
|
/// шаблон изменённым, а хвост пересобирается отдельной командой применения.
|
|
/// </summary>
|
|
public class ScheduleTemplate
|
|
{
|
|
private readonly List<GridLayer> _layers = [];
|
|
|
|
public Guid Id { get; private set; }
|
|
public Guid ChannelId { get; private set; }
|
|
public string Name { get; private set; } = string.Empty;
|
|
|
|
/// <summary>Аварийная группа, если пуст даже фоновый слой.</summary>
|
|
public Guid? FallbackGroupId { get; private set; }
|
|
|
|
/// <summary>Стык по умолчанию — для слотов, у которых свой не задан.</summary>
|
|
public Guid? DefaultJunctionId { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Правила отбора кандидатов (детское время, потолок повторов) в JSON. Домен их не разбирает —
|
|
/// схема живёт в Application, как и у стратегий слотов и условий стыка.
|
|
/// </summary>
|
|
public string? RulesJson { get; private set; }
|
|
|
|
/// <summary>Номер правки правил; входит в кэш-ключи и историю.</summary>
|
|
public int Revision { get; private set; }
|
|
|
|
/// <summary>Ревизия, по которой собрано текущее будущее расписание.</summary>
|
|
public int AppliedRevision { get; private set; }
|
|
|
|
public DateTimeOffset CreatedAt { get; private set; }
|
|
|
|
public IReadOnlyList<GridLayer> Layers => _layers;
|
|
|
|
/// <summary>Есть ли правки, не применённые к эфиру.</summary>
|
|
public bool HasPendingChanges => Revision != AppliedRevision;
|
|
|
|
private const string BackgroundLayerName = "Фон";
|
|
|
|
private ScheduleTemplate() { }
|
|
|
|
public static ScheduleTemplate Create(Guid channelId, string name)
|
|
{
|
|
var template = new ScheduleTemplate
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ChannelId = channelId,
|
|
Name = name.Trim(),
|
|
Revision = 0,
|
|
AppliedRevision = 0,
|
|
CreatedAt = DateTimeOffset.UtcNow,
|
|
};
|
|
// Фоновый слой заводится сразу: без него первая же дыра в сетке осталась бы нечем закрыть.
|
|
template._layers.Add(
|
|
GridLayer.Create(template.Id, BackgroundLayerName, GridLayer.BackgroundPriority, isBackground: true)
|
|
);
|
|
return template;
|
|
}
|
|
|
|
public void Rename(string name) => Name = name.Trim();
|
|
|
|
public void SetFallbackGroup(Guid? groupId) => FallbackGroupId = groupId;
|
|
|
|
public void SetDefaultJunction(Guid? junctionId) => DefaultJunctionId = junctionId;
|
|
|
|
public void SetRules(string? rulesJson) =>
|
|
RulesJson = string.IsNullOrWhiteSpace(rulesJson) ? null : rulesJson;
|
|
|
|
/// <summary>Отметить, что правила изменились — эфир пойдёт по старым до применения.</summary>
|
|
public void MarkChanged() => Revision++;
|
|
|
|
/// <summary>Отметить, что хвост пересобран по текущей ревизии.</summary>
|
|
public void MarkApplied() => AppliedRevision = Revision;
|
|
|
|
public GridLayer? FindLayer(Guid layerId) => _layers.FirstOrDefault(l => l.Id == layerId);
|
|
|
|
public GridLayer? Background => _layers.FirstOrDefault(l => l.IsBackground);
|
|
|
|
public GridLayer AddLayer(string name, int priority)
|
|
{
|
|
var layer = GridLayer.Create(Id, name, priority);
|
|
_layers.Add(layer);
|
|
return layer;
|
|
}
|
|
|
|
/// <summary>Удаляет слой. Фоновый удалить нельзя — вернёт false.</summary>
|
|
public bool RemoveLayer(Guid layerId)
|
|
{
|
|
var layer = _layers.FirstOrDefault(l => l.Id == layerId);
|
|
if (layer is null || layer.IsBackground)
|
|
return false;
|
|
_layers.Remove(layer);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>Слой, содержащий слот, или null.</summary>
|
|
public GridLayer? FindLayerOfSlot(Guid slotId) =>
|
|
_layers.FirstOrDefault(l => l.FindSlot(slotId) is not null);
|
|
}
|