From 9e5e44c1ed5ccd80d75fa23e2983a8112976733a Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 18 Feb 2026 06:32:52 -0500 Subject: [PATCH] feat: add BomItems and FormPrograms controllers with parse service Co-Authored-By: Claude Opus 4.6 --- .../Controllers/BomItemsController.cs | 122 ++++++++++++++++++ .../Controllers/FormProgramsController.cs | 106 +++++++++++++++ FabWorks.Api/Services/FormProgramService.cs | 39 ++++++ 3 files changed, 267 insertions(+) create mode 100644 FabWorks.Api/Controllers/BomItemsController.cs create mode 100644 FabWorks.Api/Controllers/FormProgramsController.cs create mode 100644 FabWorks.Api/Services/FormProgramService.cs diff --git a/FabWorks.Api/Controllers/BomItemsController.cs b/FabWorks.Api/Controllers/BomItemsController.cs new file mode 100644 index 0000000..e58668f --- /dev/null +++ b/FabWorks.Api/Controllers/BomItemsController.cs @@ -0,0 +1,122 @@ +using FabWorks.Api.DTOs; +using FabWorks.Core.Data; +using FabWorks.Core.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace FabWorks.Api.Controllers +{ + [ApiController] + [Route("api/exports/{exportId}/bom-items")] + public class BomItemsController : ControllerBase + { + private readonly FabWorksDbContext _db; + + public BomItemsController(FabWorksDbContext db) => _db = db; + + [HttpGet] + public async Task>> GetByExport(int exportId) + { + var items = await _db.BomItems + .Include(b => b.CutTemplate) + .Include(b => b.FormProgram) + .Where(b => b.ExportRecordId == exportId) + .OrderBy(b => b.SortOrder) + .ToListAsync(); + + return items.Select(MapToDto).ToList(); + } + + [HttpPost] + public async Task> Create(int exportId, BomItemDto dto) + { + var export = await _db.ExportRecords.FindAsync(exportId); + if (export == null) return NotFound("Export record not found"); + + var item = new BomItem + { + ExportRecordId = exportId, + ItemNo = dto.ItemNo ?? "", + PartNo = dto.PartNo ?? "", + SortOrder = dto.SortOrder, + Qty = dto.Qty, + TotalQty = dto.TotalQty, + Description = dto.Description ?? "", + PartName = dto.PartName ?? "", + ConfigurationName = dto.ConfigurationName ?? "", + Material = dto.Material ?? "" + }; + + if (dto.CutTemplate != null) + { + item.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) + { + item.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 ?? "" + }; + } + + _db.BomItems.Add(item); + await _db.SaveChangesAsync(); + + return CreatedAtAction(nameof(GetByExport), new { exportId }, MapToDto(item)); + } + + private static BomItemDto MapToDto(BomItem b) => new() + { + ID = b.ID, + ItemNo = b.ItemNo, + PartNo = b.PartNo, + SortOrder = b.SortOrder, + Qty = b.Qty, + TotalQty = b.TotalQty, + Description = b.Description, + PartName = b.PartName, + ConfigurationName = b.ConfigurationName, + Material = b.Material, + CutTemplate = b.CutTemplate == null ? null : new CutTemplateDto + { + Id = b.CutTemplate.Id, + DxfFilePath = b.CutTemplate.DxfFilePath, + ContentHash = b.CutTemplate.ContentHash, + Thickness = b.CutTemplate.Thickness, + KFactor = b.CutTemplate.KFactor, + DefaultBendRadius = b.CutTemplate.DefaultBendRadius + }, + FormProgram = b.FormProgram == null ? null : new FormProgramDto + { + Id = b.FormProgram.Id, + ProgramFilePath = b.FormProgram.ProgramFilePath, + ContentHash = b.FormProgram.ContentHash, + ProgramName = b.FormProgram.ProgramName, + Thickness = b.FormProgram.Thickness, + MaterialType = b.FormProgram.MaterialType, + KFactor = b.FormProgram.KFactor, + BendCount = b.FormProgram.BendCount, + UpperToolNames = b.FormProgram.UpperToolNames, + LowerToolNames = b.FormProgram.LowerToolNames, + SetupNotes = b.FormProgram.SetupNotes + } + }; + } +} diff --git a/FabWorks.Api/Controllers/FormProgramsController.cs b/FabWorks.Api/Controllers/FormProgramsController.cs new file mode 100644 index 0000000..39ab191 --- /dev/null +++ b/FabWorks.Api/Controllers/FormProgramsController.cs @@ -0,0 +1,106 @@ +using FabWorks.Api.DTOs; +using FabWorks.Api.Services; +using FabWorks.Core.Data; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace FabWorks.Api.Controllers +{ + [ApiController] + [Route("api/form-programs")] + public class FormProgramsController : ControllerBase + { + private readonly FabWorksDbContext _db; + private readonly FormProgramService _formService; + + public FormProgramsController(FabWorksDbContext db, FormProgramService formService) + { + _db = db; + _formService = formService; + } + + [HttpGet("by-drawing")] + public async Task>> GetByDrawing([FromQuery] string drawingNumber) + { + var programs = await _db.FormPrograms + .Include(fp => fp.BomItem) + .ThenInclude(b => b.ExportRecord) + .Where(fp => fp.BomItem.ExportRecord.DrawingNumber == drawingNumber) + .ToListAsync(); + + return programs.Select(fp => new FormProgramDto + { + Id = fp.Id, + ProgramFilePath = fp.ProgramFilePath, + ContentHash = fp.ContentHash, + ProgramName = fp.ProgramName, + Thickness = fp.Thickness, + MaterialType = fp.MaterialType, + KFactor = fp.KFactor, + BendCount = fp.BendCount, + UpperToolNames = fp.UpperToolNames, + LowerToolNames = fp.LowerToolNames, + SetupNotes = fp.SetupNotes + }).ToList(); + } + + [HttpPost("parse")] + public ActionResult Parse([FromQuery] string filePath) + { + if (!System.IO.File.Exists(filePath)) + return NotFound($"File not found: {filePath}"); + + var fp = _formService.ParseFromFile(filePath); + return new FormProgramDto + { + ProgramFilePath = fp.ProgramFilePath, + ContentHash = fp.ContentHash, + ProgramName = fp.ProgramName, + Thickness = fp.Thickness, + MaterialType = fp.MaterialType, + KFactor = fp.KFactor, + BendCount = fp.BendCount, + UpperToolNames = fp.UpperToolNames, + LowerToolNames = fp.LowerToolNames, + SetupNotes = fp.SetupNotes + }; + } + + [HttpPost("{bomItemId}")] + public async Task> AttachToItem(int bomItemId, [FromQuery] string filePath) + { + var bomItem = await _db.BomItems + .Include(b => b.FormProgram) + .FirstOrDefaultAsync(b => b.ID == bomItemId); + + if (bomItem == null) return NotFound("BOM item not found"); + + if (!System.IO.File.Exists(filePath)) + return NotFound($"File not found: {filePath}"); + + var fp = _formService.ParseFromFile(filePath); + fp.BomItemId = bomItemId; + + if (bomItem.FormProgram != null) + _db.FormPrograms.Remove(bomItem.FormProgram); + + bomItem.FormProgram = fp; + await _db.SaveChangesAsync(); + + return new FormProgramDto + { + Id = fp.Id, + ProgramFilePath = fp.ProgramFilePath, + ContentHash = fp.ContentHash, + ProgramName = fp.ProgramName, + Thickness = fp.Thickness, + MaterialType = fp.MaterialType, + KFactor = fp.KFactor, + BendCount = fp.BendCount, + UpperToolNames = fp.UpperToolNames, + LowerToolNames = fp.LowerToolNames, + SetupNotes = fp.SetupNotes + }; + } + } +} diff --git a/FabWorks.Api/Services/FormProgramService.cs b/FabWorks.Api/Services/FormProgramService.cs new file mode 100644 index 0000000..01cb56e --- /dev/null +++ b/FabWorks.Api/Services/FormProgramService.cs @@ -0,0 +1,39 @@ +using FabWorks.Core.Models; +using FabWorks.Core.PressBrake; +using System.Security.Cryptography; + +namespace FabWorks.Api.Services +{ + public class FormProgramService + { + public FormProgram ParseFromFile(string filePath) + { + var pgm = FabWorks.Core.PressBrake.Program.Load(filePath); + var hash = ComputeFileHash(filePath); + + return new FormProgram + { + ProgramFilePath = filePath, + ContentHash = hash, + ProgramName = pgm.ProgName ?? "", + Thickness = pgm.MatThick > 0 ? pgm.MatThick : null, + MaterialType = pgm.MatType.ToString(), + KFactor = pgm.KFactor > 0 ? pgm.KFactor : null, + BendCount = pgm.Steps.Count, + UpperToolNames = string.Join(", ", pgm.UpperToolSets + .Select(t => t.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct()), + LowerToolNames = string.Join(", ", pgm.LowerToolSets + .Select(t => t.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct()), + SetupNotes = pgm.SetupNotes ?? "" + }; + } + + private static string ComputeFileHash(string filePath) + { + using var sha = SHA256.Create(); + using var stream = File.OpenRead(filePath); + var bytes = sha.ComputeHash(stream); + return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); + } + } +}