This commit adds the full end-to-end SKU pricing match flow for quote items: - Add new QuoteItem and PriceListRow fields for SKU and pricing metadata - Add sku_normalised to price_list for robust format-agnostic SKU matching - Refactor price list search to use modular search builder and filter helpers - Add database migrations, backfill scripts, and new package commands - Update catalogue search UI and fix quote list accidental click behavior - Add comprehensive unit, smoke, and end-to-end test suites - Update documentation for the new price search and extraction logic
88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
/* eslint-disable */
|
|
/**
|
|
* Smoke test — exercise the full tiered SKU + description matcher
|
|
* against live PB.
|
|
*
|
|
* Run with:
|
|
* env PB_ADMIN_EMAIL=… PB_ADMIN_PASSWORD=… NEXT_PUBLIC_POCKETBASE_URL=…
|
|
* npx tsx scripts/smoke/sku-matcher.ts
|
|
*/
|
|
import {
|
|
findPriceListBySku,
|
|
findFirstPriceListBySku,
|
|
classifyMatch,
|
|
explainMatch,
|
|
matchTierLabel,
|
|
normaliseSku,
|
|
escapeForPbString,
|
|
} from "../../src/lib/server/price-list-match";
|
|
import PocketBase from "pocketbase";
|
|
|
|
async function main() {
|
|
const URL = process.env.NEXT_PUBLIC_POCKETBASE_URL;
|
|
const ADMIN_EMAIL = process.env.PB_ADMIN_EMAIL;
|
|
const ADMIN_PASSWORD = process.env.PB_ADMIN_PASSWORD;
|
|
if (!URL || !ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
|
throw new Error("Set PB_ADMIN_EMAIL, PB_ADMIN_PASSWORD, NEXT_PUBLIC_POCKETBASE_URL.");
|
|
}
|
|
const pb = new PocketBase(URL);
|
|
await pb.collection("_superusers").authWithPassword(ADMIN_EMAIL, ADMIN_PASSWORD);
|
|
|
|
async function tryMatch(label: string, sku: string, opts: { tier3?: boolean } = {}) {
|
|
const matches = await findPriceListBySku(
|
|
sku,
|
|
{ enableTier3: opts.tier3 ?? true },
|
|
pb as unknown as PocketBase,
|
|
);
|
|
if (matches.length === 0) {
|
|
console.log(` ${label.padEnd(50)} (input: "${sku}") → no match`);
|
|
return;
|
|
}
|
|
const top = matches[0];
|
|
const tier = classifyMatch(sku, top);
|
|
const why = explainMatch(sku, top);
|
|
console.log(` ${label.padEnd(50)} (input: "${sku}")`);
|
|
console.log(` tier ${tier} ${matchTierLabel(tier)} — "${why}"`);
|
|
console.log(` matched: sku="${top.sku}" desc="${top.description}" cost=${top.cost_price} sell=${top.selling_price} (${top.supplier})`);
|
|
if (matches.length > 1) {
|
|
console.log(` ${matches.length - 1} more candidates (top-tier only: full list would be Tier-sorted by picker UI)`);
|
|
}
|
|
}
|
|
|
|
console.log("=== Tiered matcher smoke test ===");
|
|
console.log("\n--- Tier 1: exact equality ---");
|
|
await tryMatch("exact, as stored", "JEITD-06");
|
|
await tryMatch("case insensitive", "jeitd-06");
|
|
await tryMatch("without dashes", "JEITD06", { tier3: false });
|
|
|
|
console.log("\n--- Tier 2: SKU substring fallback ---");
|
|
await tryMatch("partial prefix", "JEITD", { tier3: false });
|
|
await tryMatch("brand prefix in SKU", "TS-DRL", { tier3: false });
|
|
|
|
console.log("\n--- Tier 3: description / brand fuzzy (Tier 3 enabled) ---");
|
|
await tryMatch("brand-only search", "JEISA");
|
|
await tryMatch("category-ish fuzzy", "TURBO");
|
|
await tryMatch("country-ish fuzzy", "WESTSCOTT");
|
|
|
|
console.log("\n--- Tier-3 disabled (bulk auto-fill rejects fuzzy) ---");
|
|
await tryMatch("no fuzzy — empty", "bosch grinder", { tier3: false });
|
|
await tryMatch("no fuzzy — empty", "m20 bolt", { tier3: false });
|
|
|
|
console.log("\n--- Tier 3 ENABLED (picker surfaces them) ---");
|
|
await tryMatch("with fuzzy — candidates", "bosch grinder");
|
|
await tryMatch("with fuzzy — candidates", "m20 bolt");
|
|
|
|
console.log("\n--- Sanity: pure helpers ---");
|
|
console.log(` normaliseSku("jeitd-06") = "${normaliseSku("jeitd-06")}"`);
|
|
console.log(` escapeForPbString('Acme "Pro" line') = "${escapeForPbString('Acme "Pro" line')}"`);
|
|
console.log(` classifyMatch("JEITD-06", { sku: "JEITD-06", sku_normalised: "JEITD06" }) = ${classifyMatch("JEITD-06", { sku: "JEITD-06", sku_normalised: "JEITD06" })}`);
|
|
console.log(` classifyMatch("JEITD", { sku: "JEITD-D11", sku_normalised: "JEITDD11" }) = ${classifyMatch("JEITD", { sku: "JEITD-D11", sku_normalised: "JEITDD11" })}`);
|
|
|
|
await tryMatch("empty string skipped", "");
|
|
await tryMatch("garbage no match (tier3 ON)", "ZZZGARBAGE-XYZ");
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|