Files
klz-cables.com/scripts/generate-excel-datasheets.ts
Marc Mintel 17ebde407e
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 2m0s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
feat: product catalog
2026-03-01 22:35:49 +01:00

193 lines
7.3 KiB
TypeScript

#!/usr/bin/env ts-node
/**
* Excel Datasheet Generator
*
* Generates per-product .xlsx datasheets using ONLY data from Payload CMS.
* No external Excel files required.
*/
import * as fs from 'fs';
import * as path from 'path';
import * as XLSX from 'xlsx';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import { buildExcelModel, ProductData as ExcelProductData } from './lib/excel-data-parser';
const CONFIG = {
outputDir: path.join(process.cwd(), 'public/datasheets'),
} as const;
// ─── Types ──────────────────────────────────────────────────────────────────────
interface ProductData {
title: string;
slug: string;
sku: string;
locale: string;
categories: string[];
description: string;
technicalItems: Array<{ label: string; value: string; unit?: string }>;
voltageTables: Array<{
voltageLabel: string;
metaItems: Array<{ label: string; value: string; unit?: string }>;
columns: Array<{ key: string; label: string; get: (rowIndex: number) => string }>;
crossSections: string[];
}>;
}
// ─── Helpers ────────────────────────────────────────────────────────────────────
function stripHtml(html: string): string {
if (!html) return '';
return html.replace(/<[^>]*>/g, '').trim();
}
function ensureOutputDir(): void {
if (!fs.existsSync(CONFIG.outputDir)) {
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
}
}
// ─── CMS Product Loading ────────────────────────────────────────────────────────
async function fetchProductsFromCMS(locale: 'en' | 'de'): Promise<ProductData[]> {
const products: ProductData[] = [];
try {
const payload = await getPayload({ config: configPromise });
const isDev = process.env.NODE_ENV === 'development';
const result = await payload.find({
collection: 'products',
where: {
...(!isDev ? { _status: { equals: 'published' } } : {}),
},
locale: locale as any,
pagination: false,
});
for (const doc of result.docs) {
if (!doc.title || !doc.slug) continue;
const excelProductData: ExcelProductData = {
name: String(doc.title),
slug: String(doc.slug),
sku: String(doc.sku || ''),
locale,
};
const parsedModel = buildExcelModel({ product: excelProductData, locale });
products.push({
title: String(doc.title),
slug: String(doc.slug),
sku: String(doc.sku || ''),
locale,
categories: Array.isArray(doc.categories)
? doc.categories.map((c: any) => String(c.category || c)).filter(Boolean)
: [],
description: stripHtml(String(doc.description || '')),
technicalItems: parsedModel.ok ? parsedModel.technicalItems : [],
voltageTables: parsedModel.ok ? parsedModel.voltageTables : [],
});
}
} catch (error) {
console.error(`[Payload] Failed to fetch products (${locale}):`, error);
}
return products;
}
// ─── Excel Generation ───────────────────────────────────────────────────────────
function generateExcelForProduct(product: ProductData): Buffer {
const workbook = XLSX.utils.book_new();
const l = product.locale === 'de';
// Single sheet: all cross-sections from all voltage tables combined
const hasMultipleVoltages = product.voltageTables.length > 1;
const allRows: string[][] = [];
// Build unified header row
// Use columns from the first voltage table (they're the same across tables)
const refTable = product.voltageTables[0];
if (!refTable) {
// No voltage tables — create a minimal info sheet
const ws = XLSX.utils.aoa_to_sheet([
[product.title],
[l ? 'Keine Querschnittsdaten verfügbar' : 'No cross-section data available'],
]);
ws['!cols'] = [{ wch: 40 }];
XLSX.utils.book_append_sheet(workbook, ws, product.title.substring(0, 31));
return Buffer.from(XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }));
}
const headers: string[] = [
l ? 'Querschnitt' : 'Cross-section',
...(hasMultipleVoltages ? [l ? 'Spannung' : 'Voltage'] : []),
...refTable.columns.map(c => c.label),
];
allRows.push(headers);
// Merge rows from all voltage tables
for (const table of product.voltageTables) {
for (let rowIdx = 0; rowIdx < table.crossSections.length; rowIdx++) {
const row: string[] = [
table.crossSections[rowIdx],
...(hasMultipleVoltages ? [table.voltageLabel] : []),
...table.columns.map(c => c.get(rowIdx) || '-'),
];
allRows.push(row);
}
}
const ws = XLSX.utils.aoa_to_sheet(allRows);
// Auto-width: first col wider for cross-section labels
ws['!cols'] = headers.map((_, i) => ({ wch: i === 0 ? 30 : 18 }));
const sheetName = product.title.substring(0, 31);
XLSX.utils.book_append_sheet(workbook, ws, sheetName);
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
return Buffer.from(buffer);
}
// ─── Main ───────────────────────────────────────────────────────────────────────
async function main(): Promise<void> {
const start = Date.now();
console.log('Starting Excel datasheet generation (Legacy Excel Source)');
ensureOutputDir();
const locales: Array<'en' | 'de'> = ['en', 'de'];
let generated = 0;
for (const locale of locales) {
console.log(`\n[${locale.toUpperCase()}] Fetching products...`);
const products = await fetchProductsFromCMS(locale);
console.log(`Found ${products.length} products.`);
for (const product of products) {
try {
const buffer = generateExcelForProduct(product);
const fileName = `${product.slug}-${locale}.xlsx`;
const subfolder = path.join(CONFIG.outputDir, 'products');
if (!fs.existsSync(subfolder)) fs.mkdirSync(subfolder, { recursive: true });
fs.writeFileSync(path.join(subfolder, fileName), buffer);
console.log(`✓ Generated: ${fileName}`);
generated++;
} catch (error) {
console.error(`✗ Failed for ${product.title}:`, error);
}
}
}
console.log(`\n✅ Done! Generated ${generated} files.`);
console.log(`Output: ${CONFIG.outputDir}`);
console.log(`Time: ${((Date.now() - start) / 1000).toFixed(2)}s`);
}
main().catch(console.error);