94a89ee175
- New CustomerContact entity + migration (AddCustomerContactsAndCrmFields) - Customer.LeadSource + ShipToAddress/City/State/ZipCode/Country fields - Additional Contacts card on Customer Details with AJAX add/edit/delete - Lead Source dropdown on Create/Edit; Ship-To section on Create/Edit - Customer Details: side-by-side billing/ship-to when ship-to is set - Help docs: Customers (contacts, ship-to, lead source, preferred powders, outstanding pickups) - Help docs: Jobs (clone job, project name), Quotes (project name), Invoices (project name), Inventory (low stock clickable filter) - HelpKnowledgeBase.cs updated for all features above Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
71 lines
3.2 KiB
C#
71 lines
3.2 KiB
C#
namespace PowderCoating.Core.Entities;
|
|
|
|
public class Customer : BaseEntity
|
|
{
|
|
public string? CompanyName { get; set; }
|
|
public string? ContactFirstName { get; set; }
|
|
public string? ContactLastName { get; set; }
|
|
public string? Email { get; set; }
|
|
public string? BillingEmail { get; set; } // Accounting/invoicing email for commercial customers
|
|
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; } = "USA";
|
|
|
|
// Business Information
|
|
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; } // Available store credit (credit memos)
|
|
public string? PaymentTerms { get; set; }
|
|
public int? PricingTierId { get; set; }
|
|
|
|
// Tax Exemption
|
|
public bool IsTaxExempt { get; set; }
|
|
public byte[]? TaxExemptCertificateData { get; set; }
|
|
public string? TaxExemptCertificateContentType { get; set; }
|
|
public string? TaxExemptCertificateFileName { get; set; }
|
|
|
|
// Relationships
|
|
public virtual PricingTier? PricingTier { get; set; }
|
|
public virtual ICollection<Job> Jobs { get; set; } = new List<Job>();
|
|
public virtual ICollection<Quote> Quotes { get; set; } = new List<Quote>();
|
|
public virtual ICollection<CustomerNote> CustomerNotes { get; set; } = new List<CustomerNote>();
|
|
|
|
// Additional fields
|
|
public string? GeneralNotes { get; set; }
|
|
public bool IsActive { get; set; } = true;
|
|
public DateTime? LastContactDate { get; set; }
|
|
|
|
// CRM fields
|
|
/// <summary>How the customer found the shop (Walk-In, Google Search, Customer Referral, etc.).</summary>
|
|
public string? LeadSource { get; set; }
|
|
|
|
// Ship-to / alternate address (separate from billing address above)
|
|
public string? ShipToAddress { get; set; }
|
|
public string? ShipToCity { get; set; }
|
|
public string? ShipToState { get; set; }
|
|
public string? ShipToZipCode { get; set; }
|
|
public string? ShipToCountry { get; set; }
|
|
|
|
// Notification preferences
|
|
public bool NotifyByEmail { get; set; } = true;
|
|
// NotifyBySms is only set to true after explicit staff-recorded consent (TCPA compliance)
|
|
public bool NotifyBySms { get; set; } = false;
|
|
// Unique token used in email unsubscribe links (no auth required)
|
|
public string UnsubscribeToken { get; set; } = Guid.NewGuid().ToString("N");
|
|
// SMS consent tracking (TCPA compliance)
|
|
public DateTime? SmsConsentedAt { get; set; }
|
|
public string? SmsConsentMethod { get; set; }
|
|
/// <summary>Set when the customer replies STOP or is manually opted out. Null means they have never opted out.</summary>
|
|
public DateTime? SmsOptedOutAt { get; set; }
|
|
|
|
public virtual ICollection<NotificationLog> NotificationLogs { get; set; } = new List<NotificationLog>();
|
|
public virtual ICollection<Invoice> Invoices { get; set; } = new List<Invoice>();
|
|
public virtual ICollection<CustomerContact> CustomerContacts { get; set; } = new List<CustomerContact>();
|
|
}
|