Files
ersatztv/ErsatzTV/Controllers/Api/Requests/ScheduleItemRequest.cs
T
timothyandClaude Opus 4.8 02a493e95e feat(#259): id-based reconcile for schedule-items replace (backend + DTO)
Add optional `int? Id` to ScheduleItemRequest/ReplaceProgramScheduleItem so
a client can round-trip each existing item's server id. When ids are present,
ReplaceProgramScheduleItemsHandler reconciles by id (not array position), so an
item's persisted fill-group/shuffle state (PlayoutScheduleItemFillGroupIndex,
FK OnDelete Cascade) follows the logical item across reorders/inserts instead of
being inherited by whatever previously occupied its new slot (#259, split from
#252/#253). A fully id-less payload keeps the verbatim positional fallback.

Guards (inside PersistItems, after CheckVersion so 412 precedes 422): duplicate
id -> 422; id not in this schedule -> 422 (a stale id under Phase-1 force-write is
a live lost-update signal, not a new item). Index stays array-position derived.

Regenerated v1.json + TS client.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 21:43:52 +02:00

138 lines
4.2 KiB
C#

using ErsatzTV.Application.ProgramSchedules;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling;
namespace ErsatzTV.Controllers.Api.Requests;
/// <param name="Id">
/// Server-assigned identity of an existing schedule item, as returned by GET /api/schedules/{id}/items.
/// Omit (or send null / 0) for a new item. On a replace, the item carrying this id keeps its persisted
/// fill-group/shuffle progression even when moved to a different position. Unknown or duplicated ids are
/// rejected with 422 — never fabricate an id.
/// </param>
public record ScheduleItemRequest(
int? Id,
StartType StartType,
TimeSpan? StartTime,
FixedStartTimeBehavior? FixedStartTimeBehavior,
PlayoutMode PlayoutMode,
CollectionType CollectionType,
int? CollectionId,
int? MultiCollectionId,
int? SmartCollectionId,
int? RerunCollectionId,
int? MediaItemId,
int? PlaylistId,
string SearchTitle,
string SearchQuery,
PlaybackOrder PlaybackOrder,
MarathonGroupBy MarathonGroupBy,
bool MarathonShuffleGroups,
bool MarathonShuffleItems,
int? MarathonBatchSize,
FillWithGroupMode FillWithGroupMode,
MultipleMode MultipleMode,
string MultipleCount,
TimeSpan? PlayoutDuration,
TailMode TailMode,
int? DiscardToFillAttempts,
string CustomTitle,
GuideMode GuideMode,
int? PreRollFillerId,
int? MidRollFillerId,
int? PostRollFillerId,
int? TailFillerId,
int? FallbackFillerId,
List<int> WatermarkIds,
List<int> GraphicsElementIds,
string PreferredAudioLanguageCode,
string PreferredAudioTitle,
string PreferredSubtitleLanguageCode,
ChannelSubtitleMode? SubtitleMode)
{
public AddProgramScheduleItem ToAddCommand(int scheduleId) =>
new(
scheduleId,
StartType,
StartTime,
FixedStartTimeBehavior,
PlayoutMode,
CollectionType,
CollectionId,
MultiCollectionId,
SmartCollectionId,
RerunCollectionId,
MediaItemId,
PlaylistId,
SearchTitle,
SearchQuery,
PlaybackOrder,
MarathonGroupBy,
MarathonShuffleGroups,
MarathonShuffleItems,
MarathonBatchSize,
FillWithGroupMode,
MultipleMode,
MultipleCount,
PlayoutDuration,
TailMode,
DiscardToFillAttempts,
CustomTitle,
GuideMode,
PreRollFillerId,
MidRollFillerId,
PostRollFillerId,
TailFillerId,
FallbackFillerId,
WatermarkIds ?? [],
GraphicsElementIds ?? [],
PreferredAudioLanguageCode,
PreferredAudioTitle,
PreferredSubtitleLanguageCode,
SubtitleMode);
public ReplaceProgramScheduleItem ToReplaceCommand(int index) =>
new(
// normalize the two-state client value (null / 0 / positive) to the handler's null-or-real
// contract so 0-defaulting clients read as "new item" rather than "existing item 0"
Id is > 0 ? Id : null,
index,
StartType,
StartTime,
FixedStartTimeBehavior,
PlayoutMode,
CollectionType,
CollectionId,
MultiCollectionId,
SmartCollectionId,
RerunCollectionId,
MediaItemId,
PlaylistId,
SearchTitle,
SearchQuery,
PlaybackOrder,
MarathonGroupBy,
MarathonShuffleGroups,
MarathonShuffleItems,
MarathonBatchSize,
FillWithGroupMode,
MultipleMode,
MultipleCount,
PlayoutDuration,
TailMode,
DiscardToFillAttempts,
CustomTitle,
GuideMode,
PreRollFillerId,
MidRollFillerId,
PostRollFillerId,
TailFillerId,
FallbackFillerId,
WatermarkIds ?? [],
GraphicsElementIds ?? [],
PreferredAudioLanguageCode,
PreferredAudioTitle,
PreferredSubtitleLanguageCode,
SubtitleMode);
}