Enhance template and scheduling functionalities: add PlanningRules to template endpoints and commands, implement repeat limits and audience filtering in scheduling logic, and update related data structures. Refactor frontend components to support new rules and improve user experience in channel management.
This commit is contained in:
+1
@@ -88,6 +88,7 @@ public sealed class GetChannelTemplateQueryHandler(IAppDbContext dbContext)
|
||||
template.Name,
|
||||
template.FallbackGroupId,
|
||||
template.DefaultJunctionId,
|
||||
PlanningRules.FromJson(template.RulesJson),
|
||||
template.Revision,
|
||||
template.AppliedRevision,
|
||||
template.HasPendingChanges,
|
||||
|
||||
@@ -92,6 +92,7 @@ public sealed class UpdateTemplateCommandHandler(IAppDbContext dbContext)
|
||||
template.Rename(command.Name);
|
||||
template.SetFallbackGroup(command.FallbackGroupId);
|
||||
template.SetDefaultJunction(command.DefaultJunctionId);
|
||||
template.SetRules(command.Rules?.ToJson());
|
||||
template.MarkChanged();
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@ public sealed record UpdateLayerCommand(
|
||||
|
||||
public sealed record DeleteLayerCommand(Guid LayerId) : ICommand<Result>;
|
||||
|
||||
/// <summary>Имя шаблона и аварийная группа, играющая, когда пуст даже фоновый слой.</summary>
|
||||
/// <summary>Имя шаблона, аварийная группа, стык по умолчанию и правила отбора кандидатов.</summary>
|
||||
public sealed record UpdateTemplateCommand(
|
||||
Guid TemplateId,
|
||||
string Name,
|
||||
Guid? FallbackGroupId,
|
||||
Guid? DefaultJunctionId
|
||||
Guid? DefaultJunctionId,
|
||||
PlanningRules? Rules
|
||||
) : ICommand<Result>;
|
||||
|
||||
public sealed class CreateLayerCommandValidator : AbstractValidator<CreateLayerCommand>
|
||||
@@ -45,5 +46,22 @@ public sealed class UpdateLayerCommandValidator : AbstractValidator<UpdateLayerC
|
||||
|
||||
public sealed class UpdateTemplateCommandValidator : AbstractValidator<UpdateTemplateCommand>
|
||||
{
|
||||
public UpdateTemplateCommandValidator() => RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
public UpdateTemplateCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
|
||||
// Год — верхняя граница окна повторов: за ним правило перестаёт что-либо значить, а история
|
||||
// всё равно ограничена глубиной хранения ленты.
|
||||
RuleFor(x => x.Rules!.MaxRepeatsInWindow!.WindowDays)
|
||||
.InclusiveBetween(1, 365)
|
||||
.When(x => x.Rules?.MaxRepeatsInWindow is not null);
|
||||
RuleFor(x => x.Rules!.MaxRepeatsInWindow!.Max)
|
||||
.InclusiveBetween(1, 1000)
|
||||
.When(x => x.Rules?.MaxRepeatsInWindow is not null);
|
||||
|
||||
RuleForEach(x => x.Rules!.MaxAudienceByTime)
|
||||
.Must(window => window.From != window.To)
|
||||
.WithMessage("Окно нулевой длины ничего не ограничивает.")
|
||||
.When(x => x.Rules?.MaxAudienceByTime is not null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using TeleWave.Domain.Library;
|
||||
|
||||
namespace TeleWave.Application.Programming.Templates;
|
||||
|
||||
/// <summary>
|
||||
/// Окно детского времени: с <paramref name="From"/> до <paramref name="To"/> во времени канала
|
||||
/// в эфир идёт только контент не строже <paramref name="MaxAudience"/>. Окно может переходить
|
||||
/// через полночь (23:00–06:00) — тогда границы сравниваются в обратную сторону.
|
||||
/// </summary>
|
||||
public sealed record AudienceWindow(TimeOnly From, TimeOnly To, ShowAudience MaxAudience)
|
||||
{
|
||||
public bool Contains(TimeOnly moment) =>
|
||||
From <= To ? moment >= From && moment < To : moment >= From || moment < To;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Правила отбора кандидатов на уровне канала (см. 3.8). Это жёсткие фильтры: они отсекают
|
||||
/// недопустимое до жребия, поэтому не требуют пересборки и не ломают воспроизводимость.
|
||||
///
|
||||
/// Область действия — весь канал: дейпарты сюда не заведены намеренно, окна детского времени
|
||||
/// и так задаются временем, а потолок повторов осмыслен только целиком по каналу.
|
||||
/// </summary>
|
||||
public sealed record PlanningRules(
|
||||
IReadOnlyList<AudienceWindow>? MaxAudienceByTime = null,
|
||||
/// <summary>Не чаще <c>Max</c> раз за <c>WindowDays</c> суток (null — без ограничения).</summary>
|
||||
RepeatLimitRule? MaxRepeatsInWindow = null
|
||||
)
|
||||
{
|
||||
private static readonly JsonSerializerOptions Options = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
};
|
||||
|
||||
/// <summary>Возрастной потолок в указанный момент времени канала (null — без ограничения).</summary>
|
||||
public ShowAudience? AudienceAt(TimeOnly moment)
|
||||
{
|
||||
if (MaxAudienceByTime is not { Count: > 0 } windows)
|
||||
return null;
|
||||
|
||||
// Пересекающиеся окна разрешаются в пользу строгого: детское время не должно
|
||||
// отменяться более широким окном, случайно наложенным сверху.
|
||||
ShowAudience? strictest = null;
|
||||
foreach (var window in windows.Where(w => w.Contains(moment)))
|
||||
strictest = strictest is { } current && current <= window.MaxAudience
|
||||
? current
|
||||
: window.MaxAudience;
|
||||
return strictest;
|
||||
}
|
||||
|
||||
public string ToJson() => JsonSerializer.Serialize(this, Options);
|
||||
|
||||
public static PlanningRules? FromJson(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<PlanningRules>(json, Options);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record RepeatLimitRule(int WindowDays, int Max);
|
||||
@@ -41,6 +41,8 @@ public sealed record ScheduleTemplateDto(
|
||||
string Name,
|
||||
Guid? FallbackGroupId,
|
||||
Guid? DefaultJunctionId,
|
||||
/// <summary>Правила отбора кандидатов канала: детское время и потолок повторов.</summary>
|
||||
PlanningRules? Rules,
|
||||
int Revision,
|
||||
int AppliedRevision,
|
||||
/// <summary>Есть ли правки правил, ещё не применённые к эфиру.</summary>
|
||||
|
||||
Reference in New Issue
Block a user