Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Inventory/Create.cshtml
T
spouliot d77b3778ac Add vendor supply categories with inventory auto-filter
Vendors can now be tagged with one or more inventory categories (Powder,
Chemical, etc.) via checkboxes on the Create/Edit form. The inventory
Create/Edit vendor dropdown automatically filters to matching vendors when
a category is selected; falls back to all vendors if none are tagged.
Includes migration AddVendorCategories (VendorInventoryCategories join table).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 09:52:34 -04:00

480 lines
36 KiB
Plaintext

@model PowderCoating.Application.DTOs.Inventory.CreateInventoryItemDto
@{
ViewData["Title"] = "Add Inventory Item";
ViewData["PageIcon"] = "bi-box-seam";
ViewData["PageHelpTitle"] = "Add Inventory Item";
ViewData["PageHelpContent"] = "Add a new material to inventory &mdash; powder coatings, consumables, or other shop supplies. Select a category first to auto-generate a SKU. Use AI Lookup to fill in manufacturer details from a part number. Set Reorder Point and Reorder Quantity so the system can alert you when stock runs low.";
}
<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="Create" method="post">
<partial name="_ValidationSummary" />
<!-- Basic Information -->
<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-info-circle me-2 text-primary"></i>Basic 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="Basic Information"
data-bs-content="Name and SKU are required. Category drives how the item is filtered and used in quotes &mdash; choosing a Powder Coating category shows the Coating Specifications section. SKU is auto-generated from the category prefix but you can edit it. Description is optional free text for internal notes.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="InventoryCategoryId" class="form-label">Category <span class="text-danger">*</span></label>
<select asp-for="InventoryCategoryId" class="form-select" id="field-category"
asp-items="@ViewBag.Categories"
data-coating-map="@ViewBag.CategoryIsCoatingJson">
<option value="">Select category</option>
</select>
<span asp-validation-for="InventoryCategoryId" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="SKU" class="form-label">SKU <span class="text-danger">*</span></label>
<div class="input-group">
<input asp-for="SKU" class="form-control" id="field-sku" placeholder="Select a category to auto-generate" />
<button type="button" class="btn btn-outline-secondary" id="btn-regen-sku" title="Regenerate SKU" style="display:none;">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<span asp-validation-for="SKU" class="text-danger"></span>
<div class="form-text">Auto-generated when you pick a category. You can edit it.</div>
</div>
<div class="col-12" id="wrap-name">
<label asp-for="Name" class="form-label">Name <span class="text-danger">*</span></label>
<input asp-for="Name" class="form-control" id="field-name" placeholder="Enter product name" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="col-12" id="wrap-description">
<label asp-for="Description" class="form-label">Description</label>
<textarea asp-for="Description" class="form-control" id="field-description" rows="2"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
</div>
</div>
<!-- Product Details -->
<div class="mb-4">
<div class="d-flex align-items-center gap-2 border-bottom pb-2 mb-3">
<h5 class="mb-0 d-flex align-items-center">
<i class="bi bi-palette me-2 text-primary"></i>Product Details
<button type="button" class="btn btn-sm btn-primary ms-2" id="smart-lookup-btn">
<i class="bi bi-search me-1"></i>Lookup
</button>
@if ((bool)(ViewBag.AiInventoryAssistEnabled ?? false))
{
<button type="button" class="btn btn-sm btn-outline-secondary ms-1 d-lg-none" id="scan-label-btn" title="Scan a powder bag label with your camera">
<i class="bi bi-qr-code-scan me-1"></i>Scan Label
</button>
}
</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Product Details"
data-bs-content="Manufacturer, part number, color name, color code, and finish describe the physical product. Use Lookup to auto-fill these fields &mdash; it checks the product catalog first, then falls back to AI. Coverage is how many sq ft one pound coats at 1 mil thickness (typical: 30). Transfer Efficiency is what percentage of the powder actually sticks (typical: 60&ndash;70%). Both values are used to calculate Powder Needed on quotes and jobs.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div id="ai-lookup-status" class="alert alert-info d-none py-2 small mb-3"></div>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="Manufacturer" class="form-label">Manufacturer</label>
<input asp-for="Manufacturer" class="form-control" id="field-manufacturer" placeholder="e.g., Tiger Drylac, Sherwin-Williams" />
<span asp-validation-for="Manufacturer" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="ManufacturerPartNumber" class="form-label">Manufacturer Part Number</label>
<input asp-for="ManufacturerPartNumber" class="form-control" id="field-partnumber" />
<span asp-validation-for="ManufacturerPartNumber" class="text-danger"></span>
</div>
<div class="col-md-4" id="wrap-colorname">
<label asp-for="ColorName" class="form-label">Color Name</label>
<input asp-for="ColorName" class="form-control" id="field-colorname" placeholder="e.g., Illusion Malbec" />
<span asp-validation-for="ColorName" class="text-danger"></span>
</div>
<div class="col-md-4" id="wrap-colorcode">
<label asp-for="ColorCode" class="form-label">Color Code</label>
<input asp-for="ColorCode" class="form-control" id="field-colorcode" placeholder="e.g., RAL9005" />
<span asp-validation-for="ColorCode" class="text-danger"></span>
</div>
<div class="col-md-4" id="wrap-finish">
<label asp-for="Finish" class="form-label">Finish</label>
<input asp-for="Finish" class="form-control" id="field-finish" placeholder="e.g., Gloss, Matte" />
<span asp-validation-for="Finish" class="text-danger"></span>
</div>
<div class="col-12" id="wrap-specpageurl">
<label asp-for="SpecPageUrl" class="form-label">Product URL</label>
<div class="input-group">
<input asp-for="SpecPageUrl" class="form-control" id="field-specpageurl" placeholder="https://..." />
<a id="field-specpageurl-link" href="#" target="_blank" class="btn btn-outline-secondary d-none" title="Open spec page">
<i class="bi bi-box-arrow-up-right"></i>
</a>
</div>
<span asp-validation-for="SpecPageUrl" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="SdsUrl" class="form-label">Safety Data Sheet (SDS)</label>
<div class="input-group">
<input asp-for="SdsUrl" class="form-control" id="field-sdsurl" placeholder="https://&hellip;" />
<a id="field-sdsurl-link" href="@Model.SdsUrl" target="_blank"
class="btn btn-outline-secondary @(string.IsNullOrWhiteSpace(Model.SdsUrl) ? "d-none" : "")" title="Open SDS">
<i class="bi bi-file-earmark-pdf"></i>
</a>
</div>
<span asp-validation-for="SdsUrl" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="TdsUrl" class="form-label">Technical Data Sheet (TDS)</label>
<div class="input-group">
<input asp-for="TdsUrl" class="form-control" id="field-tdsurl" placeholder="https://&hellip;" />
<a id="field-tdsurl-link" href="@Model.TdsUrl" target="_blank"
class="btn btn-outline-secondary @(string.IsNullOrWhiteSpace(Model.TdsUrl) ? "d-none" : "")" title="Open TDS">
<i class="bi bi-file-earmark-text"></i>
</a>
</div>
<span asp-validation-for="TdsUrl" class="text-danger"></span>
</div>
<input asp-for="ImageUrl" type="hidden" id="field-imageurl" />
<div class="col-12" id="wrap-imagepreview" style="display:@(Model.ImageUrl != null ? "" : "none");">
<label class="form-label text-muted small">Product Image (from AI Lookup)</label>
<div class="d-flex align-items-start gap-3">
<img id="field-imagepreview-img" src="@Model.ImageUrl" alt="Product image"
style="max-height:120px;max-width:160px;object-fit:contain;border:1px solid #dee2e6;border-radius:6px;background:#f8f9fa;padding:4px;" />
<button type="button" class="btn btn-sm btn-outline-danger" id="btn-clear-image" title="Remove image">
<i class="bi bi-x me-1"></i>Remove
</button>
</div>
</div>
<div class="col-md-4" id="wrap-coverage">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="CoverageSqFtPerLb" class="form-label mb-0">Coverage (@ViewBag.CoverageUnit)</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Coverage"
data-bs-content="Manufacturer theoretical coverage for this powder, typically based on about 1.5 mil film thickness. Many powders land around 70 to 120 sq ft/lb. Used together with Transfer Efficiency to calculate how much powder to order for a job.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="CoverageSqFtPerLb" type="number" step="0.01" min="0" value="30" class="form-control" id="field-coverage" placeholder="e.g., 78" />
<span asp-validation-for="CoverageSqFtPerLb" class="text-danger"></span>
<small class="form-text text-muted">Theoretical coverage from the TDS, usually expressed in sq ft/lb</small>
</div>
<div class="col-md-4" id="wrap-specificgravity">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="SpecificGravity" class="form-label mb-0">Specific Gravity</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Specific Gravity"
data-bs-content="Specific gravity from the powder's technical data sheet. This is useful reference data on its own and can also be used to derive theoretical coverage when the TDS omits a direct coverage number.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="SpecificGravity" type="number" step="0.01" min="0" class="form-control" id="field-specificgravity" placeholder="e.g., 1.65" />
<span asp-validation-for="SpecificGravity" class="text-danger"></span>
<small class="form-text text-muted">Store the TDS specific gravity for future reference and calculations</small>
</div>
<div class="col-md-4" id="wrap-transfer">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="TransferEfficiency" class="form-label mb-0">Transfer Efficiency (%)</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Transfer Efficiency"
data-bs-content="The percentage of powder that actually adheres to the part rather than being lost as overspray. Electrostatic spray guns typically achieve 60&ndash;70%. A lower efficiency means you need to order more powder per job. The system uses this value in the Powder Needed calculation on quotes.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="TransferEfficiency" type="number" step="0.01" min="0" max="100" value="65" class="form-control" id="field-transfer" placeholder="65" />
<span asp-validation-for="TransferEfficiency" class="text-danger"></span>
<small class="form-text text-muted">Percentage of coating that sticks to the part (default: 65%)</small>
</div>
</div>
</div>
<!-- Coating Specs (shown for coating-type items) -->
<div class="mb-4" id="coating-specs-section">
<div class="d-flex align-items-center gap-2 border-bottom pb-2 mb-3">
<h5 class="mb-0">
<i class="bi bi-thermometer-half text-primary"></i>Coating Specifications
</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Coating Specifications"
data-bs-content="Cure Temperature and Cure Time come from the manufacturer's tech data sheet &mdash; they tell the oven operator the correct bake profile. Requires Clear Coat flags powders that need a clear top coat for durability or finish. Color Families tag this powder for filtering and matching in the quote wizard (e.g., a teal powder would get both Green and Blue).">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<div class="col-md-4">
<label asp-for="CureTemperatureF" class="form-label">Cure Temperature (°F)</label>
<input asp-for="CureTemperatureF" type="number" step="1" min="200" max="500" class="form-control" id="field-curetemp" placeholder="e.g., 375" />
<span asp-validation-for="CureTemperatureF" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="CureTimeMinutes" class="form-label">Cure Time (minutes)</label>
<input asp-for="CureTimeMinutes" type="number" step="1" min="1" max="120" class="form-control" id="field-curetime" placeholder="e.g., 15" />
<span asp-validation-for="CureTimeMinutes" class="text-danger"></span>
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check mb-2">
<input asp-for="RequiresClearCoat" class="form-check-input" type="checkbox" id="field-clearcoat" />
<label asp-for="RequiresClearCoat" class="form-check-label fw-medium">Requires Clear Coat</label>
<div class="form-text">Check if this powder needs a clear top coat</div>
</div>
</div>
<div class="col-12">
<div class="d-flex align-items-center gap-1 mb-1">
<label class="form-label mb-0">Color Families</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Color Families"
data-bs-content="Click chips to tag which base color families this powder belongs to. A metallic teal would be tagged Green and Blue; a bronze would be tagged Brown and Gold. These tags drive color-match filtering when customers request a specific color in a quote.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="ColorFamilies" type="hidden" id="field-colorfamilies" />
<div class="d-flex flex-wrap gap-2 mt-1" id="color-family-chips">
@foreach (var fam in new[] { "Red","Orange","Yellow","Green","Blue","Purple","Pink","Brown","Black","White","Gray","Silver","Gold","Bronze","Copper","Clear" })
{
<span class="badge color-family-chip" data-family="@fam"
style="cursor:pointer;font-size:.8rem;padding:.35em .7em;">@fam</span>
}
</div>
<div class="form-text">Click to toggle which primary color families this powder belongs to (e.g., Teal = Green + Blue)</div>
</div>
</div>
</div>
<!-- Stock Information -->
<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-boxes me-2 text-primary"></i>Stock 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="Stock Information"
data-bs-content="Quantity on Hand is your current starting stock. Reorder Point is the threshold at which the system shows a Low Stock alert &mdash; when quantity drops to this level it's time to reorder. Reorder Quantity is how much to order in one batch. Location is the shelf or bin label so staff can find the material quickly.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<div class="col-md-4">
<label asp-for="QuantityOnHand" class="form-label">Quantity on Hand</label>
<input asp-for="QuantityOnHand" type="number" step="0.01" min="0" value="0" class="form-control" />
<span asp-validation-for="QuantityOnHand" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="UnitOfMeasure" class="form-label">Unit of Measure</label>
<select asp-for="UnitOfMeasure" class="form-select" asp-items="@ViewBag.UnitsOfMeasure"></select>
<span asp-validation-for="UnitOfMeasure" class="text-danger"></span>
</div>
<div class="col-md-4">
<label asp-for="Location" class="form-label">Location</label>
<input asp-for="Location" class="form-control" placeholder="e.g., Shelf A3" />
<span asp-validation-for="Location" class="text-danger"></span>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="ReorderPoint" class="form-label mb-0">Reorder Point</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Reorder Point"
data-bs-content="When Quantity on Hand falls at or below this number a Low Stock warning appears on the item and in the Inventory summary. Set it high enough to cover your lead time &mdash; for example if delivery takes a week and you use 2 lb/day, set the reorder point to at least 14 lbs.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="ReorderPoint" type="number" step="0.01" min="0" value="0" class="form-control" />
<span asp-validation-for="ReorderPoint" class="text-danger"></span>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-1 mb-1">
<label asp-for="ReorderQuantity" class="form-label mb-0">Reorder Quantity</label>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Reorder Quantity"
data-bs-content="The standard quantity to order when restocking &mdash; typically a full case or pallet quantity from your supplier. This is informational and appears as a suggested order amount when the item is flagged as low stock.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<input asp-for="ReorderQuantity" type="number" step="0.01" min="0" value="0" class="form-control" />
<span asp-validation-for="ReorderQuantity" class="text-danger"></span>
</div>
</div>
</div>
<!-- Pricing & Vendor -->
<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-currency-dollar me-2 text-primary"></i>Pricing &amp; Vendor
</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Pricing &amp; Vendor"
data-bs-content="Unit Cost is what you pay per unit (lb, each, etc.) &mdash; this is used to calculate total stock value and feeds into job cost calculations. Primary Vendor links to your supplier record for quick reference and purchase ordering.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="UnitCost" class="form-label">Unit Cost</label>
<div class="input-group">
<span class="input-group-text">$</span>
<input asp-for="UnitCost" type="number" step="0.01" min="0" value="0" class="form-control" id="field-unitcost" />
</div>
<span asp-validation-for="UnitCost" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="PrimaryVendorId" class="form-label">Primary Vendor</label>
<select asp-for="PrimaryVendorId" class="form-select" id="field-vendor" asp-items="@ViewBag.Vendors"
data-quick-add-url="/Vendors/Create" data-quick-add-title="Add New Vendor">
<option value="">Select vendor</option>
<option value="__new__">+ Add New Vendor&hellip;</option>
</select>
<div id="vendor-filter-note" class="form-text d-none">
<i class="bi bi-funnel me-1 text-info"></i><span class="text-info">Showing vendors for this category.</span>
<a href="#" id="vendor-filter-clear" class="ms-1">Show all</a>
</div>
<span asp-validation-for="PrimaryVendorId" class="text-danger"></span>
</div>
</div>
</div>
<!-- Financial Accounts -->
<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-journal-bookmark me-2 text-primary"></i>Financial Accounts
</h5>
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Financial Accounts"
data-bs-content="Inventory Account is the asset account where the value of this stock sits on the balance sheet (e.g., 1200 Inventory &mdash; Powder). COGS Account is debited when this material is consumed on a job (e.g., 5000 Cost of Goods Sold). Leave blank to use the company defaults set in your Chart of Accounts.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<p class="text-muted small mb-3">Map this item to chart-of-account entries for proper balance sheet and cost tracking. Leave blank to use defaults.</p>
<div class="row g-3">
<div class="col-md-6">
<label asp-for="InventoryAccountId" class="form-label"></label>
<select asp-for="InventoryAccountId" class="form-select" asp-items="ViewBag.InventoryAccounts"
data-quick-add-url="/Accounts/Create?preSubType=4" data-quick-add-title="Add Inventory Account">
<option value="">(Default inventory account)</option>
<option value="__new__">+ Add New Account&hellip;</option>
</select>
<small class="form-text text-muted">Asset account where inventory value is tracked (e.g., 1200 Inventory - Powder).</small>
</div>
<div class="col-md-6">
<label asp-for="CogsAccountId" class="form-label"></label>
<select asp-for="CogsAccountId" class="form-select" asp-items="ViewBag.CogsAccounts"
data-quick-add-url="/Accounts/Create?preSubType=40" data-quick-add-title="Add COGS Account">
<option value="">(Default COGS account)</option>
<option value="__new__">+ Add New Account&hellip;</option>
</select>
<small class="form-text text-muted">Expense account debited when this material is consumed on a job.</small>
</div>
</div>
</div>
<!-- Notes & Status -->
<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="Notes" class="form-label">Additional Notes</label>
<textarea asp-for="Notes" class="form-control" rows="3"></textarea>
<span asp-validation-for="Notes" class="text-danger"></span>
</div>
<div class="col-12">
<div class="form-check">
<input asp-for="IsIncoming" class="form-check-input" id="IsIncoming" />
<label class="form-check-label fw-semibold" for="IsIncoming">
<i class="bi bi-truck me-1 text-warning"></i>Incoming / On Order
</label>
</div>
<small class="text-muted d-block mt-1">
Check this when the powder has been ordered but not yet received. It will appear with an "Incoming" badge in the inventory list and can be selected on quotes so staff can print QR codes while the powder is in transit. Pricing will charge for the full ordered quantity.
</small>
</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-check-circle me-2"></i>Create Item
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@if ((bool)(ViewBag.AiInventoryAssistEnabled ?? false))
{
<partial name="_LabelScanModal" />
}
@section Scripts {
<partial name="_ValidationScriptsPartial" />
<script>const inventoryFormIsCreate = true;</script>
<partial name="_InventoryColorFamilyScripts" />
<script src="~/js/inventory-catalog-lookup.js"></script>
@if ((bool)(ViewBag.AiInventoryAssistEnabled ?? false))
{
<script src="~/js/inventory-label-scan.js"></script>
}
<script>
(function () {
const categoryVendorMap = @Html.Raw(ViewBag.CategoryVendorMapJson ?? "{}");
const vendorSelect = document.getElementById('field-vendor');
const allVendorOptions = Array.from(vendorSelect.options).map(o => ({ v: o.value, t: o.text }));
function filterVendors(catId, forceAll) {
const vendorIds = (!forceAll && catId) ? (categoryVendorMap[catId] || []) : [];
const isFiltered = vendorIds.length > 0;
const currentVal = vendorSelect.value;
vendorSelect.innerHTML = '';
allVendorOptions.forEach(function (opt) {
if (!isFiltered || !opt.v || opt.v === '__new__' || vendorIds.includes(Number(opt.v)))
vendorSelect.add(new Option(opt.t, opt.v));
});
if (Array.from(vendorSelect.options).some(o => o.value === currentVal))
vendorSelect.value = currentVal;
document.getElementById('vendor-filter-note').classList.toggle('d-none', !isFiltered);
}
document.getElementById('field-category').addEventListener('change', function () {
filterVendors(this.value, false);
});
document.getElementById('vendor-filter-clear')?.addEventListener('click', function (e) {
e.preventDefault();
filterVendors(document.getElementById('field-category').value, true);
});
filterVendors(document.getElementById('field-category').value, false);
})();
</script>
}