Initial commit

This commit is contained in:
2026-04-23 21:38:24 -04:00
commit 63e12a9636
1762 changed files with 1672620 additions and 0 deletions
@@ -0,0 +1,190 @@
using System.ComponentModel.DataAnnotations;
namespace PowderCoating.Application.DTOs.Customer;
public class CustomerDto
{
public int Id { get; set; }
public string? CompanyName { get; set; }
public string? ContactFirstName { get; set; }
public string? ContactLastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string? MobilePhone { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? ZipCode { get; set; }
public string? Country { get; set; }
public bool IsCommercial { get; set; }
public string? TaxId { get; set; }
public decimal CreditLimit { get; set; }
public decimal CurrentBalance { get; set; }
public decimal CreditBalance { get; set; }
public string? PaymentTerms { get; set; }
public int? PricingTierId { get; set; }
public string? PricingTierName { get; set; }
public bool IsTaxExempt { get; set; }
public bool HasTaxExemptCertificate { get; set; }
public string? TaxExemptCertificateFileName { get; set; }
public string? GeneralNotes { get; set; }
public bool IsActive { get; set; }
public DateTime? LastContactDate { get; set; }
public DateTime CreatedAt { get; set; }
public bool NotifyByEmail { get; set; }
public bool NotifyBySms { get; set; }
public DateTime? SmsConsentedAt { get; set; }
public string? SmsConsentMethod { get; set; }
}
public class CreateCustomerDto : IValidatableObject
{
[Display(Name = "Company Name")]
[StringLength(200)]
public string? CompanyName { get; set; }
[Display(Name = "First Name")]
[StringLength(100)]
public string? ContactFirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(100)]
public string? ContactLastName { get; set; }
[Display(Name = "Email")]
[EmailAddress(ErrorMessage = "Please enter a valid email address")]
[StringLength(200)]
public string? Email { get; set; }
[Display(Name = "Phone")]
[Phone(ErrorMessage = "Please enter a valid phone number")]
[StringLength(20)]
public string? Phone { get; set; }
[Display(Name = "Mobile Phone")]
[Phone(ErrorMessage = "Please enter a valid mobile phone number")]
[StringLength(20)]
public string? MobilePhone { get; set; }
[Display(Name = "Street Address")]
[StringLength(500)]
public string? Address { get; set; }
[Display(Name = "City")]
[StringLength(100)]
public string? City { get; set; }
[Display(Name = "State")]
[StringLength(50)]
public string? State { get; set; }
[Display(Name = "Zip Code")]
[StringLength(20)]
public string? ZipCode { get; set; }
[Display(Name = "Country")]
[StringLength(100)]
public string? Country { get; set; } = "USA";
[Display(Name = "Customer Type")]
public bool IsCommercial { get; set; }
[Display(Name = "Tax ID / EIN")]
[StringLength(50)]
public string? TaxId { get; set; }
[Display(Name = "Credit Limit")]
[Range(0, 9999999.99)]
public decimal CreditLimit { get; set; }
[Display(Name = "Payment Terms")]
[StringLength(50)]
public string? PaymentTerms { get; set; }
[Display(Name = "Pricing Tier")]
public int? PricingTierId { get; set; }
[Display(Name = "Tax Exempt")]
public bool IsTaxExempt { get; set; }
[Display(Name = "General Notes")]
[StringLength(2000)]
public string? GeneralNotes { get; set; }
[Display(Name = "Notify by Email")]
public bool NotifyByEmail { get; set; } = true;
[Display(Name = "Notify by SMS")]
public bool NotifyBySms { get; set; } = false;
/// <summary>
/// Form-only flag: staff checks this to record that the customer gave verbal consent to receive SMS.
/// Not mapped to any entity field — the controller handles setting SmsConsentedAt and SmsConsentMethod.
/// </summary>
[Display(Name = "Customer has verbally consented to receive SMS notifications")]
public bool SmsConsentGranted { get; set; } = false;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// At least one name field is required (Company Name OR Contact First/Last Name)
if (string.IsNullOrWhiteSpace(CompanyName) &&
string.IsNullOrWhiteSpace(ContactFirstName) &&
string.IsNullOrWhiteSpace(ContactLastName))
{
yield return new ValidationResult(
"Please provide either a Company Name or a Contact Name (First and/or Last Name)",
new[] { nameof(CompanyName), nameof(ContactFirstName), nameof(ContactLastName) });
}
// At least one contact method is required (Email OR Phone)
if (string.IsNullOrWhiteSpace(Email) && string.IsNullOrWhiteSpace(Phone))
{
yield return new ValidationResult(
"Please provide at least one contact method (Email or Phone)",
new[] { nameof(Email), nameof(Phone) });
}
}
}
public class UpdateCustomerDto : CreateCustomerDto
{
public int Id { get; set; }
public bool IsActive { get; set; }
public bool HasTaxExemptCertificate { get; set; }
public string? TaxExemptCertificateFileName { get; set; }
// Read from entity so the Edit view can display consent status
public DateTime? SmsConsentedAt { get; set; }
public string? SmsConsentMethod { get; set; }
}
public class CustomerListDto
{
public int Id { get; set; }
public string? CompanyName { get; set; }
public string ContactName { get; set; } = string.Empty;
public string? Phone { get; set; }
public string? Email { get; set; }
public bool IsCommercial { get; set; }
public decimal CurrentBalance { get; set; }
public bool IsActive { get; set; }
public DateTime? LastContactDate { get; set; }
}
public class AddCreditDto
{
[Required]
[Range(0.01, 99999.99, ErrorMessage = "Amount must be between $0.01 and $99,999.99")]
public decimal Amount { get; set; }
[Required]
[StringLength(200, ErrorMessage = "Reason cannot exceed 200 characters")]
[Display(Name = "Reason")]
public string Reason { get; set; } = string.Empty;
[StringLength(1000)]
[Display(Name = "Notes")]
public string? Notes { get; set; }
[Display(Name = "Expiry Date (optional)")]
public DateTime? ExpiryDate { get; set; }
}
@@ -0,0 +1,53 @@
using System.ComponentModel.DataAnnotations;
namespace PowderCoating.Application.DTOs.Customer;
public class PricingTierDto
{
public int Id { get; set; }
public string TierName { get; set; } = string.Empty;
public string? Description { get; set; }
public decimal DiscountPercent { get; set; }
public bool IsActive { get; set; }
public int CustomerCount { get; set; }
}
public class CreatePricingTierDto
{
[Required(ErrorMessage = "Tier name is required")]
[MaxLength(100)]
[Display(Name = "Tier Name")]
public string TierName { get; set; } = string.Empty;
[MaxLength(500)]
public string? Description { get; set; }
[Required]
[Range(0, 100, ErrorMessage = "Discount must be between 0 and 100")]
[Display(Name = "Discount %")]
public decimal DiscountPercent { get; set; }
[Display(Name = "Active")]
public bool IsActive { get; set; } = true;
}
public class UpdatePricingTierDto
{
public int Id { get; set; }
[Required(ErrorMessage = "Tier name is required")]
[MaxLength(100)]
[Display(Name = "Tier Name")]
public string TierName { get; set; } = string.Empty;
[MaxLength(500)]
public string? Description { get; set; }
[Required]
[Range(0, 100, ErrorMessage = "Discount must be between 0 and 100")]
[Display(Name = "Discount %")]
public decimal DiscountPercent { get; set; }
[Display(Name = "Active")]
public bool IsActive { get; set; }
}