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
193 lines
7.3 KiB
TypeScript
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);
|