using Extrudex.API.DTOs.Materials; using Extrudex.Domain.Entities; using Extrudex.Infrastructure.Data; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Extrudex.API.Controllers; /// /// Controller for managing material finishes — the surface finish descriptor /// for a material. This is REQUIRED on every spool record. Default: "Basic". /// [ApiController] [Route("api/material-finishes")] public class MaterialFinishesController : ControllerBase { private readonly ExtrudexDbContext _dbContext; private readonly ILogger _logger; public MaterialFinishesController( ExtrudexDbContext dbContext, ILogger logger) { _dbContext = dbContext; _logger = logger; } /// /// Gets all material finishes, optionally filtered by material base. /// /// Optional filter: return finishes for this material base only. /// A list of material finishes ordered by base material name, then finish name. [HttpGet] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> GetMaterialFinishes( [FromQuery] Guid? materialBaseId) { _logger.LogDebug("Getting material finishes, filter: {MaterialBaseId}", materialBaseId); var query = _dbContext.MaterialFinishes .Include(mf => mf.MaterialBase) .AsQueryable(); if (materialBaseId.HasValue) query = query.Where(mf => mf.MaterialBaseId == materialBaseId.Value); var finishes = await query .OrderBy(mf => mf.MaterialBase.Name) .ThenBy(mf => mf.Name) .Select(mf => MapToResponse(mf)) .ToListAsync(); return Ok(finishes); } /// /// Gets a specific material finish by ID. /// /// The unique identifier of the finish. /// The material finish details. [HttpGet("{id:guid}")] [ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetMaterialFinish(Guid id) { _logger.LogDebug("Getting material finish {Id}", id); var mf = await _dbContext.MaterialFinishes .Include(mf => mf.MaterialBase) .FirstOrDefaultAsync(mf => mf.Id == id); if (mf is null) { _logger.LogWarning("Material finish {Id} not found", id); return NotFound(new { error = $"Material finish with ID '{id}' not found." }); } return Ok(MapToResponse(mf)); } /// /// Creates a new material finish. /// /// The material finish creation request. /// The created material finish. [HttpPost] [ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> CreateMaterialFinish( [FromBody] CreateMaterialFinishRequest request) { _logger.LogInformation("Creating material finish: {Name} for base {MaterialBaseId}", request.Name, request.MaterialBaseId); var materialBase = await _dbContext.MaterialBases.FindAsync(request.MaterialBaseId); if (materialBase is null) { return BadRequest(new { error = $"MaterialBase with ID '{request.MaterialBaseId}' not found." }); } var entity = new MaterialFinish { Name = request.Name, MaterialBaseId = request.MaterialBaseId }; _dbContext.MaterialFinishes.Add(entity); await _dbContext.SaveChangesAsync(); // Reload with navigation for response mapping await _dbContext.Entry(entity).Reference(e => e.MaterialBase).LoadAsync(); var response = MapToResponse(entity); return CreatedAtAction(nameof(GetMaterialFinish), new { id = entity.Id }, response); } /// /// Updates an existing material finish. /// /// The unique identifier of the finish to update. /// The material finish update request. /// The updated material finish. [HttpPut("{id:guid}")] [ProducesResponseType(typeof(MaterialFinishResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> UpdateMaterialFinish( Guid id, [FromBody] UpdateMaterialFinishRequest request) { _logger.LogInformation("Updating material finish {Id}", id); var entity = await _dbContext.MaterialFinishes.FindAsync(id); if (entity is null) { _logger.LogWarning("Material finish {Id} not found for update", id); return NotFound(new { error = $"Material finish with ID '{id}' not found." }); } var materialBase = await _dbContext.MaterialBases.FindAsync(request.MaterialBaseId); if (materialBase is null) { return BadRequest(new { error = $"MaterialBase with ID '{request.MaterialBaseId}' not found." }); } entity.Name = request.Name; entity.MaterialBaseId = request.MaterialBaseId; await _dbContext.SaveChangesAsync(); // Reload with navigation for response mapping await _dbContext.Entry(entity).Reference(e => e.MaterialBase).LoadAsync(); return Ok(MapToResponse(entity)); } /// /// Deletes a material finish. /// /// The unique identifier of the finish to delete. [HttpDelete("{id:guid}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteMaterialFinish(Guid id) { _logger.LogInformation("Deleting material finish {Id}", id); var entity = await _dbContext.MaterialFinishes.FindAsync(id); if (entity is null) { _logger.LogWarning("Material finish {Id} not found for deletion", id); return NotFound(new { error = $"Material finish with ID '{id}' not found." }); } _dbContext.MaterialFinishes.Remove(entity); await _dbContext.SaveChangesAsync(); return NoContent(); } // ── Mapping helper ────────────────────────────────────────── private static MaterialFinishResponse MapToResponse(MaterialFinish mf) => new() { Id = mf.Id, Name = mf.Name, MaterialBaseId = mf.MaterialBaseId, MaterialBaseName = mf.MaterialBase?.Name ?? string.Empty, CreatedAt = mf.CreatedAt, UpdatedAt = mf.UpdatedAt }; }