Add CRM features: Additional Contacts, Lead Source, Ship-To Address; update Help docs
- 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>
This commit is contained in:
@@ -41,6 +41,17 @@ public class Customer : BaseEntity
|
||||
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)
|
||||
@@ -55,4 +66,5 @@ public class Customer : BaseEntity
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PowderCoating.Core.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// An additional contact person associated with a customer account.
|
||||
/// Commercial customers frequently have separate billing, operations, and drop-off contacts.
|
||||
/// The primary contact remains on the Customer entity; these are supplementary.
|
||||
/// </summary>
|
||||
public class CustomerContact : BaseEntity
|
||||
{
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(100)]
|
||||
public string? LastName { get; set; }
|
||||
|
||||
/// <summary>Job title / role at the company, e.g. "Purchasing Manager".</summary>
|
||||
[StringLength(100)]
|
||||
public string? Title { get; set; }
|
||||
|
||||
/// <summary>Functional role: Billing, Operations, Drop-off, Sales, General, etc.</summary>
|
||||
[StringLength(50)]
|
||||
public string? ContactRole { get; set; }
|
||||
|
||||
[StringLength(200)]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[StringLength(20)]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[StringLength(20)]
|
||||
public string? MobilePhone { get; set; }
|
||||
|
||||
[StringLength(500)]
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public virtual Customer? Customer { get; set; }
|
||||
}
|
||||
@@ -43,6 +43,7 @@ public interface IUnitOfWork : IDisposable
|
||||
IJobPhotoRepository JobPhotos { get; }
|
||||
IRepository<JobNote> JobNotes { get; }
|
||||
IRepository<CustomerNote> CustomerNotes { get; }
|
||||
IRepository<CustomerContact> CustomerContacts { get; }
|
||||
IRepository<CustomerPreferredPowder> CustomerPreferredPowders { get; }
|
||||
IRepository<JobStatusHistory> JobStatusHistory { get; }
|
||||
IRepository<PricingTier> PricingTiers { get; }
|
||||
|
||||
Reference in New Issue
Block a user