Ship configurator updates with catalog DB included for server deploy. Co-authored-by: Cursor <[email protected]>
331 lines
12 KiB
JavaScript
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' });
|
|
}
|
|
}
|