Initial commit
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using PowderCoating.Application.DTOs.User;
|
||||
using PowderCoating.Core.Enums;
|
||||
|
||||
namespace PowderCoating.Application.DTOs.Company;
|
||||
|
||||
/// <summary>
|
||||
/// Full company details DTO
|
||||
/// </summary>
|
||||
public class CompanyDto
|
||||
{
|
||||
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 bool IsActive { get; set; }
|
||||
public DateTime SubscriptionStartDate { get; set; }
|
||||
public DateTime? SubscriptionEndDate { get; set; }
|
||||
public int SubscriptionPlan { get; set; }
|
||||
public SubscriptionStatus SubscriptionStatus { get; set; }
|
||||
public string? StripeCustomerId { get; set; }
|
||||
public string? StripeSubscriptionId { get; set; }
|
||||
|
||||
public string? TimeZone { get; set; }
|
||||
public string? LogoPath { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
// Statistics
|
||||
public int UserCount { get; set; }
|
||||
public int CustomerCount { get; set; }
|
||||
public int JobCount { get; set; }
|
||||
|
||||
// Users list for management
|
||||
public List<CompanyUserDto> Users { get; set; } = new List<CompanyUserDto>();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Company list item DTO for grid/list views
|
||||
/// </summary>
|
||||
public class CompanyListDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string CompanyName { get; set; } = string.Empty;
|
||||
public string? CompanyCode { get; set; }
|
||||
public string PrimaryContactEmail { get; set; } = string.Empty;
|
||||
public string? Phone { get; set; }
|
||||
public int SubscriptionPlan { get; set; }
|
||||
public SubscriptionStatus SubscriptionStatus { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime SubscriptionStartDate { get; set; }
|
||||
public DateTime? SubscriptionEndDate { get; set; }
|
||||
public int UserCount { get; set; }
|
||||
public int JobCount { get; set; }
|
||||
public int QuoteCount { get; set; }
|
||||
public int CustomerCount { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public bool WizardCompleted { get; set; }
|
||||
public DateTime? WizardCompletedAt { get; set; }
|
||||
public string? WizardCompletedByName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for creating a new company
|
||||
/// </summary>
|
||||
public class CreateCompanyDto
|
||||
{
|
||||
[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 address")]
|
||||
public string PrimaryContactEmail { get; set; } = string.Empty;
|
||||
|
||||
[Phone(ErrorMessage = "Invalid phone number")]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
public string? Address { get; set; }
|
||||
public string? City { get; set; }
|
||||
|
||||
[StringLength(2, ErrorMessage = "State code must be 2 characters")]
|
||||
public string? State { get; set; }
|
||||
|
||||
public string? ZipCode { get; set; }
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
[Required(ErrorMessage = "Subscription start date is required")]
|
||||
public DateTime SubscriptionStartDate { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? SubscriptionEndDate { get; set; }
|
||||
public int SubscriptionPlan { get; set; } = 0;
|
||||
|
||||
public string? TimeZone { get; set; } = "America/New_York";
|
||||
|
||||
// Initial admin user details
|
||||
[Required(ErrorMessage = "Admin first name is required")]
|
||||
public string AdminFirstName { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Admin last name is required")]
|
||||
public string AdminLastName { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Admin email is required")]
|
||||
[EmailAddress(ErrorMessage = "Invalid email address")]
|
||||
public string AdminEmail { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Admin password is required")]
|
||||
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters long")]
|
||||
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~])[A-Za-z\d@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~]{8,}$",
|
||||
ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character")]
|
||||
public string AdminPassword { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for updating an existing company
|
||||
/// </summary>
|
||||
public class UpdateCompanyDto
|
||||
{
|
||||
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 bool IsActive { get; set; }
|
||||
public DateTime SubscriptionStartDate { get; set; }
|
||||
public DateTime? SubscriptionEndDate { get; set; }
|
||||
public int SubscriptionPlan { get; set; }
|
||||
|
||||
// AI feature flags
|
||||
public bool AiPhotoQuotesEnabled { get; set; }
|
||||
public bool AiInventoryAssistEnabled { get; set; }
|
||||
public int? MaxAiPhotoQuotesPerMonthOverride { get; set; }
|
||||
|
||||
// Per-company feature overrides (null = use plan default)
|
||||
public bool? OnlinePaymentsOverride { get; set; }
|
||||
public bool? AccountingOverride { get; set; }
|
||||
|
||||
public string? TimeZone { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for creating a company admin user (SuperAdmin only)
|
||||
/// </summary>
|
||||
public class CreateCompanyAdminDto
|
||||
{
|
||||
public int CompanyId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string CompanyName { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "First name is required")]
|
||||
[StringLength(100, ErrorMessage = "First name cannot exceed 100 characters")]
|
||||
[Display(Name = "First Name")]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Last name is required")]
|
||||
[StringLength(100, ErrorMessage = "Last name cannot exceed 100 characters")]
|
||||
[Display(Name = "Last Name")]
|
||||
public string LastName { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Email is required")]
|
||||
[EmailAddress(ErrorMessage = "Invalid email address")]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Password is required")]
|
||||
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters long")]
|
||||
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~])[A-Za-z\d@$!%*?&#^()_+=\-\[\]{};':""\\|,.<>\/`~]{8,}$",
|
||||
ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter, one digit, and one special character")]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[Phone(ErrorMessage = "Invalid phone number")]
|
||||
[Display(Name = "Phone")]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[StringLength(100, ErrorMessage = "Department cannot exceed 100 characters")]
|
||||
[Display(Name = "Department")]
|
||||
public string? Department { get; set; }
|
||||
|
||||
[StringLength(100, ErrorMessage = "Position cannot exceed 100 characters")]
|
||||
[Display(Name = "Position")]
|
||||
public string? Position { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simplified company info DTO for PDF generation (avoids circular dependencies)
|
||||
/// </summary>
|
||||
public class CompanyInfoDto
|
||||
{
|
||||
public string CompanyName { 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? PrimaryContactEmail { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PowderCoating.Application.DTOs.Company;
|
||||
|
||||
public class CompanyPreferencesDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int CompanyId { get; set; }
|
||||
|
||||
// Application Defaults
|
||||
public string DefaultCurrency { get; set; } = "USD";
|
||||
public string DefaultDateFormat { get; set; } = "MM/dd/yyyy";
|
||||
public string DefaultTimeFormat { get; set; } = "12h";
|
||||
public string DefaultPaymentTerms { get; set; } = "Net 30";
|
||||
public int DefaultQuoteValidityDays { get; set; } = 30;
|
||||
public string QuoteNumberPrefix { get; set; } = "QT";
|
||||
public string JobNumberPrefix { get; set; } = "JOB";
|
||||
public bool UseMetricSystem { get; set; } = false;
|
||||
|
||||
// Job / Workflow
|
||||
public string DefaultJobPriority { get; set; } = "Normal";
|
||||
public bool RequireCustomerPO { get; set; }
|
||||
public bool AllowCustomerApproval { get; set; } = true;
|
||||
public int DefaultTurnaroundDays { get; set; } = 7;
|
||||
// Email Sender Identity
|
||||
public string? EmailFromAddress { get; set; }
|
||||
public string? EmailFromName { get; set; }
|
||||
|
||||
// Notifications
|
||||
public bool EmailNotificationsEnabled { get; set; } = true;
|
||||
public bool NotifyOnNewJob { get; set; } = true;
|
||||
public bool NotifyOnNewQuote { get; set; } = true;
|
||||
public bool NotifyOnJobStatusChange { get; set; } = true;
|
||||
public bool NotifyOnQuoteApproval { get; set; } = true;
|
||||
public bool NotifyOnPaymentReceived { get; set; } = true;
|
||||
public int QuoteExpiryWarningDays { get; set; } = 3;
|
||||
public int DueDateWarningDays { get; set; } = 2;
|
||||
public int MaintenanceAlertDays { get; set; } = 7;
|
||||
public bool PaymentRemindersEnabled { get; set; } = false;
|
||||
public string PaymentReminderDays { get; set; } = "7,14,30";
|
||||
|
||||
// Data Retention
|
||||
public int QuoteRetentionYears { get; set; } = 7;
|
||||
public int JobRetentionYears { get; set; } = 7;
|
||||
public int LogRetentionDays { get; set; } = 90;
|
||||
public int AutoArchiveJobsDays { get; set; } = 365;
|
||||
public int DeletedRecordRetentionDays { get; set; } = 30;
|
||||
|
||||
// Quote PDF Template
|
||||
public string QtAccentColor { get; set; } = "#374151";
|
||||
public string? QtDefaultTerms { get; set; }
|
||||
public string? QtFooterNote { get; set; }
|
||||
|
||||
// Invoice PDF Template
|
||||
public string InAccentColor { get; set; } = "#374151";
|
||||
public string? InDefaultTerms { get; set; }
|
||||
public string? InFooterNote { get; set; }
|
||||
|
||||
// Blank Work Order PDF Template
|
||||
public string WoAccentColor { get; set; } = "#374151";
|
||||
public string? WoTerms { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateAppDefaultsDto
|
||||
{
|
||||
[Required, StringLength(3)] public string DefaultCurrency { get; set; } = "USD";
|
||||
[Required, StringLength(20)] public string DefaultDateFormat { get; set; } = "MM/dd/yyyy";
|
||||
[Required] public string DefaultTimeFormat { get; set; } = "12h";
|
||||
[Required, StringLength(50)] public string DefaultPaymentTerms { get; set; } = "Net 30";
|
||||
[Range(1, 365)] public int DefaultQuoteValidityDays { get; set; } = 30;
|
||||
[Required, StringLength(10)] public string QuoteNumberPrefix { get; set; } = "QT";
|
||||
[Required, StringLength(10)] public string JobNumberPrefix { get; set; } = "JOB";
|
||||
public bool UseMetricSystem { get; set; } = false;
|
||||
}
|
||||
|
||||
public class UpdateJobDefaultsDto
|
||||
{
|
||||
[Required] public string DefaultJobPriority { get; set; } = "Normal";
|
||||
public bool RequireCustomerPO { get; set; }
|
||||
public bool AllowCustomerApproval { get; set; }
|
||||
[Range(1, 365)] public int DefaultTurnaroundDays { get; set; } = 7;
|
||||
}
|
||||
|
||||
public class UpdateNotificationsDto
|
||||
{
|
||||
[EmailAddress(ErrorMessage = "Invalid email address format.")]
|
||||
[StringLength(100)]
|
||||
[Display(Name = "From Email Address")]
|
||||
public string? EmailFromAddress { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
[Display(Name = "From Display Name")]
|
||||
public string? EmailFromName { get; set; }
|
||||
|
||||
public bool EmailNotificationsEnabled { get; set; }
|
||||
public bool NotifyOnNewJob { get; set; }
|
||||
public bool NotifyOnNewQuote { get; set; }
|
||||
public bool NotifyOnJobStatusChange { get; set; }
|
||||
public bool NotifyOnQuoteApproval { get; set; }
|
||||
public bool NotifyOnPaymentReceived { get; set; }
|
||||
[Range(0, 30)] public int QuoteExpiryWarningDays { get; set; } = 3;
|
||||
[Range(0, 30)] public int DueDateWarningDays { get; set; } = 2;
|
||||
[Range(0, 90)] public int MaintenanceAlertDays { get; set; } = 7;
|
||||
public bool PaymentRemindersEnabled { get; set; } = false;
|
||||
[StringLength(50)] public string PaymentReminderDays { get; set; } = "7,14,30";
|
||||
}
|
||||
|
||||
public class UpdateDataRetentionDto
|
||||
{
|
||||
[Range(1, 99)] public int QuoteRetentionYears { get; set; } = 7;
|
||||
[Range(1, 99)] public int JobRetentionYears { get; set; } = 7;
|
||||
[Range(30, 3650)] public int LogRetentionDays { get; set; } = 90;
|
||||
[Range(30, 3650)] public int AutoArchiveJobsDays { get; set; } = 365;
|
||||
[Range(7, 365)] public int DeletedRecordRetentionDays { get; set; } = 30;
|
||||
}
|
||||
|
||||
public class UpdateQuoteTemplateDto
|
||||
{
|
||||
[RegularExpression(@"^#[0-9A-Fa-f]{6}$", ErrorMessage = "Accent color must be a valid hex color (e.g. #374151)")]
|
||||
public string QtAccentColor { get; set; } = "#374151";
|
||||
[StringLength(3000)] public string? QtDefaultTerms { get; set; }
|
||||
[StringLength(200)] public string? QtFooterNote { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateInvoiceTemplateDto
|
||||
{
|
||||
[RegularExpression(@"^#[0-9A-Fa-f]{6}$", ErrorMessage = "Accent color must be a valid hex color (e.g. #374151)")]
|
||||
public string InAccentColor { get; set; } = "#374151";
|
||||
[StringLength(3000)] public string? InDefaultTerms { get; set; }
|
||||
[StringLength(200)] public string? InFooterNote { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateWorkOrderTemplateDto
|
||||
{
|
||||
[RegularExpression(@"^#[0-9A-Fa-f]{6}$", ErrorMessage = "Accent color must be a valid hex color (e.g. #374151)")]
|
||||
public string WoAccentColor { get; set; } = "#374151";
|
||||
[StringLength(2000)] public string? WoTerms { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using PowderCoating.Application.DTOs.Notification;
|
||||
using PowderCoating.Application.Services;
|
||||
using PowderCoating.Core.Enums;
|
||||
|
||||
namespace PowderCoating.Application.DTOs.Company
|
||||
{
|
||||
/// <summary>
|
||||
/// Read model for company settings including operating costs
|
||||
/// </summary>
|
||||
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<NotificationTemplateDto> 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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for updating basic company information
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read model for company operating costs
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
/// <summary>Derived blast rate — shown to the user as a sanity-check value.</summary>
|
||||
public decimal DerivedBlastRateSqFtPerHour { get; set; }
|
||||
/// <summary>Derived coating rate — shown to the user as a sanity-check value.</summary>
|
||||
public decimal DerivedCoatingRateSqFtPerHour { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for updating company operating costs
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>DTO for updating the company AI profile text used for AI Photo Quote calibration.</summary>
|
||||
public class UpdateAiProfileDto
|
||||
{
|
||||
[StringLength(2000, ErrorMessage = "AI profile cannot exceed 2000 characters")]
|
||||
public string? AiContextProfile { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>DTO for saving the Quoting Calibration / Shop Capability Profile tab.</summary>
|
||||
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; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response returned after saving or recalculating the blast profile,
|
||||
/// so the UI can display the freshly derived rates without a page reload.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Lightweight summary injected into wizard pages for the blast-setup dropdown.</summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PowderCoating.Application.DTOs.Company;
|
||||
|
||||
public class OvenCostDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Label { get; set; } = string.Empty;
|
||||
public decimal CostPerHour { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public int DisplayOrder { get; set; }
|
||||
public decimal? MaxLoadSqFt { get; set; }
|
||||
public int? DefaultCycleMinutes { get; set; }
|
||||
}
|
||||
|
||||
public class CreateOvenCostDto
|
||||
{
|
||||
[Required(ErrorMessage = "Label is required")]
|
||||
[StringLength(100, ErrorMessage = "Label cannot exceed 100 characters")]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Cost per hour is required")]
|
||||
[Range(0, 10000, ErrorMessage = "Cost per hour must be between 0 and 10,000")]
|
||||
public decimal CostPerHour { get; set; }
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public int DisplayOrder { get; set; } = 0;
|
||||
|
||||
[Range(0, 100000, ErrorMessage = "Capacity must be between 0 and 100,000 sq ft")]
|
||||
public decimal? MaxLoadSqFt { get; set; }
|
||||
|
||||
[Range(1, 1440, ErrorMessage = "Cycle time must be between 1 and 1440 minutes")]
|
||||
public int? DefaultCycleMinutes { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateOvenCostDto : CreateOvenCostDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace PowderCoating.Application.DTOs.Company;
|
||||
|
||||
/// <summary>
|
||||
/// Slim DTO passed into PdfService to control quote PDF appearance.
|
||||
/// </summary>
|
||||
public class QuoteTemplateSettingsDto
|
||||
{
|
||||
public string AccentColor { get; set; } = "#374151";
|
||||
public string? FooterNote { get; set; }
|
||||
public string? DefaultTerms { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user