Hide email controls when no email on file; show SMS hint for quote/job events

- Quotes Create/Edit: hide 'Send via email' checkbox when customer has no
  email; show badge 'send via SMS from details' or 'SMS consent required'
  when customer has a mobile number. JS responds to customer dropdown change.
- Quotes Details: hide 'Send Quote via Email' button and approval email
  checkbox; hide SMS button when no mobile; show consent-required note.
- Jobs Details (Mark Complete modal): hide email checkbox; show
  'SMS notification will be sent' badge or consent-required note.
- Jobs Index (status modal): hide email row when customer has no email.
- Jobs Edit: hide 'Notify customer if status changes' when no email.
- Invoices Details: hide Send/Re-send buttons when no email (vs. disabled).

DTOs: added CustomerEmail + CustomerNotifyByEmail to JobDto/JobListDto;
added CustomerNotifyByEmail/CustomerMobilePhone/CustomerNotifyBySms to
QuoteDto. Mapped in JobProfile and QuotesController customer blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 17:32:08 -04:00
parent d3863c713b
commit acbd9f60be
1190 changed files with 338016 additions and 88 deletions
@@ -52,6 +52,8 @@ public class JobDto
public bool RequiresCustomerApproval { get; set; }
public bool IsCustomerApproved { get; set; }
public string? CustomerEmail { get; set; }
public bool CustomerNotifyByEmail { get; set; } = true;
// Customer SMS opt-in — used for SMS compose modal on job details
public bool CustomerNotifyBySms { get; set; }
public string? CustomerMobilePhone { get; set; }
@@ -103,6 +105,7 @@ public class JobListDto
public string PriorityDisplayName { get; set; } = string.Empty;
public string PriorityColorClass { get; set; } = "secondary";
public string? CustomerEmail { get; set; }
public bool CustomerNotifyByEmail { get; set; } = true;
public DateTime? ScheduledDate { get; set; }
public DateTime? DueDate { get; set; }
@@ -45,6 +45,9 @@ public class QuoteDto
public string? CustomerContactLastName { get; set; }
public string? CustomerEmail { get; set; }
public string? CustomerPhone { get; set; }
public bool CustomerNotifyByEmail { get; set; } = true;
public string? CustomerMobilePhone { get; set; }
public bool CustomerNotifyBySms { get; set; }
public string? ProspectCompanyName { get; set; }
public string? ProspectContactFirstName { get; set; }
@@ -58,6 +58,10 @@ public class JobProfile : Profile
opt => opt.MapFrom(src => src.OriginalJob != null ? src.OriginalJob.JobNumber : null))
.ForMember(dest => dest.IntakeCheckedByName,
opt => opt.MapFrom(src => src.IntakeCheckedBy != null ? src.IntakeCheckedBy.FullName : null))
.ForMember(dest => dest.CustomerEmail,
opt => opt.MapFrom(src => src.Customer != null ? src.Customer.Email : null))
.ForMember(dest => dest.CustomerNotifyByEmail,
opt => opt.MapFrom(src => src.Customer == null || src.Customer.NotifyByEmail))
.ForMember(dest => dest.CustomerNotifyBySms,
opt => opt.MapFrom(src => src.Customer != null && src.Customer.NotifyBySms))
.ForMember(dest => dest.CustomerMobilePhone,
@@ -116,6 +120,8 @@ public class JobProfile : Profile
.ForMember(dest => dest.PriorityCode, opt => opt.MapFrom(src => src.JobPriority.PriorityCode))
.ForMember(dest => dest.PriorityDisplayName, opt => opt.MapFrom(src => src.JobPriority.DisplayName))
.ForMember(dest => dest.PriorityColorClass, opt => opt.MapFrom(src => src.JobPriority.ColorClass))
.ForMember(dest => dest.CustomerEmail,
opt => opt.MapFrom(src => src.Customer != null ? src.Customer.Email : null))
.ForMember(dest => dest.CustomerNotifyByEmail,
opt => opt.MapFrom(src => src.Customer == null || src.Customer.NotifyByEmail));