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 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 23:55:48 -05:00
parent 66ed19a1ac
commit f8020549fe
2 changed files with 264 additions and 0 deletions

View File

@@ -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<ActionResult<IEnumerable<MaterialDto>>> 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<ActionResult<MaterialDto>> 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<ActionResult<MaterialDto>> 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<ActionResult<BulkCreateResult>> CreateMaterialsBulk(List<CreateMaterialDto> materials)
{
var created = 0;
var skipped = 0;
var errors = new List<string>();
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<IActionResult> 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<string> Errors { get; set; } = new();
}