feat: include full product info and tech specs in excel datasheets
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 2m21s
Build & Deploy / 🏗️ Build (push) Successful in 3m43s
Build & Deploy / 🚀 Deploy (push) Successful in 32s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 5m19s
Build & Deploy / 🔔 Notify (push) Successful in 1s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Successful in 2m21s
Build & Deploy / 🏗️ Build (push) Successful in 3m43s
Build & Deploy / 🚀 Deploy (push) Successful in 32s
Build & Deploy / 🧪 Post-Deploy Verification (push) Failing after 5m19s
Build & Deploy / 🔔 Notify (push) Successful in 1s
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,178 +15,208 @@ import configPromise from '@payload-config';
|
|||||||
import { buildExcelModel, ProductData as ExcelProductData } from './lib/excel-data-parser';
|
import { buildExcelModel, ProductData as ExcelProductData } from './lib/excel-data-parser';
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
outputDir: path.join(process.cwd(), 'public/datasheets'),
|
outputDir: path.join(process.cwd(), 'public/datasheets'),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// ─── Types ──────────────────────────────────────────────────────────────────────
|
// ─── Types ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
interface ProductData {
|
interface ProductData {
|
||||||
title: string;
|
title: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
sku: string;
|
sku: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
categories: string[];
|
categories: string[];
|
||||||
description: string;
|
description: string;
|
||||||
technicalItems: Array<{ label: string; value: string; unit?: string }>;
|
technicalItems: Array<{ label: string; value: string; unit?: string }>;
|
||||||
voltageTables: Array<{
|
voltageTables: Array<{
|
||||||
voltageLabel: string;
|
voltageLabel: string;
|
||||||
metaItems: Array<{ label: string; value: string; unit?: string }>;
|
metaItems: Array<{ label: string; value: string; unit?: string }>;
|
||||||
columns: Array<{ key: string; label: string; get: (rowIndex: number) => string }>;
|
columns: Array<{ key: string; label: string; get: (rowIndex: number) => string }>;
|
||||||
crossSections: string[];
|
crossSections: string[];
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Helpers ────────────────────────────────────────────────────────────────────
|
// ─── Helpers ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function stripHtml(html: string): string {
|
function stripHtml(html: string): string {
|
||||||
if (!html) return '';
|
if (!html) return '';
|
||||||
return html.replace(/<[^>]*>/g, '').trim();
|
return html.replace(/<[^>]*>/g, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureOutputDir(): void {
|
function ensureOutputDir(): void {
|
||||||
if (!fs.existsSync(CONFIG.outputDir)) {
|
if (!fs.existsSync(CONFIG.outputDir)) {
|
||||||
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── CMS Product Loading ────────────────────────────────────────────────────────
|
// ─── CMS Product Loading ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function fetchProductsFromCMS(locale: 'en' | 'de'): Promise<ProductData[]> {
|
async function fetchProductsFromCMS(locale: 'en' | 'de'): Promise<ProductData[]> {
|
||||||
const products: ProductData[] = [];
|
const products: ProductData[] = [];
|
||||||
try {
|
try {
|
||||||
const payload = await getPayload({ config: configPromise });
|
const payload = await getPayload({ config: configPromise });
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
const result = await payload.find({
|
const result = await payload.find({
|
||||||
collection: 'products',
|
collection: 'products',
|
||||||
where: {
|
where: {
|
||||||
...(!isDev ? { _status: { equals: 'published' } } : {}),
|
...(!isDev ? { _status: { equals: 'published' } } : {}),
|
||||||
},
|
},
|
||||||
locale: locale as any,
|
locale: locale as any,
|
||||||
pagination: false,
|
pagination: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const doc of result.docs) {
|
for (const doc of result.docs) {
|
||||||
if (!doc.title || !doc.slug) continue;
|
if (!doc.title || !doc.slug) continue;
|
||||||
|
|
||||||
const excelProductData: ExcelProductData = {
|
const excelProductData: ExcelProductData = {
|
||||||
name: String(doc.title),
|
name: String(doc.title),
|
||||||
slug: String(doc.slug),
|
slug: String(doc.slug),
|
||||||
sku: String(doc.sku || ''),
|
sku: String(doc.sku || ''),
|
||||||
locale,
|
locale,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsedModel = buildExcelModel({ product: excelProductData, locale });
|
const parsedModel = buildExcelModel({ product: excelProductData, locale });
|
||||||
|
|
||||||
products.push({
|
products.push({
|
||||||
title: String(doc.title),
|
title: String(doc.title),
|
||||||
slug: String(doc.slug),
|
slug: String(doc.slug),
|
||||||
sku: String(doc.sku || ''),
|
sku: String(doc.sku || ''),
|
||||||
locale,
|
locale,
|
||||||
categories: Array.isArray(doc.categories)
|
categories: Array.isArray(doc.categories)
|
||||||
? doc.categories.map((c: any) => String(c.category || c)).filter(Boolean)
|
? doc.categories.map((c: any) => String(c.category || c)).filter(Boolean)
|
||||||
: [],
|
: [],
|
||||||
description: stripHtml(String(doc.description || '')),
|
description: stripHtml(String(doc.description || '')),
|
||||||
technicalItems: parsedModel.ok ? parsedModel.technicalItems : [],
|
technicalItems: parsedModel.ok ? parsedModel.technicalItems : [],
|
||||||
voltageTables: parsedModel.ok ? parsedModel.voltageTables : [],
|
voltageTables: parsedModel.ok ? parsedModel.voltageTables : [],
|
||||||
});
|
});
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[Payload] Failed to fetch products (${locale}):`, error);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[Payload] Failed to fetch products (${locale}):`, error);
|
||||||
|
}
|
||||||
|
|
||||||
return products;
|
return products;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Excel Generation ───────────────────────────────────────────────────────────
|
// ─── Excel Generation ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function generateExcelForProduct(product: ProductData): Buffer {
|
function generateExcelForProduct(product: ProductData): Buffer {
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
const l = product.locale === 'de';
|
const l = product.locale === 'de';
|
||||||
|
|
||||||
// Single sheet: all cross-sections from all voltage tables combined
|
const allRows: any[][] = [];
|
||||||
const hasMultipleVoltages = product.voltageTables.length > 1;
|
|
||||||
const allRows: string[][] = [];
|
|
||||||
|
|
||||||
// Build unified header row
|
// --- 1. Product Meta Data ---
|
||||||
// Use columns from the first voltage table (they're the same across tables)
|
allRows.push([product.title]);
|
||||||
const refTable = product.voltageTables[0];
|
const categoriesLine = product.categories.join(' • ');
|
||||||
if (!refTable) {
|
if (categoriesLine) {
|
||||||
// No voltage tables — create a minimal info sheet
|
allRows.push([l ? 'Kategorien:' : 'Categories:', categoriesLine]);
|
||||||
const ws = XLSX.utils.aoa_to_sheet([
|
}
|
||||||
[product.title],
|
if (product.sku) {
|
||||||
[l ? 'Keine Querschnittsdaten verfügbar' : 'No cross-section data available'],
|
allRows.push([l ? 'Artikelnummer:' : 'SKU:', product.sku]);
|
||||||
]);
|
}
|
||||||
ws['!cols'] = [{ wch: 40 }];
|
allRows.push([]); // blank row
|
||||||
XLSX.utils.book_append_sheet(workbook, ws, product.title.substring(0, 31));
|
|
||||||
return Buffer.from(XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }));
|
// --- 2. Application / Description ---
|
||||||
|
if (product.description) {
|
||||||
|
allRows.push([l ? 'ANWENDUNG' : 'APPLICATION']);
|
||||||
|
allRows.push([product.description]);
|
||||||
|
allRows.push([]); // blank row
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. Technical Specifications ---
|
||||||
|
if (product.technicalItems && product.technicalItems.length > 0) {
|
||||||
|
allRows.push([l ? 'TECHNISCHE DATEN' : 'TECHNICAL DATA']);
|
||||||
|
for (const item of product.technicalItems) {
|
||||||
|
const val = item.unit ? `${item.value} ${item.unit}` : item.value;
|
||||||
|
allRows.push([item.label, val]);
|
||||||
}
|
}
|
||||||
|
allRows.push([]); // blank row
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 4. Cross-section Configurations ---
|
||||||
|
const hasMultipleVoltages = product.voltageTables.length > 1;
|
||||||
|
if (product.voltageTables.length > 0) {
|
||||||
|
allRows.push([l ? 'KONFIGURATIONEN' : 'CONFIGURATIONS']);
|
||||||
|
|
||||||
|
const refTable = product.voltageTables[0];
|
||||||
const headers: string[] = [
|
const headers: string[] = [
|
||||||
l ? 'Querschnitt' : 'Cross-section',
|
l ? 'Querschnitt' : 'Cross-section',
|
||||||
...(hasMultipleVoltages ? [l ? 'Spannung' : 'Voltage'] : []),
|
...(hasMultipleVoltages ? [l ? 'Spannung' : 'Voltage'] : []),
|
||||||
...refTable.columns.map(c => c.label),
|
...refTable.columns.map((c) => c.label),
|
||||||
];
|
];
|
||||||
allRows.push(headers);
|
allRows.push(headers);
|
||||||
|
|
||||||
// Merge rows from all voltage tables
|
|
||||||
for (const table of product.voltageTables) {
|
for (const table of product.voltageTables) {
|
||||||
for (let rowIdx = 0; rowIdx < table.crossSections.length; rowIdx++) {
|
for (let rowIdx = 0; rowIdx < table.crossSections.length; rowIdx++) {
|
||||||
const row: string[] = [
|
const row: string[] = [
|
||||||
table.crossSections[rowIdx],
|
table.crossSections[rowIdx],
|
||||||
...(hasMultipleVoltages ? [table.voltageLabel] : []),
|
...(hasMultipleVoltages ? [table.voltageLabel] : []),
|
||||||
...table.columns.map(c => c.get(rowIdx) || '-'),
|
...table.columns.map((c) => c.get(rowIdx) || '-'),
|
||||||
];
|
];
|
||||||
allRows.push(row);
|
allRows.push(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
allRows.push([l ? 'Keine Querschnittsdaten verfügbar' : 'No cross-section data available']);
|
||||||
|
}
|
||||||
|
|
||||||
const ws = XLSX.utils.aoa_to_sheet(allRows);
|
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);
|
// Auto-width: Col 0 wide for description, headers.
|
||||||
XLSX.utils.book_append_sheet(workbook, ws, sheetName);
|
ws['!cols'] = [
|
||||||
|
{ wch: 45 },
|
||||||
|
{ wch: 20 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 15 },
|
||||||
|
{ wch: 15 },
|
||||||
|
];
|
||||||
|
|
||||||
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
const sheetName = product.title.substring(0, 31);
|
||||||
return Buffer.from(buffer);
|
XLSX.utils.book_append_sheet(workbook, ws, sheetName);
|
||||||
|
|
||||||
|
const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
|
||||||
|
return Buffer.from(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Main ───────────────────────────────────────────────────────────────────────
|
// ─── Main ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
console.log('Starting Excel datasheet generation (Legacy Excel Source)');
|
console.log('Starting Excel datasheet generation (Legacy Excel Source)');
|
||||||
ensureOutputDir();
|
ensureOutputDir();
|
||||||
|
|
||||||
const locales: Array<'en' | 'de'> = ['en', 'de'];
|
const locales: Array<'en' | 'de'> = ['en', 'de'];
|
||||||
let generated = 0;
|
let generated = 0;
|
||||||
|
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
console.log(`\n[${locale.toUpperCase()}] Fetching products...`);
|
console.log(`\n[${locale.toUpperCase()}] Fetching products...`);
|
||||||
const products = await fetchProductsFromCMS(locale);
|
const products = await fetchProductsFromCMS(locale);
|
||||||
console.log(`Found ${products.length} products.`);
|
console.log(`Found ${products.length} products.`);
|
||||||
|
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
try {
|
try {
|
||||||
const buffer = generateExcelForProduct(product);
|
const buffer = generateExcelForProduct(product);
|
||||||
const fileName = `${product.slug}-${locale}.xlsx`;
|
const fileName = `${product.slug}-${locale}.xlsx`;
|
||||||
|
|
||||||
const subfolder = path.join(CONFIG.outputDir, 'products');
|
const subfolder = path.join(CONFIG.outputDir, 'products');
|
||||||
if (!fs.existsSync(subfolder)) fs.mkdirSync(subfolder, { recursive: true });
|
if (!fs.existsSync(subfolder)) fs.mkdirSync(subfolder, { recursive: true });
|
||||||
|
|
||||||
fs.writeFileSync(path.join(subfolder, fileName), buffer);
|
fs.writeFileSync(path.join(subfolder, fileName), buffer);
|
||||||
console.log(`✓ Generated: ${fileName}`);
|
console.log(`✓ Generated: ${fileName}`);
|
||||||
generated++;
|
generated++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`✗ Failed for ${product.title}:`, error);
|
console.error(`✗ Failed for ${product.title}:`, error);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`\n✅ Done! Generated ${generated} files.`);
|
console.log(`\n✅ Done! Generated ${generated} files.`);
|
||||||
console.log(`Output: ${CONFIG.outputDir}`);
|
console.log(`Output: ${CONFIG.outputDir}`);
|
||||||
console.log(`Time: ${((Date.now() - start) / 1000).toFixed(2)}s`);
|
console.log(`Time: ${((Date.now() - start) / 1000).toFixed(2)}s`);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
|
|||||||
Reference in New Issue
Block a user