diff --git a/CutList.Web/Data/Entities/Material.cs b/CutList.Web/Data/Entities/Material.cs index fe516aa..270f1bf 100644 --- a/CutList.Web/Data/Entities/Material.cs +++ b/CutList.Web/Data/Entities/Material.cs @@ -11,7 +11,8 @@ public class Material public DateTime? UpdatedAt { get; set; } public ICollection SupplierStocks { get; set; } = new List(); - public ICollection Projects { get; set; } = new List(); + public ICollection StockLengths { get; set; } = new List(); + public ICollection ProjectParts { get; set; } = new List(); public string DisplayName => $"{Shape} - {Size}"; } diff --git a/CutList.Web/Data/Entities/ProjectPart.cs b/CutList.Web/Data/Entities/ProjectPart.cs index 3310dc8..662b5d0 100644 --- a/CutList.Web/Data/Entities/ProjectPart.cs +++ b/CutList.Web/Data/Entities/ProjectPart.cs @@ -4,10 +4,12 @@ public class ProjectPart { public int Id { get; set; } public int ProjectId { get; set; } + public int MaterialId { get; set; } public string Name { get; set; } = string.Empty; public decimal LengthInches { get; set; } public int Quantity { get; set; } = 1; public int SortOrder { get; set; } public Project Project { get; set; } = null!; + public Material Material { get; set; } = null!; } diff --git a/CutList.Web/Data/Entities/ProjectStockBin.cs b/CutList.Web/Data/Entities/ProjectStockBin.cs deleted file mode 100644 index e28edef..0000000 --- a/CutList.Web/Data/Entities/ProjectStockBin.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CutList.Web.Data.Entities; - -public class ProjectStockBin -{ - public int Id { get; set; } - public int ProjectId { get; set; } - public decimal LengthInches { get; set; } - public int Quantity { get; set; } = -1; - public int Priority { get; set; } = 25; - public int SortOrder { get; set; } - - public Project Project { get; set; } = null!; -} diff --git a/CutList.Web/Migrations/20260202043251_MultiMaterialProjectParts.Designer.cs b/CutList.Web/Migrations/20260202043251_MultiMaterialProjectParts.Designer.cs new file mode 100644 index 0000000..5795599 --- /dev/null +++ b/CutList.Web/Migrations/20260202043251_MultiMaterialProjectParts.Designer.cs @@ -0,0 +1,394 @@ +// +using System; +using CutList.Web.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CutList.Web.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260202043251_MultiMaterialProjectParts")] + partial class MultiMaterialProjectParts + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CutList.Web.Data.Entities.CuttingTool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("KerfInches") + .HasPrecision(6, 4) + .HasColumnType("decimal(6,4)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("CuttingTools"); + + b.HasData( + new + { + Id = 1, + IsActive = true, + IsDefault = true, + KerfInches = 0.0625m, + Name = "Bandsaw" + }, + new + { + Id = 2, + IsActive = true, + IsDefault = false, + KerfInches = 0.125m, + Name = "Chop Saw" + }, + new + { + Id = 3, + IsActive = true, + IsDefault = false, + KerfInches = 0.0625m, + Name = "Cold Cut Saw" + }, + new + { + Id = 4, + IsActive = true, + IsDefault = false, + KerfInches = 0.0625m, + Name = "Hacksaw" + }); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Material", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("Description") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Shape") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Size") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Materials"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.MaterialStockLength", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId", "LengthInches") + .IsUnique(); + + b.ToTable("MaterialStockLengths"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("Customer") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CuttingToolId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CuttingToolId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ProjectPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectParts"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContactInfo") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Suppliers"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SupplierStock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("Price") + .HasPrecision(10, 2) + .HasColumnType("decimal(10,2)"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId"); + + b.HasIndex("SupplierId", "MaterialId", "LengthInches") + .IsUnique(); + + b.ToTable("SupplierStocks"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.MaterialStockLength", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("StockLengths") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Project", b => + { + b.HasOne("CutList.Web.Data.Entities.CuttingTool", "CuttingTool") + .WithMany("Projects") + .HasForeignKey("CuttingToolId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CuttingTool"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ProjectPart", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("ProjectParts") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Project", "Project") + .WithMany("Parts") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SupplierStock", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("SupplierStocks") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany("Stocks") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.CuttingTool", b => + { + b.Navigation("Projects"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Material", b => + { + b.Navigation("ProjectParts"); + + b.Navigation("StockLengths"); + + b.Navigation("SupplierStocks"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Project", b => + { + b.Navigation("Parts"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b => + { + b.Navigation("Stocks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CutList.Web/Migrations/20260202043251_MultiMaterialProjectParts.cs b/CutList.Web/Migrations/20260202043251_MultiMaterialProjectParts.cs new file mode 100644 index 0000000..293db45 --- /dev/null +++ b/CutList.Web/Migrations/20260202043251_MultiMaterialProjectParts.cs @@ -0,0 +1,143 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CutList.Web.Migrations +{ + /// + public partial class MultiMaterialProjectParts : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Projects_Materials_MaterialId", + table: "Projects"); + + migrationBuilder.DropTable( + name: "ProjectStockBins"); + + migrationBuilder.DropIndex( + name: "IX_Projects_MaterialId", + table: "Projects"); + + migrationBuilder.DropColumn( + name: "MaterialId", + table: "Projects"); + + migrationBuilder.AlterColumn( + name: "Customer", + table: "Projects", + type: "nvarchar(100)", + maxLength: 100, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "MaterialId", + table: "ProjectParts", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "Quantity", + table: "MaterialStockLengths", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "IX_ProjectParts_MaterialId", + table: "ProjectParts", + column: "MaterialId"); + + migrationBuilder.AddForeignKey( + name: "FK_ProjectParts_Materials_MaterialId", + table: "ProjectParts", + column: "MaterialId", + principalTable: "Materials", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ProjectParts_Materials_MaterialId", + table: "ProjectParts"); + + migrationBuilder.DropIndex( + name: "IX_ProjectParts_MaterialId", + table: "ProjectParts"); + + migrationBuilder.DropColumn( + name: "MaterialId", + table: "ProjectParts"); + + migrationBuilder.DropColumn( + name: "Quantity", + table: "MaterialStockLengths"); + + migrationBuilder.AlterColumn( + name: "Customer", + table: "Projects", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100, + oldNullable: true); + + migrationBuilder.AddColumn( + name: "MaterialId", + table: "Projects", + type: "int", + nullable: true); + + migrationBuilder.CreateTable( + name: "ProjectStockBins", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ProjectId = table.Column(type: "int", nullable: false), + LengthInches = table.Column(type: "decimal(10,4)", precision: 10, scale: 4, nullable: false), + Priority = table.Column(type: "int", nullable: false), + Quantity = table.Column(type: "int", nullable: false), + SortOrder = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectStockBins", x => x.Id); + table.ForeignKey( + name: "FK_ProjectStockBins_Projects_ProjectId", + column: x => x.ProjectId, + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Projects_MaterialId", + table: "Projects", + column: "MaterialId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectStockBins_ProjectId", + table: "ProjectStockBins", + column: "ProjectId"); + + migrationBuilder.AddForeignKey( + name: "FK_Projects_Materials_MaterialId", + table: "Projects", + column: "MaterialId", + principalTable: "Materials", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + } + } +}