This commit is contained in:
seb
2026-10-08 16:33:49 +02:00
parent 8111c3f377
commit bc323f2891

View File

@@ -91,6 +91,51 @@ export function parseCsvRecords(text) {
return records; return records;
} }
/**
* Extracts or derives a variant attribute value for a child article
* based on its name relative to its parent name, or SKU suffixes.
*
* @param {string} parentName
* @param {string} childName
* @param {string} childSku
* @param {string} parentSku
* @returns {string}
*/
export function extractVariantValue(parentName, childName, childSku, parentSku) {
let pName = (parentName || '').replace(/[©®™]/g, '').trim();
let cName = (childName || '').replace(/[©®™]/g, '').trim();
if (pName && cName) {
// Child name starts with parent name
if (cName.toLowerCase().startsWith(pName.toLowerCase())) {
let val = cName.slice(pName.length).trim().replace(/^[-–—:/,\s]+/, '').trim();
if (val) return val;
}
// Match common leading tokens
const pWords = pName.toLowerCase().split(/\s+/);
const cWords = cName.split(/\s+/);
if (cWords.length > 1 && pWords[0] === cWords[0].toLowerCase()) {
let matchCount = 0;
while (matchCount < pWords.length && matchCount < cWords.length && pWords[matchCount] === cWords[matchCount].toLowerCase()) {
matchCount++;
}
if (matchCount > 0) {
const remaining = cWords.slice(matchCount).join(' ').trim().replace(/^[-–—:/,\s]+/, '').trim();
if (remaining) return remaining;
}
}
}
// SKU suffix: "42001505-1" with parent "42001505" -> "1"
if (parentSku && childSku && childSku.startsWith(parentSku)) {
let suffix = childSku.slice(parentSku.length).trim().replace(/^[-_]+/, '').trim();
if (suffix) return suffix;
}
if (cName && cName !== pName) return cName;
return childSku || 'Standard';
}
/** /**
* Executes full catalog import from preserved files in importDir. * Executes full catalog import from preserved files in importDir.
* *
@@ -502,23 +547,108 @@ export async function executeCompanyImport({
console.log(`[Import] Linking ${variantChildren.length} child variants to parents...`); console.log(`[Import] Linking ${variantChildren.length} child variants to parents...`);
onProgress('variants', 0, variantChildren.length, 'Linking variants'); onProgress('variants', 0, variantChildren.length, 'Linking variants');
let variantsLinked = 0; // Ensure a default attribute (e.g. "Variante") exists for variant parents
for (let i = 0; i < variantChildren.length; i++) { let defaultVariantAttrId = null;
const child = variantChildren[i]; try {
const childSku = String(child.cArtNr || '').trim(); const attrList = await store.attributename.list(googleAuthId);
const existingAttr = attrList.find(
(a) => (a.name || '').trim().toLowerCase() === 'variante'
);
if (existingAttr) {
defaultVariantAttrId = existingAttr.id;
} else {
const createdAttr = await store.attributename.create(googleAuthId, {
name: 'Variante',
exclusive: true,
});
defaultVariantAttrId = createdAttr.id;
}
} catch (err) {
log.warn('Could not ensure default "Variante" attribute:', err.message);
}
// Group children by parent SKU
const parentChildrenMap = new Map();
for (const child of variantChildren) {
const parentJtlId = String(child.kVaterArtikel || '').trim(); const parentJtlId = String(child.kVaterArtikel || '').trim();
const parentSku = jtlArtIdToSku.get(parentJtlId); const parentSku = jtlArtIdToSku.get(parentJtlId);
const childSku = String(child.cArtNr || '').trim();
if (!parentSku || !childSku || parentSku === childSku) continue;
if (childSku && parentSku && childSku !== parentSku) { if (!parentChildrenMap.has(parentSku)) {
try { parentChildrenMap.set(parentSku, []);
await store.article.setParentBySku(googleAuthId, childSku, parentSku); }
variantsLinked++; parentChildrenMap.get(parentSku).push(child);
} catch (err) { }
console.warn(`[Import] Failed to link child ${childSku} to parent ${parentSku}:`, err.message);
let variantsLinked = 0;
let processedCount = 0;
for (const [parentSku, children] of parentChildrenMap.entries()) {
let parentDetail = null;
try {
const res = await store.article.getBySku(googleAuthId, parentSku);
parentDetail = res.article;
} catch (err) {
log.warn(`[Import] Could not load parent ${parentSku}:`, err.message);
processedCount += children.length;
continue;
}
// Ensure parent has is_parent = 1 and parentattributename_id set
let parentAttrId = parentDetail?.parentAttributeNameId;
if (!parentDetail?.isParent || !parentAttrId) {
if (defaultVariantAttrId) {
try {
await store.article.toggleParent(googleAuthId, parentSku, {
isParent: true,
attributeNameId: defaultVariantAttrId,
});
parentAttrId = defaultVariantAttrId;
} catch (err) {
log.warn(`[Import] Could not toggle parent for ${parentSku}:`, err.message);
}
} }
} }
if (i % 20 === 0 || i === variantChildren.length - 1) {
onProgress('variants', i + 1, variantChildren.length, `${childSku} -> ${parentSku}`); const parentName = parentDetail?.name || '';
const usedValues = new Set();
for (const child of children) {
processedCount++;
const childSku = String(child.cArtNr || '').trim();
let childName = '';
try {
const desc = JSON.parse(child.descriptions || '[]');
childName = (Array.isArray(desc) ? desc[0]?.cName : desc?.cName) || '';
} catch {}
let val = extractVariantValue(parentName, childName, childSku, parentSku);
if (usedValues.has(val.toLowerCase())) {
val = `${val} (${childSku})`;
}
usedValues.add(val.toLowerCase());
try {
if (parentAttrId) {
await store.article.attachChild(googleAuthId, parentSku, {
sku: childSku,
value: val,
reparent: true,
});
variantsLinked++;
} else {
// Fallback to setParentBySku if toggleParent failed
await store.article.setParentBySku(googleAuthId, childSku, parentSku);
variantsLinked++;
}
} catch (err) {
console.warn(`[Import] Failed to attach child ${childSku} to parent ${parentSku}:`, err.message);
}
if (processedCount % 20 === 0 || processedCount === variantChildren.length) {
onProgress('variants', processedCount, variantChildren.length, `${childSku} -> ${parentSku}`);
}
} }
} }
console.log(`[Import] Linked ${variantsLinked}/${variantChildren.length} variants`); console.log(`[Import] Linked ${variantsLinked}/${variantChildren.length} variants`);