feat(templates): phase 1 — derived-template schema (additive)

Phase 1 of the design at
docs/plans/2026-05-12-derive-on-compose-design.md.

Additive schema only — no behavior changes. Existing data and code
paths continue to work; subsequent phases will start writing the
new fields.

Template gains:
  IsDerived            true when this row was auto-created to back
                       a composition slot
  OwnerCompositionId   back-ref to the owning TemplateComposition
                       (plain int, not an EF nav property — managed
                       by TemplateService for cascade-delete)

TemplateAttribute / TemplateScript each gain:
  IsInherited          row copied from base and not yet overridden;
                       changes to the base flow downward
  LockedInDerived      on a base, blocks derived from overriding;
                       enforced at the service layer in later phases

EF Core migration AddDerivedTemplateFields adds four columns:
  Templates.IsDerived              bit NOT NULL DEFAULT 0
  Templates.OwnerCompositionId     int NULL
  TemplateAttributes.IsInherited   bit NOT NULL DEFAULT 0
  TemplateAttributes.LockedInDerived bit NOT NULL DEFAULT 0
  TemplateScripts.IsInherited      bit NOT NULL DEFAULT 0
  TemplateScripts.LockedInDerived  bit NOT NULL DEFAULT 0

Existing rows get the defaults. Tests across SiteRuntime / TemplateEngine
/ CentralUI suites stay green (129 / 199 / 159).

Next: phase 2 — wire AddCompositionAsync to derive on compose for
new compositions. Old data still flows the direct-reference path
until phase 3's migration script.
This commit is contained in:
Joseph Doherty
2026-05-12 08:16:24 -04:00
parent a968cefbc2
commit 5615f3d0c7
6 changed files with 1446 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ScadaLink.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddDerivedTemplateFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsInherited",
table: "TemplateScripts",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "LockedInDerived",
table: "TemplateScripts",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "IsDerived",
table: "Templates",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "OwnerCompositionId",
table: "Templates",
type: "int",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "IsInherited",
table: "TemplateAttributes",
type: "bit",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "LockedInDerived",
table: "TemplateAttributes",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsInherited",
table: "TemplateScripts");
migrationBuilder.DropColumn(
name: "LockedInDerived",
table: "TemplateScripts");
migrationBuilder.DropColumn(
name: "IsDerived",
table: "Templates");
migrationBuilder.DropColumn(
name: "OwnerCompositionId",
table: "Templates");
migrationBuilder.DropColumn(
name: "IsInherited",
table: "TemplateAttributes");
migrationBuilder.DropColumn(
name: "LockedInDerived",
table: "TemplateAttributes");
}
}
}

View File

@@ -848,11 +848,17 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
b.Property<int?>("FolderId")
.HasColumnType("int");
b.Property<bool>("IsDerived")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<int?>("OwnerCompositionId")
.HasColumnType("int");
b.Property<int?>("ParentTemplateId")
.HasColumnType("int");
@@ -935,9 +941,15 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<bool>("IsInherited")
.HasColumnType("bit");
b.Property<bool>("IsLocked")
.HasColumnType("bit");
b.Property<bool>("LockedInDerived")
.HasColumnType("bit");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
@@ -1027,9 +1039,15 @@ namespace ScadaLink.ConfigurationDatabase.Migrations
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("IsInherited")
.HasColumnType("bit");
b.Property<bool>("IsLocked")
.HasColumnType("bit");
b.Property<bool>("LockedInDerived")
.HasColumnType("bit");
b.Property<TimeSpan?>("MinTimeBetweenRuns")
.HasColumnType("time");