Files
Extrudex/backend/API/DTOs/PrintJobs/PrintJobDtos.cs
cubecraft-agents[bot] 230c3b295d initial commit
2026-04-25 18:51:05 +00:00

223 lines
10 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.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;
}