Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Customers/Edit.cshtml
T
spouliot 94a89ee175 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>
2026-06-10 12:46:08 -04:00

509 lines
35 KiB
Plaintext

@model PowderCoating.Application.DTOs.Customer.UpdateCustomerDto
@{
ViewData["Title"] = "Edit Customer";
ViewData["PageIcon"] = "bi-pencil-square";
}
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="d-flex justify-content-end align-items-center mb-4">
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-2"></i>Back to List
</a>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="Id" />
<partial name="_ValidationSummary" />
<!-- Company Information Section -->
<div class="mb-4">
<div class="d-flex align-items-center gap-2 border-bottom pb-2 mb-3">
<h5 class="mb-0"><i class="bi bi-building me-2 text-primary"></i>Company Information</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Company Information"
data-bs-content="Company Name is used on quotes, invoices, and correspondence. Customer Type controls which features are available &mdash; Commercial customers get payment terms, credit limits, and pricing tier discounts. Status Inactive hides the customer from new quote/job dropdowns but preserves all history.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="CompanyName" class="form-label">Company Name</label>
<input asp-for="CompanyName" class="form-control" placeholder="Enter company name" />
<span asp-validation-for="CompanyName" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="IsCommercial" class="form-label">Customer Type
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Customer Type"
data-bs-content="Commercial: businesses with ongoing work, purchase orders, and invoicing. Individual: walk-in customers or one-off jobs. This affects which fields are shown and whether pricing tier discounts apply.">
<i class="bi bi-question-circle"></i>
</a>
</label>
<select asp-for="IsCommercial" class="form-select">
<option value="false">Individual</option>
<option value="true">Commercial</option>
</select>
</div>
<div class="col-md-3">
<label asp-for="IsActive" class="form-label">Status</label>
<select asp-for="IsActive" class="form-select">
<option value="true">Active</option>
<option value="false">Inactive</option>
</select>
</div>
</div>
</div>
<!-- Contact Information Section -->
<div class="mb-4">
<h5 class="border-bottom pb-2 mb-3">
<i class="bi bi-person me-2 text-primary"></i>Contact Information
</h5>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="ContactFirstName" class="form-label">First Name</label>
<input asp-for="ContactFirstName" class="form-control" placeholder="Enter first name" />
<span asp-validation-for="ContactFirstName" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="ContactLastName" class="form-label">Last Name</label>
<input asp-for="ContactLastName" class="form-control" placeholder="Enter last name" />
<span asp-validation-for="ContactLastName" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="Email" class="form-label">Email</label>
<input asp-for="Email" type="text" class="form-control" placeholder="name@example.com (comma-separate multiple)" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="Phone" class="form-label">Phone</label>
<input asp-for="Phone" type="tel" class="form-control" placeholder="(555) 123-4567" />
<span asp-validation-for="Phone" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="MobilePhone" class="form-label">Mobile Phone</label>
<input asp-for="MobilePhone" type="tel" class="form-control" placeholder="(555) 123-4567" />
<span asp-validation-for="MobilePhone" class="text-danger"></span>
</div>
<div class="col-md-6" id="billingEmailRow" style="display:none;">
<label asp-for="BillingEmail" class="form-label">Billing / Accounting Email
<span class="text-muted fw-normal">(invoices sent here)</span>
</label>
<input asp-for="BillingEmail" type="text" class="form-control" placeholder="accounting@company.com (comma-separate multiple)" />
<span asp-validation-for="BillingEmail" class="text-danger"></span>
<div class="form-text">When set, invoices are emailed here instead of the contact email.</div>
</div>
</div>
</div>
<!-- Address Section -->
<div class="mb-4">
<h5 class="border-bottom pb-2 mb-3">
<i class="bi bi-geo-alt me-2 text-primary"></i>Address
</h5>
<div class="row g-3">
<div class="col-12">
<label asp-for="Address" class="form-label">Street Address</label>
<input asp-for="Address" class="form-control" placeholder="Enter street address" />
<span asp-validation-for="Address" class="text-danger"></span>
</div>
<div class="col-md-5">
<label asp-for="City" class="form-label">City</label>
<input asp-for="City" class="form-control" placeholder="Enter city" />
<span asp-validation-for="City" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="State" class="form-label">State</label>
<input asp-for="State" class="form-control" placeholder="Enter state" />
<span asp-validation-for="State" class="text-danger"></span>
</div>
<div class="col-md-2">
<label asp-for="ZipCode" class="form-label">Zip Code</label>
<input asp-for="ZipCode" class="form-control" placeholder="12345" />
<span asp-validation-for="ZipCode" class="text-danger"></span>
</div>
<div class="col-md-2">
<label asp-for="Country" class="form-label">Country</label>
<select asp-for="Country" class="form-select">
<option value="">-- Select --</option>
<option value="USA">USA</option>
<option value="Canada">Canada</option>
<option value="Mexico">Mexico</option>
<option value="Afghanistan">Afghanistan</option>
<option value="Albania">Albania</option>
<option value="Algeria">Algeria</option>
<option value="Argentina">Argentina</option>
<option value="Australia">Australia</option>
<option value="Austria">Austria</option>
<option value="Bangladesh">Bangladesh</option>
<option value="Belgium">Belgium</option>
<option value="Bolivia">Bolivia</option>
<option value="Brazil">Brazil</option>
<option value="Chile">Chile</option>
<option value="China">China</option>
<option value="Colombia">Colombia</option>
<option value="Costa Rica">Costa Rica</option>
<option value="Croatia">Croatia</option>
<option value="Czech Republic">Czech Republic</option>
<option value="Denmark">Denmark</option>
<option value="Dominican Republic">Dominican Republic</option>
<option value="Ecuador">Ecuador</option>
<option value="Egypt">Egypt</option>
<option value="El Salvador">El Salvador</option>
<option value="Finland">Finland</option>
<option value="France">France</option>
<option value="Germany">Germany</option>
<option value="Ghana">Ghana</option>
<option value="Greece">Greece</option>
<option value="Guatemala">Guatemala</option>
<option value="Honduras">Honduras</option>
<option value="Hungary">Hungary</option>
<option value="India">India</option>
<option value="Indonesia">Indonesia</option>
<option value="Iran">Iran</option>
<option value="Iraq">Iraq</option>
<option value="Ireland">Ireland</option>
<option value="Israel">Israel</option>
<option value="Italy">Italy</option>
<option value="Japan">Japan</option>
<option value="Jordan">Jordan</option>
<option value="Kazakhstan">Kazakhstan</option>
<option value="Kenya">Kenya</option>
<option value="South Korea">South Korea</option>
<option value="Kuwait">Kuwait</option>
<option value="Malaysia">Malaysia</option>
<option value="Netherlands">Netherlands</option>
<option value="New Zealand">New Zealand</option>
<option value="Nicaragua">Nicaragua</option>
<option value="Nigeria">Nigeria</option>
<option value="Norway">Norway</option>
<option value="Pakistan">Pakistan</option>
<option value="Panama">Panama</option>
<option value="Paraguay">Paraguay</option>
<option value="Peru">Peru</option>
<option value="Philippines">Philippines</option>
<option value="Poland">Poland</option>
<option value="Portugal">Portugal</option>
<option value="Puerto Rico">Puerto Rico</option>
<option value="Romania">Romania</option>
<option value="Russia">Russia</option>
<option value="Saudi Arabia">Saudi Arabia</option>
<option value="South Africa">South Africa</option>
<option value="Spain">Spain</option>
<option value="Sweden">Sweden</option>
<option value="Switzerland">Switzerland</option>
<option value="Taiwan">Taiwan</option>
<option value="Thailand">Thailand</option>
<option value="Turkey">Turkey</option>
<option value="Ukraine">Ukraine</option>
<option value="United Arab Emirates">United Arab Emirates</option>
<option value="United Kingdom">United Kingdom</option>
<option value="Uruguay">Uruguay</option>
<option value="Venezuela">Venezuela</option>
<option value="Vietnam">Vietnam</option>
</select>
<span asp-validation-for="Country" class="text-danger"></span>
</div>
</div>
</div>
<!-- Ship-To Address Section -->
<div class="mb-4">
<div class="d-flex align-items-center gap-2 border-bottom pb-2 mb-3">
<h5 class="mb-0"><i class="bi bi-truck me-2 text-primary"></i>Ship-To / Pickup Address</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Ship-To Address"
data-bs-content="Optional. Fill in only if this customer picks up or receives deliveries at a different address than their billing address. Leave blank to use the billing address above.">
<i class="bi bi-question-circle"></i>
</a>
<span class="text-muted small fw-normal">(optional &mdash; leave blank if same as billing)</span>
</div>
<div class="row g-3">
<div class="col-12">
<label asp-for="ShipToAddress" class="form-label">Street Address</label>
<input asp-for="ShipToAddress" class="form-control" placeholder="Enter ship-to street address" />
</div>
<div class="col-md-5">
<label asp-for="ShipToCity" class="form-label">City</label>
<input asp-for="ShipToCity" class="form-control" placeholder="Enter city" />
</div>
<div class="col-md-3">
<label asp-for="ShipToState" class="form-label">State</label>
<input asp-for="ShipToState" class="form-control" placeholder="Enter state" />
</div>
<div class="col-md-2">
<label asp-for="ShipToZipCode" class="form-label">Zip Code</label>
<input asp-for="ShipToZipCode" class="form-control" placeholder="12345" />
</div>
<div class="col-md-2">
<label asp-for="ShipToCountry" class="form-label">Country</label>
<input asp-for="ShipToCountry" class="form-control" placeholder="USA" />
</div>
</div>
</div>
<!-- Business Information Section -->
<div class="mb-4">
<div class="d-flex align-items-center gap-2 border-bottom pb-2 mb-3">
<h5 class="mb-0"><i class="bi bi-briefcase me-2 text-primary"></i>Business Information</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Business Information"
data-bs-content="Payment Terms sets the default due date on invoices (e.g., Net 30 = 30 days from invoice date). Credit Limit is a soft warning cap &mdash; the system alerts when exceeded. Tax Exempt removes tax from all invoices; upload the exemption certificate in the Tax Exempt Certificate section below.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="TaxId" class="form-label">Tax ID / EIN</label>
<input asp-for="TaxId" class="form-control" placeholder="Enter tax ID" />
<span asp-validation-for="TaxId" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="PaymentTerms" class="form-label">Payment Terms</label>
<select asp-for="PaymentTerms" class="form-select">
<option value="">Select payment terms</option>
<option value="Net 15">Net 15</option>
<option value="Net 30">Net 30</option>
<option value="Net 45">Net 45</option>
<option value="Net 60">Net 60</option>
<option value="Due on Receipt">Due on Receipt</option>
<option value="Cash on Delivery">Cash on Delivery</option>
</select>
<span asp-validation-for="PaymentTerms" class="text-danger"></span>
</div>
<div class="col-md-3">
<label asp-for="PricingTierId" class="form-label">Pricing Tier</label>
<select asp-for="PricingTierId" asp-items="ViewBag.PricingTiers" class="form-select">
<option value="">&mdash; No tier &mdash;</option>
</select>
<small class="text-muted">Applies a discount to all quotes for this customer.</small>
</div>
<div class="col-md-3">
<label asp-for="CreditLimit" class="form-label">Credit Limit</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input asp-for="CreditLimit" type="number" step="0.01" min="0" class="form-control" placeholder="0.00" />
</div>
<span asp-validation-for="CreditLimit" class="text-danger"></span>
</div>
</div>
<div class="row g-3 mt-2">
<div class="col-md-6">
<div class="form-check form-switch">
<input asp-for="IsTaxExempt" class="form-check-input" type="checkbox" />
<label asp-for="IsTaxExempt" class="form-check-label">Tax Exempt Customer</label>
</div>
<small class="text-muted">Check this box if the customer is tax exempt</small>
</div>
</div>
</div>
<!-- Lead Source Section -->
<div class="mb-4">
<h5 class="border-bottom pb-2 mb-3">
<i class="bi bi-signpost me-2 text-primary"></i>How Did They Find Us?
</h5>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="LeadSource" class="form-label">Lead Source</label>
<select asp-for="LeadSource" class="form-select">
<option value="">&mdash; Not specified &mdash;</option>
<option value="Walk-In">Walk-In</option>
<option value="Google Search">Google Search</option>
<option value="Customer Referral">Customer Referral</option>
<option value="Social Media">Social Media</option>
<option value="Website">Website</option>
<option value="Repeat Customer">Repeat Customer</option>
<option value="Trade Show / Event">Trade Show / Event</option>
<option value="Flyer / Print Ad">Flyer / Print Ad</option>
<option value="Other">Other</option>
</select>
</div>
</div>
</div>
<!-- Notes Section -->
<div class="mb-4">
<h5 class="border-bottom pb-2 mb-3">
<i class="bi bi-journal-text me-2 text-primary"></i>Notes
</h5>
<div class="row g-3">
<div class="col-12">
<label asp-for="GeneralNotes" class="form-label">General Notes</label>
<textarea asp-for="GeneralNotes" class="form-control" rows="4" placeholder="Enter any additional notes about this customer"></textarea>
<span asp-validation-for="GeneralNotes" class="text-danger"></span>
</div>
</div>
</div>
<!-- Notification Preferences -->
<div class="mb-4">
<div class="d-flex align-items-center gap-2 border-bottom pb-2 mb-3">
<h5 class="mb-0"><i class="bi bi-bell me-2 text-primary"></i>Notification Preferences</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Notification Preferences"
data-bs-content="Controls when the customer receives automatic updates. Email notifications send status change alerts (e.g., job ready for pickup) to the customer's email address. SMS requires separate TCPA consent &mdash; uncheck 'SMS Notifications Active' to temporarily pause without revoking consent.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<!-- Email -->
<div class="col-md-6">
<div class="form-check form-switch">
<input asp-for="NotifyByEmail" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="NotifyByEmail" class="form-check-label">
<i class="bi bi-envelope me-1"></i>Email Notifications
</label>
<div class="form-text">Receive quote and job status updates by email.</div>
</div>
</div>
</div>
@if (ViewBag.SmsEnabled == true)
{
<!-- SMS: show consent status or consent capture depending on existing consent -->
<div class="mt-3">
@if (Model.SmsConsentedAt.HasValue)
{
<!-- Consent already recorded &mdash; show status and allow pause/resume -->
<div class="card border-success bg-success-subtle p-3 mb-2">
<div class="d-flex align-items-start gap-3">
<i class="bi bi-shield-fill-check text-success fs-4 mt-1"></i>
<div class="flex-fill">
<div class="fw-semibold text-success mb-1">SMS Consent Recorded</div>
<div class="small text-body-secondary">
Consent method: <strong>@Model.SmsConsentMethod</strong><br />
Recorded: <strong>@Model.SmsConsentedAt.Value.Tz(ViewBag.CompanyTimeZone as string).ToString("MMMM d, yyyy 'at' h:mm tt")</strong>
</div>
<div class="mt-2">
<div class="form-check form-switch">
<input asp-for="NotifyBySms" class="form-check-input" type="checkbox" role="switch" />
<label asp-for="NotifyBySms" class="form-check-label">
<i class="bi bi-phone me-1"></i>SMS Notifications Active
</label>
<div class="form-text">Uncheck to temporarily pause SMS without revoking consent.</div>
</div>
</div>
</div>
</div>
</div>
}
else
{
<!-- No consent on file &mdash; show the compliance notice and consent checkbox -->
<div class="alert alert-warning border-warning alert-permanent" role="alert">
<h6 class="alert-heading fw-bold mb-2">
<i class="bi bi-exclamation-triangle-fill me-2"></i>SMS Consent Requirement (TCPA)
</h6>
<p class="mb-2">
Federal law (TCPA) requires <strong>explicit prior verbal or written consent</strong> before sending SMS messages.
Before enabling SMS notifications, you must:
</p>
<ol class="mb-2 ps-3">
<li>Inform the customer they will receive automated texts for job updates and pickup alerts.</li>
<li>Inform them that message and data rates may apply.</li>
<li>Explain they can reply <strong>STOP</strong> at any time to opt out.</li>
<li>Obtain their clear verbal or written agreement.</li>
</ol>
<p class="mb-0 small text-muted">
Only check the box below <strong>after</strong> the customer has given consent.
A confirmation text will be sent automatically to verify enrollment.
</p>
</div>
<div class="card border-secondary bg-body-secondary p-3">
<div class="form-check">
<input asp-for="SmsConsentGranted" class="form-check-input" type="checkbox" id="SmsConsentGranted" />
<label class="form-check-label fw-semibold" for="SmsConsentGranted">
<i class="bi bi-shield-check me-1 text-success"></i>
Customer has verbally consented to receive SMS notifications
</label>
<div class="form-text">
Checking this box records consent on behalf of the customer and triggers a confirmation text.
A mobile phone number must be entered above.
</div>
</div>
</div>
}
</div>
}
</div>
<!-- Form Actions -->
<div class="d-flex gap-2 justify-content-end pt-3 border-top">
<a asp-action="Index" class="btn btn-outline-secondary px-4">Cancel</a>
<button type="submit" class="btn btn-primary px-4">
<i class="bi bi-save me-2"></i>Save Changes
</button>
</div>
</form>
</div>
</div>
<!-- Tax Exempt Certificate Section (Outside main form) -->
<div class="card border-0 shadow-sm mt-4">
<div class="card-body p-4">
<h5 class="border-bottom pb-2 mb-3">
<i class="bi bi-file-earmark-check me-2 text-primary"></i>Tax Exempt Certificate
</h5>
<div class="row g-3">
<div class="col-md-6">
@if (Model.HasTaxExemptCertificate)
{
<div class="alert alert-success alert-permanent d-flex justify-content-between align-items-center">
<div>
<i class="bi bi-file-earmark-check me-2"></i>
<strong>Certificate on file:</strong> @Model.TaxExemptCertificateFileName
</div>
<div class="btn-group">
<a asp-action="TaxExemptCertificate" asp-route-id="@Model.Id" class="btn btn-sm btn-outline-primary" target="_blank">
<i class="bi bi-download"></i> Download
</a>
<form asp-action="DeleteTaxExemptCertificate" asp-route-id="@Model.Id" method="post" style="display:inline;"
onsubmit="return confirm('Are you sure you want to delete this certificate?');">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i> Delete
</button>
</form>
</div>
</div>
}
else
{
<p class="text-muted">No tax exempt certificate on file.</p>
}
</div>
<div class="col-md-6">
<form asp-action="UploadTaxExemptCertificate" asp-route-id="@Model.Id" method="post" enctype="multipart/form-data">
@Html.AntiForgeryToken()
<div class="mb-2">
<label class="form-label">Upload New Certificate</label>
<input type="file" name="certificateFile" class="form-control" accept=".pdf,.jpg,.jpeg,.png" />
<small class="text-muted">Accepted formats: PDF, JPG, PNG (Max 10 MB)</small>
</div>
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-upload"></i> Upload Certificate
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script src="~/js/customer-billing-email.js"></script>
}