initial commit
This commit is contained in:
199
backend/API/DTOs/Filaments/FilamentDtos.cs
Normal file
199
backend/API/DTOs/Filaments/FilamentDtos.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
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>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>
|
||||
/// 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>
|
||||
/// 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;
|
||||
}
|
||||
33
backend/API/DTOs/Filaments/FilamentQueryDtos.cs
Normal file
33
backend/API/DTOs/Filaments/FilamentQueryDtos.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Filaments;
|
||||
|
||||
/// <summary>
|
||||
/// Query parameters for filtering and paginating the filament list endpoint.
|
||||
/// All parameters are optional — defaults are applied when not provided.
|
||||
/// </summary>
|
||||
public class FilamentQueryParameters
|
||||
{
|
||||
/// <summary>Page number (1-based). Defaults to 1.</summary>
|
||||
[Range(1, int.MaxValue, ErrorMessage = "PageNumber must be at least 1.")]
|
||||
public int PageNumber { get; set; } = 1;
|
||||
|
||||
/// <summary>Number of items per page. Defaults to 20, max 100.</summary>
|
||||
[Range(1, 100, ErrorMessage = "PageSize must be between 1 and 100.")]
|
||||
public int PageSize { get; set; } = 20;
|
||||
|
||||
/// <summary>Optional filter by material base ID.</summary>
|
||||
public Guid? MaterialBaseId { get; set; }
|
||||
|
||||
/// <summary>Optional filter by material finish ID.</summary>
|
||||
public Guid? MaterialFinishId { get; set; }
|
||||
|
||||
/// <summary>Optional filter by material modifier ID.</summary>
|
||||
public Guid? MaterialModifierId { get; set; }
|
||||
|
||||
/// <summary>Optional filter by brand name (case-insensitive partial match).</summary>
|
||||
public string? Brand { get; set; }
|
||||
|
||||
/// <summary>Optional filter by active status. True = active only, False = inactive only.</summary>
|
||||
public bool? IsActive { get; set; }
|
||||
}
|
||||
56
backend/API/DTOs/Materials/MaterialBaseDtos.cs
Normal file
56
backend/API/DTOs/Materials/MaterialBaseDtos.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Materials;
|
||||
|
||||
/// <summary>
|
||||
/// Response DTO for MaterialBase entity.
|
||||
/// </summary>
|
||||
public class MaterialBaseResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the material base.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Human-readable name (e.g., "PLA", "PETG", "ABS").</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Density in g/cm³ used for grams-derived calculations.</summary>
|
||||
public decimal DensityGperCm3 { 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>
|
||||
/// Request DTO for creating a new MaterialBase.
|
||||
/// </summary>
|
||||
public class CreateMaterialBaseRequest
|
||||
{
|
||||
/// <summary>Human-readable name (e.g., "PLA", "PETG", "ABS"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 50 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Density in g/cm³. Must be greater than zero.</summary>
|
||||
[Required(ErrorMessage = "Density is required.")]
|
||||
[Range(0.001, 100.0, ErrorMessage = "Density must be between 0.001 and 100.0 g/cm³.")]
|
||||
public decimal DensityGperCm3 { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for updating an existing MaterialBase.
|
||||
/// </summary>
|
||||
public class UpdateMaterialBaseRequest
|
||||
{
|
||||
/// <summary>Human-readable name (e.g., "PLA", "PETG", "ABS"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 50 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Density in g/cm³. Must be greater than zero.</summary>
|
||||
[Required(ErrorMessage = "Density is required.")]
|
||||
[Range(0.001, 100.0, ErrorMessage = "Density must be between 0.001 and 100.0 g/cm³.")]
|
||||
public decimal DensityGperCm3 { get; set; }
|
||||
}
|
||||
57
backend/API/DTOs/Materials/MaterialFinishDtos.cs
Normal file
57
backend/API/DTOs/Materials/MaterialFinishDtos.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Materials;
|
||||
|
||||
/// <summary>
|
||||
/// Response DTO for MaterialFinish entity.
|
||||
/// </summary>
|
||||
public class MaterialFinishResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the finish.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Human-readable name (e.g., "Basic", "Matte", "Silk").</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the parent MaterialBase.</summary>
|
||||
public Guid MaterialBaseId { get; set; }
|
||||
|
||||
/// <summary>Name of the parent material base (for display).</summary>
|
||||
public string MaterialBaseName { get; set; } = string.Empty;
|
||||
|
||||
/// <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>
|
||||
/// Request DTO for creating a new MaterialFinish.
|
||||
/// </summary>
|
||||
public class CreateMaterialFinishRequest
|
||||
{
|
||||
/// <summary>Human-readable name (e.g., "Basic", "Matte", "Silk"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 50 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the parent MaterialBase. Required.</summary>
|
||||
[Required(ErrorMessage = "MaterialBaseId is required.")]
|
||||
public Guid MaterialBaseId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for updating an existing MaterialFinish.
|
||||
/// </summary>
|
||||
public class UpdateMaterialFinishRequest
|
||||
{
|
||||
/// <summary>Human-readable name (e.g., "Basic", "Matte", "Silk"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 50 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the parent MaterialBase. Required.</summary>
|
||||
[Required(ErrorMessage = "MaterialBaseId is required.")]
|
||||
public Guid MaterialBaseId { get; set; }
|
||||
}
|
||||
57
backend/API/DTOs/Materials/MaterialModifierDtos.cs
Normal file
57
backend/API/DTOs/Materials/MaterialModifierDtos.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Materials;
|
||||
|
||||
/// <summary>
|
||||
/// Response DTO for MaterialModifier entity.
|
||||
/// </summary>
|
||||
public class MaterialModifierResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the modifier.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Human-readable name (e.g., "Carbon Fiber", "Wood Fill").</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the parent MaterialBase.</summary>
|
||||
public Guid MaterialBaseId { get; set; }
|
||||
|
||||
/// <summary>Name of the parent material base (for display).</summary>
|
||||
public string MaterialBaseName { get; set; } = string.Empty;
|
||||
|
||||
/// <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>
|
||||
/// Request DTO for creating a new MaterialModifier.
|
||||
/// </summary>
|
||||
public class CreateMaterialModifierRequest
|
||||
{
|
||||
/// <summary>Human-readable name (e.g., "Carbon Fiber", "Wood Fill"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 50 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the parent MaterialBase. Required.</summary>
|
||||
[Required(ErrorMessage = "MaterialBaseId is required.")]
|
||||
public Guid MaterialBaseId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for updating an existing MaterialModifier.
|
||||
/// </summary>
|
||||
public class UpdateMaterialModifierRequest
|
||||
{
|
||||
/// <summary>Human-readable name (e.g., "Carbon Fiber", "Wood Fill"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 50 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the parent MaterialBase. Required.</summary>
|
||||
[Required(ErrorMessage = "MaterialBaseId is required.")]
|
||||
public Guid MaterialBaseId { get; set; }
|
||||
}
|
||||
30
backend/API/DTOs/PagedResponse.cs
Normal file
30
backend/API/DTOs/PagedResponse.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Extrudex.API.DTOs;
|
||||
|
||||
/// <summary>
|
||||
/// Generic paginated response wrapper for list endpoints.
|
||||
/// Provides pagination metadata alongside the result items.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the page.</typeparam>
|
||||
public class PagedResponse<T>
|
||||
{
|
||||
/// <summary>The items in the current page.</summary>
|
||||
public IReadOnlyList<T> Items { get; set; } = [];
|
||||
|
||||
/// <summary>Total number of items across all pages.</summary>
|
||||
public int TotalCount { get; set; }
|
||||
|
||||
/// <summary>Current page number (1-based).</summary>
|
||||
public int PageNumber { get; set; }
|
||||
|
||||
/// <summary>Number of items per page.</summary>
|
||||
public int PageSize { get; set; }
|
||||
|
||||
/// <summary>Total number of pages.</summary>
|
||||
public int TotalPages => PageSize > 0 ? (int)Math.Ceiling(TotalCount / (double)PageSize) : 0;
|
||||
|
||||
/// <summary>Whether there is a next page.</summary>
|
||||
public bool HasNextPage => PageNumber < TotalPages;
|
||||
|
||||
/// <summary>Whether there is a previous page.</summary>
|
||||
public bool HasPreviousPage => PageNumber > 1;
|
||||
}
|
||||
223
backend/API/DTOs/PrintJobs/PrintJobDtos.cs
Normal file
223
backend/API/DTOs/PrintJobs/PrintJobDtos.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.PrintJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Response DTO for PrintJob entity. Contains all job details including
|
||||
/// denormalized printer name and spool serial for display.
|
||||
/// Audit snapshots (filament diameter and material density) preserve COGS accuracy
|
||||
/// even if the source data changes after the print.
|
||||
/// </summary>
|
||||
public class PrintJobResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the print job.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Foreign key to the printer that executed this job.</summary>
|
||||
public Guid PrinterId { get; set; }
|
||||
|
||||
/// <summary>Name of the printer that executed this job.</summary>
|
||||
public string PrinterName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Foreign key to the spool that provided filament.</summary>
|
||||
public Guid SpoolId { get; set; }
|
||||
|
||||
/// <summary>Serial number of the spool that provided filament.</summary>
|
||||
public string SpoolSerial { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Human-readable name or identifier for the print job.</summary>
|
||||
public string PrintName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Path or filename of the G-code file.</summary>
|
||||
public string? GcodeFilePath { get; set; }
|
||||
|
||||
/// <summary>Total millimeters of filament extruded during this print.</summary>
|
||||
public decimal MmExtruded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Derived grams consumed for this print, calculated as:
|
||||
/// mm_extruded × cross_section_area × material_density.
|
||||
/// Cross-section area = π × (filament_diameter / 2)² in mm².
|
||||
/// Density converted from g/cm³ to g/mm³ by dividing by 1000.
|
||||
/// </summary>
|
||||
public decimal GramsDerived { get; set; }
|
||||
|
||||
/// <summary>Calculated cost of goods sold (COGS) for this print job.</summary>
|
||||
public decimal? CostPerPrint { get; set; }
|
||||
|
||||
/// <summary>Timestamp when the print job started (UTC).</summary>
|
||||
public DateTime? StartedAt { get; set; }
|
||||
|
||||
/// <summary>Timestamp when the print job completed or failed (UTC).</summary>
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
|
||||
/// <summary>Current status of the print job (Queued, Printing, Completed, Cancelled, Failed).</summary>
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Data source that provided this job (Mqtt, Moonraker, Manual).</summary>
|
||||
public string DataSource { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Audit snapshot: filament diameter (mm) recorded at time of print.</summary>
|
||||
public decimal FilamentDiameterAtPrintMm { get; set; }
|
||||
|
||||
/// <summary>Audit snapshot: material density (g/cm³) recorded at time of print.</summary>
|
||||
public decimal MaterialDensityAtPrint { get; set; }
|
||||
|
||||
/// <summary>Optional notes about the print job.</summary>
|
||||
public string? Notes { 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>
|
||||
/// Request DTO for creating a new print job. The gram derivation formula
|
||||
/// (grams = mm_extruded × cross_section_area × material_density) can be
|
||||
/// auto-computed server-side when MmExtruded, FilamentDiameterAtPrintMm,
|
||||
/// and MaterialDensityAtPrint are provided. Alternatively, set AutoDeriveGrams
|
||||
/// to true and provide a SpoolId to pull density from the material base.
|
||||
/// </summary>
|
||||
public class CreatePrintJobRequest
|
||||
{
|
||||
/// <summary>Foreign key to the printer that will execute this job. Required.</summary>
|
||||
[Required(ErrorMessage = "PrinterId is required.")]
|
||||
public Guid PrinterId { get; set; }
|
||||
|
||||
/// <summary>Foreign key to the spool providing filament. Required.</summary>
|
||||
[Required(ErrorMessage = "SpoolId is required.")]
|
||||
public Guid SpoolId { get; set; }
|
||||
|
||||
/// <summary>Human-readable name for the print job. Required, max 200 characters.</summary>
|
||||
[Required(ErrorMessage = "PrintName is required.")]
|
||||
[StringLength(200, MinimumLength = 1, ErrorMessage = "PrintName must be between 1 and 200 characters.")]
|
||||
public string PrintName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional path or filename of the G-code file. Max 500 characters.</summary>
|
||||
[StringLength(500, ErrorMessage = "GcodeFilePath must not exceed 500 characters.")]
|
||||
public string? GcodeFilePath { get; set; }
|
||||
|
||||
/// <summary>Total millimeters of filament extruded. Must be non-negative. Defaults to 0.</summary>
|
||||
[Range(0, double.MaxValue, ErrorMessage = "MmExtruded must be non-negative.")]
|
||||
public decimal MmExtruded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Derived grams consumed. If AutoDeriveGrams is true, this is computed
|
||||
/// server-side and the provided value is ignored. Must be non-negative if manually set.
|
||||
/// </summary>
|
||||
[Range(0, double.MaxValue, ErrorMessage = "GramsDerived must be non-negative.")]
|
||||
public decimal GramsDerived { get; set; }
|
||||
|
||||
/// <summary>Optional calculated COGS. Must be non-negative if provided.</summary>
|
||||
[Range(0, double.MaxValue, ErrorMessage = "CostPerPrint must be non-negative.")]
|
||||
public decimal? CostPerPrint { get; set; }
|
||||
|
||||
/// <summary>Optional timestamp when the job started (UTC).</summary>
|
||||
public DateTime? StartedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Data source for this job. Must be "Mqtt", "Moonraker", or "Manual".
|
||||
/// Defaults to "Manual".
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "DataSource is required.")]
|
||||
[RegularExpression("^(Mqtt|Moonraker|Manual)$", ErrorMessage = "DataSource must be 'Mqtt', 'Moonraker', or 'Manual'.")]
|
||||
public string DataSource { get; set; } = "Manual";
|
||||
|
||||
/// <summary>
|
||||
/// Audit snapshot: filament diameter in mm at time of print. Must be greater than zero.
|
||||
/// Defaults to 1.75mm if not specified and AutoDeriveGrams is false.
|
||||
/// </summary>
|
||||
[Range(0.01, 100, ErrorMessage = "FilamentDiameterAtPrintMm must be between 0.01 and 100 mm.")]
|
||||
public decimal FilamentDiameterAtPrintMm { get; set; } = 1.75m;
|
||||
|
||||
/// <summary>
|
||||
/// Audit snapshot: material density in g/cm³ at time of print. Must be greater than zero.
|
||||
/// If AutoDeriveGrams is true, this is populated from the spool's material base.
|
||||
/// </summary>
|
||||
[Range(0.001, 100, ErrorMessage = "MaterialDensityAtPrint must be between 0.001 and 100 g/cm³.")]
|
||||
public decimal MaterialDensityAtPrint { get; set; }
|
||||
|
||||
/// <summary>Optional notes about the print job. Max 2000 characters.</summary>
|
||||
[StringLength(2000, ErrorMessage = "Notes must not exceed 2000 characters.")]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, the server auto-derives GramsDerived, FilamentDiameterAtPrintMm,
|
||||
/// and MaterialDensityAtPrint from the spool's material data.
|
||||
/// MmExtruded must still be provided. Overrides manual GramsDerived.
|
||||
/// </summary>
|
||||
public bool AutoDeriveGrams { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for updating an existing print job. Full replacement semantics —
|
||||
/// all required fields must be provided. Gram derivation can be recomputed
|
||||
/// by setting AutoDeriveGrams to true.
|
||||
/// </summary>
|
||||
public class UpdatePrintJobRequest
|
||||
{
|
||||
/// <summary>Foreign key to the printer. Required.</summary>
|
||||
[Required(ErrorMessage = "PrinterId is required.")]
|
||||
public Guid PrinterId { get; set; }
|
||||
|
||||
/// <summary>Foreign key to the spool. Required.</summary>
|
||||
[Required(ErrorMessage = "SpoolId is required.")]
|
||||
public Guid SpoolId { get; set; }
|
||||
|
||||
/// <summary>Human-readable name for the print job. Required, max 200 characters.</summary>
|
||||
[Required(ErrorMessage = "PrintName is required.")]
|
||||
[StringLength(200, MinimumLength = 1, ErrorMessage = "PrintName must be between 1 and 200 characters.")]
|
||||
public string PrintName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Optional path or filename of the G-code file. Max 500 characters.</summary>
|
||||
[StringLength(500, ErrorMessage = "GcodeFilePath must not exceed 500 characters.")]
|
||||
public string? GcodeFilePath { get; set; }
|
||||
|
||||
/// <summary>Total millimeters of filament extruded. Must be non-negative.</summary>
|
||||
[Range(0, double.MaxValue, ErrorMessage = "MmExtruded must be non-negative.")]
|
||||
public decimal MmExtruded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Derived grams consumed. If AutoDeriveGrams is true, this is recomputed
|
||||
/// server-side and the provided value is ignored.
|
||||
/// </summary>
|
||||
[Range(0, double.MaxValue, ErrorMessage = "GramsDerived must be non-negative.")]
|
||||
public decimal GramsDerived { get; set; }
|
||||
|
||||
/// <summary>Optional calculated COGS. Must be non-negative if provided.</summary>
|
||||
[Range(0, double.MaxValue, ErrorMessage = "CostPerPrint must be non-negative.")]
|
||||
public decimal? CostPerPrint { get; set; }
|
||||
|
||||
/// <summary>Optional notes about the print job. Max 2000 characters.</summary>
|
||||
[StringLength(2000, ErrorMessage = "Notes must not exceed 2000 characters.")]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, the server recomputes GramsDerived, FilamentDiameterAtPrintMm,
|
||||
/// and MaterialDensityAtPrint from the spool's current material data.
|
||||
/// MmExtruded must still be provided.
|
||||
/// </summary>
|
||||
public bool AutoDeriveGrams { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for the PATCH /api/printjobs/{id}/status endpoint.
|
||||
/// Validates that the transition is allowed by business rules:
|
||||
/// - Can always go to Cancelled or Failed from any state.
|
||||
/// - Can move from Queued → Printing, Printing → Completed.
|
||||
/// - Cannot move from Completed back to Printing or Queued.
|
||||
/// - Cannot move from Cancelled back to any active state.
|
||||
/// </summary>
|
||||
public class UpdatePrintJobStatusRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// New status for the print job. Must be one of: Queued, Printing, Completed, Cancelled, Failed.
|
||||
/// Case-insensitive.
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Status is required.")]
|
||||
[RegularExpression("^(Queued|Printing|Completed|Cancelled|Failed)$",
|
||||
ErrorMessage = "Status must be one of: Queued, Printing, Completed, Cancelled, Failed.")]
|
||||
public string Status { get; set; } = string.Empty;
|
||||
}
|
||||
30
backend/API/DTOs/PrintJobs/PrintJobQueryDtos.cs
Normal file
30
backend/API/DTOs/PrintJobs/PrintJobQueryDtos.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.PrintJobs;
|
||||
|
||||
/// <summary>
|
||||
/// Query parameters for filtering and paginating the print job list endpoint.
|
||||
/// All parameters are optional — defaults are applied when not provided.
|
||||
/// </summary>
|
||||
public class PrintJobQueryParameters
|
||||
{
|
||||
/// <summary>Page number (1-based). Defaults to 1.</summary>
|
||||
[Range(1, int.MaxValue, ErrorMessage = "PageNumber must be at least 1.")]
|
||||
public int PageNumber { get; set; } = 1;
|
||||
|
||||
/// <summary>Number of items per page. Defaults to 20, max 100.</summary>
|
||||
[Range(1, 100, ErrorMessage = "PageSize must be between 1 and 100.")]
|
||||
public int PageSize { get; set; } = 20;
|
||||
|
||||
/// <summary>Optional filter by printer ID. Only returns jobs for this printer.</summary>
|
||||
public Guid? PrinterId { get; set; }
|
||||
|
||||
/// <summary>Optional filter by spool ID. Only returns jobs that used this spool.</summary>
|
||||
public Guid? SpoolId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional filter by job status. Must be a valid JobStatus value
|
||||
/// (Queued, Printing, Completed, Cancelled, Failed). Case-insensitive.
|
||||
/// </summary>
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
190
backend/API/DTOs/Printers/PrinterDtos.cs
Normal file
190
backend/API/DTOs/Printers/PrinterDtos.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Printers;
|
||||
|
||||
/// <summary>
|
||||
/// Response DTO for a Printer entity. Contains all printer details
|
||||
/// including connection configuration and operational status.
|
||||
/// </summary>
|
||||
public class PrinterResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the printer.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Current operational status (Idle, Printing, Offline, Error, Paused).</summary>
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Human-readable name (e.g., "Bambu X1C #1", "Elegoo Centauri").</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Manufacturer/brand (e.g., "Bambu Lab", "Elegoo").</summary>
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Model name (e.g., "X1 Carbon", "Centauri Carbon").</summary>
|
||||
public string Model { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Printer hardware type ("Fdm" or "Resin").</summary>
|
||||
public string PrinterType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Connectivity protocol ("Mqtt" or "Moonraker").</summary>
|
||||
public string ConnectionType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Hostname or IP address for printer connection.</summary>
|
||||
public string HostnameOrIp { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Port number for the connection (8883 for MQTT/TLS, 7125 for Moonraker).</summary>
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>Whether the printer is currently active and available for jobs.</summary>
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
/// <summary>Timestamp of the last status update received from the printer (UTC).</summary>
|
||||
public DateTime? LastSeenAt { get; set; }
|
||||
|
||||
/// <summary>Timestamp when this record was created (UTC).</summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>Timestamp when this record was last modified (UTC).</summary>
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightweight response DTO for printer status. Optimized for polling
|
||||
/// and dashboard displays. For real-time updates, use the SignalR PrinterHub.
|
||||
/// </summary>
|
||||
public class PrinterStatusResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the printer.</summary>
|
||||
public Guid Id { get; set; }
|
||||
|
||||
/// <summary>Human-readable name of the printer.</summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Current operational status (Idle, Printing, Offline, Error, Paused).</summary>
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Timestamp of the last status update received from the printer (UTC).</summary>
|
||||
public DateTime? LastSeenAt { get; set; }
|
||||
|
||||
/// <summary>Whether the printer is currently active and available for jobs.</summary>
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for registering a new printer in the fleet.
|
||||
/// All string enums accept: PrinterType = "Fdm"|"Resin",
|
||||
/// ConnectionType = "Mqtt"|"Moonraker" (case-insensitive).
|
||||
/// </summary>
|
||||
public class CreatePrinterRequest
|
||||
{
|
||||
/// <summary>Human-readable name for the printer. Required, max 100 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(100, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 100 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Manufacturer/brand (e.g., "Bambu Lab", "Elegoo"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Manufacturer is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Manufacturer must be between 1 and 50 characters.")]
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Model name (e.g., "X1 Carbon", "Centauri Carbon"). Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Model is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Model must be between 1 and 50 characters.")]
|
||||
public string Model { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Printer hardware type: "Fdm" or "Resin". Defaults to "Fdm".</summary>
|
||||
[Required(ErrorMessage = "PrinterType is required.")]
|
||||
[RegularExpression("^(Fdm|Resin)$", ErrorMessage = "PrinterType must be 'Fdm' or 'Resin'.")]
|
||||
public string PrinterType { get; set; } = "Fdm";
|
||||
|
||||
/// <summary>Connectivity protocol: "Mqtt" or "Moonraker". Defaults to "Mqtt".</summary>
|
||||
[Required(ErrorMessage = "ConnectionType is required.")]
|
||||
[RegularExpression("^(Mqtt|Moonraker)$", ErrorMessage = "ConnectionType must be 'Mqtt' or 'Moonraker'.")]
|
||||
public string ConnectionType { get; set; } = "Mqtt";
|
||||
|
||||
/// <summary>Hostname or IP address for printer connection. Required, max 253 characters.</summary>
|
||||
[Required(ErrorMessage = "HostnameOrIp is required.")]
|
||||
[StringLength(253, MinimumLength = 1, ErrorMessage = "HostnameOrIp must be between 1 and 253 characters.")]
|
||||
public string HostnameOrIp { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Port number. Defaults: 8883 (MQTT/TLS), 7125 (Moonraker) if zero.</summary>
|
||||
[Range(0, 65535, ErrorMessage = "Port must be between 0 and 65535.")]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>MQTT username for Bambu Lab authentication. Used only for MQTT connection type.</summary>
|
||||
[StringLength(100, ErrorMessage = "MqttUsername must be at most 100 characters.")]
|
||||
public string MqttUsername { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>MQTT password for Bambu Lab authentication. Used only for MQTT connection type.</summary>
|
||||
[StringLength(200, ErrorMessage = "MqttPassword must be at most 200 characters.")]
|
||||
public string MqttPassword { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Whether to use TLS for MQTT. Bambu Lab printers require TLS on port 8883.</summary>
|
||||
public bool MqttUseTls { get; set; }
|
||||
|
||||
/// <summary>Moonraker API key for Elegoo Centauri Carbon. Used only for Moonraker connection type.</summary>
|
||||
[StringLength(100, ErrorMessage = "ApiKey must be at most 100 characters.")]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Whether the printer is active and available for jobs. Defaults to true.</summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for updating an existing printer's configuration and connection info.
|
||||
/// All fields are provided on update (full replacement semantics).
|
||||
/// </summary>
|
||||
public class UpdatePrinterRequest
|
||||
{
|
||||
/// <summary>Human-readable name for the printer. Required, max 100 characters.</summary>
|
||||
[Required(ErrorMessage = "Name is required.")]
|
||||
[StringLength(100, MinimumLength = 1, ErrorMessage = "Name must be between 1 and 100 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Manufacturer/brand. Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Manufacturer is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Manufacturer must be between 1 and 50 characters.")]
|
||||
public string Manufacturer { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Model name. Required, max 50 characters.</summary>
|
||||
[Required(ErrorMessage = "Model is required.")]
|
||||
[StringLength(50, MinimumLength = 1, ErrorMessage = "Model must be between 1 and 50 characters.")]
|
||||
public string Model { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Printer hardware type: "Fdm" or "Resin".</summary>
|
||||
[Required(ErrorMessage = "PrinterType is required.")]
|
||||
[RegularExpression("^(Fdm|Resin)$", ErrorMessage = "PrinterType must be 'Fdm' or 'Resin'.")]
|
||||
public string PrinterType { get; set; } = "Fdm";
|
||||
|
||||
/// <summary>Connectivity protocol: "Mqtt" or "Moonraker".</summary>
|
||||
[Required(ErrorMessage = "ConnectionType is required.")]
|
||||
[RegularExpression("^(Mqtt|Moonraker)$", ErrorMessage = "ConnectionType must be 'Mqtt' or 'Moonraker'.")]
|
||||
public string ConnectionType { get; set; } = "Mqtt";
|
||||
|
||||
/// <summary>Hostname or IP address. Required, max 253 characters.</summary>
|
||||
[Required(ErrorMessage = "HostnameOrIp is required.")]
|
||||
[StringLength(253, MinimumLength = 1, ErrorMessage = "HostnameOrIp must be between 1 and 253 characters.")]
|
||||
public string HostnameOrIp { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Port number. Defaults: 8883 (MQTT/TLS), 7125 (Moonraker) if zero.</summary>
|
||||
[Range(0, 65535, ErrorMessage = "Port must be between 0 and 65535.")]
|
||||
public int Port { get; set; }
|
||||
|
||||
/// <summary>MQTT username. Used only for MQTT connection type.</summary>
|
||||
[StringLength(100, ErrorMessage = "MqttUsername must be at most 100 characters.")]
|
||||
public string MqttUsername { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>MQTT password. Used only for MQTT connection type.</summary>
|
||||
[StringLength(200, ErrorMessage = "MqttPassword must be at most 200 characters.")]
|
||||
public string MqttPassword { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Whether to use TLS for MQTT.</summary>
|
||||
public bool MqttUseTls { get; set; }
|
||||
|
||||
/// <summary>Moonraker API key. Used only for Moonraker connection type.</summary>
|
||||
[StringLength(100, ErrorMessage = "ApiKey must be at most 100 characters.")]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Whether the printer is active and available for jobs.</summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
193
backend/API/DTOs/Spools/SpoolDtos.cs
Normal file
193
backend/API/DTOs/Spools/SpoolDtos.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Spools;
|
||||
|
||||
/// <summary>
|
||||
/// Response DTO for Spool entity — the core inventory unit of Extrudex.
|
||||
/// Contains all spool details including denormalized material names for display.
|
||||
/// </summary>
|
||||
public class SpoolResponse
|
||||
{
|
||||
/// <summary>Unique identifier for the 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>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>
|
||||
/// Request DTO for creating a new spool.
|
||||
/// All required fields must be provided. MaterialFinish is required — use "Basic" as the default.
|
||||
/// </summary>
|
||||
public class CreateSpoolRequest
|
||||
{
|
||||
/// <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>
|
||||
/// Request DTO for updating an existing spool.
|
||||
/// All required fields must be provided for a full update.
|
||||
/// </summary>
|
||||
public class UpdateSpoolRequest
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
27
backend/API/DTOs/Spools/SpoolQueryDtos.cs
Normal file
27
backend/API/DTOs/Spools/SpoolQueryDtos.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Extrudex.API.DTOs.Spools;
|
||||
|
||||
/// <summary>
|
||||
/// Query parameters for filtering and paginating the spool list endpoint.
|
||||
/// All parameters are optional — defaults are applied when not provided.
|
||||
/// </summary>
|
||||
public class SpoolQueryParameters
|
||||
{
|
||||
/// <summary>Page number (1-based). Defaults to 1.</summary>
|
||||
[Range(1, int.MaxValue, ErrorMessage = "PageNumber must be at least 1.")]
|
||||
public int PageNumber { get; set; } = 1;
|
||||
|
||||
/// <summary>Number of items per page. Defaults to 20, max 100.</summary>
|
||||
[Range(1, 100, ErrorMessage = "PageSize must be between 1 and 100.")]
|
||||
public int PageSize { get; set; } = 20;
|
||||
|
||||
/// <summary>Optional filter by material base ID.</summary>
|
||||
public Guid? MaterialBaseId { get; set; }
|
||||
|
||||
/// <summary>Optional filter by material finish ID.</summary>
|
||||
public Guid? MaterialFinishId { get; set; }
|
||||
|
||||
/// <summary>Optional filter by active status. True = active only, False = inactive only.</summary>
|
||||
public bool? IsActive { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user