diff --git a/backend/API/Controllers/UsageLogsController.cs b/backend/API/Controllers/UsageLogsController.cs new file mode 100644 index 0000000..f6727f3 --- /dev/null +++ b/backend/API/Controllers/UsageLogsController.cs @@ -0,0 +1,117 @@ +using Extrudex.API.DTOs.UsageLogs; +using Extrudex.Domain.Enums; +using Extrudex.Domain.Interfaces; +using Microsoft.AspNetCore.Mvc; + +namespace Extrudex.API.Controllers; + +/// +/// API controller for recording and querying filament usage logs. +/// Usage logs provide a fine-grained audit trail of filament consumption +/// from printer integrations or manual input. +/// +[ApiController] +[Route("api/[controller]")] +[Produces("application/json")] +public class UsageLogsController : ControllerBase +{ + private readonly IUsageLogService _usageLogService; + + /// + /// Initializes a new instance of the class. + /// + /// The usage log service for recording and querying usage. + public UsageLogsController(IUsageLogService usageLogService) + { + _usageLogService = usageLogService; + } + + /// + /// Records a new filament usage entry. + /// + /// The usage entry details. + /// The created usage log entry. + [HttpPost] + [ProducesResponseType(typeof(UsageLogResponse), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task> Create([FromBody] CreateUsageLogRequest request) + { + if (!Enum.TryParse(request.DataSource, ignoreCase: true, out var dataSource)) + { + return BadRequest($"Invalid data source: '{request.DataSource}'. Valid values: Mqtt, Moonraker, Manual."); + } + + var entry = await _usageLogService.RecordUsageAsync( + spoolId: request.SpoolId, + gramsUsed: request.GramsUsed, + dataSource: dataSource, + printerId: request.PrinterId, + printJobId: request.PrintJobId, + mmExtruded: request.MmExtruded, + usageTimestamp: request.UsageTimestamp, + notes: request.Notes + ); + + return CreatedAtAction( + nameof(GetBySpool), + new { spoolId = entry.SpoolId }, + MapToResponse(entry)); + } + + /// + /// Gets usage logs for a specific spool, ordered by most recent first. + /// + /// The spool ID to filter by. + /// A collection of usage log entries for the spool. + [HttpGet("spool/{spoolId:guid}")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task>> GetBySpool(Guid spoolId) + { + var logs = await _usageLogService.GetBySpoolAsync(spoolId); + return Ok(logs.Select(MapToResponse)); + } + + /// + /// Gets usage logs for a specific printer, ordered by most recent first. + /// + /// The printer ID to filter by. + /// A collection of usage log entries for the printer. + [HttpGet("printer/{printerId:guid}")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task>> GetByPrinter(Guid printerId) + { + var logs = await _usageLogService.GetByPrinterAsync(printerId); + return Ok(logs.Select(MapToResponse)); + } + + /// + /// Gets usage logs for a specific print job, ordered by most recent first. + /// + /// The print job ID to filter by. + /// A collection of usage log entries for the print job. + [HttpGet("print-job/{printJobId:guid}")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task>> GetByPrintJob(Guid printJobId) + { + var logs = await _usageLogService.GetByPrintJobAsync(printJobId); + return Ok(logs.Select(MapToResponse)); + } + + /// + /// Maps a UsageLog domain entity to a UsageLogResponse DTO. + /// + private static UsageLogResponse MapToResponse(Domain.Entities.UsageLog log) => new() + { + Id = log.Id, + SpoolId = log.SpoolId, + PrinterId = log.PrinterId, + PrintJobId = log.PrintJobId, + GramsUsed = log.GramsUsed, + MmExtruded = log.MmExtruded, + UsageTimestamp = log.UsageTimestamp, + DataSource = log.DataSource.ToString(), + Notes = log.Notes, + CreatedAt = log.CreatedAt, + UpdatedAt = log.UpdatedAt + }; +} \ No newline at end of file diff --git a/backend/API/DTOs/UsageLogs/UsageLogDtos.cs b/backend/API/DTOs/UsageLogs/UsageLogDtos.cs new file mode 100644 index 0000000..a1087de --- /dev/null +++ b/backend/API/DTOs/UsageLogs/UsageLogDtos.cs @@ -0,0 +1,115 @@ +using System.ComponentModel.DataAnnotations; + +namespace Extrudex.API.DTOs.UsageLogs; + +/// +/// Request DTO for recording a filament usage entry. +/// +public class CreateUsageLogRequest +{ + /// + /// The ID of the spool that provided the filament. + /// + [Required] + public Guid SpoolId { get; set; } + + /// + /// The number of grams of filament consumed. + /// + [Required] + [Range(0.01, double.MaxValue, ErrorMessage = "GramsUsed must be a positive value.")] + public decimal GramsUsed { get; set; } + + /// + /// The source of the usage data (Mqtt, Moonraker, Manual). + /// + [Required] + public string DataSource { get; set; } = string.Empty; + + /// + /// The ID of the printer that consumed the filament. Optional. + /// + public Guid? PrinterId { get; set; } + + /// + /// The ID of the print job associated with this usage. Optional. + /// + public Guid? PrintJobId { get; set; } + + /// + /// The number of millimeters of filament extruded. Optional. + /// + public decimal? MmExtruded { get; set; } + + /// + /// When the usage occurred (UTC). Defaults to now if not specified. + /// + public DateTime? UsageTimestamp { get; set; } + + /// + /// Optional notes about this usage entry. + /// + [MaxLength(2000)] + public string? Notes { get; set; } +} + +/// +/// Response DTO for a usage log entry. +/// +public class UsageLogResponse +{ + /// + /// Unique identifier for the usage log entry. + /// + public Guid Id { get; set; } + + /// + /// The spool that provided the filament. + /// + public Guid SpoolId { get; set; } + + /// + /// The printer that consumed the filament, if applicable. + /// + public Guid? PrinterId { get; set; } + + /// + /// The print job associated with this usage, if applicable. + /// + public Guid? PrintJobId { get; set; } + + /// + /// Grams of filament consumed. + /// + public decimal GramsUsed { get; set; } + + /// + /// Millimeters of filament extruded, if available. + /// + public decimal? MmExtruded { get; set; } + + /// + /// When the usage occurred (UTC). + /// + public DateTime UsageTimestamp { get; set; } + + /// + /// Source of the usage data (Mqtt, Moonraker, Manual). + /// + public string DataSource { get; set; } = string.Empty; + + /// + /// Optional notes about this usage entry. + /// + public string? Notes { get; set; } + + /// + /// When the record was created (UTC). + /// + public DateTime CreatedAt { get; set; } + + /// + /// When the record was last updated (UTC). + /// + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/backend/Domain/Entities/UsageLog.cs b/backend/Domain/Entities/UsageLog.cs new file mode 100644 index 0000000..c9cfed0 --- /dev/null +++ b/backend/Domain/Entities/UsageLog.cs @@ -0,0 +1,72 @@ +using Extrudex.Domain.Base; +using Extrudex.Domain.Enums; + +namespace Extrudex.Domain.Entities; + +/// +/// Represents a single filament usage log entry. Records how much filament +/// was consumed, by which printer, at what time, and optionally linked to +/// a print job. This provides a fine-grained audit trail of filament consumption +/// independent of print job lifecycle. +/// +public class UsageLog : AuditableEntity +{ + /// + /// Foreign key to the spool that provided the filament. + /// + public Guid SpoolId { get; set; } + + /// + /// Navigation to the spool that provided the filament. + /// + public Spool Spool { get; set; } = null!; + + /// + /// Foreign key to the printer that consumed the filament. + /// Nullable to support manual entries without a specific printer. + /// + public Guid? PrinterId { get; set; } + + /// + /// Navigation to the printer that consumed the filament. + /// + public Printer? Printer { get; set; } + + /// + /// Foreign key to the print job associated with this usage entry. + /// Nullable because usage can be logged before or without a print job. + /// + public Guid? PrintJobId { get; set; } + + /// + /// Navigation to the print job associated with this usage entry. + /// + public PrintJob? PrintJob { get; set; } + + /// + /// The number of grams of filament consumed in this usage event. + /// + public decimal GramsUsed { get; set; } + + /// + /// The number of millimeters of filament extruded in this usage event. + /// Optional — may not be available for all data sources. + /// + public decimal? MmExtruded { get; set; } + + /// + /// Timestamp when the usage occurred (UTC). This is the actual time of + /// consumption, which may differ from CreatedAt if the entry was recorded later. + /// + public DateTime UsageTimestamp { get; set; } = DateTime.UtcNow; + + /// + /// The source of the usage data (which integration path provided it). + /// + public DataSource DataSource { get; set; } = DataSource.Manual; + + /// + /// Optional notes about this usage entry. + /// + public string? Notes { get; set; } +} \ No newline at end of file diff --git a/backend/Domain/Interfaces/IUsageLogService.cs b/backend/Domain/Interfaces/IUsageLogService.cs new file mode 100644 index 0000000..1d99f64 --- /dev/null +++ b/backend/Domain/Interfaces/IUsageLogService.cs @@ -0,0 +1,57 @@ +using Extrudex.Domain.Entities; +using Extrudex.Domain.Enums; + +namespace Extrudex.Domain.Interfaces; + +/// +/// Service for recording filament usage entries. Writes to the usage_logs table +/// and provides query capabilities for usage history. +/// +public interface IUsageLogService +{ + /// + /// Records a filament usage entry. + /// + /// The spool that provided the filament. + /// Grams of filament consumed. + /// Where the data came from. + /// Optional printer ID. + /// Optional print job ID. + /// Optional mm extruded. + /// When the usage occurred (defaults to UTC now). + /// Optional notes. + /// The created UsageLog entity. + Task RecordUsageAsync( + Guid spoolId, + decimal gramsUsed, + DataSource dataSource, + Guid? printerId = null, + Guid? printJobId = null, + decimal? mmExtruded = null, + DateTime? usageTimestamp = null, + string? notes = null); + + /// + /// Retrieves usage logs for a specific spool, ordered by usage timestamp descending. + /// + /// The spool ID to filter by. + /// Cancellation token. + /// A collection of usage logs for the spool. + Task> GetBySpoolAsync(Guid spoolId, CancellationToken cancellationToken = default); + + /// + /// Retrieves usage logs for a specific printer, ordered by usage timestamp descending. + /// + /// The printer ID to filter by. + /// Cancellation token. + /// A collection of usage logs for the printer. + Task> GetByPrinterAsync(Guid printerId, CancellationToken cancellationToken = default); + + /// + /// Retrieves usage logs for a specific print job, ordered by usage timestamp descending. + /// + /// The print job ID to filter by. + /// Cancellation token. + /// A collection of usage logs for the print job. + Task> GetByPrintJobAsync(Guid printJobId, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/backend/Infrastructure/Data/Configurations/UsageLogConfiguration.cs b/backend/Infrastructure/Data/Configurations/UsageLogConfiguration.cs new file mode 100644 index 0000000..127cccb --- /dev/null +++ b/backend/Infrastructure/Data/Configurations/UsageLogConfiguration.cs @@ -0,0 +1,91 @@ +using Extrudex.Domain.Entities; +using Extrudex.Domain.Enums; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Extrudex.Infrastructure.Data.Configurations; + +/// +/// EF Core configuration for the UsageLog entity. +/// Maps to the usage_logs table with snake_case columns and appropriate indexes. +/// +public class UsageLogConfiguration : BaseEntityConfiguration +{ + /// + public override void Configure(EntityTypeBuilder builder) + { + base.Configure(builder); + + builder.Property(e => e.SpoolId) + .HasColumnName("spool_id") + .IsRequired(); + + builder.Property(e => e.PrinterId) + .HasColumnName("printer_id"); + + builder.Property(e => e.PrintJobId) + .HasColumnName("print_job_id"); + + builder.Property(e => e.GramsUsed) + .HasColumnName("grams_used") + .HasPrecision(10, 2) + .IsRequired(); + + builder.Property(e => e.MmExtruded) + .HasColumnName("mm_extruded") + .HasPrecision(12, 2); + + builder.Property(e => e.UsageTimestamp) + .HasColumnName("usage_timestamp") + .IsRequired(); + + builder.Property(e => e.DataSource) + .HasColumnName("data_source") + .HasConversion() + .HasMaxLength(50) + .IsRequired(); + + builder.Property(e => e.Notes) + .HasColumnName("notes") + .HasMaxLength(2000); + + // Index on spool_id for querying usage by spool + builder.HasIndex(e => e.SpoolId) + .HasDatabaseName("ix_usage_logs_spool_id"); + + // Index on printer_id for querying usage by printer + builder.HasIndex(e => e.PrinterId) + .HasDatabaseName("ix_usage_logs_printer_id"); + + // Index on print_job_id for querying usage by print job + builder.HasIndex(e => e.PrintJobId) + .HasDatabaseName("ix_usage_logs_print_job_id"); + + // Index on usage_timestamp for chronological queries + builder.HasIndex(e => e.UsageTimestamp) + .HasDatabaseName("ix_usage_logs_usage_timestamp"); + + // Index on data_source for filtering by integration path + builder.HasIndex(e => e.DataSource) + .HasDatabaseName("ix_usage_logs_data_source"); + + // Relationships + builder.HasOne(e => e.Spool) + .WithMany() + .HasForeignKey(e => e.SpoolId) + .HasConstraintName("fk_usage_logs_spool") + .OnDelete(DeleteBehavior.Restrict); + + builder.HasOne(e => e.Printer) + .WithMany() + .HasForeignKey(e => e.PrinterId) + .HasConstraintName("fk_usage_logs_printer") + .OnDelete(DeleteBehavior.SetNull); + + builder.HasOne(e => e.PrintJob) + .WithMany() + .HasForeignKey(e => e.PrintJobId) + .HasConstraintName("fk_usage_logs_print_job") + .OnDelete(DeleteBehavior.SetNull); + } +} \ No newline at end of file diff --git a/backend/Infrastructure/Data/ExtrudexDbContext.cs b/backend/Infrastructure/Data/ExtrudexDbContext.cs index 270718c..3e5fe9a 100644 --- a/backend/Infrastructure/Data/ExtrudexDbContext.cs +++ b/backend/Infrastructure/Data/ExtrudexDbContext.cs @@ -24,6 +24,7 @@ public class ExtrudexDbContext : DbContext public DbSet AmsSlots => Set(); public DbSet PrintJobs => Set(); public DbSet FilamentUsages => Set(); + public DbSet UsageLogs => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/backend/Infrastructure/Data/Migrations/20260426184329_AddUsageLogTable.Designer.cs b/backend/Infrastructure/Data/Migrations/20260426184329_AddUsageLogTable.Designer.cs new file mode 100644 index 0000000..2e6cf81 --- /dev/null +++ b/backend/Infrastructure/Data/Migrations/20260426184329_AddUsageLogTable.Designer.cs @@ -0,0 +1,1061 @@ +// +using System; +using Extrudex.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Extrudex.Infrastructure.Data.Migrations +{ + [DbContext(typeof(ExtrudexDbContext))] + [Migration("20260426184329_AddUsageLogTable")] + partial class AddUsageLogTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Extrudex.Domain.Entities.AmsSlot", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AmsUnitId") + .HasColumnType("uuid") + .HasColumnName("ams_unit_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("RemainingWeightG") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("remaining_weight_g"); + + b.Property("SpoolId") + .HasColumnType("uuid") + .HasColumnName("spool_id"); + + b.Property("TrayIndex") + .HasColumnType("integer") + .HasColumnName("tray_index"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("SpoolId") + .HasDatabaseName("ix_ams_slots_spool_id"); + + b.HasIndex("AmsUnitId", "TrayIndex") + .IsUnique() + .HasDatabaseName("ix_ams_slots_ams_unit_id_tray_index"); + + b.ToTable("ams_slots", (string)null); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("PrinterId") + .HasColumnType("uuid") + .HasColumnName("printer_id"); + + b.Property("UnitIndex") + .HasColumnType("integer") + .HasColumnName("unit_index"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("PrinterId", "UnitIndex") + .IsUnique() + .HasDatabaseName("ix_ams_units_printer_id_unit_index"); + + b.ToTable("ams_units", (string)null); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialBase", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("DensityGperCm3") + .HasPrecision(10, 4) + .HasColumnType("numeric(10,4)") + .HasColumnName("density_g_per_cm3"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("ix_material_bases_name"); + + b.ToTable("material_bases", (string)null); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(6535), + DensityGperCm3 = 1.24m, + Name = "PLA", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(6535) + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7016), + DensityGperCm3 = 1.27m, + Name = "PETG", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7016) + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7027), + DensityGperCm3 = 1.04m, + Name = "ABS", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7028) + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7034), + DensityGperCm3 = 1.07m, + Name = "ASA", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7035) + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7042), + DensityGperCm3 = 1.21m, + Name = "TPU", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7042) + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7049), + DensityGperCm3 = 1.14m, + Name = "Nylon", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7049) + }); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("MaterialBaseId") + .HasColumnType("uuid") + .HasColumnName("material_base_id"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("MaterialBaseId", "Name") + .IsUnique() + .HasDatabaseName("ix_material_finishes_material_base_id_name"); + + b.ToTable("material_finishes", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7291), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Basic", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7292) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7453), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Matte", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7453) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7461), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Silk", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7461) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7468), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Glitter", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7468) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7474), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Marble", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7474) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7480), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Sparkle", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7481) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000007"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7487), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), + Name = "Basic", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7487) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000008"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7493), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), + Name = "Matte", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7493) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000009"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7500), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), + Name = "Silk", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7500) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000010"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7507), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), + Name = "Basic", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7507) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000011"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7513), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), + Name = "Matte", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7513) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000012"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7519), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"), + Name = "Basic", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7520) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000013"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7526), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"), + Name = "Matte", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7526) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000014"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7532), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000005"), + Name = "Basic", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7532) + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000015"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7538), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"), + Name = "Basic", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7539) + }); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialModifier", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("MaterialBaseId") + .HasColumnType("uuid") + .HasColumnName("material_base_id"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("name"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("MaterialBaseId", "Name") + .IsUnique() + .HasDatabaseName("ix_material_modifiers_material_base_id_name"); + + b.ToTable("material_modifiers", (string)null); + + b.HasData( + new + { + Id = new Guid("30000000-0000-0000-0000-000000000001"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7690), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Carbon Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7690) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000002"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7838), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Glass Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7838) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000003"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7846), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Wood Fill", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7846) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000004"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7853), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), + Name = "Glow-in-the-Dark", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7853) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000005"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7859), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), + Name = "Carbon Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7859) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000006"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7865), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), + Name = "Glass Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7866) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000007"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7872), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), + Name = "Carbon Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7872) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000008"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7878), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), + Name = "Glass Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7879) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000009"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7885), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"), + Name = "Carbon Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7885) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000010"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7891), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"), + Name = "Carbon Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7891) + }, + new + { + Id = new Guid("30000000-0000-0000-0000-000000000011"), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7898), + MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"), + Name = "Glass Fiber", + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7898) + }); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.PrintJob", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("CostPerPrint") + .HasPrecision(10, 4) + .HasColumnType("numeric(10,4)") + .HasColumnName("cost_per_print"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("DataSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("data_source"); + + b.Property("FilamentDiameterAtPrintMm") + .HasPrecision(6, 3) + .HasColumnType("numeric(6,3)") + .HasColumnName("filament_diameter_at_print_mm"); + + b.Property("GcodeFilePath") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("gcode_file_path"); + + b.Property("GramsDerived") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("grams_derived"); + + b.Property("MaterialDensityAtPrint") + .HasPrecision(10, 4) + .HasColumnType("numeric(10,4)") + .HasColumnName("material_density_at_print"); + + b.Property("MmExtruded") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("mm_extruded"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("notes"); + + b.Property("PrintName") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("print_name"); + + b.Property("PrinterId") + .HasColumnType("uuid") + .HasColumnName("printer_id"); + + b.Property("SpoolId") + .HasColumnType("uuid") + .HasColumnName("spool_id"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasDefaultValue("Queued") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("DataSource") + .HasDatabaseName("ix_print_jobs_data_source"); + + b.HasIndex("PrinterId") + .HasDatabaseName("ix_print_jobs_printer_id"); + + b.HasIndex("SpoolId") + .HasDatabaseName("ix_print_jobs_spool_id"); + + b.HasIndex("Status") + .HasDatabaseName("ix_print_jobs_status"); + + b.ToTable("print_jobs", (string)null); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.Printer", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("api_key"); + + b.Property("ConnectionType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("connection_type"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("HostnameOrIp") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("hostname_or_ip"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("is_active"); + + b.Property("LastSeenAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen_at"); + + b.Property("Manufacturer") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("manufacturer"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("model"); + + b.Property("MqttPassword") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("mqtt_password"); + + b.Property("MqttUseTls") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("mqtt_use_tls"); + + b.Property("MqttUsername") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("mqtt_username"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Port") + .HasColumnType("integer") + .HasColumnName("port"); + + b.Property("PrinterType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("printer_type"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasDefaultValue("Offline") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.HasKey("Id"); + + b.HasIndex("ConnectionType") + .HasDatabaseName("ix_printers_connection_type"); + + b.HasIndex("IsActive") + .HasDatabaseName("ix_printers_is_active"); + + b.HasIndex("PrinterType") + .HasDatabaseName("ix_printers_printer_type"); + + b.HasIndex("Status") + .HasDatabaseName("ix_printers_status"); + + b.ToTable("printers", (string)null); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.Spool", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("brand"); + + b.Property("ColorHex") + .IsRequired() + .HasMaxLength(7) + .HasColumnType("character varying(7)") + .HasColumnName("color_hex"); + + b.Property("ColorName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("color_name"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("FilamentDiameterMm") + .HasPrecision(6, 3) + .HasColumnType("numeric(6,3)") + .HasColumnName("filament_diameter_mm"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("is_active"); + + b.Property("MaterialBaseId") + .HasColumnType("uuid") + .HasColumnName("material_base_id"); + + b.Property("MaterialFinishId") + .HasColumnType("uuid") + .HasColumnName("material_finish_id"); + + b.Property("MaterialModifierId") + .HasColumnType("uuid") + .HasColumnName("material_modifier_id"); + + b.Property("PurchaseDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("purchase_date"); + + b.Property("PurchasePrice") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("purchase_price"); + + b.Property("SpoolSerial") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("spool_serial"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("WeightRemainingGrams") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("weight_remaining_grams"); + + b.Property("WeightTotalGrams") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("weight_total_grams"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("ix_spools_is_active"); + + b.HasIndex("MaterialBaseId") + .HasDatabaseName("ix_spools_material_base_id"); + + b.HasIndex("MaterialFinishId") + .HasDatabaseName("ix_spools_material_finish_id"); + + b.HasIndex("MaterialModifierId") + .HasDatabaseName("ix_spools_material_modifier_id"); + + b.HasIndex("SpoolSerial") + .IsUnique() + .HasDatabaseName("ix_spools_spool_serial"); + + b.ToTable("spools", (string)null); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.UsageLog", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("DataSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("data_source"); + + b.Property("GramsUsed") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("grams_used"); + + b.Property("MmExtruded") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("mm_extruded"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("notes"); + + b.Property("PrintJobId") + .HasColumnType("uuid") + .HasColumnName("print_job_id"); + + b.Property("PrinterId") + .HasColumnType("uuid") + .HasColumnName("printer_id"); + + b.Property("SpoolId") + .HasColumnType("uuid") + .HasColumnName("spool_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("UsageTimestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("usage_timestamp"); + + b.HasKey("Id"); + + b.HasIndex("DataSource") + .HasDatabaseName("ix_usage_logs_data_source"); + + b.HasIndex("PrintJobId") + .HasDatabaseName("ix_usage_logs_print_job_id"); + + b.HasIndex("PrinterId") + .HasDatabaseName("ix_usage_logs_printer_id"); + + b.HasIndex("SpoolId") + .HasDatabaseName("ix_usage_logs_spool_id"); + + b.HasIndex("UsageTimestamp") + .HasDatabaseName("ix_usage_logs_usage_timestamp"); + + b.ToTable("usage_logs", (string)null); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.AmsSlot", b => + { + b.HasOne("Extrudex.Domain.Entities.AmsUnit", "AmsUnit") + .WithMany("Slots") + .HasForeignKey("AmsUnitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_ams_slots_ams_unit"); + + b.HasOne("Extrudex.Domain.Entities.Spool", "Spool") + .WithMany("AmsSlots") + .HasForeignKey("SpoolId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_ams_slots_spool"); + + b.Navigation("AmsUnit"); + + b.Navigation("Spool"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b => + { + b.HasOne("Extrudex.Domain.Entities.Printer", "Printer") + .WithMany("AmsUnits") + .HasForeignKey("PrinterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_ams_units_printer"); + + b.Navigation("Printer"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b => + { + b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase") + .WithMany("Finishes") + .HasForeignKey("MaterialBaseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_material_finishes_material_base"); + + b.Navigation("MaterialBase"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialModifier", b => + { + b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase") + .WithMany("Modifiers") + .HasForeignKey("MaterialBaseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_material_modifiers_material_base"); + + b.Navigation("MaterialBase"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.PrintJob", b => + { + b.HasOne("Extrudex.Domain.Entities.Printer", "Printer") + .WithMany("PrintJobs") + .HasForeignKey("PrinterId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_print_jobs_printer"); + + b.HasOne("Extrudex.Domain.Entities.Spool", "Spool") + .WithMany("PrintJobs") + .HasForeignKey("SpoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_print_jobs_spool"); + + b.Navigation("Printer"); + + b.Navigation("Spool"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.Spool", b => + { + b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase") + .WithMany("Spools") + .HasForeignKey("MaterialBaseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_spools_material_base"); + + b.HasOne("Extrudex.Domain.Entities.MaterialFinish", "MaterialFinish") + .WithMany("Spools") + .HasForeignKey("MaterialFinishId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_spools_material_finish"); + + b.HasOne("Extrudex.Domain.Entities.MaterialModifier", "MaterialModifier") + .WithMany("Spools") + .HasForeignKey("MaterialModifierId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_spools_material_modifier"); + + b.Navigation("MaterialBase"); + + b.Navigation("MaterialFinish"); + + b.Navigation("MaterialModifier"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.UsageLog", b => + { + b.HasOne("Extrudex.Domain.Entities.PrintJob", "PrintJob") + .WithMany() + .HasForeignKey("PrintJobId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_usage_logs_print_job"); + + b.HasOne("Extrudex.Domain.Entities.Printer", "Printer") + .WithMany() + .HasForeignKey("PrinterId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_usage_logs_printer"); + + b.HasOne("Extrudex.Domain.Entities.Spool", "Spool") + .WithMany() + .HasForeignKey("SpoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_usage_logs_spool"); + + b.Navigation("PrintJob"); + + b.Navigation("Printer"); + + b.Navigation("Spool"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b => + { + b.Navigation("Slots"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialBase", b => + { + b.Navigation("Finishes"); + + b.Navigation("Modifiers"); + + b.Navigation("Spools"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b => + { + b.Navigation("Spools"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.MaterialModifier", b => + { + b.Navigation("Spools"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.Printer", b => + { + b.Navigation("AmsUnits"); + + b.Navigation("PrintJobs"); + }); + + modelBuilder.Entity("Extrudex.Domain.Entities.Spool", b => + { + b.Navigation("AmsSlots"); + + b.Navigation("PrintJobs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Infrastructure/Data/Migrations/20260426184329_AddUsageLogTable.cs b/backend/Infrastructure/Data/Migrations/20260426184329_AddUsageLogTable.cs new file mode 100644 index 0000000..483a8a6 --- /dev/null +++ b/backend/Infrastructure/Data/Migrations/20260426184329_AddUsageLogTable.cs @@ -0,0 +1,534 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Extrudex.Infrastructure.Data.Migrations +{ + /// + public partial class AddUsageLogTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "usage_logs", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + spool_id = table.Column(type: "uuid", nullable: false), + printer_id = table.Column(type: "uuid", nullable: true), + print_job_id = table.Column(type: "uuid", nullable: true), + grams_used = table.Column(type: "numeric(10,2)", precision: 10, scale: 2, nullable: false), + mm_extruded = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: true), + usage_timestamp = table.Column(type: "timestamp with time zone", nullable: false), + data_source = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + notes = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now() at time zone 'utc'") + }, + constraints: table => + { + table.PrimaryKey("PK_usage_logs", x => x.id); + table.ForeignKey( + name: "fk_usage_logs_print_job", + column: x => x.print_job_id, + principalTable: "print_jobs", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_usage_logs_printer", + column: x => x.printer_id, + principalTable: "printers", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_usage_logs_spool", + column: x => x.spool_id, + principalTable: "spools", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000001"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(6535), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(6535) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000002"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7016), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7016) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000003"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7027), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7028) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000004"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7034), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7035) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000005"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7042), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7042) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000006"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7049), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7049) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000001"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7291), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7292) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000002"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7453), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7453) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000003"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7461), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7461) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000004"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7468), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7468) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000005"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7474), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7474) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000006"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7480), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7481) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000007"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7487), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7487) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000008"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7493), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7493) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000009"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7500), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7500) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000010"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7507), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7507) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000011"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7513), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7513) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000012"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7519), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7520) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000013"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7526), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7526) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000014"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7532), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7532) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000015"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7538), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7539) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000001"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7690), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7690) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000002"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7838), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7838) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000003"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7846), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7846) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000004"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7853), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7853) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000005"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7859), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7859) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000006"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7865), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7866) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000007"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7872), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7872) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000008"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7878), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7879) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000009"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7885), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7885) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000010"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7891), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7891) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000011"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7898), new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7898) }); + + migrationBuilder.CreateIndex( + name: "ix_usage_logs_data_source", + table: "usage_logs", + column: "data_source"); + + migrationBuilder.CreateIndex( + name: "ix_usage_logs_print_job_id", + table: "usage_logs", + column: "print_job_id"); + + migrationBuilder.CreateIndex( + name: "ix_usage_logs_printer_id", + table: "usage_logs", + column: "printer_id"); + + migrationBuilder.CreateIndex( + name: "ix_usage_logs_spool_id", + table: "usage_logs", + column: "spool_id"); + + migrationBuilder.CreateIndex( + name: "ix_usage_logs_usage_timestamp", + table: "usage_logs", + column: "usage_timestamp"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "usage_logs"); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000001"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1096) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000002"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1620) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000003"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1630) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000004"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1638) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000005"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1645) }); + + migrationBuilder.UpdateData( + table: "material_bases", + keyColumn: "id", + keyValue: new Guid("10000000-0000-0000-0000-000000000006"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1651), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1652) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000001"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(1850) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000002"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2041) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000003"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2049) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000004"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2055), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2056) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000005"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2062) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000006"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2068) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000007"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2075) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000008"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2081) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000009"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2100) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000010"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2107) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000011"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2113) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000012"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2120) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000013"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2126) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000014"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2132), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2133) }); + + migrationBuilder.UpdateData( + table: "material_finishes", + keyColumn: "id", + keyValue: new Guid("20000000-0000-0000-0000-000000000015"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2139) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000001"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2304) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000002"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2463) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000003"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2471) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000004"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2477), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2478) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000005"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2484) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000006"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2490), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2491) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000007"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2497) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000008"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2503) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000009"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2510) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000010"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2516) }); + + migrationBuilder.UpdateData( + table: "material_modifiers", + keyColumn: "id", + keyValue: new Guid("30000000-0000-0000-0000-000000000011"), + columns: new[] { "created_at", "updated_at" }, + values: new object[] { new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2522), new DateTime(2026, 4, 26, 13, 14, 18, 745, DateTimeKind.Utc).AddTicks(2523) }); + } + } +} diff --git a/backend/Infrastructure/Data/Migrations/ExtrudexDbContextModelSnapshot.cs b/backend/Infrastructure/Data/Migrations/ExtrudexDbContextModelSnapshot.cs index f214506..e20d707 100644 --- a/backend/Infrastructure/Data/Migrations/ExtrudexDbContextModelSnapshot.cs +++ b/backend/Infrastructure/Data/Migrations/ExtrudexDbContextModelSnapshot.cs @@ -104,77 +104,6 @@ namespace Extrudex.Infrastructure.Data.Migrations b.ToTable("ams_units", (string)null); }); - modelBuilder.Entity("Extrudex.Domain.Entities.FilamentUsage", b => - { - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now() at time zone 'utc'"); - - b.Property("GramsUsed") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("grams_used"); - - b.Property("MmExtruded") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("mm_extruded"); - - b.Property("Notes") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("notes"); - - b.Property("PrintJobId") - .HasColumnType("uuid") - .HasColumnName("print_job_id"); - - b.Property("PrinterId") - .HasColumnType("uuid") - .HasColumnName("printer_id"); - - b.Property("RecordedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("recorded_at") - .HasDefaultValueSql("now() at time zone 'utc'"); - - b.Property("SpoolId") - .HasColumnType("uuid") - .HasColumnName("spool_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now() at time zone 'utc'"); - - b.HasKey("Id"); - - b.HasIndex("PrintJobId") - .HasDatabaseName("ix_filament_usages_print_job_id"); - - b.HasIndex("PrinterId") - .HasDatabaseName("ix_filament_usages_printer_id"); - - b.HasIndex("RecordedAt") - .HasDatabaseName("ix_filament_usages_recorded_at"); - - b.HasIndex("SpoolId") - .HasDatabaseName("ix_filament_usages_spool_id"); - - b.HasIndex("SpoolId", "RecordedAt") - .HasDatabaseName("ix_filament_usages_spool_id_recorded_at"); - - b.ToTable("filament_usages", (string)null); - }); - modelBuilder.Entity("Extrudex.Domain.Entities.MaterialBase", b => { b.Property("Id") @@ -216,50 +145,50 @@ namespace Extrudex.Infrastructure.Data.Migrations new { Id = new Guid("10000000-0000-0000-0000-000000000001"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9388), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(6535), DensityGperCm3 = 1.24m, Name = "PLA", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9388) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(6535) }, new { Id = new Guid("10000000-0000-0000-0000-000000000002"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9871), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7016), DensityGperCm3 = 1.27m, Name = "PETG", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9871) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7016) }, new { Id = new Guid("10000000-0000-0000-0000-000000000003"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9881), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7027), DensityGperCm3 = 1.04m, Name = "ABS", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9881) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7028) }, new { Id = new Guid("10000000-0000-0000-0000-000000000004"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9888), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7034), DensityGperCm3 = 1.07m, Name = "ASA", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9888) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7035) }, new { Id = new Guid("10000000-0000-0000-0000-000000000005"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9895), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7042), DensityGperCm3 = 1.21m, Name = "TPU", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9895) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7042) }, new { Id = new Guid("10000000-0000-0000-0000-000000000006"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9901), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7049), DensityGperCm3 = 1.14m, Name = "Nylon", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 291, DateTimeKind.Utc).AddTicks(9902) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7049) }); }); @@ -303,122 +232,122 @@ namespace Extrudex.Infrastructure.Data.Migrations new { Id = new Guid("20000000-0000-0000-0000-000000000001"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(90), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7291), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Basic", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(90) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7292) }, new { Id = new Guid("20000000-0000-0000-0000-000000000002"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(251), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7453), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Matte", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(251) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7453) }, new { Id = new Guid("20000000-0000-0000-0000-000000000003"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(259), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7461), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Silk", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(259) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7461) }, new { Id = new Guid("20000000-0000-0000-0000-000000000004"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(266), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7468), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Glitter", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(266) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7468) }, new { Id = new Guid("20000000-0000-0000-0000-000000000005"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(272), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7474), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Marble", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(272) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7474) }, new { Id = new Guid("20000000-0000-0000-0000-000000000006"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(278), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7480), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Sparkle", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(278) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7481) }, new { Id = new Guid("20000000-0000-0000-0000-000000000007"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(285), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7487), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), Name = "Basic", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(285) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7487) }, new { Id = new Guid("20000000-0000-0000-0000-000000000008"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(291), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7493), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), Name = "Matte", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(291) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7493) }, new { Id = new Guid("20000000-0000-0000-0000-000000000009"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(297), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7500), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), Name = "Silk", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(298) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7500) }, new { Id = new Guid("20000000-0000-0000-0000-000000000010"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(304), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7507), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), Name = "Basic", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(304) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7507) }, new { Id = new Guid("20000000-0000-0000-0000-000000000011"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(310), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7513), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), Name = "Matte", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(310) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7513) }, new { Id = new Guid("20000000-0000-0000-0000-000000000012"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(316), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7519), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"), Name = "Basic", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(317) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7520) }, new { Id = new Guid("20000000-0000-0000-0000-000000000013"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(323), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7526), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"), Name = "Matte", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(323) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7526) }, new { Id = new Guid("20000000-0000-0000-0000-000000000014"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(329), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7532), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000005"), Name = "Basic", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(329) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7532) }, new { Id = new Guid("20000000-0000-0000-0000-000000000015"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(336), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7538), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"), Name = "Basic", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(336) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7539) }); }); @@ -462,90 +391,90 @@ namespace Extrudex.Infrastructure.Data.Migrations new { Id = new Guid("30000000-0000-0000-0000-000000000001"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(482), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7690), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Carbon Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(482) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7690) }, new { Id = new Guid("30000000-0000-0000-0000-000000000002"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(805), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7838), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Glass Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(806) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7838) }, new { Id = new Guid("30000000-0000-0000-0000-000000000003"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(815), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7846), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Wood Fill", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(815) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7846) }, new { Id = new Guid("30000000-0000-0000-0000-000000000004"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(821), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7853), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000001"), Name = "Glow-in-the-Dark", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(821) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7853) }, new { Id = new Guid("30000000-0000-0000-0000-000000000005"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(828), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7859), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), Name = "Carbon Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(828) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7859) }, new { Id = new Guid("30000000-0000-0000-0000-000000000006"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(834), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7865), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000002"), Name = "Glass Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(834) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7866) }, new { Id = new Guid("30000000-0000-0000-0000-000000000007"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(840), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7872), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), Name = "Carbon Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(840) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7872) }, new { Id = new Guid("30000000-0000-0000-0000-000000000008"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(847), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7878), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000003"), Name = "Glass Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(847) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7879) }, new { Id = new Guid("30000000-0000-0000-0000-000000000009"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(853), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7885), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000004"), Name = "Carbon Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(853) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7885) }, new { Id = new Guid("30000000-0000-0000-0000-000000000010"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(859), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7891), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"), Name = "Carbon Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(860) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7891) }, new { Id = new Guid("30000000-0000-0000-0000-000000000011"), - CreatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(866), + CreatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7898), MaterialBaseId = new Guid("10000000-0000-0000-0000-000000000006"), Name = "Glass Fiber", - UpdatedAt = new DateTime(2026, 4, 26, 18, 34, 33, 292, DateTimeKind.Utc).AddTicks(866) + UpdatedAt = new DateTime(2026, 4, 26, 18, 43, 28, 895, DateTimeKind.Utc).AddTicks(7898) }); }); @@ -877,6 +806,81 @@ namespace Extrudex.Infrastructure.Data.Migrations b.ToTable("spools", (string)null); }); + modelBuilder.Entity("Extrudex.Domain.Entities.UsageLog", b => + { + b.Property("Id") + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("DataSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("data_source"); + + b.Property("GramsUsed") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)") + .HasColumnName("grams_used"); + + b.Property("MmExtruded") + .HasPrecision(12, 2) + .HasColumnType("numeric(12,2)") + .HasColumnName("mm_extruded"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasColumnName("notes"); + + b.Property("PrintJobId") + .HasColumnType("uuid") + .HasColumnName("print_job_id"); + + b.Property("PrinterId") + .HasColumnType("uuid") + .HasColumnName("printer_id"); + + b.Property("SpoolId") + .HasColumnType("uuid") + .HasColumnName("spool_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now() at time zone 'utc'"); + + b.Property("UsageTimestamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("usage_timestamp"); + + b.HasKey("Id"); + + b.HasIndex("DataSource") + .HasDatabaseName("ix_usage_logs_data_source"); + + b.HasIndex("PrintJobId") + .HasDatabaseName("ix_usage_logs_print_job_id"); + + b.HasIndex("PrinterId") + .HasDatabaseName("ix_usage_logs_printer_id"); + + b.HasIndex("SpoolId") + .HasDatabaseName("ix_usage_logs_spool_id"); + + b.HasIndex("UsageTimestamp") + .HasDatabaseName("ix_usage_logs_usage_timestamp"); + + b.ToTable("usage_logs", (string)null); + }); + modelBuilder.Entity("Extrudex.Domain.Entities.AmsSlot", b => { b.HasOne("Extrudex.Domain.Entities.AmsUnit", "AmsUnit") @@ -909,36 +913,6 @@ namespace Extrudex.Infrastructure.Data.Migrations b.Navigation("Printer"); }); - modelBuilder.Entity("Extrudex.Domain.Entities.FilamentUsage", b => - { - b.HasOne("Extrudex.Domain.Entities.PrintJob", "PrintJob") - .WithMany("FilamentUsages") - .HasForeignKey("PrintJobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_filament_usages_print_job"); - - b.HasOne("Extrudex.Domain.Entities.Printer", "Printer") - .WithMany("FilamentUsages") - .HasForeignKey("PrinterId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_filament_usages_printer"); - - b.HasOne("Extrudex.Domain.Entities.Spool", "Spool") - .WithMany("FilamentUsages") - .HasForeignKey("SpoolId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_filament_usages_spool"); - - b.Navigation("PrintJob"); - - b.Navigation("Printer"); - - b.Navigation("Spool"); - }); - modelBuilder.Entity("Extrudex.Domain.Entities.MaterialFinish", b => { b.HasOne("Extrudex.Domain.Entities.MaterialBase", "MaterialBase") @@ -1013,6 +987,34 @@ namespace Extrudex.Infrastructure.Data.Migrations b.Navigation("MaterialModifier"); }); + modelBuilder.Entity("Extrudex.Domain.Entities.UsageLog", b => + { + b.HasOne("Extrudex.Domain.Entities.PrintJob", "PrintJob") + .WithMany() + .HasForeignKey("PrintJobId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_usage_logs_print_job"); + + b.HasOne("Extrudex.Domain.Entities.Printer", "Printer") + .WithMany() + .HasForeignKey("PrinterId") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_usage_logs_printer"); + + b.HasOne("Extrudex.Domain.Entities.Spool", "Spool") + .WithMany() + .HasForeignKey("SpoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_usage_logs_spool"); + + b.Navigation("PrintJob"); + + b.Navigation("Printer"); + + b.Navigation("Spool"); + }); + modelBuilder.Entity("Extrudex.Domain.Entities.AmsUnit", b => { b.Navigation("Slots"); @@ -1037,17 +1039,10 @@ namespace Extrudex.Infrastructure.Data.Migrations b.Navigation("Spools"); }); - modelBuilder.Entity("Extrudex.Domain.Entities.PrintJob", b => - { - b.Navigation("FilamentUsages"); - }); - modelBuilder.Entity("Extrudex.Domain.Entities.Printer", b => { b.Navigation("AmsUnits"); - b.Navigation("FilamentUsages"); - b.Navigation("PrintJobs"); }); @@ -1055,8 +1050,6 @@ namespace Extrudex.Infrastructure.Data.Migrations { b.Navigation("AmsSlots"); - b.Navigation("FilamentUsages"); - b.Navigation("PrintJobs"); }); #pragma warning restore 612, 618 diff --git a/backend/Infrastructure/Services/UsageLogService.cs b/backend/Infrastructure/Services/UsageLogService.cs new file mode 100644 index 0000000..4629b65 --- /dev/null +++ b/backend/Infrastructure/Services/UsageLogService.cs @@ -0,0 +1,81 @@ +using Extrudex.Domain.Entities; +using Extrudex.Domain.Enums; +using Extrudex.Domain.Interfaces; +using Extrudex.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Extrudex.Infrastructure.Services; + +/// +/// Implementation of that persists usage entries +/// to the usage_logs table via EF Core. +/// +public class UsageLogService : IUsageLogService +{ + private readonly ExtrudexDbContext _dbContext; + + /// + /// Initializes a new instance of the class. + /// + /// The EF Core database context for data persistence. + public UsageLogService(ExtrudexDbContext dbContext) + { + _dbContext = dbContext; + } + + /// + public async Task RecordUsageAsync( + Guid spoolId, + decimal gramsUsed, + DataSource dataSource, + Guid? printerId = null, + Guid? printJobId = null, + decimal? mmExtruded = null, + DateTime? usageTimestamp = null, + string? notes = null) + { + var entry = new UsageLog + { + SpoolId = spoolId, + GramsUsed = gramsUsed, + DataSource = dataSource, + PrinterId = printerId, + PrintJobId = printJobId, + MmExtruded = mmExtruded, + UsageTimestamp = usageTimestamp ?? DateTime.UtcNow, + Notes = notes + }; + + _dbContext.UsageLogs.Add(entry); + await _dbContext.SaveChangesAsync(); + + return entry; + } + + /// + public async Task> GetBySpoolAsync(Guid spoolId, CancellationToken cancellationToken = default) + { + return await _dbContext.UsageLogs + .Where(u => u.SpoolId == spoolId) + .OrderByDescending(u => u.UsageTimestamp) + .ToListAsync(cancellationToken); + } + + /// + public async Task> GetByPrinterAsync(Guid printerId, CancellationToken cancellationToken = default) + { + return await _dbContext.UsageLogs + .Where(u => u.PrinterId == printerId) + .OrderByDescending(u => u.UsageTimestamp) + .ToListAsync(cancellationToken); + } + + /// + public async Task> GetByPrintJobAsync(Guid printJobId, CancellationToken cancellationToken = default) + { + return await _dbContext.UsageLogs + .Where(u => u.PrintJobId == printJobId) + .OrderByDescending(u => u.UsageTimestamp) + .ToListAsync(cancellationToken); + } +} \ No newline at end of file diff --git a/backend/Program.cs b/backend/Program.cs index ad24693..d664d0c 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -58,6 +58,9 @@ builder.Services.AddScoped(); // ── Low Stock Detection ──────────────────────────────────── builder.Services.AddSingleton(); +// ── Usage Logging ─────────────────────────────────────────── +builder.Services.AddScoped(); + // ── FluentValidation ────────────────────────────────────── // Registers all validators from the API assembly into DI. builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());