feat(flexoptix): populate flexoptix_product_map from API compatibility data
Parse Flexoptix API compat entries (compatible_to_vendor + original_part_number) and batch-upsert into flexoptix_product_map during each catalog sync. - Fix extractCompatibility() to recognise compatibleToVendor key from API - Add batchUpsertProductMap(): 500-row batch INSERTs with ON CONFLICT update - Add writeProductMapEntries() per product, called after price/stock writes - Track mapWrites in FlexoptixSyncResult and log in completion message First sync: 9254 upserts → 2903 unique rows, 95 vendors, 343 Flexoptix SKUs. Example: CWDM-SFP10G-1330 (Cisco) → P.1696.10.D.I (EUR 150.77, SFP+ 10G).
This commit is contained in:
parent
764f16d68f
commit
eb2888cb0e
@ -81,6 +81,7 @@ export interface FlexoptixSyncResult {
|
||||
skipped: number;
|
||||
priceWrites: number;
|
||||
stockWrites: number;
|
||||
mapWrites: number;
|
||||
}
|
||||
|
||||
// ── Generic helpers ────────────────────────────────────────────────────────
|
||||
@ -239,7 +240,7 @@ function extractCompatibility(row: JsonRecord): CatalogProduct["compatibility"]
|
||||
const rows = Array.isArray(rawCompat) ? rawCompat.filter(isRecord) : [];
|
||||
return rows.flatMap(entry => {
|
||||
const flat = flatLookup(entry);
|
||||
const vendor = asString(pick(flat, ["vendor", "manufacturer", "brand", "systemVendor"]));
|
||||
const vendor = asString(pick(flat, ["vendor", "manufacturer", "brand", "systemVendor", "compatibleToVendor"]));
|
||||
if (!vendor) return [];
|
||||
return [{
|
||||
vendor,
|
||||
@ -362,7 +363,7 @@ async function importProduct(
|
||||
speedGbps: product.optics.speedGbps ?? undefined,
|
||||
speed: speedLabel(product.optics.speedGbps),
|
||||
reachMeters: product.optics.reachM ?? undefined,
|
||||
reachLabel: reachLabel(product.optics.reachM),
|
||||
reachLabel: reachLabel(product.optics.reachM) ?? undefined,
|
||||
fiberType: product.optics.fiberType ?? undefined,
|
||||
wavelengths: product.optics.wavelengthNm === null ? undefined : `${product.optics.wavelengthNm}nm`,
|
||||
category: categoryFor(product),
|
||||
@ -456,6 +457,22 @@ async function importProduct(
|
||||
return { priceWritten, stockWritten };
|
||||
}
|
||||
|
||||
async function writeProductMapEntries(product: CatalogProduct): Promise<number> {
|
||||
const entries = product.compatibility
|
||||
.filter(c => c.coding !== null && c.vendor !== null)
|
||||
.map(c => ({
|
||||
oemPartNumber: c.coding!,
|
||||
oemVendor: c.vendor,
|
||||
flexoptixSku: product.sku,
|
||||
priceEur: product.price.amount,
|
||||
formFactor: product.optics.formFactor,
|
||||
speedGbps: product.optics.speedGbps,
|
||||
reachLabel: reachLabel(product.optics.reachM) ?? null,
|
||||
fiberType: product.optics.fiberType,
|
||||
}));
|
||||
return batchUpsertProductMap(entries);
|
||||
}
|
||||
|
||||
// ── API client ─────────────────────────────────────────────────────────────
|
||||
|
||||
function validateEnv(): { baseUrl: string; username: string | null; password: string | null; token: string | null } {
|
||||
@ -585,6 +602,54 @@ async function fetchSelfConfigurePrices(
|
||||
return priceMap;
|
||||
}
|
||||
|
||||
|
||||
// ── Product map batch upsert ─────────────────────────────────────────────────
|
||||
// Writes OEM vendor coding entries into flexoptix_product_map.
|
||||
// One row per (oem_part_number, oem_vendor) pair — ON CONFLICT updates specs.
|
||||
|
||||
async function batchUpsertProductMap(entries: Array<{
|
||||
oemPartNumber: string;
|
||||
oemVendor: string;
|
||||
flexoptixSku: string;
|
||||
priceEur: number | null;
|
||||
formFactor: string | null;
|
||||
speedGbps: number | null;
|
||||
reachLabel: string | null;
|
||||
fiberType: string | null;
|
||||
}>): Promise<number> {
|
||||
if (entries.length === 0) return 0;
|
||||
const BATCH_SIZE = 500;
|
||||
let written = 0;
|
||||
for (let i = 0; i < entries.length; i += BATCH_SIZE) {
|
||||
const chunk = entries.slice(i, i + BATCH_SIZE);
|
||||
const placeholders: string[] = [];
|
||||
const params: unknown[] = [];
|
||||
let n = 1;
|
||||
for (const e of chunk) {
|
||||
placeholders.push(`($${n},$${n+1},$${n+2},$${n+3},$${n+4},$${n+5},$${n+6},$${n+7},'exact',NOW())`);
|
||||
params.push(e.oemPartNumber, e.oemVendor, e.flexoptixSku, e.priceEur, e.formFactor, e.speedGbps, e.reachLabel, e.fiberType);
|
||||
n += 8;
|
||||
}
|
||||
await pool.query(`
|
||||
INSERT INTO flexoptix_product_map
|
||||
(oem_part_number, oem_vendor, flexoptix_sku, flexoptix_price_eur,
|
||||
form_factor, speed_gbps, reach_label, fiber_type, match_type, last_verified)
|
||||
VALUES ${placeholders.join(',')}
|
||||
ON CONFLICT (oem_part_number, oem_vendor) DO UPDATE SET
|
||||
flexoptix_sku = EXCLUDED.flexoptix_sku,
|
||||
flexoptix_price_eur = EXCLUDED.flexoptix_price_eur,
|
||||
form_factor = EXCLUDED.form_factor,
|
||||
speed_gbps = EXCLUDED.speed_gbps,
|
||||
reach_label = EXCLUDED.reach_label,
|
||||
fiber_type = EXCLUDED.fiber_type,
|
||||
last_verified = EXCLUDED.last_verified,
|
||||
updated_at = NOW()
|
||||
`, params);
|
||||
written += chunk.length;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
export async function syncFlexoptixCatalog(): Promise<FlexoptixSyncResult> {
|
||||
const { baseUrl, username, password, token } = validateEnv();
|
||||
const timeoutMs = parseInt(process.env["FLEXOPTIX_API_TIMEOUT_MS"]?.trim() ?? "30000", 10);
|
||||
@ -634,19 +699,21 @@ export async function syncFlexoptixCatalog(): Promise<FlexoptixSyncResult> {
|
||||
|
||||
let priceWrites = 0;
|
||||
let stockWrites = 0;
|
||||
let mapWrites = 0;
|
||||
|
||||
for (const product of importable) {
|
||||
try {
|
||||
const result = await importProduct(product, vendorId);
|
||||
if (result.priceWritten) priceWrites++;
|
||||
if (result.stockWritten) stockWrites++;
|
||||
mapWrites += await writeProductMapEntries(product);
|
||||
} catch (err: unknown) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.warn(`[${new Date().toISOString()}] Flexoptix import error (${product.sku}): ${message.slice(0, 100)}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[${new Date().toISOString()}] Flexoptix API sync complete: ${importable.length} products, ${priceWrites} price writes, ${stockWrites} stock writes`);
|
||||
console.log(`[${new Date().toISOString()}] Flexoptix API sync complete: ${importable.length} products, ${priceWrites} price writes, ${stockWrites} stock writes, ${mapWrites} product-map upserts`);
|
||||
|
||||
return {
|
||||
fetched: rawRows.length,
|
||||
@ -654,5 +721,6 @@ export async function syncFlexoptixCatalog(): Promise<FlexoptixSyncResult> {
|
||||
skipped,
|
||||
priceWrites,
|
||||
stockWrites,
|
||||
mapWrites,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user