687aedf7a4
Inventory vendor auto-select: match the dropdown off the Manufacturer field (almost always populated and equal to the vendor for the shop's distributors) instead of the AI's price-conditional vendorName, which was only returned when a price was scraped. Centralizes the logic in a shared inventory-vendor-match.js used by catalog lookup, AI lookup, label scan, and manual entry; skips brands sold by multiple distributors (PPG, KP Pigments) so those stay manual. Account dropdowns filtered by sub-type now filter by parent AccountType, so accounts a company classifies under a non-standard sub-type still appear: Inventory account (Asset), AP account (Liability), pay-from/bank and Bank Reconciliation pickers (Asset + Liability). Deposit account is now a user-selectable dropdown on the Job and Quote deposit modals (Asset + Liability accounts) instead of a silent auto-pick of the first Checking/Cash account; falls back to the old behavior when left blank, and validates the chosen account belongs to the company. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
3.2 KiB
JavaScript
68 lines
3.2 KiB
JavaScript
/**
|
|
* Shared vendor-dropdown auto-select for the Inventory Create/Edit forms.
|
|
*
|
|
* Why this exists: catalog lookup, AI lookup, label scan, and manual manufacturer entry
|
|
* all need to pick the right Vendor option, and they used to each carry their own copy of
|
|
* the matching logic. They disagreed on WHAT to match on — the AI path keyed off the
|
|
* price-derived `vendorName` (which is null unless a price was scraped), so the vendor
|
|
* only got selected "sometimes". This centralizes the rule:
|
|
*
|
|
* For ~95% of powders the manufacturer IS the vendor (Prismatic, Columbia,
|
|
* All Powder Paints, Tiger, Powder Buy The Pound). So match on the Manufacturer
|
|
* field first — it's almost always populated — and only fall back to the
|
|
* AI/catalog-supplied vendor name when the manufacturer is blank.
|
|
*
|
|
* Brands sold by more than one distributor (e.g. PPG, KP Pigments) are intentionally
|
|
* skipped so the user picks the vendor manually rather than getting a wrong guess.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
// Brands carried by multiple distributors — never auto-pick a vendor for these.
|
|
// Lowercase; matched as a substring against the manufacturer name. Extend as needed.
|
|
const AMBIGUOUS_BRANDS = ['ppg', 'kp pigments', 'kp pigment'];
|
|
|
|
function normalize(s) {
|
|
return (s || '').toLowerCase().trim();
|
|
}
|
|
|
|
function isAmbiguousBrand(name) {
|
|
const n = normalize(name);
|
|
return n.length > 0 && AMBIGUOUS_BRANDS.some(b => n.includes(b));
|
|
}
|
|
|
|
/**
|
|
* Selects the vendor dropdown option that best matches a manufacturer/vendor name.
|
|
*
|
|
* @param {HTMLSelectElement} vendorSelect the #field-vendor element
|
|
* @param {string} manufacturerName primary name to match on (the Manufacturer field)
|
|
* @param {string} fallbackVendorName AI/catalog vendor name, used only if manufacturer is blank
|
|
* @param {{force?: boolean}} [opts] force=true overrides an existing selection (bad-match retry)
|
|
* @returns {boolean} true if a vendor option was selected.
|
|
*/
|
|
window.matchInventoryVendor = function (vendorSelect, manufacturerName, fallbackVendorName, opts) {
|
|
opts = opts || {};
|
|
if (!vendorSelect) return false;
|
|
// Don't clobber a choice the user (or a prior fill) already made, unless forcing a re-fill.
|
|
if (vendorSelect.value && !opts.force) return false;
|
|
|
|
// Manufacturer drives the match; the price-derived vendor name is only a fallback.
|
|
const name = normalize(manufacturerName) || normalize(fallbackVendorName);
|
|
if (!name) return false;
|
|
|
|
// Brands sold by multiple distributors stay manual — don't guess.
|
|
if (isAmbiguousBrand(name)) return false;
|
|
|
|
const match = Array.from(vendorSelect.options).find(function (o) {
|
|
const t = normalize(o.text);
|
|
// Skip the placeholder and the "Add new vendor" sentinel; require a real name to
|
|
// avoid spurious substring hits (e.g. empty option text matches everything).
|
|
if (!o.value || o.value === '__new__' || t.length < 3) return false;
|
|
return t.includes(name) || name.includes(t);
|
|
});
|
|
|
|
if (match) { vendorSelect.value = match.value; return true; }
|
|
return false;
|
|
};
|
|
})();
|