Add inventory duplicate detection on add and label scan
Introduce a shared InventoryDuplicateMatcher (SKU, manufacturer part number, manufacturer color) used by both manual inventory creation and powder-label scanning, so the two paths flag duplicates consistently. Surfaces a duplicate warning in the Create/Edit forms via inventory-duplicate-check.js and the catalog-lookup / label-scan flows. Callers pass tenant-restricted inventory; the matcher re-checks CompanyId as defense in depth. Adds InventoryDuplicateMatcherTests covering the match precedence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
using PowderCoating.Application.Services;
|
||||
using PowderCoating.Core.Entities;
|
||||
|
||||
namespace PowderCoating.UnitTests;
|
||||
|
||||
public class InventoryDuplicateMatcherTests
|
||||
{
|
||||
[Fact]
|
||||
public void Find_SkuMatch_IsRestrictedToRequestedCompany()
|
||||
{
|
||||
var otherTenantItem = Item(
|
||||
id: 1,
|
||||
companyId: 22,
|
||||
sku: "POW-001",
|
||||
manufacturer: "Prismatic Powders",
|
||||
colorName: "Illusion Malbec");
|
||||
|
||||
var result = InventoryDuplicateMatcher.Find(
|
||||
new[] { otherTenantItem },
|
||||
companyId: 11,
|
||||
sku: "POW-001",
|
||||
manufacturer: null,
|
||||
manufacturerPartNumber: null,
|
||||
colorName: null,
|
||||
isCoating: false);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_SkuMatch_IsCaseAndWhitespaceInsensitive()
|
||||
{
|
||||
var item = Item(1, 11, "POW-001", null, null);
|
||||
|
||||
var result = InventoryDuplicateMatcher.Find(
|
||||
new[] { item },
|
||||
companyId: 11,
|
||||
sku: " pow-001 ",
|
||||
manufacturer: null,
|
||||
manufacturerPartNumber: null,
|
||||
colorName: null,
|
||||
isCoating: false);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(InventoryDuplicateMatchType.Sku, result!.MatchType);
|
||||
Assert.Same(item, result.Item);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_PowderManufacturerAndColor_NormalizesSpacingAndCase()
|
||||
{
|
||||
var item = Item(
|
||||
id: 1,
|
||||
companyId: 11,
|
||||
sku: "POW-001",
|
||||
manufacturer: "Prismatic Powders",
|
||||
colorName: "Illusion Malbec");
|
||||
|
||||
var result = InventoryDuplicateMatcher.Find(
|
||||
new[] { item },
|
||||
companyId: 11,
|
||||
sku: null,
|
||||
manufacturer: " prismatic powders ",
|
||||
manufacturerPartNumber: null,
|
||||
colorName: "illusion malbec",
|
||||
isCoating: true);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(InventoryDuplicateMatchType.ManufacturerColor, result!.MatchType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_SameColorFromDifferentManufacturer_IsNotDuplicate()
|
||||
{
|
||||
var item = Item(
|
||||
id: 1,
|
||||
companyId: 11,
|
||||
sku: "POW-001",
|
||||
manufacturer: "Prismatic Powders",
|
||||
colorName: "Gloss Black");
|
||||
|
||||
var result = InventoryDuplicateMatcher.Find(
|
||||
new[] { item },
|
||||
companyId: 11,
|
||||
sku: null,
|
||||
manufacturer: "Columbia Coatings",
|
||||
manufacturerPartNumber: null,
|
||||
colorName: "Gloss Black",
|
||||
isCoating: true);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_ManufacturerPartNumber_UsesSharedPowderRule()
|
||||
{
|
||||
var item = Item(
|
||||
id: 1,
|
||||
companyId: 11,
|
||||
sku: "POW-001",
|
||||
manufacturer: "Columbia Coatings",
|
||||
colorName: "Smokey Blue",
|
||||
manufacturerPartNumber: "S5704126");
|
||||
|
||||
var result = InventoryDuplicateMatcher.Find(
|
||||
new[] { item },
|
||||
companyId: 11,
|
||||
sku: null,
|
||||
manufacturer: "columbia coatings",
|
||||
manufacturerPartNumber: " s5704126 ",
|
||||
colorName: null,
|
||||
isCoating: true);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(InventoryDuplicateMatchType.ManufacturerPartNumber, result!.MatchType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Find_NonCoating_DoesNotUsePowderIdentityRules()
|
||||
{
|
||||
var item = Item(
|
||||
id: 1,
|
||||
companyId: 11,
|
||||
sku: "SUP-001",
|
||||
manufacturer: "Acme",
|
||||
colorName: "Blue");
|
||||
|
||||
var result = InventoryDuplicateMatcher.Find(
|
||||
new[] { item },
|
||||
companyId: 11,
|
||||
sku: null,
|
||||
manufacturer: "Acme",
|
||||
manufacturerPartNumber: null,
|
||||
colorName: "Blue",
|
||||
isCoating: false);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
private static InventoryItem Item(
|
||||
int id,
|
||||
int companyId,
|
||||
string sku,
|
||||
string? manufacturer,
|
||||
string? colorName,
|
||||
string? manufacturerPartNumber = null)
|
||||
{
|
||||
return new InventoryItem
|
||||
{
|
||||
Id = id,
|
||||
CompanyId = companyId,
|
||||
SKU = sku,
|
||||
Name = colorName ?? sku,
|
||||
Manufacturer = manufacturer,
|
||||
ManufacturerPartNumber = manufacturerPartNumber,
|
||||
ColorName = colorName,
|
||||
InventoryCategory = new InventoryCategoryLookup
|
||||
{
|
||||
Id = id,
|
||||
CompanyId = companyId,
|
||||
DisplayName = "Powder",
|
||||
CategoryCode = "POWDER",
|
||||
IsCoating = true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user