using System.ComponentModel.DataAnnotations; using PowderCoating.Application.DTOs.Notification; using PowderCoating.Application.Services; using PowderCoating.Core.Enums; namespace PowderCoating.Application.DTOs.Company { /// /// Read model for company settings including operating costs /// public class CompanySettingsDto { public int Id { get; set; } public string CompanyName { get; set; } = string.Empty; public string? CompanyCode { get; set; } public string PrimaryContactName { get; set; } = string.Empty; public string PrimaryContactEmail { get; set; } = string.Empty; public string? Phone { get; set; } public string? Address { get; set; } public string? City { get; set; } public string? State { get; set; } public string? ZipCode { get; set; } public string? TimeZone { get; set; } public bool HasLogo { get; set; } public CompanyOperatingCostsDto? OperatingCosts { get; set; } public CompanyPreferencesDto? Preferences { get; set; } public bool AiPhotoQuotesEnabled { get; set; } public List NotificationTemplates { get; set; } = new(); // Stripe Connect / online payments public StripeConnectStatus StripeConnectStatus { get; set; } public string? StripeAccountId { get; set; } public OnlinePaymentSurchargeType OnlinePaymentSurchargeType { get; set; } public decimal OnlinePaymentSurchargeValue { get; set; } public bool OnlineSurchargeAcknowledged { get; set; } public bool AllowOnlinePayments { get; set; } } /// /// DTO for updating basic company information /// public class UpdateCompanySettingsDto { [Required(ErrorMessage = "Company name is required")] [StringLength(200, ErrorMessage = "Company name cannot exceed 200 characters")] public string CompanyName { get; set; } = string.Empty; [StringLength(10, ErrorMessage = "Company code cannot exceed 10 characters")] public string? CompanyCode { get; set; } [Required(ErrorMessage = "Primary contact name is required")] [StringLength(100, ErrorMessage = "Contact name cannot exceed 100 characters")] public string PrimaryContactName { get; set; } = string.Empty; [Required(ErrorMessage = "Primary contact email is required")] [EmailAddress(ErrorMessage = "Invalid email format")] [StringLength(100, ErrorMessage = "Email cannot exceed 100 characters")] public string PrimaryContactEmail { get; set; } = string.Empty; [Phone(ErrorMessage = "Invalid phone number format")] [StringLength(20, ErrorMessage = "Phone number cannot exceed 20 characters")] public string? Phone { get; set; } [StringLength(200, ErrorMessage = "Address cannot exceed 200 characters")] public string? Address { get; set; } [StringLength(100, ErrorMessage = "City cannot exceed 100 characters")] public string? City { get; set; } [StringLength(2, ErrorMessage = "State must be 2 characters")] public string? State { get; set; } [StringLength(10, ErrorMessage = "Zip code cannot exceed 10 characters")] public string? ZipCode { get; set; } [StringLength(50, ErrorMessage = "Time zone cannot exceed 50 characters")] public string? TimeZone { get; set; } } /// /// Read model for company operating costs /// public class CompanyOperatingCostsDto { public int Id { get; set; } public int CompanyId { get; set; } // Labor Rates public decimal StandardLaborRate { get; set; } public decimal AdditionalCoatLaborPercent { get; set; } // Equipment Operating Costs public decimal OvenOperatingCostPerHour { get; set; } public decimal SandblasterCostPerHour { get; set; } public decimal CoatingBoothCostPerHour { get; set; } // Material Costs public decimal PowderCoatingCostPerSqFt { get; set; } // Tax public decimal TaxPercent { get; set; } // Shop Supplies Rate public decimal ShopSuppliesRate { get; set; } // Markup / Margin public PowderCoating.Core.Enums.PricingMode PricingMode { get; set; } = PowderCoating.Core.Enums.PricingMode.MarkupOnMaterial; public decimal GeneralMarkupPercentage { get; set; } public decimal TargetMarginPercent { get; set; } // Rush Charge public string RushChargeType { get; set; } = "Percentage"; // "Percentage" or "FixedAmount" public decimal RushChargePercentage { get; set; } public decimal RushChargeFixedAmount { get; set; } // Shop Minimum public decimal ShopMinimumCharge { get; set; } // Part Complexity Multipliers (%) public decimal ComplexitySimplePercent { get; set; } public decimal ComplexityModeratePercent { get; set; } public decimal ComplexityComplexPercent { get; set; } public decimal ComplexityExtremePercent { get; set; } // AI Profile public string? AiContextProfile { get; set; } // Shop Capability public ShopCapabilityTier ShopCapabilityTier { get; set; } public BlastSetupType BlastSetupType { get; set; } public decimal CompressorCfm { get; set; } public int BlastNozzleSize { get; set; } public BlastSubstrateType PrimaryBlastSubstrate { get; set; } public decimal? BlastRateSqFtPerHourOverride { get; set; } public CoatingGunType CoatingGunType { get; set; } public decimal? CoatingRateSqFtPerHourOverride { get; set; } /// Derived blast rate — shown to the user as a sanity-check value. public decimal DerivedBlastRateSqFtPerHour { get; set; } /// Derived coating rate — shown to the user as a sanity-check value. public decimal DerivedCoatingRateSqFtPerHour { get; set; } // Facility Overhead public decimal MonthlyRent { get; set; } public decimal MonthlyUtilities { get; set; } public int MonthlyBillableHours { get; set; } = 160; /// Derived facility overhead rate = (MonthlyRent + MonthlyUtilities) / MonthlyBillableHours. public decimal FacilityOverheadRatePerHour { get; set; } } /// /// DTO for updating company operating costs /// public class UpdateOperatingCostsDto { // Labor Rates (per hour) [Required(ErrorMessage = "Standard labor rate is required")] [Range(0, 10000, ErrorMessage = "Standard labor rate must be between 0 and 10,000")] [Display(Name = "Standard Labor Rate ($/hr)")] public decimal StandardLaborRate { get; set; } [Range(0, 100, ErrorMessage = "Additional coat labor percent must be between 0 and 100")] [Display(Name = "Additional Coat Labor (%)")] public decimal AdditionalCoatLaborPercent { get; set; } = 30m; // Equipment Operating Costs (per hour) [Required(ErrorMessage = "Oven operating cost is required")] [Range(0, 10000, ErrorMessage = "Oven operating cost must be between 0 and 10,000")] [Display(Name = "Oven Operating Cost ($/hr)")] public decimal OvenOperatingCostPerHour { get; set; } [Range(0, 10000, ErrorMessage = "Sandblaster cost must be between 0 and 10,000")] [Display(Name = "Sandblaster Cost ($/hr)")] public decimal SandblasterCostPerHour { get; set; } [Range(0, 10000, ErrorMessage = "Coating booth cost must be between 0 and 10,000")] [Display(Name = "Coating Booth Cost ($/hr)")] public decimal CoatingBoothCostPerHour { get; set; } // Material Costs [Range(0, 1000, ErrorMessage = "Powder coating cost must be between 0 and 1,000")] [Display(Name = "Powder Coating Cost Per Sq Ft ($/sq ft)")] public decimal PowderCoatingCostPerSqFt { get; set; } // Tax [Range(0, 100, ErrorMessage = "Tax percent must be between 0 and 100")] [Display(Name = "Tax Percent (%)")] public decimal TaxPercent { get; set; } // Shop Supplies Rate [Range(0, 100, ErrorMessage = "Shop supplies rate must be between 0 and 100")] [Display(Name = "Shop Supplies Rate (%)")] public decimal ShopSuppliesRate { get; set; } // Markup / Margin Mode [Display(Name = "Pricing Mode")] public PowderCoating.Core.Enums.PricingMode PricingMode { get; set; } = PowderCoating.Core.Enums.PricingMode.MarkupOnMaterial; [Range(0, 100, ErrorMessage = "General markup percentage must be between 0 and 100")] [Display(Name = "General Markup (%)")] public decimal GeneralMarkupPercentage { get; set; } [Range(0, 99, ErrorMessage = "Target margin must be between 0 and 99")] [Display(Name = "Target Margin (%)")] public decimal TargetMarginPercent { get; set; } // Rush Charge [StringLength(20, ErrorMessage = "Rush charge type cannot exceed 20 characters")] [Display(Name = "Rush Charge Type")] public string RushChargeType { get; set; } = "Percentage"; // "Percentage" or "FixedAmount" [Range(0, 100, ErrorMessage = "Rush charge percentage must be between 0 and 100")] [Display(Name = "Rush Charge (%)")] public decimal RushChargePercentage { get; set; } [Range(0, 100000, ErrorMessage = "Rush charge fixed amount must be between 0 and 100,000")] [Display(Name = "Rush Charge Fixed Amount ($)")] public decimal RushChargeFixedAmount { get; set; } // Shop Minimum [Range(0, 100000, ErrorMessage = "Shop minimum charge must be between 0 and 100,000")] [Display(Name = "Shop Minimum Charge ($)")] public decimal ShopMinimumCharge { get; set; } // Part Complexity Multipliers [Range(0, 500)] public decimal ComplexitySimplePercent { get; set; } = 0m; [Range(0, 500)] public decimal ComplexityModeratePercent { get; set; } = 5m; [Range(0, 500)] public decimal ComplexityComplexPercent { get; set; } = 15m; [Range(0, 500)] public decimal ComplexityExtremePercent { get; set; } = 25m; // Facility Overhead [Range(0, 1000000, ErrorMessage = "Monthly rent must be between 0 and 1,000,000")] [Display(Name = "Monthly Rent ($)")] public decimal MonthlyRent { get; set; } = 0m; [Range(0, 1000000, ErrorMessage = "Monthly utilities must be between 0 and 1,000,000")] [Display(Name = "Monthly Utilities ($)")] public decimal MonthlyUtilities { get; set; } = 0m; [Range(1, 10000, ErrorMessage = "Billable hours must be between 1 and 10,000")] [Display(Name = "Billable Hours/Month")] public int MonthlyBillableHours { get; set; } = 160; } /// DTO for updating the company AI profile text used for AI Photo Quote calibration. public class UpdateAiProfileDto { [StringLength(2000, ErrorMessage = "AI profile cannot exceed 2000 characters")] public string? AiContextProfile { get; set; } } /// DTO for saving the Quoting Calibration / Shop Capability Profile tab. public class UpdateBlastProfileDto { public ShopCapabilityTier ShopCapabilityTier { get; set; } public BlastSetupType BlastSetupType { get; set; } [Range(0, 2000, ErrorMessage = "CFM must be between 0 and 2000")] public decimal CompressorCfm { get; set; } [Range(3, 8, ErrorMessage = "Nozzle size must be between #3 and #8")] public int BlastNozzleSize { get; set; } = 4; public BlastSubstrateType PrimaryBlastSubstrate { get; set; } [Range(0, 5000, ErrorMessage = "Blast rate override must be between 0 and 5000")] public decimal? BlastRateSqFtPerHourOverride { get; set; } public CoatingGunType CoatingGunType { get; set; } [Range(0, 5000, ErrorMessage = "Coating rate override must be between 0 and 5000")] public decimal? CoatingRateSqFtPerHourOverride { get; set; } } /// /// Response returned after saving or recalculating the blast profile, /// so the UI can display the freshly derived rates without a page reload. /// public class BlastProfileResultDto { public decimal DerivedBlastRate { get; set; } public decimal DerivedCoatingRate { get; set; } } // ── Named blast setups ────────────────────────────────────────────────────── public class BlastSetupDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; public BlastSetupType SetupType { get; set; } public decimal CompressorCfm { get; set; } public int BlastNozzleSize { get; set; } public BlastSubstrateType PrimarySubstrate { get; set; } public decimal? BlastRateSqFtPerHourOverride { get; set; } public bool IsDefault { get; set; } public bool IsActive { get; set; } public int DisplayOrder { get; set; } public decimal DerivedRate { get; set; } } public class SaveBlastSetupDto { public int? Id { get; set; } [Required] [StringLength(100)] public string Name { get; set; } = string.Empty; public BlastSetupType SetupType { get; set; } [Range(0, 9999)] public decimal CompressorCfm { get; set; } [Range(2, 8)] public int BlastNozzleSize { get; set; } = 5; public BlastSubstrateType PrimarySubstrate { get; set; } [Range(0, 99999)] public decimal? BlastRateSqFtPerHourOverride { get; set; } public bool IsDefault { get; set; } public bool IsActive { get; set; } = true; } /// Lightweight summary injected into wizard pages for the blast-setup dropdown. public class BlastSetupSummaryDto { public int Id { get; set; } public string Name { get; set; } = string.Empty; public decimal DerivedRate { get; set; } public bool IsDefault { get; set; } } }