diff --git a/CutList.Web/Data/ApplicationDbContext.cs b/CutList.Web/Data/ApplicationDbContext.cs index 06ace95..9c8c430 100644 --- a/CutList.Web/Data/ApplicationDbContext.cs +++ b/CutList.Web/Data/ApplicationDbContext.cs @@ -20,6 +20,7 @@ public class ApplicationDbContext : DbContext public DbSet Jobs => Set(); public DbSet JobParts => Set(); public DbSet JobStocks => Set(); + public DbSet PurchaseItems => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -281,6 +282,34 @@ public class ApplicationDbContext : DbContext .OnDelete(DeleteBehavior.SetNull); }); + // PurchaseItem + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id); + entity.Property(e => e.Notes).HasMaxLength(500); + entity.Property(e => e.Status) + .HasMaxLength(20) + .HasConversion( + v => v.ToString(), + v => Enum.Parse(v)); + entity.Property(e => e.CreatedAt).HasDefaultValueSql("GETUTCDATE()"); + + entity.HasOne(e => e.StockItem) + .WithMany() + .HasForeignKey(e => e.StockItemId) + .OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.Supplier) + .WithMany() + .HasForeignKey(e => e.SupplierId) + .OnDelete(DeleteBehavior.SetNull); + + entity.HasOne(e => e.Job) + .WithMany() + .HasForeignKey(e => e.JobId) + .OnDelete(DeleteBehavior.SetNull); + }); + // Seed default cutting tools modelBuilder.Entity().HasData( new CuttingTool { Id = 1, Name = "Bandsaw", KerfInches = 0.0625m, IsDefault = true, IsActive = true }, diff --git a/CutList.Web/Data/Entities/Job.cs b/CutList.Web/Data/Entities/Job.cs index 6c63133..72d49e0 100644 --- a/CutList.Web/Data/Entities/Job.cs +++ b/CutList.Web/Data/Entities/Job.cs @@ -10,6 +10,9 @@ public class Job public string? Notes { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; set; } + public DateTime? LockedAt { get; set; } + + public bool IsLocked => LockedAt.HasValue; public CuttingTool? CuttingTool { get; set; } public ICollection Parts { get; set; } = new List(); diff --git a/CutList.Web/Data/Entities/PurchaseItem.cs b/CutList.Web/Data/Entities/PurchaseItem.cs new file mode 100644 index 0000000..f737b09 --- /dev/null +++ b/CutList.Web/Data/Entities/PurchaseItem.cs @@ -0,0 +1,25 @@ +namespace CutList.Web.Data.Entities; + +public enum PurchaseItemStatus +{ + Pending, + Ordered, + Received +} + +public class PurchaseItem +{ + public int Id { get; set; } + public int StockItemId { get; set; } + public int? SupplierId { get; set; } + public int Quantity { get; set; } + public int? JobId { get; set; } + public string? Notes { get; set; } + public PurchaseItemStatus Status { get; set; } = PurchaseItemStatus.Pending; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime? UpdatedAt { get; set; } + + public StockItem StockItem { get; set; } = null!; + public Supplier? Supplier { get; set; } + public Job? Job { get; set; } +} diff --git a/CutList.Web/Migrations/20260207195807_AddPurchaseItem.Designer.cs b/CutList.Web/Migrations/20260207195807_AddPurchaseItem.Designer.cs new file mode 100644 index 0000000..eed5c6a --- /dev/null +++ b/CutList.Web/Migrations/20260207195807_AddPurchaseItem.Designer.cs @@ -0,0 +1,896 @@ +// +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("20260207195807_AddPurchaseItem")] + partial class AddPurchaseItem + { + /// + 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.Job", 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("JobNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CuttingToolId"); + + b.HasIndex("JobNumber") + .IsUnique(); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("JobId") + .HasColumnType("int"); + + 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("Quantity") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("MaterialId"); + + b.ToTable("JobParts"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobStock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCustomLength") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("MaterialId"); + + b.HasIndex("StockItemId"); + + b.ToTable("JobStocks"); + }); + + 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("Grade") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + 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("SortOrder") + .HasColumnType("int"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Materials"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.MaterialDimensions", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DimensionType") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId") + .IsUnique(); + + b.ToTable("MaterialDimensions"); + + b.HasDiscriminator("DimensionType").HasValue("MaterialDimensions"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.PurchaseItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseItems"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("QuantityOnHand") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId", "LengthInches") + .IsUnique(); + + b.ToTable("StockItems"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(10, 2) + .HasColumnType("decimal(10,2)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId"); + + b.ToTable("StockTransactions"); + }); + + 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.SupplierOffering", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("PartNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Price") + .HasPrecision(10, 2) + .HasColumnType("decimal(10,2)"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierDescription") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId", "StockItemId") + .IsUnique(); + + b.ToTable("SupplierOfferings"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.AngleDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Leg1") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Leg2") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Thickness") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Thickness"); + + b.HasIndex("Leg1"); + + b.HasDiscriminator().HasValue("Angle"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ChannelDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Flange") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Height") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Height"); + + b.Property("Web") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.HasIndex("Height"); + + b.HasDiscriminator().HasValue("Channel"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.FlatBarDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Thickness") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Thickness"); + + b.Property("Width") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Width"); + + b.HasIndex("Width"); + + b.HasDiscriminator().HasValue("FlatBar"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.IBeamDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Height") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Height"); + + b.Property("WeightPerFoot") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.HasIndex("Height"); + + b.HasDiscriminator().HasValue("IBeam"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.PipeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("NominalSize") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Schedule") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.HasIndex("NominalSize"); + + b.HasDiscriminator().HasValue("Pipe"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.RectangularTubeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Height") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Height"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.Property("Width") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Width"); + + b.HasIndex("Width"); + + b.HasDiscriminator().HasValue("RectangularTube"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.RoundBarDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Diameter") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.HasIndex("Diameter"); + + b.HasDiscriminator().HasValue("RoundBar"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.RoundTubeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("OuterDiameter") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.HasIndex("OuterDiameter"); + + b.HasDiscriminator().HasValue("RoundTube"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SquareBarDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Size") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Size"); + + b.HasIndex("Size"); + + b.HasDiscriminator().HasValue("SquareBar"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SquareTubeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Size") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Size"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.HasIndex("Size"); + + b.HasDiscriminator().HasValue("SquareTube"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Job", b => + { + b.HasOne("CutList.Web.Data.Entities.CuttingTool", "CuttingTool") + .WithMany("Jobs") + .HasForeignKey("CuttingToolId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CuttingTool"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobPart", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany("Parts") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("JobParts") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobStock", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany("Stock") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany() + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany() + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("Material"); + + b.Navigation("StockItem"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.MaterialDimensions", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithOne("Dimensions") + .HasForeignKey("CutList.Web.Data.Entities.MaterialDimensions", "MaterialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.PurchaseItem", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany() + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("StockItems") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockTransaction", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany("Transactions") + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SupplierOffering", b => + { + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany("SupplierOfferings") + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany("Offerings") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.CuttingTool", b => + { + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Job", b => + { + b.Navigation("Parts"); + + b.Navigation("Stock"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Material", b => + { + b.Navigation("Dimensions"); + + b.Navigation("JobParts"); + + b.Navigation("StockItems"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => + { + b.Navigation("SupplierOfferings"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b => + { + b.Navigation("Offerings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CutList.Web/Migrations/20260207195807_AddPurchaseItem.cs b/CutList.Web/Migrations/20260207195807_AddPurchaseItem.cs new file mode 100644 index 0000000..816ac32 --- /dev/null +++ b/CutList.Web/Migrations/20260207195807_AddPurchaseItem.cs @@ -0,0 +1,75 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CutList.Web.Migrations +{ + /// + public partial class AddPurchaseItem : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PurchaseItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StockItemId = table.Column(type: "int", nullable: false), + SupplierId = table.Column(type: "int", nullable: true), + Quantity = table.Column(type: "int", nullable: false), + JobId = table.Column(type: "int", nullable: true), + Notes = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Status = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false, defaultValueSql: "GETUTCDATE()"), + UpdatedAt = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PurchaseItems", x => x.Id); + table.ForeignKey( + name: "FK_PurchaseItems_Jobs_JobId", + column: x => x.JobId, + principalTable: "Jobs", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_PurchaseItems_StockItems_StockItemId", + column: x => x.StockItemId, + principalTable: "StockItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PurchaseItems_Suppliers_SupplierId", + column: x => x.SupplierId, + principalTable: "Suppliers", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseItems_JobId", + table: "PurchaseItems", + column: "JobId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseItems_StockItemId", + table: "PurchaseItems", + column: "StockItemId"); + + migrationBuilder.CreateIndex( + name: "IX_PurchaseItems_SupplierId", + table: "PurchaseItems", + column: "SupplierId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PurchaseItems"); + } + } +} diff --git a/CutList.Web/Migrations/20260207201007_AddJobLockedAt.Designer.cs b/CutList.Web/Migrations/20260207201007_AddJobLockedAt.Designer.cs new file mode 100644 index 0000000..f732678 --- /dev/null +++ b/CutList.Web/Migrations/20260207201007_AddJobLockedAt.Designer.cs @@ -0,0 +1,899 @@ +// +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("20260207201007_AddJobLockedAt")] + partial class AddJobLockedAt + { + /// + 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.Job", 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("JobNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("LockedAt") + .HasColumnType("datetime2"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CuttingToolId"); + + b.HasIndex("JobNumber") + .IsUnique(); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobPart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("JobId") + .HasColumnType("int"); + + 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("Quantity") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("MaterialId"); + + b.ToTable("JobParts"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobStock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsCustomLength") + .HasColumnType("bit"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("MaterialId"); + + b.HasIndex("StockItemId"); + + b.ToTable("JobStocks"); + }); + + 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("Grade") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + 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("SortOrder") + .HasColumnType("int"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Materials"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.MaterialDimensions", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DimensionType") + .IsRequired() + .HasMaxLength(21) + .HasColumnType("nvarchar(21)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId") + .IsUnique(); + + b.ToTable("MaterialDimensions"); + + b.HasDiscriminator("DimensionType").HasValue("MaterialDimensions"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.PurchaseItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseItems"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("LengthInches") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("MaterialId") + .HasColumnType("int"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("QuantityOnHand") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("MaterialId", "LengthInches") + .IsUnique(); + + b.ToTable("StockItems"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasPrecision(10, 2) + .HasColumnType("decimal(10,2)"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId"); + + b.ToTable("StockTransactions"); + }); + + 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.SupplierOffering", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Notes") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("PartNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Price") + .HasPrecision(10, 2) + .HasColumnType("decimal(10,2)"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierDescription") + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId", "StockItemId") + .IsUnique(); + + b.ToTable("SupplierOfferings"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.AngleDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Leg1") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Leg2") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Thickness") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Thickness"); + + b.HasIndex("Leg1"); + + b.HasDiscriminator().HasValue("Angle"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.ChannelDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Flange") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Height") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Height"); + + b.Property("Web") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.HasIndex("Height"); + + b.HasDiscriminator().HasValue("Channel"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.FlatBarDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Thickness") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Thickness"); + + b.Property("Width") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Width"); + + b.HasIndex("Width"); + + b.HasDiscriminator().HasValue("FlatBar"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.IBeamDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Height") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Height"); + + b.Property("WeightPerFoot") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.HasIndex("Height"); + + b.HasDiscriminator().HasValue("IBeam"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.PipeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("NominalSize") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Schedule") + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.HasIndex("NominalSize"); + + b.HasDiscriminator().HasValue("Pipe"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.RectangularTubeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Height") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Height"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.Property("Width") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Width"); + + b.HasIndex("Width"); + + b.HasDiscriminator().HasValue("RectangularTube"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.RoundBarDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Diameter") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.HasIndex("Diameter"); + + b.HasDiscriminator().HasValue("RoundBar"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.RoundTubeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("OuterDiameter") + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.HasIndex("OuterDiameter"); + + b.HasDiscriminator().HasValue("RoundTube"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SquareBarDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Size") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Size"); + + b.HasIndex("Size"); + + b.HasDiscriminator().HasValue("SquareBar"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SquareTubeDimensions", b => + { + b.HasBaseType("CutList.Web.Data.Entities.MaterialDimensions"); + + b.Property("Size") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Size"); + + b.Property("Wall") + .ValueGeneratedOnUpdateSometimes() + .HasPrecision(10, 4) + .HasColumnType("decimal(10,4)") + .HasColumnName("Wall"); + + b.HasIndex("Size"); + + b.HasDiscriminator().HasValue("SquareTube"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Job", b => + { + b.HasOne("CutList.Web.Data.Entities.CuttingTool", "CuttingTool") + .WithMany("Jobs") + .HasForeignKey("CuttingToolId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CuttingTool"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobPart", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany("Parts") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("JobParts") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Job"); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.JobStock", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany("Stock") + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany() + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany() + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("Material"); + + b.Navigation("StockItem"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.MaterialDimensions", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithOne("Dimensions") + .HasForeignKey("CutList.Web.Data.Entities.MaterialDimensions", "MaterialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.PurchaseItem", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany() + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => + { + b.HasOne("CutList.Web.Data.Entities.Material", "Material") + .WithMany("StockItems") + .HasForeignKey("MaterialId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Material"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockTransaction", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany("Transactions") + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.SupplierOffering", b => + { + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany("SupplierOfferings") + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany("Offerings") + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.CuttingTool", b => + { + b.Navigation("Jobs"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Job", b => + { + b.Navigation("Parts"); + + b.Navigation("Stock"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Material", b => + { + b.Navigation("Dimensions"); + + b.Navigation("JobParts"); + + b.Navigation("StockItems"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => + { + b.Navigation("SupplierOfferings"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("CutList.Web.Data.Entities.Supplier", b => + { + b.Navigation("Offerings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CutList.Web/Migrations/20260207201007_AddJobLockedAt.cs b/CutList.Web/Migrations/20260207201007_AddJobLockedAt.cs new file mode 100644 index 0000000..fab65eb --- /dev/null +++ b/CutList.Web/Migrations/20260207201007_AddJobLockedAt.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CutList.Web.Migrations +{ + /// + public partial class AddJobLockedAt : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LockedAt", + table: "Jobs", + type: "datetime2", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LockedAt", + table: "Jobs"); + } + } +} diff --git a/CutList.Web/Migrations/ApplicationDbContextModelSnapshot.cs b/CutList.Web/Migrations/ApplicationDbContextModelSnapshot.cs index 03e4a70..5351370 100644 --- a/CutList.Web/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/CutList.Web/Migrations/ApplicationDbContextModelSnapshot.cs @@ -109,6 +109,9 @@ namespace CutList.Web.Migrations .HasMaxLength(20) .HasColumnType("nvarchar(20)"); + b.Property("LockedAt") + .HasColumnType("datetime2"); + b.Property("Name") .HasMaxLength(100) .HasColumnType("nvarchar(100)"); @@ -289,6 +292,54 @@ namespace CutList.Web.Migrations b.UseTphMappingStrategy(); }); + modelBuilder.Entity("CutList.Web.Data.Entities.PurchaseItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("JobId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("StockItemId") + .HasColumnType("int"); + + b.Property("SupplierId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("JobId"); + + b.HasIndex("StockItemId"); + + b.HasIndex("SupplierId"); + + b.ToTable("PurchaseItems"); + }); + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => { b.Property("Id") @@ -727,6 +778,31 @@ namespace CutList.Web.Migrations b.Navigation("Material"); }); + modelBuilder.Entity("CutList.Web.Data.Entities.PurchaseItem", b => + { + b.HasOne("CutList.Web.Data.Entities.Job", "Job") + .WithMany() + .HasForeignKey("JobId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("CutList.Web.Data.Entities.StockItem", "StockItem") + .WithMany() + .HasForeignKey("StockItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CutList.Web.Data.Entities.Supplier", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Job"); + + b.Navigation("StockItem"); + + b.Navigation("Supplier"); + }); + modelBuilder.Entity("CutList.Web.Data.Entities.StockItem", b => { b.HasOne("CutList.Web.Data.Entities.Material", "Material") diff --git a/CutList.Web/Program.cs b/CutList.Web/Program.cs index 1fecae3..a562994 100644 --- a/CutList.Web/Program.cs +++ b/CutList.Web/Program.cs @@ -21,6 +21,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); var app = builder.Build(); diff --git a/CutList.Web/Services/JobService.cs b/CutList.Web/Services/JobService.cs index bc4f63f..9b47167 100644 --- a/CutList.Web/Services/JobService.cs +++ b/CutList.Web/Services/JobService.cs @@ -76,6 +76,26 @@ public class JobService await _context.SaveChangesAsync(); } + public async Task LockAsync(int id) + { + var job = await _context.Jobs.FindAsync(id); + if (job != null) + { + job.LockedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(); + } + } + + public async Task UnlockAsync(int id) + { + var job = await _context.Jobs.FindAsync(id); + if (job != null) + { + job.LockedAt = null; + await _context.SaveChangesAsync(); + } + } + public async Task DeleteAsync(int id) { var job = await _context.Jobs.FindAsync(id); diff --git a/CutList.Web/Services/PurchaseItemService.cs b/CutList.Web/Services/PurchaseItemService.cs new file mode 100644 index 0000000..fc77119 --- /dev/null +++ b/CutList.Web/Services/PurchaseItemService.cs @@ -0,0 +1,103 @@ +using CutList.Web.Data; +using CutList.Web.Data.Entities; +using Microsoft.EntityFrameworkCore; + +namespace CutList.Web.Services; + +public class PurchaseItemService +{ + private readonly ApplicationDbContext _context; + + public PurchaseItemService(ApplicationDbContext context) + { + _context = context; + } + + public async Task> GetAllAsync(PurchaseItemStatus? status = null) + { + var query = _context.PurchaseItems + .Include(p => p.StockItem) + .ThenInclude(s => s.Material) + .Include(p => p.Supplier) + .Include(p => p.Job) + .AsQueryable(); + + if (status.HasValue) + { + query = query.Where(p => p.Status == status.Value); + } + + return await query + .OrderBy(p => p.Status) + .ThenByDescending(p => p.CreatedAt) + .ToListAsync(); + } + + public async Task GetByIdAsync(int id) + { + return await _context.PurchaseItems + .Include(p => p.StockItem) + .ThenInclude(s => s.Material) + .Include(p => p.Supplier) + .Include(p => p.Job) + .FirstOrDefaultAsync(p => p.Id == id); + } + + public async Task CreateAsync(PurchaseItem item) + { + item.CreatedAt = DateTime.UtcNow; + _context.PurchaseItems.Add(item); + await _context.SaveChangesAsync(); + return item; + } + + public async Task CreateBulkAsync(List items) + { + var now = DateTime.UtcNow; + foreach (var item in items) + { + item.CreatedAt = now; + } + _context.PurchaseItems.AddRange(items); + await _context.SaveChangesAsync(); + } + + public async Task UpdateAsync(PurchaseItem item) + { + item.UpdatedAt = DateTime.UtcNow; + _context.PurchaseItems.Update(item); + await _context.SaveChangesAsync(); + } + + public async Task UpdateStatusAsync(int id, PurchaseItemStatus status) + { + var item = await _context.PurchaseItems.FindAsync(id); + if (item != null) + { + item.Status = status; + item.UpdatedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(); + } + } + + public async Task UpdateSupplierAsync(int id, int? supplierId) + { + var item = await _context.PurchaseItems.FindAsync(id); + if (item != null) + { + item.SupplierId = supplierId; + item.UpdatedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(); + } + } + + public async Task DeleteAsync(int id) + { + var item = await _context.PurchaseItems.FindAsync(id); + if (item != null) + { + _context.PurchaseItems.Remove(item); + await _context.SaveChangesAsync(); + } + } +}