From cca569ae814c653692b36b17d937a51f8484ad0d Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 1 Feb 2026 23:54:23 -0500 Subject: [PATCH] feat: Add MaterialStockLength entity for inventory tracking Introduces a new entity to track available stock lengths per material, enabling in-stock vs. purchase-needed distinction during optimization. Co-Authored-By: Claude Opus 4.5 --- .../Data/Entities/MaterialStockLength.cs | 13 + ...033321_AddMaterialStockLengths.Designer.cs | 430 ++++++++++++++++++ .../20260202033321_AddMaterialStockLengths.cs | 49 ++ 3 files changed, 492 insertions(+) create mode 100644 CutList.Web/Data/Entities/MaterialStockLength.cs create mode 100644 CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.Designer.cs create mode 100644 CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.cs diff --git a/CutList.Web/Data/Entities/MaterialStockLength.cs b/CutList.Web/Data/Entities/MaterialStockLength.cs new file mode 100644 index 0000000..7f73daf --- /dev/null +++ b/CutList.Web/Data/Entities/MaterialStockLength.cs @@ -0,0 +1,13 @@ +namespace CutList.Web.Data.Entities; + +public class MaterialStockLength +{ + public int Id { get; set; } + public int MaterialId { get; set; } + public decimal LengthInches { get; set; } + public int Quantity { get; set; } = 0; + public string? Notes { get; set; } + public bool IsActive { get; set; } = true; + + public Material Material { get; set; } = null!; +} diff --git a/CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.Designer.cs b/CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.Designer.cs new file mode 100644 index 0000000..9da38c2 --- /dev/null +++ b/CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.Designer.cs @@ -0,0 +1,430 @@ +// +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("20260202033321_AddMaterialStockLengths")] + partial class AddMaterialStockLengths + { + /// + 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.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("CuttingToolId") + .HasColumnType("int"); + + b.Property("MaterialId") + .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.HasIndex("MaterialId"); + + 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("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("ProjectId"); + + b.ToTable("ProjectParts"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ProjectStockBin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectStockBins"); + }); + + 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.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("Projects") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CuttingTool"); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ProjectPart", b => + { + b.HasOne("CutList.Web.Data.Entities.Project", "Project") + .WithMany("Parts") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ProjectStockBin", b => + { + b.HasOne("CutList.Web.Data.Entities.Project", "Project") + .WithMany("StockBins") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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("Projects"); + + b.Navigation("StockLengths"); + + b.Navigation("SupplierStocks"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Project", b => + { + b.Navigation("Parts"); + + b.Navigation("StockBins"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b => + { + b.Navigation("Stocks"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.cs b/CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.cs new file mode 100644 index 0000000..969d61f --- /dev/null +++ b/CutList.Web/Migrations/20260202033321_AddMaterialStockLengths.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CutList.Web.Migrations +{ + /// + public partial class AddMaterialStockLengths : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MaterialStockLengths", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + MaterialId = table.Column(type: "int", nullable: false), + LengthInches = table.Column(type: "decimal(10,4)", precision: 10, scale: 4, nullable: false), + Notes = table.Column(type: "nvarchar(255)", maxLength: 255, nullable: true), + IsActive = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_MaterialStockLengths", x => x.Id); + table.ForeignKey( + name: "FK_MaterialStockLengths_Materials_MaterialId", + column: x => x.MaterialId, + principalTable: "Materials", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_MaterialStockLengths_MaterialId_LengthInches", + table: "MaterialStockLengths", + columns: new[] { "MaterialId", "LengthInches" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MaterialStockLengths"); + } + } +}