fix(catalog): drill P4 VIN group nodes on live click (Ford/Nissan/Opel parent-gate gap)

getCategoryWithPartsInner had parent gates for PSA (psa::/json-vin-main-groups),
pcat (hasSubgroups), pl24 link_wid *Group* tables, Volvo (vin-group.action) and
emex — but P4 VIN/legacy group nodes whose link_wid is a positional code
("1","2","CAP1"…) and whose path is a *…group.action* drill (Ford
json-vin-sub-group, Nissan/Opel json-sub-group, etc.) matched none of them. They
fell through to the leaf parts path, fetched no parts and rendered a silent empty
panel — the dominant current '0 parça' cluster (mekanik/kaporta/Elektrikli aksam…
across many vehicles; PostHog-confirmed). Add a gate that drills any pl24
…group.action node (excluding Volvo's own vin-group.action branch) → subgroups or
retryable loadError. Lazy-heals existing flag-less rows, no reseed.

Also set has_subgroups at seed time: PSA scopes → true (always parents);
P4-legacy cats → true when linkPath is a …group.action drill. Fixes folder
signposting + lets prefetch pre-drill them instead of treating them as leaves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:48:04 +03:00
parent e91159003e
commit e6c8c9c32e

View File

@@ -120,6 +120,10 @@ export class CategoriesService {
externalId: s.code,
linkPath: s.linkPath || null,
linkWid: null as string | null,
// PSA scopes are always parents (they hold main-groups, never direct
// parts) — flag them so the tree UI signposts them as folders and the
// prefetch worker pre-drills them instead of treating them as leaves.
hasSubgroups: true,
source: "pl24" as const,
}));
dbCategories = await this.db
@@ -181,6 +185,10 @@ export class CategoriesService {
externalId: c.code,
linkPath: c.linkPath || null,
linkWid: c.linkWid || null,
// Mark P4 VIN/legacy group-drill nodes (…group.action) as parents so
// the tree UI shows them as folders and prefetch pre-drills them. Leaf
// pages (image-board.action/bom) stay null (unknown→leaf downstream).
hasSubgroups: c.linkPath?.includes("group.action") ? true : null,
source: "pl24" as const,
}));
@@ -1123,6 +1131,39 @@ export class CategoriesService {
// No subgroups → leaf; fall through to the parts path below.
}
// PL24 P4 VIN / legacy group node whose link_wid is a positional code
// ("1","2","CAP1"…) rather than a *Group* table, so the link_wid branch above
// misses it, and which is neither PSA- nor Volvo-special. Its linkPath is a
// sub-group / main-group drill action (…group.action — never a parts leaf like
// image-board.action). This is the Ford / Nissan / Opel / Hyundai-Kia analog of
// the PSA-parent-gate gap: without it these VIN scopes ("mekanik", "kaporta",
// "Elektrikli aksam"…) fell through every parent branch to the leaf parts path,
// fetched no parts and rendered a silent empty panel — confirmed the dominant
// current "0 parça" cluster. Volvo's vin-group.action keeps its own branch above
// (it may legitimately bottom out in parts), so exclude it here. Drill on demand:
// subgroups → parent; an empty drill is a transient upstream failure → retryable
// load error (a group node is never legitimately a parts leaf).
if (
category.source === "pl24" &&
category.linkPath?.includes("group.action") &&
!category.linkPath.includes("vin-group.action") &&
category.vehicleId
) {
const p4Children = await this.getChildren(categoryId);
const base = {
id: category.id,
name: category.name,
description: category.nameOriginal || null,
parentId: category.parentId || null,
parts: [],
schemaPics: [],
hotspots: [],
};
return p4Children.length > 0
? { ...base, children: p4Children }
: { ...base, loadError: true };
}
// EMEX Vehicle.aspx group node (linkWid="emex-group") — a parent, never a
// parts leaf. Return its children (seeded sub-groups, or Unit leaves drilled
// on demand by getChildren). Mirrors the pl24 group-node branch above; an