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:
@@ -216,27 +216,50 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (!string.IsNullOrEmpty(Model.Address))
|
||||
@{
|
||||
bool hasBilling = !string.IsNullOrEmpty(Model.Address);
|
||||
bool hasShipTo = !string.IsNullOrEmpty(Model.ShipToAddress) || !string.IsNullOrEmpty(Model.ShipToCity);
|
||||
}
|
||||
@if (hasShipTo)
|
||||
{
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="text-muted small mb-1">Billing Address</label>
|
||||
@if (hasBilling)
|
||||
{
|
||||
<p class="mb-1">@Model.Address</p>
|
||||
<p class="mb-0">
|
||||
@if (!string.IsNullOrEmpty(Model.City)) { <span>@Model.City</span> }
|
||||
@if (!string.IsNullOrEmpty(Model.State)) { <span>, @Model.State</span> }
|
||||
@if (!string.IsNullOrEmpty(Model.ZipCode)) { <span> @Model.ZipCode</span> }
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(Model.Country)) { <p class="mb-0 text-muted">@Model.Country</p> }
|
||||
}
|
||||
else { <p class="text-muted mb-0">Not provided</p> }
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="text-muted small mb-1">
|
||||
<i class="bi bi-truck me-1"></i>Ship-To / Pickup Address
|
||||
</label>
|
||||
<p class="mb-1">@Model.ShipToAddress</p>
|
||||
<p class="mb-0">
|
||||
@if (!string.IsNullOrEmpty(Model.ShipToCity)) { <span>@Model.ShipToCity</span> }
|
||||
@if (!string.IsNullOrEmpty(Model.ShipToState)) { <span>, @Model.ShipToState</span> }
|
||||
@if (!string.IsNullOrEmpty(Model.ShipToZipCode)) { <span> @Model.ShipToZipCode</span> }
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(Model.ShipToCountry)) { <p class="mb-0 text-muted">@Model.ShipToCountry</p> }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (hasBilling)
|
||||
{
|
||||
<p class="mb-2">@Model.Address</p>
|
||||
<p class="mb-0">
|
||||
@if (!string.IsNullOrEmpty(Model.City))
|
||||
{
|
||||
<span>@Model.City</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.State))
|
||||
{
|
||||
<span>, @Model.State</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.ZipCode))
|
||||
{
|
||||
<span> @Model.ZipCode</span>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.City)) { <span>@Model.City</span> }
|
||||
@if (!string.IsNullOrEmpty(Model.State)) { <span>, @Model.State</span> }
|
||||
@if (!string.IsNullOrEmpty(Model.ZipCode)) { <span> @Model.ZipCode</span> }
|
||||
</p>
|
||||
@if (!string.IsNullOrEmpty(Model.Country))
|
||||
{
|
||||
<p class="mb-0 text-muted">@Model.Country</p>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.Country)) { <p class="mb-0 text-muted">@Model.Country</p> }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -262,6 +285,15 @@
|
||||
<label class="text-muted small mb-1">Payment Terms</label>
|
||||
<p class="mb-0">@(Model.PaymentTerms ?? "Not set")</p>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(Model.LeadSource))
|
||||
{
|
||||
<div class="col-md-6">
|
||||
<label class="text-muted small mb-1">Lead Source</label>
|
||||
<p class="mb-0">
|
||||
<i class="bi bi-signpost me-1 text-muted"></i>@Model.LeadSource
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<div class="col-md-6">
|
||||
<label class="text-muted small mb-1">Credit Limit</label>
|
||||
<p class="mb-0 fw-semibold">@Model.CreditLimit.ToString("C")</p>
|
||||
@@ -329,6 +361,96 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Additional Contacts -->
|
||||
@{
|
||||
var customerContacts = ViewBag.CustomerContacts as List<PowderCoating.Core.Entities.CustomerContact>;
|
||||
}
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-semibold">
|
||||
<i class="bi bi-people me-2 text-primary"></i>Additional Contacts
|
||||
</h5>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="text-muted small">
|
||||
<i class="bi bi-info-circle me-1"></i>For staff reference — automated notifications still go to the primary contact above.
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-bs-toggle="modal" data-bs-target="#contactModal"
|
||||
onclick="openAddContactModal()">
|
||||
<i class="bi bi-plus-circle me-1"></i>Add Contact
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="ps-3">Name / Role</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th class="text-end pe-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="contacts-table-body">
|
||||
@if (customerContacts != null && customerContacts.Count > 0)
|
||||
{
|
||||
@foreach (var c in customerContacts)
|
||||
{
|
||||
var displayName = string.IsNullOrWhiteSpace(c.LastName) ? c.FirstName : $"{c.FirstName} {c.LastName}";
|
||||
<tr data-contact-id="@c.Id">
|
||||
<td class="ps-3">
|
||||
<div class="fw-semibold">
|
||||
@displayName
|
||||
@if (!string.IsNullOrEmpty(c.ContactRole))
|
||||
{
|
||||
<span class="badge bg-secondary bg-opacity-10 text-secondary ms-1">@c.ContactRole</span>
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(c.Title))
|
||||
{
|
||||
<div class="text-muted" style="font-size:0.75rem;">@c.Title</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(c.Email))
|
||||
{
|
||||
<a href="mailto:@c.Email" class="text-decoration-none small">@c.Email</a>
|
||||
}
|
||||
else { <span class="text-muted small">—</span> }
|
||||
</td>
|
||||
<td>
|
||||
@if (!string.IsNullOrEmpty(c.Phone ?? c.MobilePhone))
|
||||
{
|
||||
<span class="small">@(c.Phone ?? c.MobilePhone)</span>
|
||||
}
|
||||
else { <span class="text-muted small">—</span> }
|
||||
</td>
|
||||
<td class="text-end pe-3">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary me-1"
|
||||
onclick="editContact(@Model.Id, @c.Id)" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
onclick="deleteContact(@Model.Id, @c.Id)" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<tr id="no-contacts-placeholder">
|
||||
<td colspan="4" class="text-muted small px-3 py-2">No additional contacts. Click “Add Contact” to add billing, ops, or drop-off contacts.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customer Notes -->
|
||||
@{
|
||||
var customerNotes = ViewBag.CustomerNotes as List<PowderCoating.Core.Entities.CustomerNote>;
|
||||
@@ -776,6 +898,72 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add / Edit Contact Modal -->
|
||||
<div class="modal fade" id="contactModal" tabindex="-1" aria-labelledby="contactModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="contactModalLabel">
|
||||
<i class="bi bi-person-plus me-2 text-primary"></i><span id="contactModalTitle">Add Contact</span>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="contactId" value="0" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-semibold">First Name <span class="text-danger">*</span></label>
|
||||
<input type="text" id="contactFirstName" class="form-control" maxlength="100" placeholder="First name" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Last Name</label>
|
||||
<input type="text" id="contactLastName" class="form-control" maxlength="100" placeholder="Last name" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Job Title</label>
|
||||
<input type="text" id="contactTitle" class="form-control" maxlength="100" placeholder="e.g. Purchasing Manager" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Role</label>
|
||||
<select id="contactRole" class="form-select">
|
||||
<option value="">— Select —</option>
|
||||
<option value="Billing">Billing</option>
|
||||
<option value="Operations">Operations</option>
|
||||
<option value="Drop-Off">Drop-Off</option>
|
||||
<option value="Sales">Sales</option>
|
||||
<option value="General">General</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Email</label>
|
||||
<input type="email" id="contactEmail" class="form-control" maxlength="200" placeholder="email@example.com" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Phone</label>
|
||||
<input type="tel" id="contactPhone" class="form-control" maxlength="20" placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Mobile Phone</label>
|
||||
<input type="tel" id="contactMobilePhone" class="form-control" maxlength="20" placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Notes</label>
|
||||
<textarea id="contactNotes" class="form-control" rows="2" maxlength="500" placeholder="Optional notes about this contact..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contactModalError" class="alert alert-danger alert-permanent mt-3 d-none"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveContact(@Model.Id)" id="saveContactBtn">
|
||||
<i class="bi bi-check-circle me-1"></i>Save Contact
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Store Credit Modal -->
|
||||
@if (User.IsInRole("SuperAdmin") || User.IsInRole("Administrator"))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user