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:
@@ -0,0 +1,64 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PowderCoating.Application.DTOs.Customer;
|
||||
|
||||
public class CustomerContactDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int CustomerId { get; set; }
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
public string? LastName { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? ContactRole { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public string? Phone { get; set; }
|
||||
public string? MobilePhone { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(LastName) ? FirstName : $"{FirstName} {LastName}";
|
||||
}
|
||||
|
||||
public class CreateCustomerContactDto
|
||||
{
|
||||
[Required(ErrorMessage = "First name is required.")]
|
||||
[StringLength(100)]
|
||||
[Display(Name = "First Name")]
|
||||
public string FirstName { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(100)]
|
||||
[Display(Name = "Last Name")]
|
||||
public string? LastName { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
[Display(Name = "Job Title")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
[Display(Name = "Role")]
|
||||
public string? ContactRole { get; set; }
|
||||
|
||||
[EmailAddress]
|
||||
[StringLength(200)]
|
||||
[Display(Name = "Email")]
|
||||
public string? Email { get; set; }
|
||||
|
||||
[Phone]
|
||||
[StringLength(20)]
|
||||
[Display(Name = "Phone")]
|
||||
public string? Phone { get; set; }
|
||||
|
||||
[Phone]
|
||||
[StringLength(20)]
|
||||
[Display(Name = "Mobile Phone")]
|
||||
public string? MobilePhone { get; set; }
|
||||
|
||||
[StringLength(500)]
|
||||
[Display(Name = "Notes")]
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateCustomerContactDto : CreateCustomerContactDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int CustomerId { get; set; }
|
||||
}
|
||||
@@ -36,6 +36,16 @@ public class CustomerDto
|
||||
public bool NotifyBySms { get; set; }
|
||||
public DateTime? SmsConsentedAt { get; set; }
|
||||
public string? SmsConsentMethod { get; set; }
|
||||
|
||||
// CRM
|
||||
public string? LeadSource { get; set; }
|
||||
|
||||
// Ship-to address
|
||||
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; }
|
||||
}
|
||||
|
||||
public class CreateCustomerDto : IValidatableObject
|
||||
@@ -115,6 +125,31 @@ public class CreateCustomerDto : IValidatableObject
|
||||
[StringLength(2000)]
|
||||
public string? GeneralNotes { get; set; }
|
||||
|
||||
[Display(Name = "How did you find us?")]
|
||||
[StringLength(100)]
|
||||
public string? LeadSource { get; set; }
|
||||
|
||||
// Ship-to / alternate address
|
||||
[Display(Name = "Ship-To Street Address")]
|
||||
[StringLength(500)]
|
||||
public string? ShipToAddress { get; set; }
|
||||
|
||||
[Display(Name = "City")]
|
||||
[StringLength(100)]
|
||||
public string? ShipToCity { get; set; }
|
||||
|
||||
[Display(Name = "State")]
|
||||
[StringLength(50)]
|
||||
public string? ShipToState { get; set; }
|
||||
|
||||
[Display(Name = "Zip Code")]
|
||||
[StringLength(20)]
|
||||
public string? ShipToZipCode { get; set; }
|
||||
|
||||
[Display(Name = "Country")]
|
||||
[StringLength(100)]
|
||||
public string? ShipToCountry { get; set; }
|
||||
|
||||
[Display(Name = "Notify by Email")]
|
||||
public bool NotifyByEmail { get; set; } = true;
|
||||
|
||||
|
||||
@@ -41,5 +41,12 @@ public class CustomerProfile : Profile
|
||||
opt => opt.MapFrom(src => !string.IsNullOrEmpty(src.ContactFirstName) || !string.IsNullOrEmpty(src.ContactLastName)
|
||||
? $"{src.ContactFirstName} {src.ContactLastName}".Trim()
|
||||
: string.Empty));
|
||||
|
||||
// CustomerContact
|
||||
CreateMap<CustomerContact, CustomerContactDto>();
|
||||
CreateMap<CreateCustomerContactDto, CustomerContact>();
|
||||
CreateMap<UpdateCustomerContactDto, CustomerContact>()
|
||||
.ForMember(dest => dest.Id, opt => opt.Ignore()); // Id is set by the controller, not mapped
|
||||
CreateMap<CustomerContact, UpdateCustomerContactDto>();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user