Add AI Quick Quote widget and inline customer reassignment

- New AI Quick Quote floating button: staff type a verbal description to
  get an instant price estimate for phone/walk-in customers; detected
  color names are fuzzy-matched against inventory for stock status;
  saves draft quote under a Walk-In / Phone customer with one click
- Inline customer change on Quote Details and Job Details: always-visible
  native select with inline confirmation banner (no TomSelect dependency);
  ChangeCustomer AJAX endpoints on QuotesController and JobsController
- Quote Edit page: customer dropdown is now editable (lock removed)
- Fix AutoMapper missing CatalogCategory -> UpdateCategoryDto mapping
  that caused a crash on the catalog category Edit page
- Help docs and AI knowledge base updated for all three features

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 17:02:03 -04:00
parent fc9ddc6d17
commit 8d94013895
18 changed files with 1611 additions and 37 deletions
@@ -80,12 +80,24 @@
else
{
<div class="col-md-12">
<p>
<strong>Customer:</strong>
<a asp-controller="Customers" asp-action="Details" asp-route-id="@Model.CustomerId">
@Model.CustomerName
</a>
</p>
@Html.AntiForgeryToken()
<strong>Customer:</strong>
<div data-cc-wrap data-cc-id="@Model.Id"
data-cc-url="@Url.Action("ChangeCustomer", "Quotes")"
class="d-inline-block ms-1">
<select class="form-select form-select-sm cc-select" style="max-width:300px;">
@foreach (var c in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.CustomerSelectList)
{
<option value="@c.Value" selected="@(c.Value == Model.CustomerId.ToString() ? "selected" : null)">@c.Text</option>
}
</select>
<div class="cc-confirm-banner d-none mt-2 p-2 bg-light border rounded d-flex align-items-center gap-2 flex-wrap">
<span class="cc-confirm-text small fw-semibold"></span>
<button type="button" class="btn btn-success btn-sm" data-cc-save>Save</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-cc-cancel>Cancel</button>
</div>
<div class="cc-error text-danger small mt-1 d-none"></div>
</div>
</div>
}
</div>
@@ -2030,6 +2042,7 @@
</div>
@section Scripts {
<script src="~/js/customer-change.js" asp-append-version="true"></script>
<script>
function resendQuote(quoteId) {
// Reset modal state
+10 -9
View File
@@ -23,14 +23,13 @@
<input type="hidden" asp-for="QuoteStatusId" />
<partial name="_ValidationSummary" />
<!-- Section 1: Customer / Prospect/Walk-In (Read-Only) -->
<!-- Section 1: Customer / Prospect/Walk-In -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-person-circle me-2"></i>Customer / Prospect/Walk-In</h5>
</div>
<div class="card-body">
<input type="hidden" asp-for="IsForProspect" />
<input type="hidden" asp-for="CustomerId" />
@if (Model.IsForProspect)
{
@@ -78,13 +77,13 @@
}
else
{
<!-- Existing Customer (Read-Only Display) -->
<div class="alert alert-light alert-permanent border mb-0 d-flex align-items-center gap-2">
<i class="bi bi-building text-success fs-5"></i>
<div>
<span class="fw-semibold">@ViewBag.CustomerName</span>
<span class="text-muted ms-2 small">Customer cannot be changed after quote creation.</span>
</div>
<!-- Customer Dropdown (now editable) -->
<div class="col-md-6">
<label asp-for="CustomerId" class="form-label fw-semibold">Customer</label>
<select asp-for="CustomerId" asp-items="ViewBag.Customers" id="customerSelect" class="form-select">
<option value="">-- Select Customer --</option>
</select>
<span asp-validation-for="CustomerId" class="text-danger"></span>
</div>
}
</div>
@@ -637,6 +636,8 @@
<script>
document.addEventListener('DOMContentLoaded', function () {
initTagInput('quoteTags', 'quoteTagsContainer');
var custEl = document.getElementById('customerSelect');
if (custEl) new TomSelect(custEl, { placeholder: '-- Select Customer --', openOnFocus: true, maxOptions: false });
});
// Discount type toggle