Files
apex/server/services/configValidation.js
T
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

166 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
DRAWERS,
NICHE,
PLINTH,
MAX_PANEL_DIMENSION_MM,
MAX_PANEL_SHORT_SIDE_MM,
validateHorizontalSpans
} from '../../shared/manufacturing.js';
export function validateConfig(config = {}, settings = {}) {
const errors = [];
const warnings = [];
const installationType = config.installationType || 'free';
const doorType = config.doorType || 'hinged';
const widthMm = Number(config.widthMm || 0);
const heightMm = Number(config.heightMm || 0);
const depthMm = Number(config.depthMm || 0);
const modules = Array.isArray(config.modules) ? config.modules : [];
const allowSliding = String(settings.allow_sliding_doors ?? '0') === '1';
const sanitaryGapMm = Number(settings.sanitary_gap_mm || NICHE.sanitaryGapAround);
const plinthHeightMm = Number(config.plinthHeightMm ?? 100);
const maxPanelLongMm = Number(
settings.panel_max_width_mm ??
settings.max_panel_width_mm ??
settings.max_panel_dimension_mm ??
MAX_PANEL_DIMENSION_MM
);
const maxPanelShortMm = Number(
settings.panel_max_short_side_mm ??
settings.max_panel_short_side_mm ??
MAX_PANEL_SHORT_SIDE_MM
);
if (widthMm > maxPanelLongMm) {
errors.push({
field: 'widthMm',
message: `Largeur maximale ${maxPanelLongMm} mm (capacité CNC).`
});
}
if (depthMm > maxPanelShortMm) {
errors.push({
field: 'depthMm',
message: `Profondeur maximale ${maxPanelShortMm} mm (capacité CNC).`
});
}
if (config.horizontalSpans != null && !Array.isArray(config.horizontalSpans)) {
errors.push({
field: 'horizontalSpans',
message: 'Les traverses horizontales doivent être fournies sous forme de tableau.'
});
} else {
validateHorizontalSpans(config, { maxPanelWidthMm: maxPanelLongMm }).forEach((issue) => {
errors.push({
field: issue.field,
code: issue.code,
message: issue.message
});
});
}
if (!PLINTH.targetHeightsMm.includes(plinthHeightMm)) {
errors.push({
field: 'plinthHeightMm',
message: `Hauteur AXILO 78 invalide. Valeurs admises : ${PLINTH.targetHeightsMm.join(', ')} mm.`
});
}
if (doorType === 'sliding' && !allowSliding) {
errors.push({ field: 'doorType', message: 'Portes coulissantes désactivées.' });
}
if (installationType === 'niche') {
const nm = config.nicheMeasurements || {};
const minW = Math.min(nm.widthTopMm || widthMm, nm.widthMidMm || widthMm, nm.widthBottomMm || widthMm);
const minD = Math.min(nm.depthTopMm || depthMm, nm.depthMidMm || depthMm, nm.depthBottomMm || depthMm);
const minH = Math.min(nm.heightLeftMm || heightMm, nm.heightRightMm || heightMm);
const expectedW = Math.max(400, minW - sanitaryGapMm * 2);
const expectedD = Math.max(400, minD - sanitaryGapMm * 2);
const expectedH = Math.max(1800, minH - sanitaryGapMm * 2);
if (Math.abs(expectedW - widthMm) > 2 || Math.abs(expectedD - depthMm) > 2 || Math.abs(expectedH - heightMm) > 2) {
errors.push({ field: 'nicheMeasurements', message: 'Dimensions niche incohérentes avec les mesures mini et vide sanitaire.' });
}
}
const drawerDepthMin = Number(settings.drawer_depth_min_mm || DRAWERS.minDepth);
const drawerDepthMax = Number(settings.drawer_depth_max_mm || DRAWERS.maxDepth);
const drawerDepthStep = Number(settings.drawer_depth_step_mm || DRAWERS.depthStep);
/** Même logique que `useWardrobeConfig` : pas de 50 mm sur la profondeur utile, puis réserve tiroir caché. */
const inferredEffectiveDrawerDepthMm = (drawerType = config.hardware?.drawerType || 'visible') => {
const rawGap = depthMm - NICHE.drawerBackGap;
const clamped = Math.max(drawerDepthMin, Math.min(drawerDepthMax, rawGap));
const steppedMm = Math.round(clamped / drawerDepthStep) * drawerDepthStep;
const depthReductionMm =
drawerType === 'hidden' || drawerType === 'hidden_with_handle' ? DRAWERS.hiddenHandleDepthReduction : 0;
return Math.max(350, steppedMm - depthReductionMm);
};
for (const [index, m] of modules.entries()) {
const isDrawerCol = (m.drawers || 0) > 0 || m.type === 'tiroirs';
if (isDrawerCol && Number(m.widthMm || 0) > DRAWERS.maxColumnWidth) {
errors.push({ field: `modules[${index}]`, message: `Colonne tiroirs > ${DRAWERS.maxColumnWidth} mm.` });
}
if (m.hasRod && heightMm < 1200) {
errors.push({ field: `modules[${index}]`, message: 'Tringle autorisée à partir de 1200 mm de hauteur armoire.' });
}
if (isDrawerCol) {
const moduleDepth = inferredEffectiveDrawerDepthMm(m.drawerType ?? config.hardware?.drawerType);
if (moduleDepth < 350 || moduleDepth > drawerDepthMax) {
errors.push({
field: `modules[${index}].drawerType`,
message: `Profondeur tiroir utile hors bornes (350-${drawerDepthMax} mm) pour ce block.`
});
}
}
}
/**
* Profondeur utile tiroir — alignée sur useWardrobeConfig (pas de 50 sur prof. caisse puis −40 si caché).
* Ne pas exiger un multiple de 50 sur la valeur finale : ex. 550 − 40 = 510 mm est valide.
*/
const drawerModules = modules.filter((m) => (m.drawers || 0) > 0 || m.type === 'tiroirs');
const canonicalDrawerDepths = drawerModules.map((m) =>
inferredEffectiveDrawerDepthMm(m.drawerType ?? config.hardware?.drawerType)
);
const canonicalEffectiveDrawerDepthMm =
canonicalDrawerDepths[0] ?? inferredEffectiveDrawerDepthMm();
const effectiveDrawerMinMm = 350;
if (
canonicalEffectiveDrawerDepthMm < effectiveDrawerMinMm ||
canonicalEffectiveDrawerDepthMm > drawerDepthMax
) {
errors.push({
field: 'effectiveDrawerDepthMm',
message: `Profondeur tiroir utile hors bornes (${effectiveDrawerMinMm}-${drawerDepthMax} mm) pour cette profondeur caisse.`
});
}
const explicitEff = Number(config.effectiveDrawerDepthMm);
if (
Number.isFinite(explicitEff) &&
explicitEff > 0 &&
canonicalDrawerDepths.every((depth) => depth === canonicalEffectiveDrawerDepthMm) &&
Math.abs(explicitEff - canonicalEffectiveDrawerDepthMm) > 2
) {
warnings.push({
field: 'effectiveDrawerDepthMm',
message:
`Valeur stockée (${explicitEff} mm) différente du calcul attendu (${canonicalEffectiveDrawerDepthMm} mm) — le serveur utilise le calcul canonique.`
});
}
const handleAxis = Number(config.hardware?.handleBoreHeightMm || 1000);
const stdAxis = Number(settings.standard_handle_axis_mm || 1000);
const tolerance = Number(settings.standard_handle_tolerance_mm || 200);
if (handleAxis < stdAxis - tolerance || handleAxis > stdAxis + tolerance) {
warnings.push({ field: 'handleBoreHeightMm', message: 'Perçage poignée hors plage standard.' });
}
return { errors, warnings };
}
export default { validateConfig };