Files
lejayandCursor 30921aade6 v2.1
Ship configurator updates with catalog DB included for server deploy.

Co-authored-by: Cursor <[email protected]>
2026-10-07 17:13:03 +02:00

331 lines
12 KiB
JavaScript

/**
* Plans, cutting list, and packing routes
*/
import { generateAllPlans, generatePDFHtml } from '../services/planGenerator.js';
import { generateCuttingList, formatCuttingListForExport } from '../services/cuttingEngine.js';
import { generatePackingList } from '../services/packingEngine.js';
import { generateCncProgram } from '../services/cncGenerator.js';
import { validateConfig } from '../services/configValidation.js';
import { generateHardwareList } from '../services/hardwareListEngine.js';
import { generateMprZipBuffer, generateMprPieceDownload } from '../services/mprGenerator.js';
import { generateCuttingPlan } from '../services/cuttingPlanEngine.js';
import { buildCuttingListXlsx } from '../services/exportExcel.js';
/**
* POST /api/plans/generate
* Generate all 2D plans for a configuration
*/
export async function generatePlansRoute(req, res) {
try {
const { config, modules = [], colors = {} } = req.body;
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const plans = generateAllPlans(config, modules, colors);
res.json({ success: true, plans, warnings: validation.warnings });
} catch (error) {
console.error('Plan generation error:', error);
res.status(500).json({ error: error.message || 'Erreur de génération des plans' });
}
}
/**
* POST /api/plans/cutting-list
* Generate cutting list + packing list for a configuration
*/
export async function generateCuttingListRoute(req, res) {
try {
const { config, modules = [], colors = {} } = req.body;
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const cuttingList = generateCuttingList(config, colors, modules);
const packing = generatePackingList(cuttingList);
res.json({ success: true, cuttingList, packing, warnings: validation.warnings });
} catch (error) {
console.error('Cutting list error:', error);
res.status(500).json({ error: error.message || 'Erreur de génération de la liste de débitage' });
}
}
/**
* POST /api/plans/export-text
* Export cutting list as text file
*/
export async function exportCuttingListText(req, res) {
try {
const { config, modules = [], colors = {} } = req.body;
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const cuttingList = generateCuttingList(config, colors, modules);
const textContent = formatCuttingListForExport(cuttingList);
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="liste-debitage.txt"');
res.send(textContent);
} catch (error) {
console.error('Export text error:', error);
res.status(500).json({ error: error.message || 'Erreur d\'export' });
}
}
/**
* POST /api/plans/export-pdf
* Export plans, cutting list, and packing as HTML (for PDF conversion)
*/
export async function exportPDFHtml(req, res) {
try {
const { config, modules = [], colors = {} } = req.body;
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const cuttingList = generateCuttingList(config, colors, modules);
const packing = generatePackingList(cuttingList);
const html = generatePDFHtml(config, modules, cuttingList, packing);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.send(html);
} catch (error) {
console.error('Export PDF error:', error);
res.status(500).json({ error: error.message || 'Erreur d\'export PDF' });
}
}
/**
* POST /api/plans/export-cnc
* Export CNC program as standard ISO G-code (.nc)
*/
export async function exportCncRoute(req, res) {
try {
const { config, modules = [], colors = {} } = req.body;
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const cnc = generateCncProgram(config, modules, colors);
res.setHeader('Content-Type', cnc.mimeType);
res.setHeader('Content-Disposition', `attachment; filename="${cnc.fileName}"`);
res.send(cnc.content);
} catch (error) {
console.error('Export CNC error:', error);
res.status(500).json({ error: error.message || 'Erreur d\'export CNC' });
}
}
/**
* POST /api/plans/export-mpr
* Archive ZIP avec un fichier .mpr Homag/Woodwop par panneau.
*/
export async function exportMprRoute(req, res) {
try {
const { config, modules = [], colors = {}, orderReference = '', stackListKm = '', mprMaterialArticle = '', mprGroupIndex = 1 } = req.body || {};
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const gi = Number(mprGroupIndex);
const groupIndex = Number.isFinite(gi) && gi >= 1 ? Math.floor(gi) : 1;
const { buffer, fileName, mimeType } = await generateMprZipBuffer({
config,
modules,
colors,
orderReference,
stackListKm: typeof stackListKm === 'string' ? stackListKm : String(stackListKm || '01'),
defaultMaterialArticle: typeof mprMaterialArticle === 'string' ? mprMaterialArticle : '',
groupIndex
});
const safeName =
/^[\w\-_.]+$/.test(String(fileName)) ? fileName : 'apex-mpr.zip';
res.setHeader('Content-Type', mimeType);
res.setHeader('Content-Disposition', `attachment; filename="${safeName}"`);
res.send(Buffer.from(buffer));
} catch (error) {
console.error('Export MPR error:', error);
res.status(500).json({ error: error.message || 'Erreur d\'export MPR' });
}
}
/**
* POST /api/plans/export-mpr-piece
* Export MPR pour une ligne de la liste de débitage (fichier unique ou mini-ZIP si plusieurs exemplaires).
*/
export async function exportMprPieceRoute(req, res) {
try {
const {
config,
modules = [],
colors = {},
pieceId,
instanceIndex,
orderReference = '',
stackListKm = '',
mprMaterialArticle = '',
mprGroupIndex = 1
} = req.body || {};
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
if (!pieceId || typeof pieceId !== 'string') {
return res.status(400).json({ error: 'pieceId requis' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const gi = Number(mprGroupIndex);
const groupIndex = Number.isFinite(gi) && gi >= 1 ? Math.floor(gi) : 1;
const { buffer, fileName, mimeType } = await generateMprPieceDownload({
config,
modules,
colors,
pieceId,
instanceIndex,
orderReference,
stackListKm: typeof stackListKm === 'string' ? stackListKm : String(stackListKm || '01'),
defaultMaterialArticle: typeof mprMaterialArticle === 'string' ? mprMaterialArticle : '',
groupIndex
});
const safeName = /^[\w\-_.]+$/.test(String(fileName)) ? fileName : 'piece.mpr';
res.setHeader('Content-Type', mimeType);
res.setHeader('Content-Disposition', `attachment; filename="${safeName}"`);
res.send(Buffer.from(buffer));
} catch (error) {
console.error('Export MPR piece error:', error);
const status = /introuvable|requis|invalide/i.test(error.message || '') ? 400 : 500;
res.status(status).json({ error: error.message || 'Erreur d\'export MPR pièce' });
}
}
/**
* POST /api/plans/cutting-plan
* Génère le plan de coupe (calepinage) avec % de chute par panneau et global.
*/
export async function generateCuttingPlanRoute(req, res) {
try {
const { config, modules = [], colors = {}, stockBoard, kerfMm, trimMm, allowRotation } = req.body || {};
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const cuttingList = generateCuttingList(config, colors, modules);
const cuttingPlan = generateCuttingPlan(cuttingList, { stockBoard, kerfMm, trimMm, allowRotation });
res.json({ success: true, cuttingPlan, warnings: validation.warnings });
} catch (error) {
console.error('Cutting plan error:', error);
res.status(500).json({ error: error.message || 'Erreur de génération du plan de coupe' });
}
}
/**
* POST /api/plans/export-excel
* Export de la liste de débitage (+ quincaillerie + calepinage) au format Excel (.xlsx).
*/
export async function exportExcelRoute(req, res) {
try {
const { config, modules = [], colors = {} } = req.body || {};
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const cuttingList = generateCuttingList(config, colors, modules);
const cuttingPlan = generateCuttingPlan(cuttingList, {});
let hardware = null;
try {
hardware = await generateHardwareList({ config, modules, colors });
} catch (e) {
console.warn('[export-excel] hardware list unavailable:', e.message);
}
const buffer = await buildCuttingListXlsx({ cuttingList, hardware, cuttingPlan });
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename="liste-debitage.xlsx"');
res.send(Buffer.from(buffer));
} catch (error) {
console.error('Export Excel error:', error);
res.status(500).json({ error: error.message || 'Erreur d\'export Excel' });
}
}
/**
* POST /api/plans/hardware-list
* Generate bill of materials for hardware/quincaillerie with prices (best-effort).
*/
export async function generateHardwareListRoute(req, res) {
try {
const { config, modules = [], colors = {} } = req.body;
if (!config) {
return res.status(400).json({ error: 'Configuration requise' });
}
const validation = validateConfig(config);
if (validation.errors.length > 0) {
return res.status(400).json({ error: 'Configuration invalide', details: validation.errors });
}
const result = await generateHardwareList({ config, modules, colors });
res.json({ success: true, ...result, warnings: validation.warnings });
} catch (error) {
console.error('Hardware list error:', error);
res.status(500).json({ error: error.message || 'Erreur de génération de la liste de quincaillerie' });
}
}