All checks were successful
Dev Build / build-test (pull_request) Successful in 2m12s
234 lines
11 KiB
C#
234 lines
11 KiB
C#
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 (0–100).
|
||
/// 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; }
|
||
} |