Files
Extrudex/backend/API/DTOs/Filaments/FilamentDtos.cs
dex-bot 9192ece040
All checks were successful
Dev Build / build-test (pull_request) Successful in 2m12s
CUB-38: implement low filament alert logic with configurable threshold
2026-04-28 12:42:03 +00:00

234 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel.DataAnnotations;
namespace Extrudex.API.DTOs.Filaments;
/// <summary>
/// Response DTO for a filament spool — the core inventory unit of Extrudex.
/// Contains all spool details including denormalized material names for display.
/// </summary>
public class FilamentResponse
{
/// <summary>Unique identifier for the filament spool.</summary>
public Guid Id { get; set; }
/// <summary>Foreign key to the base material.</summary>
public Guid MaterialBaseId { get; set; }
/// <summary>Name of the base material (e.g., "PLA", "PETG").</summary>
public string MaterialBaseName { get; set; } = string.Empty;
/// <summary>Foreign key to the material finish.</summary>
public Guid MaterialFinishId { get; set; }
/// <summary>Name of the material finish (e.g., "Basic", "Matte").</summary>
public string MaterialFinishName { get; set; } = string.Empty;
/// <summary>Foreign key to the optional material modifier. Null if none.</summary>
public Guid? MaterialModifierId { get; set; }
/// <summary>Name of the material modifier (e.g., "Carbon Fiber"). Null if none.</summary>
public string? MaterialModifierName { get; set; }
/// <summary>Brand name (e.g., "Bambu Lab", "Polymaker").</summary>
public string Brand { get; set; } = string.Empty;
/// <summary>Human-readable color name (e.g., "Fire Engine Red").</summary>
public string ColorName { get; set; } = string.Empty;
/// <summary>Hex color code (e.g., "#FF0000").</summary>
public string ColorHex { get; set; } = string.Empty;
/// <summary>Total spool weight in grams when full.</summary>
public decimal WeightTotalGrams { get; set; }
/// <summary>Current remaining weight in grams.</summary>
public decimal WeightRemainingGrams { get; set; }
/// <summary>Filament diameter in millimeters. Typically 1.75mm.</summary>
public decimal FilamentDiameterMm { get; set; }
/// <summary>Manufacturer-assigned serial number. Must be unique.</summary>
public string SpoolSerial { get; set; } = string.Empty;
/// <summary>Purchase price per spool. Null if not tracked.</summary>
public decimal? PurchasePrice { get; set; }
/// <summary>Date the spool was purchased or received.</summary>
public DateTime? PurchaseDate { get; set; }
/// <summary>Whether the spool is currently active and available.</summary>
public bool IsActive { get; set; }
/// <summary>Whether the spool has been archived (removed from active inventory).</summary>
public bool IsArchived { get; set; }
/// <summary>Physical storage location (e.g., "Shelf A", "Drawer 3"). Null if unset.</summary>
public string? StorageLocation { get; set; }
/// <summary>Timestamp when this record was created (UTC).</summary>
public DateTime CreatedAt { get; set; }
/// <summary>Timestamp when this record was last updated (UTC).</summary>
public DateTime UpdatedAt { get; set; }
/// <summary>
/// URL to the QR code image for this spool.
/// Encodes a deep link to the spool's detail page.
/// </summary>
public string QrCodeUrl { get; set; } = string.Empty;
/// <summary>
/// Whether this spool is flagged as low stock — remaining weight is at or
/// below the configured low-stock threshold percentage.
/// Useful for UI alerts and inventory dashboards.
/// </summary>
public bool IsLowStock { get; set; }
/// <summary>
/// Remaining filament weight as a percentage of total weight (0100).
/// Rounded to one decimal place. Returns 0 if total weight is zero.
/// </summary>
public decimal RemainingWeightPercent { get; set; }
}
/// <summary>
/// Request DTO for creating a new filament spool.
/// All required fields must be provided. MaterialFinish is required — use "Basic" as the default.
/// </summary>
public class CreateFilamentRequest
{
/// <summary>Foreign key to the base material. Required.</summary>
[Required(ErrorMessage = "MaterialBaseId is required.")]
public Guid MaterialBaseId { get; set; }
/// <summary>Foreign key to the material finish. Required — default is "Basic".</summary>
[Required(ErrorMessage = "MaterialFinishId is required.")]
public Guid MaterialFinishId { get; set; }
/// <summary>Foreign key to the optional material modifier. Null if none applies.</summary>
public Guid? MaterialModifierId { get; set; }
/// <summary>Brand name (e.g., "Bambu Lab", "Polymaker"). Required, max 200 characters.</summary>
[Required(ErrorMessage = "Brand is required.")]
[StringLength(200, MinimumLength = 1, ErrorMessage = "Brand must be between 1 and 200 characters.")]
public string Brand { get; set; } = string.Empty;
/// <summary>Human-readable color name (e.g., "Fire Engine Red"). Required, max 200 characters.</summary>
[Required(ErrorMessage = "ColorName is required.")]
[StringLength(200, MinimumLength = 1, ErrorMessage = "ColorName must be between 1 and 200 characters.")]
public string ColorName { get; set; } = string.Empty;
/// <summary>Hex color code (e.g., "#FF0000"). Required, must be valid 7-char hex.</summary>
[Required(ErrorMessage = "ColorHex is required.")]
[RegularExpression(@"^#[0-9A-Fa-f]{6}$", ErrorMessage = "ColorHex must be a valid hex color code (e.g., #FF0000).")]
[StringLength(7, MinimumLength = 7, ErrorMessage = "ColorHex must be exactly 7 characters (e.g., #FF0000).")]
public string ColorHex { get; set; } = string.Empty;
/// <summary>Total spool weight in grams when full. Must be greater than zero.</summary>
[Required(ErrorMessage = "WeightTotalGrams is required.")]
[Range(0.01, 100000, ErrorMessage = "Total weight must be between 0.01 and 100,000 grams.")]
public decimal WeightTotalGrams { get; set; }
/// <summary>Current remaining weight in grams. Must be non-negative.</summary>
[Required(ErrorMessage = "WeightRemainingGrams is required.")]
[Range(0, 100000, ErrorMessage = "Remaining weight must be between 0 and 100,000 grams.")]
public decimal WeightRemainingGrams { get; set; }
/// <summary>Filament diameter in mm. Defaults to 1.75. Must be greater than zero.</summary>
[Range(0.1, 10.0, ErrorMessage = "Filament diameter must be between 0.1 and 10.0 mm.")]
public decimal FilamentDiameterMm { get; set; } = 1.75m;
/// <summary>Manufacturer-assigned serial number. Must be unique, max 200 characters.</summary>
[Required(ErrorMessage = "SpoolSerial is required.")]
[StringLength(200, MinimumLength = 1, ErrorMessage = "SpoolSerial must be between 1 and 200 characters.")]
public string SpoolSerial { get; set; } = string.Empty;
/// <summary>Optional purchase price per spool. Must be non-negative if provided.</summary>
[Range(0, 1000000, ErrorMessage = "Purchase price must be between 0 and 1,000,000.")]
public decimal? PurchasePrice { get; set; }
/// <summary>Optional purchase date. Must be a valid date if provided.</summary>
public DateTime? PurchaseDate { get; set; }
/// <summary>Whether the spool is active. Defaults to true.</summary>
public bool IsActive { get; set; } = true;
/// <summary>Whether the spool is archived. Defaults to false.
/// </summary>
public bool IsArchived { get; set; } = false;
/// <summary>Physical storage location (e.g., "Shelf A", "Drawer 3"). Optional.
/// </summary>
[StringLength(200, ErrorMessage = "StorageLocation must not exceed 200 characters.")]
public string? StorageLocation { get; set; }
}
/// <summary>
/// Request DTO for updating an existing filament spool.
/// All required fields must be provided for a full update.
/// </summary>
public class UpdateFilamentRequest
{
/// <summary>Foreign key to the base material. Required.</summary>
[Required(ErrorMessage = "MaterialBaseId is required.")]
public Guid MaterialBaseId { get; set; }
/// <summary>Foreign key to the material finish. Required.</summary>
[Required(ErrorMessage = "MaterialFinishId is required.")]
public Guid MaterialFinishId { get; set; }
/// <summary>Foreign key to the optional material modifier. Null if none applies.</summary>
public Guid? MaterialModifierId { get; set; }
/// <summary>Brand name. Required, max 200 characters.</summary>
[Required(ErrorMessage = "Brand is required.")]
[StringLength(200, MinimumLength = 1, ErrorMessage = "Brand must be between 1 and 200 characters.")]
public string Brand { get; set; } = string.Empty;
/// <summary>Human-readable color name. Required, max 200 characters.</summary>
[Required(ErrorMessage = "ColorName is required.")]
[StringLength(200, MinimumLength = 1, ErrorMessage = "ColorName must be between 1 and 200 characters.")]
public string ColorName { get; set; } = string.Empty;
/// <summary>Hex color code (e.g., "#FF0000"). Required, must be valid 7-char hex.</summary>
[Required(ErrorMessage = "ColorHex is required.")]
[RegularExpression(@"^#[0-9A-Fa-f]{6}$", ErrorMessage = "ColorHex must be a valid hex color code (e.g., #FF0000).")]
[StringLength(7, MinimumLength = 7, ErrorMessage = "ColorHex must be exactly 7 characters (e.g., #FF0000).")]
public string ColorHex { get; set; } = string.Empty;
/// <summary>Total spool weight in grams when full. Must be greater than zero.</summary>
[Required(ErrorMessage = "WeightTotalGrams is required.")]
[Range(0.01, 100000, ErrorMessage = "Total weight must be between 0.01 and 100,000 grams.")]
public decimal WeightTotalGrams { get; set; }
/// <summary>Current remaining weight in grams. Must be non-negative.</summary>
[Required(ErrorMessage = "WeightRemainingGrams is required.")]
[Range(0, 100000, ErrorMessage = "Remaining weight must be between 0 and 100,000 grams.")]
public decimal WeightRemainingGrams { get; set; }
/// <summary>Filament diameter in mm. Must be greater than zero.</summary>
[Range(0.1, 10.0, ErrorMessage = "Filament diameter must be between 0.1 and 10.0 mm.")]
public decimal FilamentDiameterMm { get; set; } = 1.75m;
/// <summary>Manufacturer-assigned serial number. Must be unique, max 200 characters.</summary>
[Required(ErrorMessage = "SpoolSerial is required.")]
[StringLength(200, MinimumLength = 1, ErrorMessage = "SpoolSerial must be between 1 and 200 characters.")]
public string SpoolSerial { get; set; } = string.Empty;
/// <summary>Optional purchase price per spool. Must be non-negative if provided.</summary>
[Range(0, 1000000, ErrorMessage = "Purchase price must be between 0 and 1,000,000.")]
public decimal? PurchasePrice { get; set; }
/// <summary>Optional purchase date.</summary>
public DateTime? PurchaseDate { get; set; }
/// <summary>Whether the spool is active.</summary>
public bool IsActive { get; set; } = true;
/// <summary>Whether the spool is archived. Defaults to false.</summary>
public bool IsArchived { get; set; } = false;
/// <summary>Physical storage location (e.g., "Shelf A", "Drawer 3"). Optional.</summary>
[StringLength(200, ErrorMessage = "StorageLocation must not exceed 200 characters.")]
public string? StorageLocation { get; set; }
}