From f8020549fed8fbace4cccb197f42a82decf0c2cd Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 1 Feb 2026 23:55:48 -0500 Subject: [PATCH] feat: Add REST API controllers for materials Adds MaterialsController with bulk import support and SeedController for development data seeding. Co-Authored-By: Claude Opus 4.5 --- .../Controllers/MaterialsController.cs | 170 ++++++++++++++++++ CutList.Web/Controllers/SeedController.cs | 94 ++++++++++ 2 files changed, 264 insertions(+) create mode 100644 CutList.Web/Controllers/MaterialsController.cs create mode 100644 CutList.Web/Controllers/SeedController.cs diff --git a/CutList.Web/Controllers/MaterialsController.cs b/CutList.Web/Controllers/MaterialsController.cs new file mode 100644 index 0000000..6ea4a83 --- /dev/null +++ b/CutList.Web/Controllers/MaterialsController.cs @@ -0,0 +1,170 @@ +using CutList.Web.Data; +using CutList.Web.Data.Entities; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace CutList.Web.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class MaterialsController : ControllerBase +{ + private readonly ApplicationDbContext _context; + + public MaterialsController(ApplicationDbContext context) + { + _context = context; + } + + [HttpGet] + public async Task>> GetMaterials() + { + var materials = await _context.Materials + .Where(m => m.IsActive) + .OrderBy(m => m.Shape) + .ThenBy(m => m.Size) + .Select(m => new MaterialDto + { + Id = m.Id, + Shape = m.Shape, + Size = m.Size, + Description = m.Description + }) + .ToListAsync(); + + return Ok(materials); + } + + [HttpGet("{id}")] + public async Task> GetMaterial(int id) + { + var material = await _context.Materials.FindAsync(id); + + if (material == null || !material.IsActive) + return NotFound(); + + return Ok(new MaterialDto + { + Id = material.Id, + Shape = material.Shape, + Size = material.Size, + Description = material.Description + }); + } + + [HttpPost] + public async Task> CreateMaterial(CreateMaterialDto dto) + { + if (string.IsNullOrWhiteSpace(dto.Shape)) + return BadRequest("Shape is required"); + + if (string.IsNullOrWhiteSpace(dto.Size)) + return BadRequest("Size is required"); + + // Check for duplicates + var exists = await _context.Materials + .AnyAsync(m => m.Shape == dto.Shape && m.Size == dto.Size && m.IsActive); + + if (exists) + return Conflict($"Material '{dto.Shape} - {dto.Size}' already exists"); + + var material = new Material + { + Shape = dto.Shape, + Size = dto.Size, + Description = dto.Description, + CreatedAt = DateTime.UtcNow + }; + + _context.Materials.Add(material); + await _context.SaveChangesAsync(); + + return CreatedAtAction(nameof(GetMaterial), new { id = material.Id }, new MaterialDto + { + Id = material.Id, + Shape = material.Shape, + Size = material.Size, + Description = material.Description + }); + } + + [HttpPost("bulk")] + public async Task> CreateMaterialsBulk(List materials) + { + var created = 0; + var skipped = 0; + var errors = new List(); + + foreach (var dto in materials) + { + if (string.IsNullOrWhiteSpace(dto.Shape) || string.IsNullOrWhiteSpace(dto.Size)) + { + errors.Add($"Invalid material: Shape and Size are required"); + continue; + } + + var exists = await _context.Materials + .AnyAsync(m => m.Shape == dto.Shape && m.Size == dto.Size && m.IsActive); + + if (exists) + { + skipped++; + continue; + } + + _context.Materials.Add(new Material + { + Shape = dto.Shape, + Size = dto.Size, + Description = dto.Description, + CreatedAt = DateTime.UtcNow + }); + created++; + } + + await _context.SaveChangesAsync(); + + return Ok(new BulkCreateResult + { + Created = created, + Skipped = skipped, + Errors = errors + }); + } + + [HttpDelete("{id}")] + public async Task DeleteMaterial(int id) + { + var material = await _context.Materials.FindAsync(id); + + if (material == null) + return NotFound(); + + material.IsActive = false; + await _context.SaveChangesAsync(); + + return NoContent(); + } +} + +public class MaterialDto +{ + public int Id { get; set; } + public string Shape { get; set; } = string.Empty; + public string Size { get; set; } = string.Empty; + public string? Description { get; set; } +} + +public class CreateMaterialDto +{ + public string Shape { get; set; } = string.Empty; + public string Size { get; set; } = string.Empty; + public string? Description { get; set; } +} + +public class BulkCreateResult +{ + public int Created { get; set; } + public int Skipped { get; set; } + public List Errors { get; set; } = new(); +} diff --git a/CutList.Web/Controllers/SeedController.cs b/CutList.Web/Controllers/SeedController.cs new file mode 100644 index 0000000..ca5ee86 --- /dev/null +++ b/CutList.Web/Controllers/SeedController.cs @@ -0,0 +1,94 @@ +using CutList.Web.Data; +using CutList.Web.Data.Entities; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace CutList.Web.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SeedController : ControllerBase +{ + private readonly ApplicationDbContext _context; + + public SeedController(ApplicationDbContext context) + { + _context = context; + } + + [HttpPost("alro-1018-round")] + public async Task SeedAlro1018Round() + { + // Add Alro supplier if not exists + var alro = await _context.Suppliers.FirstOrDefaultAsync(s => s.Name == "Alro"); + if (alro == null) + { + alro = new Supplier + { + Name = "Alro", + ContactInfo = "https://www.alro.com", + CreatedAt = DateTime.UtcNow + }; + _context.Suppliers.Add(alro); + await _context.SaveChangesAsync(); + } + + // 1018 CF Round bar sizes from the screenshot + var sizes = new[] + { + "1/8\"", + "5/32\"", + "3/16\"", + "7/32\"", + ".236\"", + "1/4\"", + "9/32\"", + "5/16\"", + "11/32\"", + "3/8\"", + ".394\"", + "13/32\"", + "7/16\"", + "15/32\"", + ".472\"", + "1/2\"", + "17/32\"", + "9/16\"", + ".593\"" + }; + + var created = 0; + var skipped = 0; + + foreach (var size in sizes) + { + var exists = await _context.Materials + .AnyAsync(m => m.Shape == "Round Bar" && m.Size == size && m.IsActive); + + if (exists) + { + skipped++; + continue; + } + + _context.Materials.Add(new Material + { + Shape = "Round Bar", + Size = size, + Description = "1018 Cold Finished", + CreatedAt = DateTime.UtcNow + }); + created++; + } + + await _context.SaveChangesAsync(); + + return Ok(new + { + Message = "Alro 1018 CF Round materials seeded", + SupplierId = alro.Id, + MaterialsCreated = created, + MaterialsSkipped = skipped + }); + } +}