fix(pl24): Volvo subgroup drilling via vin-group.action HTML
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Volvo's VIN catalog is a 3-level vin-group.action?group1=…[&group2=…] HTML tree; PL24's json-vin-*-group.action JSON endpoints now 404. Add HTML subgroup extraction (keep links one group-level deeper than the current path) in ford-legacy fetchSubGroupsByPath, and route vin-group.action?group1= nodes through getChildren (drill-first, fall back to parts) in getCategoryWithParts. Recovers Volvo group1→group2 navigation. Leaf parts handled separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -895,6 +895,34 @@ export class CategoriesService {
|
||||
: { ...base, loadError: true };
|
||||
}
|
||||
|
||||
// Volvo VIN-indexed catalog: vin-group.action?group1=…[&group2=…] nodes are
|
||||
// parents whose subgroups live in the same HTML (PL24's old JSON
|
||||
// json-vin-main-group.action path now 404s). They carry no link_wid, so the
|
||||
// group-node branch above misses them. Drill: if subgroups come back it's a
|
||||
// parent; if none, fall through to the leaf parts path (the deepest group
|
||||
// level carries an image-board + parts).
|
||||
if (
|
||||
category.source === "pl24" &&
|
||||
category.linkPath?.includes("vin-group.action") &&
|
||||
category.linkPath?.includes("group1=") &&
|
||||
category.vehicleId
|
||||
) {
|
||||
const volvoChildren = await this.getChildren(categoryId);
|
||||
if (volvoChildren.length > 0) {
|
||||
return {
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
description: category.nameOriginal || null,
|
||||
parentId: category.parentId || null,
|
||||
parts: [],
|
||||
schemaPics: [],
|
||||
hotspots: [],
|
||||
children: volvoChildren,
|
||||
};
|
||||
}
|
||||
// No subgroups → leaf; fall through to the parts path below.
|
||||
}
|
||||
|
||||
// Leaf category — get or fetch parts
|
||||
let loadError = false;
|
||||
let discoveredChildren: any[] = [];
|
||||
@@ -996,7 +1024,11 @@ export class CategoriesService {
|
||||
}));
|
||||
|
||||
if (allParts.length > 0) {
|
||||
dbParts = await this.db.insert(parts).values(allParts).returning();
|
||||
dbParts = await this.db
|
||||
.insert(parts)
|
||||
.values(allParts)
|
||||
.onConflictDoNothing()
|
||||
.returning();
|
||||
this.logger.log(
|
||||
`Stored ${dbParts.length} PartsCatalogs parts for category ${categoryId}`,
|
||||
);
|
||||
@@ -1146,7 +1178,11 @@ export class CategoriesService {
|
||||
};
|
||||
});
|
||||
|
||||
dbParts = await this.db.insert(parts).values(insertData).returning();
|
||||
dbParts = await this.db
|
||||
.insert(parts)
|
||||
.values(insertData)
|
||||
.onConflictDoNothing()
|
||||
.returning();
|
||||
this.logger.log(`Stored ${dbParts.length} EMEX parts for category ${categoryId}`);
|
||||
}
|
||||
|
||||
@@ -1242,7 +1278,11 @@ export class CategoriesService {
|
||||
source: "pl24" as const,
|
||||
}));
|
||||
|
||||
dbParts = await this.db.insert(parts).values(insertData).returning();
|
||||
dbParts = await this.db
|
||||
.insert(parts)
|
||||
.values(insertData)
|
||||
.onConflictDoNothing()
|
||||
.returning();
|
||||
}
|
||||
|
||||
// Store schema image if available
|
||||
|
||||
@@ -130,6 +130,43 @@ export class PL24FordLegacyService {
|
||||
const html = await this.fetchP4Page(linkPath, serviceName, false, account);
|
||||
if (!html) return [];
|
||||
|
||||
// Volvo (VIN-indexed legacy catalog): vin-group.action?group1=…[&group2=…]
|
||||
// HTML pages list the next group level as <a href="vin-group.action?…&
|
||||
// groupN=…"> links. PL24's old JSON json-vin-*-group.action endpoints now
|
||||
// 404, so we scrape the HTML here. Keep only links with MORE groupN=
|
||||
// params than the current path (one level deeper); drop sibling/parent nav
|
||||
// and the VIN-dialog crumb. An empty result ⇒ this node is a leaf (parts).
|
||||
if (linkPath.includes("vin-group.action")) {
|
||||
const cfg = getServiceConfig(serviceName);
|
||||
const basePath = cfg ? `${cfg.basePath}/${serviceName}` : `/volvo/${serviceName}`;
|
||||
const parentDepth = [...linkPath.matchAll(/\bgroup\d+=/g)].length;
|
||||
const seenHref = new Set<string>();
|
||||
const subs: PL24MainGroup[] = [];
|
||||
const anchorRe =
|
||||
/<a[^>]+href="([^"]*vin-group\.action\?[^"]*group\d+=[^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
|
||||
for (const m of html.matchAll(anchorRe)) {
|
||||
const href = m[1].replace(/&/g, "&");
|
||||
if (href.includes("openVinDialog")) continue;
|
||||
const childDepth = [...href.matchAll(/\bgroup\d+=/g)].length;
|
||||
if (childDepth <= parentDepth) continue; // sibling/parent nav, not a child
|
||||
if (seenHref.has(href)) continue;
|
||||
seenHref.add(href);
|
||||
const name = m[2]
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.replace(/^\d+\s+/, "");
|
||||
if (name.length < 2) continue;
|
||||
const lp = href.startsWith("/") ? href : `${basePath}/${href}`;
|
||||
subs.push({ id: href, code: href, name, linkPath: lp });
|
||||
}
|
||||
if (subs.length > 0) {
|
||||
await this.redis.setJson(cacheKey, subs, 86400);
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
// Opel/Ford: json-main-group.action → { vCfgData: [...] } (Opel) OR { maingroups: [...] } (Ford)
|
||||
if (linkPath.includes("json-main-group.action")) {
|
||||
const basePath = linkPath.substring(0, linkPath.lastIndexOf("/") + 1);
|
||||
|
||||
Reference in New Issue
Block a user