diff --git a/prerender/seo/feeds.cjs b/prerender/seo/feeds.cjs index a1c5de9..cd8c9b6 100644 --- a/prerender/seo/feeds.cjs +++ b/prerender/seo/feeds.cjs @@ -299,7 +299,24 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { let processedCount = 0; let skippedCount = 0; - // Track products with missing data for logging + // Track skip reasons with counts and product lists + const skipReasons = { + noProductOrSeoName: { count: 0, products: [] }, + excludedCategory: { count: 0, products: [] }, + excludedTermsTitle: { count: 0, products: [] }, + excludedTermsDescription: { count: 0, products: [] }, + missingGTIN: { count: 0, products: [] }, + invalidGTINChecksum: { count: 0, products: [] }, + missingPicture: { count: 0, products: [] }, + missingWeight: { count: 0, products: [] }, + insufficientDescription: { count: 0, products: [] }, + nameTooShort: { count: 0, products: [] }, + outOfStock: { count: 0, products: [] }, + zeroPriceOrInvalid: { count: 0, products: [] }, + processingError: { count: 0, products: [] } + }; + + // Legacy arrays for backward compatibility const productsNeedingWeight = []; const productsNeedingDescription = []; @@ -317,6 +334,12 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { // Skip products without essential data if (!product || !product.seoName) { skippedCount++; + skipReasons.noProductOrSeoName.count++; + skipReasons.noProductOrSeoName.products.push({ + id: product?.articleNumber || 'N/A', + name: product?.name || 'N/A', + url: product?.seoName ? `/Artikel/${product.seoName}` : 'N/A' + }); return; } @@ -324,6 +347,13 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { const productCategoryId = product.categoryId || product.category_id || product.category || null; if (productCategoryId && skipCategoryIds.includes(parseInt(productCategoryId))) { skippedCount++; + skipReasons.excludedCategory.count++; + skipReasons.excludedCategory.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + categoryId: productCategoryId, + url: `/Artikel/${product.seoName}` + }); return; } @@ -339,20 +369,42 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { }; // Check title for excluded terms - if (excludedTerms.title.some(term => productTitle.includes(term))) { + const excludedTitleTerm = excludedTerms.title.find(term => productTitle.includes(term)); + if (excludedTitleTerm) { skippedCount++; + skipReasons.excludedTermsTitle.count++; + skipReasons.excludedTermsTitle.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + term: excludedTitleTerm, + url: `/Artikel/${product.seoName}` + }); return; } // Check description for excluded terms - if (excludedTerms.description.some(term => productDescription.toLowerCase().includes(term))) { + const excludedDescTerm = excludedTerms.description.find(term => productDescription.toLowerCase().includes(term)); + if (excludedDescTerm) { skippedCount++; + skipReasons.excludedTermsDescription.count++; + skipReasons.excludedTermsDescription.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + term: excludedDescTerm, + url: `/Artikel/${product.seoName}` + }); return; } // Skip products without GTIN or with invalid GTIN if (!product.gtin || !product.gtin.toString().trim()) { skippedCount++; + skipReasons.missingGTIN.count++; + skipReasons.missingGTIN.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + url: `/Artikel/${product.seoName}` + }); return; } @@ -401,23 +453,39 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { if (!isValidGTIN(gtinString)) { skippedCount++; + skipReasons.invalidGTINChecksum.count++; + skipReasons.invalidGTINChecksum.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + gtin: gtinString, + url: `/Artikel/${product.seoName}` + }); return; } // Skip products without pictures if (!product.pictureList || !product.pictureList.trim()) { skippedCount++; + skipReasons.missingPicture.count++; + skipReasons.missingPicture.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + url: `/Artikel/${product.seoName}` + }); return; } // Check if product has weight data - validate BEFORE building XML if (!product.weight || isNaN(product.weight)) { // Track products without weight - productsNeedingWeight.push({ + const productInfo = { id: product.articleNumber || product.seoName, name: product.name || 'Unnamed', url: `/Artikel/${product.seoName}` - }); + }; + productsNeedingWeight.push(productInfo); + skipReasons.missingWeight.count++; + skipReasons.missingWeight.products.push(productInfo); skippedCount++; return; } @@ -425,12 +493,15 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { // Check if description is missing or too short (less than 20 characters) - skip if insufficient const originalDescription = productDescription ? cleanTextContent(productDescription) : ''; if (!originalDescription || originalDescription.length < 20) { - productsNeedingDescription.push({ + const productInfo = { id: product.articleNumber || product.seoName, name: product.name || 'Unnamed', currentDescription: originalDescription || 'NONE', url: `/Artikel/${product.seoName}` - }); + }; + productsNeedingDescription.push(productInfo); + skipReasons.insufficientDescription.count++; + skipReasons.insufficientDescription.products.push(productInfo); skippedCount++; return; } @@ -446,6 +517,13 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { // Validate essential fields if (!cleanName || cleanName.length < 2) { skippedCount++; + skipReasons.nameTooShort.count++; + skipReasons.nameTooShort.products.push({ + id: product.articleNumber || product.seoName, + name: rawName, + cleanedName: cleanName, + url: `/Artikel/${product.seoName}` + }); return; } @@ -470,6 +548,12 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { // Skip products that are out of stock if (!product.available) { skippedCount++; + skipReasons.outOfStock.count++; + skipReasons.outOfStock.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + url: `/Artikel/${product.seoName}` + }); return; } @@ -481,6 +565,13 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { // Skip products with price == 0 if (!product.price || parseFloat(product.price) === 0) { skippedCount++; + skipReasons.zeroPriceOrInvalid.count++; + skipReasons.zeroPriceOrInvalid.products.push({ + id: product.articleNumber || product.seoName, + name: product.name || 'N/A', + price: product.price, + url: `/Artikel/${product.seoName}` + }); return; } @@ -547,6 +638,13 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { } catch (itemError) { console.log(` ⚠️ Skipped product ${index + 1}: ${itemError.message}`); skippedCount++; + skipReasons.processingError.count++; + skipReasons.processingError.products.push({ + id: product?.articleNumber || product?.seoName || 'N/A', + name: product?.name || 'N/A', + error: itemError.message, + url: product?.seoName ? `/Artikel/${product.seoName}` : 'N/A' + }); } }); @@ -554,7 +652,43 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { `; - console.log(` 📊 Processing summary: ${processedCount} products included, ${skippedCount} skipped`); + console.log(`\n 📊 Processing summary: ${processedCount} products included, ${skippedCount} skipped`); + + // Display skip reason totals + console.log(`\n 📋 Skip Reasons Breakdown:`); + console.log(` ────────────────────────────────────────────────────────────`); + + const skipReasonLabels = { + noProductOrSeoName: 'No Product or SEO Name', + excludedCategory: 'Excluded Category', + excludedTermsTitle: 'Excluded Terms in Title', + excludedTermsDescription: 'Excluded Terms in Description', + missingGTIN: 'Missing GTIN', + invalidGTINChecksum: 'Invalid GTIN Checksum', + missingPicture: 'Missing Picture', + missingWeight: 'Missing Weight', + insufficientDescription: 'Insufficient Description', + nameTooShort: 'Name Too Short', + outOfStock: 'Out of Stock', + zeroPriceOrInvalid: 'Zero or Invalid Price', + processingError: 'Processing Error' + }; + + let hasAnySkips = false; + Object.entries(skipReasons).forEach(([key, data]) => { + if (data.count > 0) { + hasAnySkips = true; + const label = skipReasonLabels[key] || key; + console.log(` • ${label}: ${data.count}`); + } + }); + + if (!hasAnySkips) { + console.log(` ✅ No products were skipped`); + } + + console.log(` ────────────────────────────────────────────────────────────`); + console.log(` Total: ${skippedCount} products skipped\n`); // Write log files for products needing attention const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); @@ -565,7 +699,56 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => { fs.mkdirSync(logsDir, { recursive: true }); } - // Write missing weight log + // Write comprehensive skip reasons log + const skipLogPath = path.join(logsDir, `skip-reasons-${timestamp}.log`); + let skipLogContent = `# Product Skip Reasons Report +# Generated: ${new Date().toISOString()} +# Total products processed: ${processedCount} +# Total products skipped: ${skippedCount} +# Base URL: ${baseUrl} + +`; + + Object.entries(skipReasons).forEach(([key, data]) => { + if (data.count > 0) { + const label = skipReasonLabels[key] || key; + skipLogContent += `\n## ${label} (${data.count} products)\n`; + skipLogContent += `${'='.repeat(80)}\n`; + + data.products.forEach(product => { + skipLogContent += `ID: ${product.id}\n`; + skipLogContent += `Name: ${product.name}\n`; + if (product.categoryId !== undefined) { + skipLogContent += `Category ID: ${product.categoryId}\n`; + } + if (product.term !== undefined) { + skipLogContent += `Excluded Term: ${product.term}\n`; + } + if (product.gtin !== undefined) { + skipLogContent += `GTIN: ${product.gtin}\n`; + } + if (product.currentDescription !== undefined) { + skipLogContent += `Current Description: "${product.currentDescription}"\n`; + } + if (product.cleanedName !== undefined) { + skipLogContent += `Cleaned Name: "${product.cleanedName}"\n`; + } + if (product.price !== undefined) { + skipLogContent += `Price: ${product.price}\n`; + } + if (product.error !== undefined) { + skipLogContent += `Error: ${product.error}\n`; + } + skipLogContent += `URL: ${baseUrl}${product.url}\n`; + skipLogContent += `${'-'.repeat(80)}\n`; + }); + } + }); + + fs.writeFileSync(skipLogPath, skipLogContent, 'utf8'); + console.log(` 📄 Detailed skip reasons report saved to: ${skipLogPath}`); + + // Write missing weight log (for backward compatibility) if (productsNeedingWeight.length > 0) { const weightLogContent = `# Products Missing Weight Data # Generated: ${new Date().toISOString()} @@ -576,10 +759,10 @@ ${productsNeedingWeight.map(product => `${product.id}\t${product.name}\t${baseUr const weightLogPath = path.join(logsDir, `missing-weight-${timestamp}.log`); fs.writeFileSync(weightLogPath, weightLogContent, 'utf8'); - console.log(`\n ⚠️ Products missing weight (${productsNeedingWeight.length}) - saved to: ${weightLogPath}`); + console.log(` ⚠️ Products missing weight (${productsNeedingWeight.length}) - saved to: ${weightLogPath}`); } - // Write missing description log + // Write missing description log (for backward compatibility) if (productsNeedingDescription.length > 0) { const descLogContent = `# Products With Insufficient Description Data # Generated: ${new Date().toISOString()} @@ -590,7 +773,7 @@ ${productsNeedingDescription.map(product => `${product.id}\t${product.name}\t"${ const descLogPath = path.join(logsDir, `missing-description-${timestamp}.log`); fs.writeFileSync(descLogPath, descLogContent, 'utf8'); - console.log(`\n ⚠️ Products with insufficient description (${productsNeedingDescription.length}) - saved to: ${descLogPath}`); + console.log(` ⚠️ Products with insufficient description (${productsNeedingDescription.length}) - saved to: ${descLogPath}`); } if (productsNeedingWeight.length === 0 && productsNeedingDescription.length === 0) {