From 696bf2f72c753342007f049aa8377ef4f79d3ec0 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 18 Feb 2026 20:36:52 -0500 Subject: [PATCH] feat: add BomItem upsert and find endpoints Add find-existing endpoint and upsert logic to POST so re-exporting a part updates the existing BomItem rather than creating duplicates. Co-Authored-By: Claude Opus 4.6 --- .../Controllers/BomItemsController.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/FabWorks.Api/Controllers/BomItemsController.cs b/FabWorks.Api/Controllers/BomItemsController.cs index e58668f..be41f59 100644 --- a/FabWorks.Api/Controllers/BomItemsController.cs +++ b/FabWorks.Api/Controllers/BomItemsController.cs @@ -14,6 +14,26 @@ namespace FabWorks.Api.Controllers public BomItemsController(FabWorksDbContext db) => _db = db; + [HttpGet("find")] + public async Task> FindExisting(int exportId, [FromQuery] string partName, [FromQuery] string configurationName) + { + var export = await _db.ExportRecords.FindAsync(exportId); + if (export == null) return NotFound(); + + var existing = await _db.BomItems + .Include(b => b.CutTemplate) + .Include(b => b.FormProgram) + .Include(b => b.ExportRecord) + .Where(b => b.ExportRecord.DrawingNumber == export.DrawingNumber + && b.PartName == (partName ?? "") + && b.ConfigurationName == (configurationName ?? "")) + .OrderByDescending(b => b.ID) + .FirstOrDefaultAsync(); + + if (existing == null) return NotFound(); + return MapToDto(existing); + } + [HttpGet] public async Task>> GetByExport(int exportId) { @@ -33,6 +53,89 @@ namespace FabWorks.Api.Controllers var export = await _db.ExportRecords.FindAsync(exportId); if (export == null) return NotFound("Export record not found"); + // Look for existing BomItem with same PartName + ConfigurationName under the same drawing + var existing = await _db.BomItems + .Include(b => b.CutTemplate) + .Include(b => b.FormProgram) + .Include(b => b.ExportRecord) + .Where(b => b.ExportRecord.DrawingNumber == export.DrawingNumber + && b.PartName == (dto.PartName ?? "") + && b.ConfigurationName == (dto.ConfigurationName ?? "")) + .OrderByDescending(b => b.ID) + .FirstOrDefaultAsync(); + + if (existing != null) + { + // Update existing: move to new export record and refresh fields + existing.ExportRecordId = exportId; + existing.PartNo = dto.PartNo ?? ""; + existing.SortOrder = dto.SortOrder; + existing.Qty = dto.Qty; + existing.TotalQty = dto.TotalQty; + existing.Description = dto.Description ?? ""; + existing.Material = dto.Material ?? ""; + + if (dto.CutTemplate != null) + { + if (existing.CutTemplate != null) + { + existing.CutTemplate.DxfFilePath = dto.CutTemplate.DxfFilePath ?? ""; + existing.CutTemplate.ContentHash = dto.CutTemplate.ContentHash; + existing.CutTemplate.Thickness = dto.CutTemplate.Thickness; + existing.CutTemplate.KFactor = dto.CutTemplate.KFactor; + existing.CutTemplate.DefaultBendRadius = dto.CutTemplate.DefaultBendRadius; + } + else + { + existing.CutTemplate = new CutTemplate + { + DxfFilePath = dto.CutTemplate.DxfFilePath ?? "", + ContentHash = dto.CutTemplate.ContentHash, + Thickness = dto.CutTemplate.Thickness, + KFactor = dto.CutTemplate.KFactor, + DefaultBendRadius = dto.CutTemplate.DefaultBendRadius + }; + } + } + + if (dto.FormProgram != null) + { + if (existing.FormProgram != null) + { + existing.FormProgram.ProgramFilePath = dto.FormProgram.ProgramFilePath ?? ""; + existing.FormProgram.ContentHash = dto.FormProgram.ContentHash; + existing.FormProgram.ProgramName = dto.FormProgram.ProgramName ?? ""; + existing.FormProgram.Thickness = dto.FormProgram.Thickness; + existing.FormProgram.MaterialType = dto.FormProgram.MaterialType ?? ""; + existing.FormProgram.KFactor = dto.FormProgram.KFactor; + existing.FormProgram.BendCount = dto.FormProgram.BendCount; + existing.FormProgram.UpperToolNames = dto.FormProgram.UpperToolNames ?? ""; + existing.FormProgram.LowerToolNames = dto.FormProgram.LowerToolNames ?? ""; + existing.FormProgram.SetupNotes = dto.FormProgram.SetupNotes ?? ""; + } + else + { + existing.FormProgram = new FormProgram + { + ProgramFilePath = dto.FormProgram.ProgramFilePath ?? "", + ContentHash = dto.FormProgram.ContentHash, + ProgramName = dto.FormProgram.ProgramName ?? "", + Thickness = dto.FormProgram.Thickness, + MaterialType = dto.FormProgram.MaterialType ?? "", + KFactor = dto.FormProgram.KFactor, + BendCount = dto.FormProgram.BendCount, + UpperToolNames = dto.FormProgram.UpperToolNames ?? "", + LowerToolNames = dto.FormProgram.LowerToolNames ?? "", + SetupNotes = dto.FormProgram.SetupNotes ?? "" + }; + } + } + + await _db.SaveChangesAsync(); + return Ok(MapToDto(existing)); + } + + // No existing match — create new var item = new BomItem { ExportRecordId = exportId,