using System.ComponentModel.DataAnnotations; using PowderCoating.Application.DTOs.PrepService; using PowderCoating.Application.DTOs.Quote; namespace PowderCoating.Application.DTOs.Job; public class JobDto { public int Id { get; set; } public string JobNumber { get; set; } = string.Empty; public int CustomerId { get; set; } public string CustomerName { get; set; } = string.Empty; public string? CustomerCompanyName { get; set; } public string? CustomerContactName { get; set; } public int? QuoteId { get; set; } public string? QuoteNumber { get; set; } public string? AssignedUserId { get; set; } public string? AssignedWorkerName { get; set; } public string Description { get; set; } = string.Empty; // Job Status (from lookup table) public int JobStatusId { get; set; } public string StatusCode { get; set; } = string.Empty; // For code logic public string StatusDisplayName { get; set; } = string.Empty; // For UI public string StatusColorClass { get; set; } = "secondary"; // For badges public string? StatusIconClass { get; set; } // For icons public int StatusDisplayOrder { get; set; } // For comparisons public bool StatusIsTerminal { get; set; } // For filtering public bool StatusIsWIP { get; set; } // For stats // Job Priority (from lookup table) public int JobPriorityId { get; set; } public string PriorityCode { get; set; } = string.Empty; public string PriorityDisplayName { get; set; } = string.Empty; public string PriorityColorClass { get; set; } = "secondary"; public string? PriorityIconClass { get; set; } public int PriorityDisplayOrder { get; set; } public DateTime? ScheduledDate { get; set; } public DateTime? DueDate { get; set; } public DateTime? CompletedDate { get; set; } // Oven selection (carried over from quote) public int? OvenCostId { get; set; } public string? OvenLabel { get; set; } public decimal QuotedPrice { get; set; } public decimal FinalPrice { get; set; } public decimal ShopSuppliesAmount { get; set; } public decimal ShopSuppliesPercent { get; set; } public bool IsRushJob { get; set; } public string DiscountType { get; set; } = "None"; public decimal DiscountValue { get; set; } public string? DiscountReason { get; set; } public string? CustomerPO { get; set; } public string? SpecialInstructions { get; set; } public string? InternalNotes { get; set; } public string? Tags { get; set; } public bool RequiresCustomerApproval { get; set; } public bool IsCustomerApproved { get; set; } public string? CustomerEmail { get; set; } public bool CustomerNotifyByEmail { get; set; } = true; // Customer SMS opt-in — used for SMS compose modal on job details public bool CustomerNotifyBySms { get; set; } public string? CustomerMobilePhone { get; set; } // Job Completion Details public decimal? ActualTimeSpentHours { get; set; } // Part intake / receiving public DateTime? IntakeDate { get; set; } public string? IntakeConditionNotes { get; set; } public int? IntakePartCount { get; set; } public string? IntakeCheckedByUserId { get; set; } public string? IntakeCheckedByName { get; set; } // Time tracking public List TimeEntries { get; set; } = new(); public decimal TotalLoggedHours => TimeEntries.Sum(t => t.HoursWorked); // Rework public bool IsReworkJob { get; set; } public int? OriginalJobId { get; set; } public string? OriginalJobNumber { get; set; } public List Items { get; set; } = new(); public List PrepServices { get; set; } = new(); public List PrepServiceIds { get; set; } = new(); public DateTime CreatedAt { get; set; } } public class JobListDto { public int Id { get; set; } public string JobNumber { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string CustomerName { get; set; } = string.Empty; public string? AssignedUserId { get; set; } public string? AssignedWorkerName { get; set; } // Job Status (from lookup table) public int JobStatusId { get; set; } public string StatusCode { get; set; } = string.Empty; public string StatusDisplayName { get; set; } = string.Empty; public string StatusColorClass { get; set; } = "secondary"; public bool StatusIsWIP { get; set; } // Job Priority (from lookup table) public int JobPriorityId { get; set; } public string PriorityCode { get; set; } = string.Empty; public string PriorityDisplayName { get; set; } = string.Empty; public string PriorityColorClass { get; set; } = "secondary"; public string? CustomerEmail { get; set; } public bool CustomerNotifyByEmail { get; set; } = true; public DateTime? ScheduledDate { get; set; } public DateTime? DueDate { get; set; } public decimal FinalPrice { get; set; } public DateTime CreatedAt { get; set; } public string? Tags { get; set; } public bool IsReworkJob { get; set; } public int? OriginalJobId { get; set; } } public class CreateJobDto { [Required(ErrorMessage = "Customer is required")] [Display(Name = "Customer")] public int CustomerId { get; set; } [Display(Name = "Quote")] public int? QuoteId { get; set; } [Display(Name = "Assigned Worker")] public string? AssignedUserId { get; set; } [Display(Name = "Oven")] public int? OvenCostId { get; set; } [Display(Name = "Batches")] [Range(1, 999)] public int OvenBatches { get; set; } = 1; [Display(Name = "Cycle Time (min)")] public int? OvenCycleMinutes { get; set; } [Required(ErrorMessage = "Description is required")] [StringLength(2000, ErrorMessage = "Description cannot exceed 2000 characters")] [Display(Name = "Description")] public string Description { get; set; } = string.Empty; [Required(ErrorMessage = "Priority is required")] [Display(Name = "Priority")] public int JobPriorityId { get; set; } // FK to lookup table [Display(Name = "Scheduled Date")] public DateTime? ScheduledDate { get; set; } [Display(Name = "Due Date")] public DateTime? DueDate { get; set; } [Range(0, 9999999.99, ErrorMessage = "Quoted price must be between 0 and 9,999,999.99")] [Display(Name = "Quoted Price")] public decimal QuotedPrice { get; set; } [StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")] [Display(Name = "Customer PO")] public string? CustomerPO { get; set; } [StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")] [Display(Name = "Special Instructions")] public string? SpecialInstructions { get; set; } [StringLength(2000, ErrorMessage = "Internal notes cannot exceed 2000 characters")] [Display(Name = "Internal Notes")] public string? InternalNotes { get; set; } [Display(Name = "Tags")] [StringLength(500)] public string? Tags { get; set; } [Display(Name = "Requires Customer Approval")] public bool RequiresCustomerApproval { get; set; } [Display(Name = "Rush Job")] public bool IsRushJob { get; set; } [Display(Name = "Discount Type")] public string DiscountType { get; set; } = "None"; [Range(0, double.MaxValue, ErrorMessage = "Discount value must be 0 or greater")] [Display(Name = "Discount Value")] public decimal DiscountValue { get; set; } [StringLength(500)] [Display(Name = "Discount Reason")] public string? DiscountReason { get; set; } public List JobItems { get; set; } = new(); public List PrepServiceIds { get; set; } = new(); } public class UpdateJobDto { [Required] public int Id { get; set; } [Required(ErrorMessage = "Customer is required")] [Display(Name = "Customer")] public int CustomerId { get; set; } [Display(Name = "Quote")] public int? QuoteId { get; set; } [Display(Name = "Assigned Worker")] public string? AssignedUserId { get; set; } [Display(Name = "Oven")] public int? OvenCostId { get; set; } [Display(Name = "Batches")] [Range(1, 999)] public int OvenBatches { get; set; } = 1; [Display(Name = "Cycle Time (min)")] public int? OvenCycleMinutes { get; set; } [Required(ErrorMessage = "Description is required")] [StringLength(2000, ErrorMessage = "Description cannot exceed 2000 characters")] [Display(Name = "Description")] public string Description { get; set; } = string.Empty; [Required(ErrorMessage = "Status is required")] [Display(Name = "Status")] public int JobStatusId { get; set; } // FK to lookup table [Required(ErrorMessage = "Priority is required")] [Display(Name = "Priority")] public int JobPriorityId { get; set; } // FK to lookup table [Display(Name = "Scheduled Date")] public DateTime? ScheduledDate { get; set; } [Display(Name = "Due Date")] public DateTime? DueDate { get; set; } [Range(0, 9999999.99, ErrorMessage = "Quoted price must be between 0 and 9,999,999.99")] [Display(Name = "Quoted Price")] public decimal QuotedPrice { get; set; } [StringLength(100, ErrorMessage = "Customer PO cannot exceed 100 characters")] [Display(Name = "Customer PO")] public string? CustomerPO { get; set; } [StringLength(2000, ErrorMessage = "Special instructions cannot exceed 2000 characters")] [Display(Name = "Special Instructions")] public string? SpecialInstructions { get; set; } [StringLength(2000, ErrorMessage = "Internal notes cannot exceed 2000 characters")] [Display(Name = "Internal Notes")] public string? InternalNotes { get; set; } [Display(Name = "Tags")] [StringLength(500)] public string? Tags { get; set; } [Display(Name = "Requires Customer Approval")] public bool RequiresCustomerApproval { get; set; } [Display(Name = "Rush Job")] public bool IsRushJob { get; set; } [Display(Name = "Discount Type")] public string DiscountType { get; set; } = "None"; [Range(0, double.MaxValue, ErrorMessage = "Discount value must be 0 or greater")] [Display(Name = "Discount Value")] public decimal DiscountValue { get; set; } [StringLength(500)] [Display(Name = "Discount Reason")] public string? DiscountReason { get; set; } public List JobItems { get; set; } = new(); public List PrepServiceIds { get; set; } = new(); [Display(Name = "Notify customer of status change via email")] public bool SendEmailOnStatusChange { get; set; } = false; } public class UpdateJobItemDto { public int Id { get; set; } public string Description { get; set; } = string.Empty; public decimal Quantity { get; set; } public string? ColorName { get; set; } public string? ColorCode { get; set; } public decimal? SurfaceArea { get; set; } public decimal UnitPrice { get; set; } public decimal TotalPrice { get; set; } public bool RequiresSandblasting { get; set; } public bool RequiresMasking { get; set; } public int EstimatedMinutes { get; set; } public string? Notes { get; set; } } public class JobItemDto { public int Id { get; set; } public string Description { get; set; } = string.Empty; public decimal Quantity { get; set; } public string? ColorName { get; set; } public string? ColorCode { get; set; } public string? Finish { get; set; } public decimal? SurfaceArea { get; set; } public decimal SurfaceAreaSqFt { get; set; } public int EstimatedMinutes { get; set; } public decimal UnitPrice { get; set; } public decimal TotalPrice { get; set; } public decimal LaborCost { get; set; } public bool RequiresSandblasting { get; set; } public bool RequiresMasking { get; set; } public string? Notes { get; set; } public int? CatalogItemId { get; set; } public bool IsGenericItem { get; set; } public bool IsLaborItem { get; set; } public bool IsSalesItem { get; set; } public string? Sku { get; set; } public List Coats { get; set; } = new(); public List PrepServices { get; set; } = new(); } public class JobItemPrepServiceDto { public int PrepServiceId { get; set; } public string? PrepServiceName { get; set; } public int EstimatedMinutes { get; set; } /// Blast setup selected in wizard for this sandblasting prep service. public int? BlastSetupId { get; set; } } public class CreateJobItemDto { public string Description { get; set; } = string.Empty; public decimal Quantity { get; set; } public string? ColorName { get; set; } public string? ColorCode { get; set; } public string? Finish { get; set; } public decimal UnitPrice { get; set; } public bool RequiresSandblasting { get; set; } public bool RequiresMasking { get; set; } public int EstimatedMinutes { get; set; } public string? Notes { get; set; } } // DTO for Shop Floor Display public class ShopFloorJobDto { public int Id { get; set; } public string JobNumber { get; set; } = string.Empty; public string CustomerName { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; // Job Status (from lookup table) public int JobStatusId { get; set; } public string StatusCode { get; set; } = string.Empty; public string StatusDisplayName { get; set; } = string.Empty; public string StatusColorClass { get; set; } = "secondary"; // Job Priority (from lookup table) public int JobPriorityId { get; set; } public string PriorityCode { get; set; } = string.Empty; public string PriorityDisplayName { get; set; } = string.Empty; public string PriorityColorClass { get; set; } = "secondary"; public string? AssignedWorkerName { get; set; } public DateTime? ScheduledDate { get; set; } public DateTime? DueDate { get; set; } public int ItemCount { get; set; } public List NextSteps { get; set; } = new(); } // DTO for Job Item Coat (multi-coat support) public class JobItemCoatDto { public int Id { get; set; } public int JobItemId { get; set; } public string CoatName { get; set; } = string.Empty; public int Sequence { get; set; } public int? InventoryItemId { get; set; } public string? ColorName { get; set; } public int? VendorId { get; set; } public string? VendorName { get; set; } public string? ColorCode { get; set; } public string? Finish { get; set; } public decimal CoverageSqFtPerLb { get; set; } public decimal TransferEfficiency { get; set; } public decimal? PowderCostPerLb { get; set; } public decimal? PowderToOrder { get; set; } public decimal? ActualPowderUsedLbs { get; set; } // Filled during job completion public bool NoExtraLayerCharge { get; set; } public string? Notes { get; set; } } // DTO for completing a job public class CompleteJobDto { public int JobId { get; set; } public decimal? ActualTimeSpentHours { get; set; } public List PowderUsages { get; set; } = new(); public bool SendEmailToCustomer { get; set; } = false; } // DTO for the Admin/Manager compose-before-send SMS endpoint public class SendJobSmsRequest { public int JobId { get; set; } public string Message { get; set; } = string.Empty; } // DTO for tracking actual powder usage per inventory item (color) for the whole job public class JobPowderUsageDto { public int InventoryItemId { get; set; } public decimal? ActualPowderUsedLbs { get; set; } } // ── Time Tracking DTOs ──────────────────────────────────────────────────────── public class JobTimeEntryDto { public int Id { get; set; } public int JobId { get; set; } public string? UserId { get; set; } public string WorkerName { get; set; } = string.Empty; public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } public string? Notes { get; set; } public DateTime CreatedAt { get; set; } } public class CreateJobTimeEntryDto { public int JobId { get; set; } public string UserId { get; set; } = string.Empty; public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } public string? Notes { get; set; } } public class UpdateJobTimeEntryDto { public int Id { get; set; } public string UserId { get; set; } = string.Empty; public DateTime WorkDate { get; set; } public decimal HoursWorked { get; set; } public string? Stage { get; set; } public string? Notes { get; set; } } // ── Rework / Warranty DTOs ──────────────────────────────────────────────────── public class ReworkRecordDto { public int Id { get; set; } public int JobId { get; set; } public int? JobItemId { get; set; } public string? JobItemDescription { get; set; } public int? ReworkJobId { get; set; } public string? ReworkJobNumber { get; set; } public PowderCoating.Core.Enums.ReworkType ReworkType { get; set; } public string ReworkTypeDisplay { get; set; } = string.Empty; public PowderCoating.Core.Enums.ReworkReason Reason { get; set; } public string ReasonDisplay { get; set; } = string.Empty; public string DefectDescription { get; set; } = string.Empty; public PowderCoating.Core.Enums.ReworkDiscoveredBy DiscoveredBy { get; set; } public string DiscoveredByDisplay { get; set; } = string.Empty; public DateTime DiscoveredDate { get; set; } public string? ReportedByName { get; set; } public decimal EstimatedReworkCost { get; set; } public decimal ActualReworkCost { get; set; } public bool IsBillableToCustomer { get; set; } public string? BillingNotes { get; set; } public PowderCoating.Core.Enums.ReworkStatus Status { get; set; } public string StatusDisplay { get; set; } = string.Empty; public string StatusColorClass { get; set; } = "secondary"; public PowderCoating.Core.Enums.ReworkResolution? Resolution { get; set; } public string? ResolutionDisplay { get; set; } public DateTime? ResolvedDate { get; set; } public string? ResolutionNotes { get; set; } public DateTime CreatedAt { get; set; } } public class CreateReworkRecordDto { public int JobId { get; set; } public int? JobItemId { get; set; } public PowderCoating.Core.Enums.ReworkType ReworkType { get; set; } public PowderCoating.Core.Enums.ReworkReason Reason { get; set; } public string DefectDescription { get; set; } = string.Empty; public PowderCoating.Core.Enums.ReworkDiscoveredBy DiscoveredBy { get; set; } public DateTime DiscoveredDate { get; set; } = DateTime.Today; public string? ReportedByName { get; set; } public decimal EstimatedReworkCost { get; set; } public bool IsBillableToCustomer { get; set; } public string? BillingNotes { get; set; } } public class UpdateReworkRecordDto { public int Id { get; set; } public PowderCoating.Core.Enums.ReworkStatus Status { get; set; } public PowderCoating.Core.Enums.ReworkResolution? Resolution { get; set; } public decimal ActualReworkCost { get; set; } public bool IsBillableToCustomer { get; set; } public string? BillingNotes { get; set; } public DateTime? ResolvedDate { get; set; } public string? ResolutionNotes { get; set; } public int? ReworkJobId { get; set; } } // ViewModel for the Edit Items wizard page public class JobEditItemsViewModel { public int JobId { get; set; } public string JobNumber { get; set; } = string.Empty; public int? CustomerId { get; set; } public decimal TaxPercent { get; set; } public int? OvenCostId { get; set; } public int OvenBatches { get; set; } = 1; public int? OvenCycleMinutes { get; set; } public List JobItems { get; set; } = new(); } // DTO for the part intake / receiving form public class IntakeJobDto { [Required] public int JobId { get; set; } [Display(Name = "Actual Part Count")] [Range(0, 10000, ErrorMessage = "Part count must be between 0 and 10,000")] public int? ActualPartCount { get; set; } [StringLength(2000, ErrorMessage = "Condition notes cannot exceed 2000 characters")] [Display(Name = "Condition Notes")] public string? ConditionNotes { get; set; } [Display(Name = "Advance status to In Preparation")] public bool AdvanceToInPreparation { get; set; } = true; }