feat: drop supplier import/export from the catalog format

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
aj
2026-08-01 08:45:21 -04:00
co-authored by Claude Sonnet 5
parent 063c844310
commit 41944638c6
2 changed files with 20 additions and 166 deletions
-22
View File
@@ -3,18 +3,10 @@ namespace CutList.Web.DTOs;
public class CatalogData public class CatalogData
{ {
public DateTime ExportedAt { get; set; } public DateTime ExportedAt { get; set; }
public List<CatalogSupplierDto> Suppliers { get; set; } = [];
public List<CatalogCuttingToolDto> CuttingTools { get; set; } = []; public List<CatalogCuttingToolDto> CuttingTools { get; set; } = [];
public CatalogMaterialsDto Materials { get; set; } = new(); public CatalogMaterialsDto Materials { get; set; } = new();
} }
public class CatalogSupplierDto
{
public string Name { get; set; } = "";
public string? ContactInfo { get; set; }
public string? Notes { get; set; }
}
public class CatalogCuttingToolDto public class CatalogCuttingToolDto
{ {
public string Name { get; set; } = ""; public string Name { get; set; } = "";
@@ -113,30 +105,16 @@ public class CatalogStockItemDto
public string? Name { get; set; } public string? Name { get; set; }
public int QuantityOnHand { get; set; } public int QuantityOnHand { get; set; }
public string? Notes { get; set; } public string? Notes { get; set; }
public List<CatalogSupplierOfferingDto> SupplierOfferings { get; set; } = [];
}
public class CatalogSupplierOfferingDto
{
public string SupplierName { get; set; } = "";
public string? PartNumber { get; set; }
public string? SupplierDescription { get; set; }
public decimal? Price { get; set; }
public string? Notes { get; set; }
} }
public class ImportResultDto public class ImportResultDto
{ {
public int SuppliersCreated { get; set; }
public int SuppliersUpdated { get; set; }
public int CuttingToolsCreated { get; set; } public int CuttingToolsCreated { get; set; }
public int CuttingToolsUpdated { get; set; } public int CuttingToolsUpdated { get; set; }
public int MaterialsCreated { get; set; } public int MaterialsCreated { get; set; }
public int MaterialsUpdated { get; set; } public int MaterialsUpdated { get; set; }
public int StockItemsCreated { get; set; } public int StockItemsCreated { get; set; }
public int StockItemsUpdated { get; set; } public int StockItemsUpdated { get; set; }
public int OfferingsCreated { get; set; }
public int OfferingsUpdated { get; set; }
public List<string> Errors { get; set; } = []; public List<string> Errors { get; set; } = [];
public List<string> Warnings { get; set; } = []; public List<string> Warnings { get; set; } = [];
} }
+20 -144
View File
@@ -20,12 +20,6 @@ public class CatalogService
{ {
await using var context = _factory.CreateDbContext(); await using var context = _factory.CreateDbContext();
var suppliers = await context.Suppliers
.Where(s => s.IsActive)
.OrderBy(s => s.Name)
.AsNoTracking()
.ToListAsync();
var cuttingTools = await context.CuttingTools var cuttingTools = await context.CuttingTools
.Where(t => t.IsActive) .Where(t => t.IsActive)
.OrderBy(t => t.Name) .OrderBy(t => t.Name)
@@ -35,7 +29,6 @@ public class CatalogService
var materials = await context.Materials var materials = await context.Materials
.Include(m => m.Dimensions) .Include(m => m.Dimensions)
.Include(m => m.StockItems.Where(s => s.IsActive)) .Include(m => m.StockItems.Where(s => s.IsActive))
.ThenInclude(s => s.SupplierOfferings.Where(o => o.IsActive))
.Where(m => m.IsActive) .Where(m => m.IsActive)
.OrderBy(m => m.Shape).ThenBy(m => m.SortOrder) .OrderBy(m => m.Shape).ThenBy(m => m.SortOrder)
.AsNoTracking() .AsNoTracking()
@@ -48,7 +41,7 @@ public class CatalogService
{ {
foreach (var m in group) foreach (var m in group)
{ {
var stockItems = MapStockItems(m, suppliers); var stockItems = MapStockItems(m);
switch (m.Shape) switch (m.Shape)
{ {
@@ -139,12 +132,6 @@ public class CatalogService
return new CatalogData return new CatalogData
{ {
ExportedAt = DateTime.UtcNow, ExportedAt = DateTime.UtcNow,
Suppliers = suppliers.Select(s => new CatalogSupplierDto
{
Name = s.Name,
ContactInfo = s.ContactInfo,
Notes = s.Notes
}).ToList(),
CuttingTools = cuttingTools.Select(t => new CatalogCuttingToolDto CuttingTools = cuttingTools.Select(t => new CatalogCuttingToolDto
{ {
Name = t.Name, Name = t.Name,
@@ -164,14 +151,11 @@ public class CatalogService
try try
{ {
// 1. Suppliers - upsert by name // 1. Cutting tools - upsert by name
var supplierMap = await ImportSuppliersAsync(context, data.Suppliers, result);
// 2. Cutting tools - upsert by name
await ImportCuttingToolsAsync(context, data.CuttingTools, result); await ImportCuttingToolsAsync(context, data.CuttingTools, result);
// 3. Materials + stock items + offerings // 2. Materials + stock items
await ImportAllMaterialsAsync(context, data.Materials, supplierMap, result); await ImportAllMaterialsAsync(context, data.Materials, result);
await transaction.CommitAsync(); await transaction.CommitAsync();
} }
@@ -184,54 +168,6 @@ public class CatalogService
return result; return result;
} }
private async Task<Dictionary<string, int>> ImportSuppliersAsync(
ApplicationDbContext context, List<CatalogSupplierDto> suppliers, ImportResultDto result)
{
var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var existingSuppliers = await context.Suppliers.ToListAsync();
foreach (var dto in suppliers)
{
try
{
var existing = existingSuppliers.FirstOrDefault(
s => s.Name.Equals(dto.Name, StringComparison.OrdinalIgnoreCase));
if (existing != null)
{
existing.ContactInfo = dto.ContactInfo ?? existing.ContactInfo;
existing.Notes = dto.Notes ?? existing.Notes;
existing.IsActive = true;
map[dto.Name] = existing.Id;
result.SuppliersUpdated++;
}
else
{
var supplier = new Supplier
{
Name = dto.Name,
ContactInfo = dto.ContactInfo,
Notes = dto.Notes,
CreatedAt = DateTime.UtcNow
};
context.Suppliers.Add(supplier);
await context.SaveChangesAsync();
existingSuppliers.Add(supplier);
map[dto.Name] = supplier.Id;
result.SuppliersCreated++;
}
}
catch (Exception ex)
{
result.Errors.Add($"Supplier '{dto.Name}': {ex.Message}");
}
}
await context.SaveChangesAsync();
return map;
}
private async Task ImportCuttingToolsAsync( private async Task ImportCuttingToolsAsync(
ApplicationDbContext context, List<CatalogCuttingToolDto> tools, ImportResultDto result) ApplicationDbContext context, List<CatalogCuttingToolDto> tools, ImportResultDto result)
{ {
@@ -273,68 +209,67 @@ public class CatalogService
} }
private async Task ImportAllMaterialsAsync( private async Task ImportAllMaterialsAsync(
ApplicationDbContext context, CatalogMaterialsDto materials, Dictionary<string, int> supplierMap, ImportResultDto result) ApplicationDbContext context, CatalogMaterialsDto materials, ImportResultDto result)
{ {
var existingMaterials = await context.Materials var existingMaterials = await context.Materials
.Include(m => m.Dimensions) .Include(m => m.Dimensions)
.Include(m => m.StockItems) .Include(m => m.StockItems)
.ThenInclude(s => s.SupplierOfferings)
.ToListAsync(); .ToListAsync();
foreach (var dto in materials.Angles) foreach (var dto in materials.Angles)
await ImportMaterialAsync(context, dto, MaterialShape.Angle, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.Angle, existingMaterials, result,
() => new AngleDimensions { Leg1 = dto.Leg1, Leg2 = dto.Leg2, Thickness = dto.Thickness }, () => new AngleDimensions { Leg1 = dto.Leg1, Leg2 = dto.Leg2, Thickness = dto.Thickness },
dim => { var d = (AngleDimensions)dim; d.Leg1 = dto.Leg1; d.Leg2 = dto.Leg2; d.Thickness = dto.Thickness; }); dim => { var d = (AngleDimensions)dim; d.Leg1 = dto.Leg1; d.Leg2 = dto.Leg2; d.Thickness = dto.Thickness; });
foreach (var dto in materials.Channels) foreach (var dto in materials.Channels)
await ImportMaterialAsync(context, dto, MaterialShape.Channel, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.Channel, existingMaterials, result,
() => new ChannelDimensions { Height = dto.Height, Flange = dto.Flange, Web = dto.Web }, () => new ChannelDimensions { Height = dto.Height, Flange = dto.Flange, Web = dto.Web },
dim => { var d = (ChannelDimensions)dim; d.Height = dto.Height; d.Flange = dto.Flange; d.Web = dto.Web; }); dim => { var d = (ChannelDimensions)dim; d.Height = dto.Height; d.Flange = dto.Flange; d.Web = dto.Web; });
foreach (var dto in materials.FlatBars) foreach (var dto in materials.FlatBars)
await ImportMaterialAsync(context, dto, MaterialShape.FlatBar, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.FlatBar, existingMaterials, result,
() => new FlatBarDimensions { Width = dto.Width, Thickness = dto.Thickness }, () => new FlatBarDimensions { Width = dto.Width, Thickness = dto.Thickness },
dim => { var d = (FlatBarDimensions)dim; d.Width = dto.Width; d.Thickness = dto.Thickness; }); dim => { var d = (FlatBarDimensions)dim; d.Width = dto.Width; d.Thickness = dto.Thickness; });
foreach (var dto in materials.IBeams) foreach (var dto in materials.IBeams)
await ImportMaterialAsync(context, dto, MaterialShape.IBeam, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.IBeam, existingMaterials, result,
() => new IBeamDimensions { Height = dto.Height, WeightPerFoot = dto.WeightPerFoot }, () => new IBeamDimensions { Height = dto.Height, WeightPerFoot = dto.WeightPerFoot },
dim => { var d = (IBeamDimensions)dim; d.Height = dto.Height; d.WeightPerFoot = dto.WeightPerFoot; }); dim => { var d = (IBeamDimensions)dim; d.Height = dto.Height; d.WeightPerFoot = dto.WeightPerFoot; });
foreach (var dto in materials.Pipes) foreach (var dto in materials.Pipes)
await ImportMaterialAsync(context, dto, MaterialShape.Pipe, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.Pipe, existingMaterials, result,
() => new PipeDimensions { NominalSize = dto.NominalSize, Wall = dto.Wall, Schedule = dto.Schedule }, () => new PipeDimensions { NominalSize = dto.NominalSize, Wall = dto.Wall, Schedule = dto.Schedule },
dim => { var d = (PipeDimensions)dim; d.NominalSize = dto.NominalSize; d.Wall = (decimal?)dto.Wall; d.Schedule = dto.Schedule; }); dim => { var d = (PipeDimensions)dim; d.NominalSize = dto.NominalSize; d.Wall = (decimal?)dto.Wall; d.Schedule = dto.Schedule; });
foreach (var dto in materials.RectangularTubes) foreach (var dto in materials.RectangularTubes)
await ImportMaterialAsync(context, dto, MaterialShape.RectangularTube, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.RectangularTube, existingMaterials, result,
() => new RectangularTubeDimensions { Width = dto.Width, Height = dto.Height, Wall = dto.Wall }, () => new RectangularTubeDimensions { Width = dto.Width, Height = dto.Height, Wall = dto.Wall },
dim => { var d = (RectangularTubeDimensions)dim; d.Width = dto.Width; d.Height = dto.Height; d.Wall = dto.Wall; }); dim => { var d = (RectangularTubeDimensions)dim; d.Width = dto.Width; d.Height = dto.Height; d.Wall = dto.Wall; });
foreach (var dto in materials.RoundBars) foreach (var dto in materials.RoundBars)
await ImportMaterialAsync(context, dto, MaterialShape.RoundBar, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.RoundBar, existingMaterials, result,
() => new RoundBarDimensions { Diameter = dto.Diameter }, () => new RoundBarDimensions { Diameter = dto.Diameter },
dim => { var d = (RoundBarDimensions)dim; d.Diameter = dto.Diameter; }); dim => { var d = (RoundBarDimensions)dim; d.Diameter = dto.Diameter; });
foreach (var dto in materials.RoundTubes) foreach (var dto in materials.RoundTubes)
await ImportMaterialAsync(context, dto, MaterialShape.RoundTube, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.RoundTube, existingMaterials, result,
() => new RoundTubeDimensions { OuterDiameter = dto.OuterDiameter, Wall = dto.Wall }, () => new RoundTubeDimensions { OuterDiameter = dto.OuterDiameter, Wall = dto.Wall },
dim => { var d = (RoundTubeDimensions)dim; d.OuterDiameter = dto.OuterDiameter; d.Wall = dto.Wall; }); dim => { var d = (RoundTubeDimensions)dim; d.OuterDiameter = dto.OuterDiameter; d.Wall = dto.Wall; });
foreach (var dto in materials.SquareBars) foreach (var dto in materials.SquareBars)
await ImportMaterialAsync(context, dto, MaterialShape.SquareBar, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.SquareBar, existingMaterials, result,
() => new SquareBarDimensions { Size = dto.SideLength }, () => new SquareBarDimensions { Size = dto.SideLength },
dim => { var d = (SquareBarDimensions)dim; d.Size = dto.SideLength; }); dim => { var d = (SquareBarDimensions)dim; d.Size = dto.SideLength; });
foreach (var dto in materials.SquareTubes) foreach (var dto in materials.SquareTubes)
await ImportMaterialAsync(context, dto, MaterialShape.SquareTube, existingMaterials, supplierMap, result, await ImportMaterialAsync(context, dto, MaterialShape.SquareTube, existingMaterials, result,
() => new SquareTubeDimensions { Size = dto.SideLength, Wall = dto.Wall }, () => new SquareTubeDimensions { Size = dto.SideLength, Wall = dto.Wall },
dim => { var d = (SquareTubeDimensions)dim; d.Size = dto.SideLength; d.Wall = dto.Wall; }); dim => { var d = (SquareTubeDimensions)dim; d.Size = dto.SideLength; d.Wall = dto.Wall; });
} }
private async Task ImportMaterialAsync( private async Task ImportMaterialAsync(
ApplicationDbContext context, CatalogMaterialBaseDto dto, MaterialShape shape, ApplicationDbContext context, CatalogMaterialBaseDto dto, MaterialShape shape,
List<Material> existingMaterials, Dictionary<string, int> supplierMap, List<Material> existingMaterials,
ImportResultDto result, ImportResultDto result,
Func<MaterialDimensions> createDimensions, Func<MaterialDimensions> createDimensions,
Action<MaterialDimensions> updateDimensions) Action<MaterialDimensions> updateDimensions)
@@ -389,7 +324,7 @@ public class CatalogService
await context.SaveChangesAsync(); await context.SaveChangesAsync();
await ImportStockItemsAsync(context, material, dto.StockItems, supplierMap, result); await ImportStockItemsAsync(context, material, dto.StockItems, result);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -399,10 +334,9 @@ public class CatalogService
private async Task ImportStockItemsAsync( private async Task ImportStockItemsAsync(
ApplicationDbContext context, Material material, List<CatalogStockItemDto> stockItems, ApplicationDbContext context, Material material, List<CatalogStockItemDto> stockItems,
Dictionary<string, int> supplierMap, ImportResultDto result) ImportResultDto result)
{ {
var existingStockItems = await context.StockItems var existingStockItems = await context.StockItems
.Include(s => s.SupplierOfferings)
.Where(s => s.MaterialId == material.Id) .Where(s => s.MaterialId == material.Id)
.ToListAsync(); .ToListAsync();
@@ -440,56 +374,6 @@ public class CatalogService
existingStockItems.Add(stockItem); existingStockItems.Add(stockItem);
result.StockItemsCreated++; result.StockItemsCreated++;
} }
foreach (var offeringDto in dto.SupplierOfferings)
{
try
{
if (!supplierMap.TryGetValue(offeringDto.SupplierName, out var supplierId))
{
result.Warnings.Add(
$"Offering for stock '{material.DisplayName} @ {dto.LengthInches}\"': " +
$"Unknown supplier '{offeringDto.SupplierName}', skipped");
continue;
}
var existingOffering = stockItem.SupplierOfferings.FirstOrDefault(
o => o.SupplierId == supplierId);
if (existingOffering != null)
{
existingOffering.PartNumber = offeringDto.PartNumber ?? existingOffering.PartNumber;
existingOffering.SupplierDescription = offeringDto.SupplierDescription ?? existingOffering.SupplierDescription;
existingOffering.Price = offeringDto.Price ?? existingOffering.Price;
existingOffering.Notes = offeringDto.Notes ?? existingOffering.Notes;
existingOffering.IsActive = true;
result.OfferingsUpdated++;
}
else
{
var offering = new SupplierOffering
{
StockItemId = stockItem.Id,
SupplierId = supplierId,
PartNumber = offeringDto.PartNumber,
SupplierDescription = offeringDto.SupplierDescription,
Price = offeringDto.Price,
Notes = offeringDto.Notes
};
context.SupplierOfferings.Add(offering);
stockItem.SupplierOfferings.Add(offering);
result.OfferingsCreated++;
}
}
catch (Exception ex)
{
result.Errors.Add(
$"Offering for '{material.DisplayName} @ {dto.LengthInches}\"' " +
$"from '{offeringDto.SupplierName}': {ex.Message}");
}
}
await context.SaveChangesAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -499,22 +383,14 @@ public class CatalogService
} }
} }
private static List<CatalogStockItemDto> MapStockItems(Material m, List<Supplier> suppliers) private static List<CatalogStockItemDto> MapStockItems(Material m)
{ {
return m.StockItems.OrderBy(s => s.LengthInches).Select(s => new CatalogStockItemDto return m.StockItems.OrderBy(s => s.LengthInches).Select(s => new CatalogStockItemDto
{ {
LengthInches = s.LengthInches, LengthInches = s.LengthInches,
Name = s.Name, Name = s.Name,
QuantityOnHand = s.QuantityOnHand, QuantityOnHand = s.QuantityOnHand,
Notes = s.Notes, Notes = s.Notes
SupplierOfferings = s.SupplierOfferings.Select(o => new CatalogSupplierOfferingDto
{
SupplierName = suppliers.FirstOrDefault(sup => sup.Id == o.SupplierId)?.Name ?? "Unknown",
PartNumber = o.PartNumber,
SupplierDescription = o.SupplierDescription,
Price = o.Price,
Notes = o.Notes
}).ToList()
}).ToList(); }).ToList();
} }
} }