Auto-receive catalog powders and fix soft-deleted SKU collision
Two fixes to the "Got It" powder receive flow: 1. Skip the modal when the powder is in the master catalog. Clicking "Got It" now first calls ReceivePowderFromCatalog, which — if the powder resolves in the catalog — creates a fully populated inventory record (specs, cure, SDS/ TDS, image, pricing) and marks the coat received, no modal. Only when the powder isn't in the catalog does it fall back to the manual entry modal. The catalog match/apply and the receive finalize (opening txn, mark received, sibling-coat linking) are extracted into shared helpers used by both the modal save and the auto-receive path. 2. Fix a crash re-receiving a previously-deleted powder. The unique index IX_InventoryItems_CompanyId_SKU had no filter, so a soft-deleted item still reserved its SKU; re-creating it generated the same SKU and violated the constraint. The index is now filtered on IsDeleted = 0, matching the app's soft-delete semantics. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1511,6 +1511,9 @@ modelBuilder.Entity<Job>()
|
||||
modelBuilder.Entity<InventoryItem>()
|
||||
.HasIndex(i => new { i.CompanyId, i.SKU })
|
||||
.IsUnique()
|
||||
// Filter on IsDeleted so soft-deleted items don't reserve their SKU and block a new
|
||||
// (or re-created) item from reusing it — matching the app's soft-delete semantics.
|
||||
.HasFilter("[IsDeleted] = 0")
|
||||
.HasDatabaseName("IX_InventoryItems_CompanyId_SKU");
|
||||
|
||||
modelBuilder.Entity<Company>()
|
||||
|
||||
+11376
File diff suppressed because it is too large
Load Diff
+82
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PowderCoating.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FilterInventorySkuUniqueIndexOnSoftDelete : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_InventoryItems_CompanyId_SKU",
|
||||
table: "InventoryItems");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 17, 16, 33, 6, 662, DateTimeKind.Utc).AddTicks(4251));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 2,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 17, 16, 33, 6, 662, DateTimeKind.Utc).AddTicks(4258));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 3,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 17, 16, 33, 6, 662, DateTimeKind.Utc).AddTicks(4259));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InventoryItems_CompanyId_SKU",
|
||||
table: "InventoryItems",
|
||||
columns: new[] { "CompanyId", "SKU" },
|
||||
unique: true,
|
||||
filter: "[IsDeleted] = 0");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_InventoryItems_CompanyId_SKU",
|
||||
table: "InventoryItems");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 1,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 17, 14, 44, 40, 147, DateTimeKind.Utc).AddTicks(5997));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 2,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 17, 14, 44, 40, 147, DateTimeKind.Utc).AddTicks(6002));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "PricingTiers",
|
||||
keyColumn: "Id",
|
||||
keyValue: 3,
|
||||
column: "CreatedAt",
|
||||
value: new DateTime(2026, 6, 17, 14, 44, 40, 147, DateTimeKind.Utc).AddTicks(6003));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InventoryItems_CompanyId_SKU",
|
||||
table: "InventoryItems",
|
||||
columns: new[] { "CompanyId", "SKU" },
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4244,7 +4244,8 @@ namespace PowderCoating.Infrastructure.Migrations
|
||||
|
||||
b.HasIndex("CompanyId", "SKU")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_InventoryItems_CompanyId_SKU");
|
||||
.HasDatabaseName("IX_InventoryItems_CompanyId_SKU")
|
||||
.HasFilter("[IsDeleted] = 0");
|
||||
|
||||
b.HasIndex("CompanyId", "QuantityOnHand", "ReorderPoint")
|
||||
.HasDatabaseName("IX_InventoryItems_CompanyId_Quantity_Reorder");
|
||||
@@ -7234,7 +7235,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
||||
{
|
||||
Id = 1,
|
||||
CompanyId = 0,
|
||||
CreatedAt = new DateTime(2026, 6, 17, 14, 44, 40, 147, DateTimeKind.Utc).AddTicks(5997),
|
||||
CreatedAt = new DateTime(2026, 6, 17, 16, 33, 6, 662, DateTimeKind.Utc).AddTicks(4251),
|
||||
Description = "Standard pricing for regular customers",
|
||||
DiscountPercent = 0m,
|
||||
IsActive = true,
|
||||
@@ -7245,7 +7246,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
||||
{
|
||||
Id = 2,
|
||||
CompanyId = 0,
|
||||
CreatedAt = new DateTime(2026, 6, 17, 14, 44, 40, 147, DateTimeKind.Utc).AddTicks(6002),
|
||||
CreatedAt = new DateTime(2026, 6, 17, 16, 33, 6, 662, DateTimeKind.Utc).AddTicks(4258),
|
||||
Description = "5% discount for preferred customers",
|
||||
DiscountPercent = 5m,
|
||||
IsActive = true,
|
||||
@@ -7256,7 +7257,7 @@ namespace PowderCoating.Infrastructure.Migrations
|
||||
{
|
||||
Id = 3,
|
||||
CompanyId = 0,
|
||||
CreatedAt = new DateTime(2026, 6, 17, 14, 44, 40, 147, DateTimeKind.Utc).AddTicks(6003),
|
||||
CreatedAt = new DateTime(2026, 6, 17, 16, 33, 6, 662, DateTimeKind.Utc).AddTicks(4259),
|
||||
Description = "10% discount for premium customers",
|
||||
DiscountPercent = 10m,
|
||||
IsActive = true,
|
||||
|
||||
Reference in New Issue
Block a user