Initial commit
This commit is contained in:
@@ -0,0 +1,832 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using PowderCoating.Application.DTOs.PrepService;
|
||||
|
||||
namespace PowderCoating.Application.DTOs.Quote;
|
||||
|
||||
// ============================================================================
|
||||
// LIST DTO - For Index page listing
|
||||
// ============================================================================
|
||||
public class QuoteListDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string QuoteNumber { get; set; } = string.Empty;
|
||||
public string CustomerOrProspectName { get; set; } = string.Empty;
|
||||
|
||||
// Quote Status (from lookup table)
|
||||
public int QuoteStatusId { get; set; }
|
||||
public string StatusCode { get; set; } = string.Empty;
|
||||
public string StatusDisplayName { get; set; } = string.Empty;
|
||||
public string StatusColorClass { get; set; } = "secondary";
|
||||
public bool StatusIsDraft { get; set; }
|
||||
|
||||
public DateTime QuoteDate { get; set; }
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Tags { get; set; }
|
||||
public bool IsExpired => ExpirationDate.HasValue && ExpirationDate.Value < DateTime.UtcNow && StatusIsDraft;
|
||||
public bool IsProspect { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FULL QUOTE DTO - For Details page
|
||||
// ============================================================================
|
||||
public class QuoteDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string QuoteNumber { get; set; } = string.Empty;
|
||||
|
||||
// Customer or Prospect Info
|
||||
public int? CustomerId { get; set; }
|
||||
public string? CustomerName { get; set; }
|
||||
public string? CustomerCompanyName { get; set; }
|
||||
public string? CustomerContactFirstName { get; set; }
|
||||
public string? CustomerContactLastName { get; set; }
|
||||
public string? CustomerEmail { get; set; }
|
||||
public string? CustomerPhone { get; set; }
|
||||
|
||||
public string? ProspectCompanyName { get; set; }
|
||||
public string? ProspectContactFirstName { get; set; }
|
||||
public string? ProspectContactLastName { get; set; }
|
||||
public string? ProspectContactName { get; set; }
|
||||
public string? ProspectEmail { get; set; }
|
||||
public string? ProspectPhone { get; set; }
|
||||
public string? ProspectAddress { get; set; }
|
||||
public string? ProspectCity { get; set; }
|
||||
public string? ProspectState { get; set; }
|
||||
public string? ProspectZipCode { get; set; }
|
||||
|
||||
public string? PreparedById { get; set; }
|
||||
public string? PreparedByName { get; set; }
|
||||
public string? PreparedByEmail { get; set; }
|
||||
|
||||
// Quote Status (from lookup table)
|
||||
public int QuoteStatusId { get; set; }
|
||||
public string StatusCode { get; set; } = string.Empty;
|
||||
public string StatusDisplayName { get; set; } = string.Empty;
|
||||
public string StatusColorClass { get; set; } = "secondary";
|
||||
public string? StatusIconClass { get; set; }
|
||||
public bool StatusIsApproved { get; set; }
|
||||
public bool StatusIsConverted { get; set; }
|
||||
public bool StatusIsDraft { get; set; }
|
||||
|
||||
public bool IsCommercial { get; set; }
|
||||
|
||||
// Dates
|
||||
public DateTime QuoteDate { get; set; }
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public DateTime? SentDate { get; set; }
|
||||
public DateTime? ApprovedDate { get; set; }
|
||||
|
||||
// Pricing
|
||||
public decimal SubTotal { get; set; }
|
||||
|
||||
// Discount Information
|
||||
public string DiscountType { get; set; } = "None"; // None, Percentage, FixedAmount
|
||||
public decimal DiscountValue { get; set; }
|
||||
public decimal DiscountPercent { get; set; }
|
||||
public decimal DiscountAmount { get; set; }
|
||||
public string? DiscountReason { get; set; }
|
||||
public bool HideDiscountFromCustomer { get; set; }
|
||||
|
||||
public decimal TaxPercent { get; set; }
|
||||
public decimal TaxAmount { get; set; }
|
||||
public decimal RushFee { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
|
||||
public bool IsRushJob { get; set; }
|
||||
|
||||
// Additional Information
|
||||
public string? Description { get; set; }
|
||||
public string? Terms { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public string? CustomerPO { get; set; }
|
||||
public string? Tags { get; set; }
|
||||
|
||||
// Items
|
||||
public List<QuoteItemDto> QuoteItems { get; set; } = new();
|
||||
|
||||
// Prep Services
|
||||
public List<PrepServiceDto> PrepServices { get; set; } = new();
|
||||
public List<int> PrepServiceIds { get; set; } = new();
|
||||
|
||||
// Pricing Breakdown
|
||||
public QuotePricingBreakdownDto? PricingBreakdown { get; set; }
|
||||
|
||||
// Oven selection
|
||||
public int? OvenCostId { get; set; }
|
||||
public string? OvenLabel { get; set; }
|
||||
|
||||
// Oven batch pricing
|
||||
public int OvenBatches { get; set; } = 1;
|
||||
public int? OvenCycleMinutes { get; set; }
|
||||
|
||||
// Conversion Tracking
|
||||
public int? ConvertedToJobId { get; set; }
|
||||
|
||||
// Customer Approval Tracking
|
||||
public string? ApprovalToken { get; set; }
|
||||
public DateTime? ApprovalTokenExpiresAt { get; set; }
|
||||
public DateTime? ApprovalTokenUsedAt { get; set; }
|
||||
public string? DeclineReason { get; set; }
|
||||
|
||||
public bool IsProspect => !CustomerId.HasValue;
|
||||
public bool IsExpired => ExpirationDate.HasValue && ExpirationDate.Value < DateTime.UtcNow && StatusIsDraft;
|
||||
public string CustomerOrProspectName => CustomerName ?? ProspectCompanyName ?? ProspectContactName ?? "Unknown";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CREATE QUOTE DTO - For creating new quotes
|
||||
// ============================================================================
|
||||
public class CreateQuoteDto
|
||||
{
|
||||
// Customer or Prospect Selection
|
||||
public bool IsForProspect { get; set; }
|
||||
|
||||
[Display(Name = "Customer")]
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
// Prospect Contact Information
|
||||
[Display(Name = "Company Name")]
|
||||
[StringLength(100)]
|
||||
public string? ProspectCompanyName { get; set; }
|
||||
|
||||
[Display(Name = "Contact Name")]
|
||||
[StringLength(100)]
|
||||
public string? ProspectContactName { get; set; }
|
||||
|
||||
[Display(Name = "Email")]
|
||||
[EmailAddress]
|
||||
[StringLength(100)]
|
||||
public string? ProspectEmail { get; set; }
|
||||
|
||||
[Display(Name = "Phone")]
|
||||
[Phone]
|
||||
[StringLength(20)]
|
||||
public string? ProspectPhone { get; set; }
|
||||
|
||||
[Display(Name = "Address")]
|
||||
[StringLength(200)]
|
||||
public string? ProspectAddress { get; set; }
|
||||
|
||||
[Display(Name = "City")]
|
||||
[StringLength(100)]
|
||||
public string? ProspectCity { get; set; }
|
||||
|
||||
[Display(Name = "State")]
|
||||
[StringLength(2)]
|
||||
public string? ProspectState { get; set; }
|
||||
|
||||
[Display(Name = "Zip Code")]
|
||||
[StringLength(10)]
|
||||
public string? ProspectZipCode { get; set; }
|
||||
|
||||
// Oven Selection
|
||||
[Display(Name = "Oven")]
|
||||
public int? OvenCostId { get; set; }
|
||||
|
||||
// Oven Batch Pricing
|
||||
[Display(Name = "Oven Batches")]
|
||||
[Range(1, 9999)]
|
||||
public int OvenBatches { get; set; } = 1;
|
||||
|
||||
[Display(Name = "Oven Cycle Time (min)")]
|
||||
[Range(1, 1440)]
|
||||
public int? OvenCycleMinutes { get; set; }
|
||||
|
||||
// Quote Information
|
||||
[Display(Name = "Commercial Quote")]
|
||||
public bool IsCommercial { get; set; }
|
||||
|
||||
[Display(Name = "Rush Job")]
|
||||
public bool IsRushJob { get; set; }
|
||||
|
||||
[Display(Name = "Quote Date")]
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime QuoteDate { get; set; } = DateTime.Today;
|
||||
|
||||
[Display(Name = "Expiration Date")]
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
|
||||
[Display(Name = "Description")]
|
||||
[StringLength(500)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Display(Name = "Terms & Conditions")]
|
||||
[DataType(DataType.MultilineText)]
|
||||
public string? Terms { get; set; }
|
||||
|
||||
[Display(Name = "Internal Notes")]
|
||||
[DataType(DataType.MultilineText)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
[Display(Name = "Customer PO Number")]
|
||||
[StringLength(50)]
|
||||
public string? CustomerPO { get; set; }
|
||||
|
||||
[Display(Name = "Tags")]
|
||||
[StringLength(500)]
|
||||
public string? Tags { get; set; }
|
||||
|
||||
// Pricing
|
||||
[Display(Name = "Tax Percent (%)")]
|
||||
[Range(0, 100)]
|
||||
public decimal TaxPercent { get; set; }
|
||||
|
||||
// Discount
|
||||
[Display(Name = "Discount Type")]
|
||||
public string DiscountType { get; set; } = "None"; // None, Percentage, FixedAmount
|
||||
|
||||
[Display(Name = "Discount Value")]
|
||||
[Range(0, 999999)]
|
||||
public decimal DiscountValue { get; set; }
|
||||
|
||||
[Display(Name = "Discount Reason")]
|
||||
[StringLength(200)]
|
||||
public string? DiscountReason { get; set; }
|
||||
|
||||
[Display(Name = "Hide discount from customer")]
|
||||
public bool HideDiscountFromCustomer { get; set; } = false;
|
||||
|
||||
// Items
|
||||
[Required]
|
||||
// Note: MinLength validation removed to prevent false positives on initial page load
|
||||
// JavaScript validation handles this check on form submission
|
||||
public List<CreateQuoteItemDto> QuoteItems { get; set; } = new();
|
||||
|
||||
// Prep Services
|
||||
[Display(Name = "Preparation Services")]
|
||||
public List<int> PrepServiceIds { get; set; } = new();
|
||||
|
||||
// Email Options
|
||||
[Display(Name = "Send quote via email")]
|
||||
public bool SendEmailToCustomer { get; set; } = false;
|
||||
|
||||
// AI Photo TempIds — list of uploaded temp photo IDs to promote on save
|
||||
public List<string> AiPhotoTempIds { get; set; } = new();
|
||||
|
||||
// Quote Photo TempIds — general (non-AI) photos staged on the Create page
|
||||
public List<string> QuotePhotoTempIds { get; set; } = new();
|
||||
public List<string> QuotePhotoFileNames { get; set; } = new();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UPDATE QUOTE DTO - For editing existing quotes
|
||||
// ============================================================================
|
||||
public class UpdateQuoteDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Customer or Prospect Info (read-only after creation, but included for completeness)
|
||||
public int? CustomerId { get; set; }
|
||||
public bool IsForProspect { get; set; }
|
||||
|
||||
// Prospect Contact Information
|
||||
[Display(Name = "Company Name")]
|
||||
[StringLength(100)]
|
||||
public string? ProspectCompanyName { get; set; }
|
||||
|
||||
[Display(Name = "Contact Name")]
|
||||
[StringLength(100)]
|
||||
public string? ProspectContactName { get; set; }
|
||||
|
||||
[Display(Name = "Email")]
|
||||
[EmailAddress]
|
||||
[StringLength(100)]
|
||||
public string? ProspectEmail { get; set; }
|
||||
|
||||
[Display(Name = "Phone")]
|
||||
[Phone]
|
||||
[StringLength(20)]
|
||||
public string? ProspectPhone { get; set; }
|
||||
|
||||
[Display(Name = "Address")]
|
||||
[StringLength(200)]
|
||||
public string? ProspectAddress { get; set; }
|
||||
|
||||
[Display(Name = "City")]
|
||||
[StringLength(100)]
|
||||
public string? ProspectCity { get; set; }
|
||||
|
||||
[Display(Name = "State")]
|
||||
[StringLength(2)]
|
||||
public string? ProspectState { get; set; }
|
||||
|
||||
[Display(Name = "Zip Code")]
|
||||
[StringLength(10)]
|
||||
public string? ProspectZipCode { get; set; }
|
||||
|
||||
// Oven Selection
|
||||
[Display(Name = "Oven")]
|
||||
public int? OvenCostId { get; set; }
|
||||
|
||||
// Oven Batch Pricing
|
||||
[Display(Name = "Oven Batches")]
|
||||
[Range(1, 9999)]
|
||||
public int OvenBatches { get; set; } = 1;
|
||||
|
||||
[Display(Name = "Oven Cycle Time (min)")]
|
||||
[Range(1, 1440)]
|
||||
public int? OvenCycleMinutes { get; set; }
|
||||
|
||||
// Quote Information
|
||||
[Display(Name = "Status")]
|
||||
public int QuoteStatusId { get; set; } // FK to lookup table
|
||||
|
||||
[Display(Name = "Commercial Quote")]
|
||||
public bool IsCommercial { get; set; }
|
||||
|
||||
[Display(Name = "Rush Job")]
|
||||
public bool IsRushJob { get; set; }
|
||||
|
||||
[Display(Name = "Quote Date")]
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime QuoteDate { get; set; }
|
||||
|
||||
[Display(Name = "Expiration Date")]
|
||||
[DataType(DataType.Date)]
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
|
||||
[Display(Name = "Description")]
|
||||
[StringLength(500)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[Display(Name = "Terms & Conditions")]
|
||||
[DataType(DataType.MultilineText)]
|
||||
public string? Terms { get; set; }
|
||||
|
||||
[Display(Name = "Internal Notes")]
|
||||
[DataType(DataType.MultilineText)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
[Display(Name = "Customer PO Number")]
|
||||
[StringLength(50)]
|
||||
public string? CustomerPO { get; set; }
|
||||
|
||||
[Display(Name = "Tags")]
|
||||
[StringLength(500)]
|
||||
public string? Tags { get; set; }
|
||||
|
||||
// Pricing
|
||||
[Display(Name = "Tax Percent (%)")]
|
||||
[Range(0, 100)]
|
||||
public decimal TaxPercent { get; set; }
|
||||
|
||||
// Discount
|
||||
[Display(Name = "Discount Type")]
|
||||
public string DiscountType { get; set; } = "None"; // None, Percentage, FixedAmount
|
||||
|
||||
[Display(Name = "Discount Value")]
|
||||
[Range(0, 999999)]
|
||||
public decimal DiscountValue { get; set; }
|
||||
|
||||
[Display(Name = "Discount Reason")]
|
||||
[StringLength(200)]
|
||||
public string? DiscountReason { get; set; }
|
||||
|
||||
[Display(Name = "Hide discount from customer")]
|
||||
public bool HideDiscountFromCustomer { get; set; } = false;
|
||||
|
||||
// Items
|
||||
[Required]
|
||||
// Note: MinLength validation removed to prevent false positives on initial page load
|
||||
// JavaScript validation handles this check on form submission
|
||||
public List<CreateQuoteItemDto> QuoteItems { get; set; } = new();
|
||||
|
||||
// Prep Services
|
||||
[Display(Name = "Preparation Services")]
|
||||
public List<int> PrepServiceIds { get; set; } = new();
|
||||
|
||||
// AI Photo TempIds — list of uploaded temp photo IDs to promote on save
|
||||
public List<string> AiPhotoTempIds { get; set; } = new();
|
||||
|
||||
// Quote Photo TempIds — general (non-AI) photos staged on the Edit page (not used by direct-upload Edit path,
|
||||
// kept for symmetry / future use)
|
||||
public List<string> QuotePhotoTempIds { get; set; } = new();
|
||||
public List<string> QuotePhotoFileNames { get; set; } = new();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// QUOTE ITEM DTO - For displaying quote items
|
||||
// ============================================================================
|
||||
public class QuoteItemDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int QuoteId { get; set; }
|
||||
|
||||
[Display(Name = "Description")]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Display(Name = "Quantity")]
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
[Display(Name = "Surface Area (sq ft)")]
|
||||
public decimal SurfaceAreaSqFt { get; set; }
|
||||
|
||||
[Display(Name = "Estimated Minutes")]
|
||||
public int EstimatedMinutes { get; set; }
|
||||
|
||||
[Display(Name = "Requires Sandblasting")]
|
||||
public bool RequiresSandblasting { get; set; }
|
||||
|
||||
[Display(Name = "Requires Masking")]
|
||||
public bool RequiresMasking { get; set; }
|
||||
|
||||
[Display(Name = "Catalog Item")]
|
||||
public int? CatalogItemId { get; set; }
|
||||
public string? CatalogItemName { get; set; }
|
||||
|
||||
[Display(Name = "Unit Price")]
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
[Display(Name = "Total Price")]
|
||||
public decimal TotalPrice { get; set; }
|
||||
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public bool IsGenericItem { get; set; }
|
||||
public bool IsLaborItem { get; set; }
|
||||
public bool IsSalesItem { get; set; }
|
||||
public string? Sku { get; set; }
|
||||
public decimal? ManualUnitPrice { get; set; }
|
||||
|
||||
// Coating layers
|
||||
public List<QuoteItemCoatDto> Coats { get; set; } = new();
|
||||
|
||||
// Preparation services
|
||||
public List<CreateQuoteItemPrepServiceDto> PrepServices { get; set; } = new();
|
||||
|
||||
// Part complexity level for calculated items
|
||||
public string? Complexity { get; set; }
|
||||
|
||||
public bool IsAiItem { get; set; }
|
||||
|
||||
// Cost breakdown snapshot
|
||||
public decimal ItemMaterialCost { get; set; }
|
||||
public decimal ItemLaborCost { get; set; }
|
||||
public decimal ItemEquipmentCost { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CREATE QUOTE ITEM DTO - For creating quote items
|
||||
// ============================================================================
|
||||
public class CreateQuoteItemDto
|
||||
{
|
||||
// Description is required for calculated items but auto-populated for catalog items
|
||||
[Display(Name = "Description")]
|
||||
[StringLength(200)]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Quantity")]
|
||||
[Range(0.01, 10000)]
|
||||
public decimal Quantity { get; set; } = 1;
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Surface Area (sq ft)")]
|
||||
[Range(0, 100000)] // 0 is valid for generic items
|
||||
public decimal SurfaceAreaSqFt { get; set; }
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Estimated Minutes")]
|
||||
[Range(0, 10000, ErrorMessage = "Estimated minutes must be between 0 and 10,000 (0 for catalog items)")]
|
||||
public int EstimatedMinutes { get; set; }
|
||||
|
||||
[Display(Name = "Requires Sandblasting")]
|
||||
public bool RequiresSandblasting { get; set; }
|
||||
|
||||
[Display(Name = "Requires Masking")]
|
||||
public bool RequiresMasking { get; set; }
|
||||
|
||||
[Display(Name = "Catalog Item")]
|
||||
public int? CatalogItemId { get; set; }
|
||||
|
||||
[Display(Name = "Notes")]
|
||||
[StringLength(500)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
// Generic item — price entered manually, bypasses calculation engine
|
||||
public bool IsGenericItem { get; set; }
|
||||
|
||||
[Range(0, 1000000)]
|
||||
public decimal? ManualUnitPrice { get; set; }
|
||||
|
||||
// Labor item — Quantity = hours; priced using StandardLaborRate × markup, no coats
|
||||
public bool IsLaborItem { get; set; }
|
||||
|
||||
// Sales item — off-the-shelf merchandise (T-shirts, tumblers, etc.)
|
||||
public bool IsSalesItem { get; set; }
|
||||
public string? Sku { get; set; }
|
||||
|
||||
// Coating layers
|
||||
public List<CreateQuoteItemCoatDto> Coats { get; set; } = new();
|
||||
|
||||
// Prep services (each has its own time estimate)
|
||||
// For catalog items, prep cost is only included when IncludePrepCost = true
|
||||
public List<CreateQuoteItemPrepServiceDto> PrepServices { get; set; } = new();
|
||||
|
||||
// Controls whether prep service labor cost is added to the item price.
|
||||
// Always true for calculated items; catalog items default to false (costs baked in)
|
||||
// but can be opted in via the wizard toggle.
|
||||
public bool IncludePrepCost { get; set; } = true;
|
||||
|
||||
// Part complexity — drives a price multiplier on calculated items only
|
||||
// Values: null | "Simple" | "Moderate" | "Complex" | "Extreme"
|
||||
public string? Complexity { get; set; }
|
||||
|
||||
// Optional unit price override for catalog items — overrides the catalog default price
|
||||
public decimal? PowderCostOverride { get; set; }
|
||||
|
||||
// True when this item was generated via AI photo analysis
|
||||
public bool IsAiItem { get; set; }
|
||||
|
||||
// AI-generated standardized tags (comma-separated, e.g. "automotive,tubular")
|
||||
public string? AiTags { get; set; }
|
||||
|
||||
// ID of the AiItemPrediction record captured at analysis time (null for non-AI items)
|
||||
public int? AiPredictionId { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// QUOTE ITEM PREP SERVICE DTO
|
||||
// ============================================================================
|
||||
public class CreateQuoteItemPrepServiceDto
|
||||
{
|
||||
[Required]
|
||||
public int PrepServiceId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Range(0, 10000)]
|
||||
public int EstimatedMinutes { get; set; }
|
||||
|
||||
/// <summary>Blast setup selected in wizard for this sandblasting prep service.</summary>
|
||||
public int? BlastSetupId { get; set; }
|
||||
|
||||
// Populated when mapping from entity (for display purposes)
|
||||
public string? PrepServiceName { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRICING BREAKDOWN DTO - For transparent cost breakdown
|
||||
// ============================================================================
|
||||
public class QuotePricingBreakdownDto
|
||||
{
|
||||
// Per-Item Costs
|
||||
public decimal MaterialCosts { get; set; }
|
||||
public decimal LaborCosts { get; set; }
|
||||
public decimal EquipmentCosts { get; set; }
|
||||
public decimal ItemSubtotal { get; set; }
|
||||
|
||||
// Quote-Level Costs
|
||||
public decimal ItemsSubtotal { get; set; }
|
||||
public decimal ShopSuppliesAmount { get; set; }
|
||||
public decimal ShopSuppliesPercent { get; set; }
|
||||
|
||||
public decimal OverheadCosts { get; set; }
|
||||
public decimal OverheadPercent { get; set; }
|
||||
|
||||
public decimal ProfitMargin { get; set; }
|
||||
public decimal ProfitPercent { get; set; }
|
||||
|
||||
public decimal SubtotalBeforeDiscount { get; set; }
|
||||
|
||||
public decimal DiscountAmount { get; set; }
|
||||
public decimal DiscountPercent { get; set; }
|
||||
|
||||
public decimal RushFee { get; set; }
|
||||
|
||||
public decimal SubtotalAfterDiscount { get; set; }
|
||||
|
||||
public decimal TaxAmount { get; set; }
|
||||
public decimal TaxPercent { get; set; }
|
||||
|
||||
public decimal OvenBatchCost { get; set; }
|
||||
public int OvenBatches { get; set; }
|
||||
public int OvenCycleMinutes { get; set; }
|
||||
|
||||
public decimal Total { get; set; }
|
||||
|
||||
// Cost Breakdown Details
|
||||
public string CostBreakdownDetails { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONVERT TO CUSTOMER DTO - For prospect-to-customer conversion
|
||||
// ============================================================================
|
||||
public class ConvertQuoteToCustomerDto
|
||||
{
|
||||
public int QuoteId { get; set; }
|
||||
public string QuoteNumber { get; set; } = string.Empty;
|
||||
|
||||
// Pre-filled from prospect data
|
||||
[Display(Name = "Company Name")]
|
||||
[StringLength(100)]
|
||||
public string? CompanyName { get; set; }
|
||||
|
||||
[Required]
|
||||
[Display(Name = "Contact Name")]
|
||||
[StringLength(100)]
|
||||
public string ContactName { get; set; } = string.Empty;
|
||||
|
||||
[Display(Name = "Email")]
|
||||
[EmailAddress]
|
||||
[StringLength(100)]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[Display(Name = "Phone")]
|
||||
[Phone]
|
||||
[StringLength(20)]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[Display(Name = "Address")]
|
||||
[StringLength(200)]
|
||||
public string? Address { get; set; }
|
||||
|
||||
[Display(Name = "City")]
|
||||
[StringLength(100)]
|
||||
public string? City { get; set; }
|
||||
|
||||
[Display(Name = "State")]
|
||||
[StringLength(2)]
|
||||
public string? State { get; set; }
|
||||
|
||||
[Display(Name = "Zip Code")]
|
||||
[StringLength(10)]
|
||||
public string? ZipCode { get; set; }
|
||||
|
||||
// Additional customer fields
|
||||
[Display(Name = "Tax ID / EIN")]
|
||||
[StringLength(50)]
|
||||
public string? TaxId { get; set; }
|
||||
|
||||
[Display(Name = "Credit Limit")]
|
||||
[Range(0, 1000000)]
|
||||
public decimal CreditLimit { get; set; }
|
||||
|
||||
[Display(Name = "Pricing Tier")]
|
||||
public int? PricingTierId { get; set; }
|
||||
|
||||
[Display(Name = "Customer Type")]
|
||||
public bool IsCommercial { get; set; } = true;
|
||||
|
||||
[Display(Name = "Payment Terms")]
|
||||
[StringLength(100)]
|
||||
public string? PaymentTerms { get; set; }
|
||||
|
||||
[Display(Name = "Notes")]
|
||||
[DataType(DataType.MultilineText)]
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// QUOTE ITEM COAT DTOs - For multi-coat support
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>
|
||||
/// DTO for creating/editing a coating layer on a quote item
|
||||
/// </summary>
|
||||
public class CreateQuoteItemCoatDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
[Display(Name = "Coat Name")]
|
||||
public string CoatName { get; set; } = "Base Coat";
|
||||
|
||||
public int Sequence { get; set; } = 1;
|
||||
|
||||
[Display(Name = "Inventory Powder")]
|
||||
public int? InventoryItemId { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
[Display(Name = "Color Name")]
|
||||
public string? ColorName { get; set; }
|
||||
|
||||
[Display(Name = "Vendor")]
|
||||
public int? VendorId { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
[Display(Name = "Color Code")]
|
||||
public string? ColorCode { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
[Display(Name = "Finish")]
|
||||
public string? Finish { get; set; }
|
||||
|
||||
[Range(1, 500)]
|
||||
[Display(Name = "Coverage (sq ft/lb)")]
|
||||
public decimal CoverageSqFtPerLb { get; set; } = 30m;
|
||||
|
||||
[Range(1, 100)]
|
||||
[Display(Name = "Transfer Efficiency (%)")]
|
||||
public decimal TransferEfficiency { get; set; } = 65m;
|
||||
|
||||
[Range(0, 1000)]
|
||||
[Display(Name = "Powder Cost ($/lb)")]
|
||||
public decimal? PowderCostPerLb { get; set; }
|
||||
|
||||
[Range(0, 10000)]
|
||||
[Display(Name = "Powder to Order (lbs)")]
|
||||
public decimal? PowderToOrder { get; set; }
|
||||
|
||||
[StringLength(500)]
|
||||
[Display(Name = "Notes")]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, the additional layer labor charge is not applied even if this is not the first coat.
|
||||
/// </summary>
|
||||
public bool NoExtraLayerCharge { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO for displaying a coating layer on a quote item
|
||||
/// </summary>
|
||||
public class QuoteItemCoatDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string CoatName { get; set; } = string.Empty;
|
||||
public int Sequence { get; set; }
|
||||
public int? InventoryItemId { get; set; }
|
||||
public string? InventoryItemName { get; set; }
|
||||
public int? VendorId { get; set; }
|
||||
public string? VendorName { get; set; }
|
||||
public string? ColorName { get; set; }
|
||||
public string? ColorCode { get; set; }
|
||||
public string? Finish { get; set; }
|
||||
public decimal CoverageSqFtPerLb { get; set; }
|
||||
public decimal TransferEfficiency { get; set; }
|
||||
public decimal? PowderToOrder { get; set; }
|
||||
public decimal CoatMaterialCost { get; set; }
|
||||
public decimal CoatLaborCost { get; set; }
|
||||
public decimal CoatTotalCost { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PRICING CALCULATION RESULT - Internal use for pricing service
|
||||
// ============================================================================
|
||||
|
||||
/// <summary>
|
||||
/// Result of calculating costs for a single coating layer
|
||||
/// </summary>
|
||||
public class QuoteItemCoatPricingResult
|
||||
{
|
||||
public decimal CoatMaterialCost { get; set; }
|
||||
public decimal CoatLaborCost { get; set; }
|
||||
public decimal CoatTotalCost { get; set; }
|
||||
}
|
||||
|
||||
public class QuoteItemPricingResult
|
||||
{
|
||||
public decimal MaterialCost { get; set; }
|
||||
public decimal LaborCost { get; set; }
|
||||
public decimal EquipmentCost { get; set; }
|
||||
public decimal ItemSubtotal { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public decimal TotalPrice { get; set; }
|
||||
}
|
||||
|
||||
public class QuotePricingResult
|
||||
{
|
||||
public decimal ItemsSubtotal { get; set; }
|
||||
public decimal ShopSuppliesAmount { get; set; }
|
||||
public decimal ShopSuppliesPercent { get; set; }
|
||||
public decimal OverheadCosts { get; set; }
|
||||
public decimal OverheadPercent { get; set; }
|
||||
public decimal ProfitMargin { get; set; }
|
||||
public decimal ProfitPercent { get; set; }
|
||||
public decimal SubtotalBeforeDiscount { get; set; }
|
||||
|
||||
// Pricing Tier Discount (from customer's pricing tier)
|
||||
public decimal PricingTierDiscountAmount { get; set; }
|
||||
public decimal PricingTierDiscountPercent { get; set; }
|
||||
|
||||
// Quote-Level Discount (applied on the quote)
|
||||
public decimal QuoteDiscountAmount { get; set; }
|
||||
public decimal QuoteDiscountPercent { get; set; }
|
||||
|
||||
// Combined Discount (for backward compatibility)
|
||||
public decimal DiscountAmount { get; set; }
|
||||
public decimal DiscountPercent { get; set; }
|
||||
|
||||
public decimal SubtotalAfterDiscount { get; set; }
|
||||
public decimal RushFee { get; set; }
|
||||
public decimal TaxAmount { get; set; }
|
||||
public decimal TaxPercent { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
|
||||
// Oven batch cost (quote-level, separate from per-item calculations)
|
||||
public decimal OvenBatchCost { get; set; }
|
||||
public int OvenBatches { get; set; }
|
||||
public int OvenCycleMinutes { get; set; }
|
||||
|
||||
// Detailed breakdown for transparency
|
||||
public decimal MaterialCosts { get; set; }
|
||||
public decimal LaborCosts { get; set; }
|
||||
public decimal EquipmentCosts { get; set; }
|
||||
|
||||
// Per-item results (same order as input items)
|
||||
public List<QuoteItemPricingResult> ItemResults { get; set; } = new();
|
||||
}
|
||||
Reference in New Issue
Block a user