@@ -0,0 +1,522 @@
|
||||
/**
|
||||
* Export MPR (WOODWOP / Homag-family .mpr) pour le débit APEX — un fichier par panneau, ZIP.
|
||||
* Coordonnées XY = même repère que le G-code (.nc commentaires): origine bas-gauche, mm.
|
||||
* Macros RSO/* : chemins exemple — à présence confirmée sur le poste/cellule.
|
||||
*/
|
||||
|
||||
import JSZip from 'jszip';
|
||||
import { generateCuttingList } from './cuttingEngine.js';
|
||||
import { collectHoleSpecsForPiece } from './holeSpecifications.js';
|
||||
import { GROOVE, getBackGroovePlanOrientation, BACK_GROOVE_PLAN_VERTICAL_TYPES } from '../../shared/manufacturing.js';
|
||||
|
||||
const MPR_ARTICLE_CODES = Object.freeze({
|
||||
plintheBas: 'PL_BAS',
|
||||
bandeauHaut: 'BH',
|
||||
dessus: 'BO_U',
|
||||
fond: 'BO_L',
|
||||
cotes: 'SE',
|
||||
cotesDoublage: 'CD',
|
||||
fileur: 'FI',
|
||||
habillageFinitionGauche: 'FR_L_G',
|
||||
habillageFinitionDroite: 'FR_L_D',
|
||||
habillageFinitionDessus: 'FR_H',
|
||||
dos: 'DO',
|
||||
renforts: 'RE',
|
||||
portes: 'TU',
|
||||
panneauxInterieurs: 'ZA_O',
|
||||
rayonFixe: 'RF_X',
|
||||
etageres: 'EB_M',
|
||||
renfortsTiroir: 'LABO'
|
||||
});
|
||||
|
||||
function fmtMm(n) {
|
||||
const v = Number(n);
|
||||
if (!Number.isFinite(v)) return '0.0';
|
||||
const rounded = Math.round(v * 10) / 10;
|
||||
return Number.isInteger(rounded) ? `${rounded}.0` : `${rounded}`;
|
||||
}
|
||||
|
||||
function toSafe(value, fb) {
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? n : fb;
|
||||
}
|
||||
|
||||
function escQuoted(s) {
|
||||
return String(s ?? '').replace(/"/g, "'");
|
||||
}
|
||||
|
||||
/** Scinde ligne « gauche+droit » qty 2 en deux fichiers SE_L / SE_R. */
|
||||
export function flattenPiecesForMprExport(pieces = []) {
|
||||
const out = [];
|
||||
for (const piece of pieces) {
|
||||
const qty = Math.max(1, Math.round(toSafe(piece.quantity, 1)));
|
||||
const splitSides =
|
||||
piece.elementType === 'cotes' &&
|
||||
qty === 2 &&
|
||||
piece.id === 'piece-cotes' &&
|
||||
String(piece.description || '').includes('gauche et droit');
|
||||
|
||||
if (splitSides) {
|
||||
out.push({ piece: { ...piece, quantity: 1 }, slug: 'SE_L', qtyIndex: 0 });
|
||||
out.push({ piece: { ...piece, quantity: 1 }, slug: 'SE_R', qtyIndex: 1 });
|
||||
continue;
|
||||
}
|
||||
|
||||
let baseSlug = MPR_ARTICLE_CODES[piece.elementType] || String(piece.elementType || '').toUpperCase() || 'PIECE';
|
||||
|
||||
for (let qi = 0; qi < qty; qi++) {
|
||||
let slug = baseSlug;
|
||||
if (
|
||||
qty > 1 &&
|
||||
['panneauxInterieurs', 'renforts', 'etageres', 'rayonFixe', 'bandeauHaut'].includes(piece.elementType || '')
|
||||
) {
|
||||
slug = `${baseSlug}_${qi + 1}`;
|
||||
}
|
||||
out.push({ piece, slug, qtyIndex: qi });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function mprAnglesForPiece(piece) {
|
||||
const et = piece.elementType || '';
|
||||
if (habillageEtPorte(et)) {
|
||||
return {
|
||||
DRWIN: '270.0',
|
||||
beschGedreht: '0',
|
||||
turnMsg: et === 'portes' ? 'DREHEN X-ACHSE' : 'DREHEN X-ACHSE'
|
||||
};
|
||||
}
|
||||
if (['dessus', 'fond'].includes(et)) {
|
||||
return {
|
||||
DRWIN: '0.0',
|
||||
beschGedreht: '1',
|
||||
turnMsg: 'DREHEN Y-ACHSE'
|
||||
};
|
||||
}
|
||||
return {
|
||||
DRWIN: '0.0',
|
||||
beschGedreht: '0',
|
||||
turnMsg: 'DREHEN Y-ACHSE'
|
||||
};
|
||||
}
|
||||
|
||||
function habillageEtPorte(et) {
|
||||
return (
|
||||
['habillageFinitionGauche', 'habillageFinitionDroite', 'habillageFinitionDessus', 'portes'].includes(
|
||||
et
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** Kantendicken : habillages façon exemple FR_* (tous faces 1 mm). */
|
||||
function kantenFlags(piece) {
|
||||
const fin = habillageEtPorte(piece.elementType || '');
|
||||
if (fin) {
|
||||
return { kavo: '1.0', kali: '1.0', kahi: '1.0', kare: '1.0' };
|
||||
}
|
||||
return { kavo: '1.0', kali: '0.0', kahi: '0.0', kare: '0.0' };
|
||||
}
|
||||
|
||||
function grooveSection(piece, _Lmm, _Bmm) {
|
||||
const m = piece?.machining || {};
|
||||
const orient = piece?.elementType ? getBackGroovePlanOrientation(piece.elementType) : null;
|
||||
if (!m.groove || !orient) return [];
|
||||
|
||||
const gw = fmtMm(Number(m.groove.width ?? GROOVE.back.width));
|
||||
const gd = fmtMm(Number(m.groove.depth ?? GROOVE.back.depth));
|
||||
const abst = fmtMm(Number(m.groove.axisFromPanelEdge ?? m.groove.axisFromBackEdge ?? GROOVE.back.axisFromPanelEdge));
|
||||
const vertical = BACK_GROOVE_PLAN_VERTICAL_TYPES.includes(piece.elementType);
|
||||
const win = vertical ? '180.0' : '270.0';
|
||||
|
||||
return [
|
||||
'<121 \\Block\\',
|
||||
'MNM="Kantenbearbeitungen von oben"',
|
||||
'DP="1"',
|
||||
'KO="04"',
|
||||
'<139 \\Komponente\\',
|
||||
'IN="RSO/Makro/RSO_Nut_Flaeche.mpr"',
|
||||
'MNM="Nut Flaeche rueckseite"',
|
||||
'XA="L"',
|
||||
'YA="B"',
|
||||
'VA="LA L"',
|
||||
`VA="BR ${gw}"`,
|
||||
`VA="TI ${gd}"`,
|
||||
`VA="ABST ${abst}"`,
|
||||
`VA="WIN ${win}"`,
|
||||
'VA="ANF 0.0"',
|
||||
'VA="END 0.0"',
|
||||
'VA="MAT MAT"',
|
||||
'KO="04"'
|
||||
];
|
||||
}
|
||||
|
||||
/** Un trou → lignes BohrVert (projection face Z+ ; perçages chant restent approximation LS/LSL). */
|
||||
function bohrVertLines(spec, thicknessMm) {
|
||||
const dRaw = Number(spec.diameterMm) || 5;
|
||||
const depthRaw = Number(spec.depthMm) || 12;
|
||||
const edgeHint = Boolean(spec.toolMode === 'edge' || spec.face === 'end');
|
||||
const throughHint = Boolean(spec.toolMode === 'through');
|
||||
const looksThrough = depthRaw >= thicknessMm - 0.5;
|
||||
let bm = 'LS';
|
||||
let ti;
|
||||
if (throughHint || looksThrough) {
|
||||
bm = 'LSL';
|
||||
ti = `${fmtMm(thicknessMm)} + 1`;
|
||||
} else if (edgeHint && depthRaw >= 38) {
|
||||
bm = 'LSL';
|
||||
ti = fmtMm(depthRaw);
|
||||
} else {
|
||||
ti = fmtMm(depthRaw);
|
||||
}
|
||||
|
||||
const name = fmtMm(Math.round(dRaw * 10) / 10);
|
||||
const du = fmtMm(Math.round(dRaw * 10) / 10);
|
||||
const xa = fmtMm(spec.xMm);
|
||||
const ya = fmtMm(spec.yMm);
|
||||
|
||||
return [
|
||||
'<102 \\BohrVert\\',
|
||||
`MNM="Bohrung ${name} mm"`,
|
||||
`XA="${xa}"`,
|
||||
`YA="${ya}"`,
|
||||
`BM="${bm}"`,
|
||||
`TI="${ti}"`,
|
||||
`DU="${du}"`,
|
||||
'AN="1"',
|
||||
'MI="0"',
|
||||
'S_="2"',
|
||||
'ASG="2"',
|
||||
'MLM="0"',
|
||||
'KO="04"'
|
||||
];
|
||||
}
|
||||
|
||||
/** Supprime les doublons numériques (tolérance 0.02 mm — sans fusionner trous légitimes proches). */
|
||||
function dedupeHoleSpecs(specs, eps = 0.02) {
|
||||
const out = [];
|
||||
for (const s of specs) {
|
||||
const hit = out.find(
|
||||
(o) =>
|
||||
Math.abs(o.xMm - s.xMm) < eps &&
|
||||
Math.abs(o.yMm - s.yMm) < eps &&
|
||||
Math.abs((o.diameterMm || 0) - (s.diameterMm || 0)) < eps
|
||||
);
|
||||
if (!hit) out.push(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function buildSingleMprText({
|
||||
piece,
|
||||
slug,
|
||||
qtyIndex,
|
||||
seqGlobal,
|
||||
groupIndex,
|
||||
orderReference,
|
||||
stackListKm,
|
||||
defaultMaterialArticle
|
||||
}) {
|
||||
const angles = mprAnglesForPiece(piece);
|
||||
const kant = kantenFlags(piece);
|
||||
|
||||
const L = toSafe(piece.rawCutWidthMm, piece.widthMm);
|
||||
const B = toSafe(piece.rawCutHeightMm, piece.heightMm);
|
||||
const D = toSafe(piece.thicknessMm, 19);
|
||||
|
||||
const posLabel = `${groupIndex}.${seqGlobal}`;
|
||||
const { specs, thicknessMm } = collectHoleSpecsForPiece(piece, L, B, qtyIndex);
|
||||
const unique = dedupeHoleSpecs(specs);
|
||||
|
||||
/* Bloc Bohrungen */
|
||||
const holeLines = [];
|
||||
for (const sp of unique) {
|
||||
holeLines.push(...bohrVertLines(sp, thicknessMm));
|
||||
}
|
||||
|
||||
const dp = Math.max(1, Math.min(98, holeLines.filter((ln) => ln.startsWith('<102')).length));
|
||||
|
||||
const matKm = escQuoted(
|
||||
piece.finishId != null && piece.finishId !== '' ? piece.finishId : defaultMaterialArticle || 'APEX'
|
||||
);
|
||||
|
||||
const lines = [];
|
||||
|
||||
lines.push('[H', 'VERSION="4.0 Alpha"', 'FW="1400"', 'FM="1"', 'ZS="40"', 'OP="1"');
|
||||
lines.push('[001', 'MAT="2"', 'KM="Materialart"', `DRWIN="${angles.DRWIN}"`, 'KM="Drehwinkel 0,90,180,270"');
|
||||
lines.push(`AUFT="${escQuoted(orderReference)}"`, `KM="AUFTRAG: ${escQuoted(orderReference)}"`);
|
||||
lines.push(`STKL="${escQuoted(stackListKm)}"`, `KM="STUECKLISTE: ${escQuoted(stackListKm)}"`);
|
||||
lines.push(`POS="${escQuoted(posLabel)}"`, `KM="POSITION: ${escQuoted(posLabel)}"`);
|
||||
lines.push('BEZ="0"', `KM="Bezeichnung: ${escQuoted(slug)}"`);
|
||||
lines.push(`L="${fmtMm(L)}"`, 'KM="Laenge in X"', `B="${fmtMm(B)}"`, 'KM="Breite in Y"', `D="${fmtMm(D)}"`, 'KM="Dicke in Z"');
|
||||
lines.push('ZF="2.0"', 'KM="Zugabe Fraesung"', 'ZB="3.0"', 'KM="Zugabe Bohrung"');
|
||||
lines.push('RITZEN="1.0"', 'KM="Vorritztiefe mm"');
|
||||
lines.push(`KAVODI="${kant.kavo}"`, 'KM="Kantendicke vorne"', `KALIDI="${kant.kali}"`, 'KM="Kantendicke links"');
|
||||
lines.push(`KAHIDI="${kant.kahi}"`, 'KM="Kantendicke hinten"', `KAREDI="${kant.kare}"`, 'KM="Kantendicke rechts"');
|
||||
lines.push('FORMAT="0"', 'KM="Formatieren 0 oder 1"');
|
||||
lines.push('ZUGLI="0.0"', 'ZUGRE="0.0"', 'ZUGVO="0.0"', 'ZUGHI="0.0"');
|
||||
lines.push('DW="IF DRWIN=0 OR DRWIN=90 OR DRWIN=180 THEN DRWIN ELSE 270"');
|
||||
lines.push('ld="IF DW=90 OR DW=270 THEN B ELSE L"', 'bd="IF DW=90 OR DW=270 THEN L ELSE B"');
|
||||
lines.push(
|
||||
'offxL="IF DW=180 THEN ZUGRE ELSE ZUGLI"',
|
||||
'offxB="IF DW=90 THEN ZUGHI ELSE ZUGVO"',
|
||||
'offyL="IF DW=180 THEN ZUGHI ELSE ZUGVO"',
|
||||
'offyB="IF DW=90 THEN ZUGLI ELSE ZUGRE"'
|
||||
);
|
||||
lines.push(
|
||||
'[K',
|
||||
'<00 \\Koordinatensystem\\',
|
||||
'NR="04"',
|
||||
'XP="IF DW=0 OR DW=270 THEN 0 ELSE ld"',
|
||||
'YP="IF DW=0 OR DW=90 THEN 0 ELSE bd"',
|
||||
'ZP="0"',
|
||||
'D1="DW"',
|
||||
'KI="0"',
|
||||
'D2="0"',
|
||||
'MI="0"',
|
||||
'<00 \\Koordinatensystem\\',
|
||||
'NR="F1"',
|
||||
'XP="IF DW=0 OR DW=270 THEN 0 ELSE ld"',
|
||||
'YP="IF DW=0 OR DW=90 THEN 0 ELSE bd"',
|
||||
'ZP="0"',
|
||||
'D1="DW"',
|
||||
'KI="90"',
|
||||
'D2="0"',
|
||||
'MI="0"',
|
||||
'<00 \\Koordinatensystem\\',
|
||||
'NR="F2"',
|
||||
'XP="IF DW=0 OR DW=270 THEN 0 ELSE ld"',
|
||||
'YP="IF DW=90 OR DW=0 THEN 0 ELSE bd"',
|
||||
'ZP="0"',
|
||||
'D1="DW+270"',
|
||||
'KI="90"',
|
||||
'D2="0"',
|
||||
'MI="1"',
|
||||
'<00 \\Koordinatensystem\\',
|
||||
'NR="F3"',
|
||||
'XP="IF DW=0 OR DW=90 THEN 0 ELSE ld"',
|
||||
'YP="IF DW=180 OR DW=90 THEN 0 ELSE bd"',
|
||||
'ZP="0"',
|
||||
'D1="DW+180"',
|
||||
'KI="90"',
|
||||
'D2="0"',
|
||||
'MI="1"',
|
||||
'<00 \\Koordinatensystem\\',
|
||||
'NR="F4"',
|
||||
'XP="IF DW=180 OR DW=270 THEN 0 ELSE ld"',
|
||||
'YP="IF DW=0 OR DW=270 THEN 0 ELSE bd"',
|
||||
'ZP="0"',
|
||||
'D1="DW+90"',
|
||||
'KI="90"',
|
||||
'D2="0"',
|
||||
'MI="0"',
|
||||
'<100 \\WerkStck\\',
|
||||
'LA="IF DW=90 OR DW=270 THEN B ELSE L"',
|
||||
'BR="IF DW=90 OR DW=270 THEN L ELSE B"',
|
||||
'DI="D"',
|
||||
'FNX="IF DW=90 OR DW=270 THEN offxB ELSE offxL"',
|
||||
'FNY="IF DW=90 OR DW=270 THEN offyB ELSE offyL"',
|
||||
'AX="IF DW=90 OR DW=270 THEN ZUGVO+ZUGHI ELSE ZUGLI+ZUGRE"',
|
||||
'AY="IF DW=90 OR DW=270 THEN ZUGLI+ZUGRE ELSE ZUGVO+ZUGHI"',
|
||||
'<101 \\Kommentar\\',
|
||||
`KM="AUFTRAG : ${escQuoted(orderReference)}"`,
|
||||
`KM="STUECKLISTE : ${escQuoted(stackListKm)}"`,
|
||||
`KM="POSITION : ${escQuoted(posLabel)}"`,
|
||||
`KM="Bezeichnung: ${escQuoted(slug)}"`,
|
||||
'KM=""',
|
||||
`KM="${escQuoted(angles.turnMsg)}"`,
|
||||
'KM=""',
|
||||
'KM="Teileinfo"',
|
||||
`KM="Groesse: ${fmtMm(L)} x ${fmtMm(B)}"`,
|
||||
`KM="Staerke: ${fmtMm(D)}"`,
|
||||
`KM=""`,
|
||||
`KM="Material: ${matKm}"`,
|
||||
'KM="Belag IN: "',
|
||||
'KM="Belag AU: "',
|
||||
'KM=""',
|
||||
'<139 \\Komponente\\',
|
||||
'MNM="Beschriftung"',
|
||||
'IN="RSO/Makro/RSO_Beschriftung.mpr"',
|
||||
'VA="L L"',
|
||||
'VA="B B"',
|
||||
'VA="KAVODI KAVODI"',
|
||||
'VA="KALIDI KALIDI"',
|
||||
'VA="KAHIDI KAHIDI"',
|
||||
'VA="KAREDI KAREDI"',
|
||||
`VA="GEDREHT ${angles.beschGedreht}"`,
|
||||
'VA="NCSTOPP 0"',
|
||||
'KO="04"',
|
||||
'<139 \\Komponente\\',
|
||||
'MNM="Formatierung"',
|
||||
'IN="RSO/Makro/RSO_Format.mpr"',
|
||||
'VA="L L"',
|
||||
'VA="B B"',
|
||||
'VA="D D"',
|
||||
'VA="WKZ 316"',
|
||||
'VA="KAVODI KAVODI"',
|
||||
'VA="KALIDI KALIDI"',
|
||||
'VA="KAHIDI KAHIDI"',
|
||||
'VA="KAREDI KAREDI"',
|
||||
'VA="DIST 0.0"',
|
||||
'VA="ECKRUND 0.1"',
|
||||
'VA="KAFR 0"',
|
||||
'VA="KAFRMAX 2.0"',
|
||||
'VA="MAT MAT"',
|
||||
'??="FORMAT"',
|
||||
'KO="04"',
|
||||
'<121 \\Block\\',
|
||||
'MNM="Bearbeitungen von oben "',
|
||||
`DP="${dp}"`,
|
||||
'KO="04"'
|
||||
);
|
||||
|
||||
lines.push(...holeLines);
|
||||
lines.push(...grooveSection(piece, L, B));
|
||||
lines.push('!');
|
||||
return lines.join('\r\n');
|
||||
}
|
||||
|
||||
function attachGlobalSeq(pieceFlat, allFlat) {
|
||||
return pieceFlat.map((entry) => {
|
||||
const globalIdx = allFlat.findIndex(
|
||||
(f) =>
|
||||
f.piece.id === entry.piece.id &&
|
||||
f.qtyIndex === entry.qtyIndex &&
|
||||
f.slug === entry.slug
|
||||
);
|
||||
return { ...entry, seqGlobal: globalIdx >= 0 ? globalIdx + 1 : 1 };
|
||||
});
|
||||
}
|
||||
|
||||
function buildMprFileRecords(entries, mprOptions) {
|
||||
const { groupIndex = 1, orderReference = '', stackListKm = '01', defaultMaterialArticle = '' } = mprOptions;
|
||||
return entries.map((entry) => {
|
||||
const fileName = `${groupIndex}_${entry.seqGlobal}_${safeZipFileName(entry.slug)}.mpr`;
|
||||
const content = buildSingleMprText({
|
||||
piece: entry.piece,
|
||||
slug: entry.slug,
|
||||
qtyIndex: entry.qtyIndex,
|
||||
seqGlobal: entry.seqGlobal,
|
||||
groupIndex,
|
||||
orderReference,
|
||||
stackListKm,
|
||||
defaultMaterialArticle
|
||||
});
|
||||
return { fileName, content };
|
||||
});
|
||||
}
|
||||
|
||||
async function packMprRecords(records, zipBaseName) {
|
||||
if (records.length === 1) {
|
||||
const only = records[0];
|
||||
return {
|
||||
buffer: Buffer.from(only.content, 'utf8'),
|
||||
fileName: only.fileName,
|
||||
mimeType: 'application/octet-stream',
|
||||
fileCount: 1
|
||||
};
|
||||
}
|
||||
|
||||
const zip = new JSZip();
|
||||
for (const rec of records) {
|
||||
zip.file(rec.fileName, rec.content);
|
||||
}
|
||||
const safeBase = safeZipFileName(zipBaseName || 'piece-mpr');
|
||||
return {
|
||||
buffer: await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' }),
|
||||
fileName: `${safeBase}.zip`,
|
||||
mimeType: 'application/zip',
|
||||
fileCount: records.length
|
||||
};
|
||||
}
|
||||
|
||||
/** Nombre de fichiers MPR générés pour une ligne de débitage (ex. côtés L+R → 2). */
|
||||
export function countMprInstancesForPiece(piece) {
|
||||
return flattenPiecesForMprExport([piece]).length;
|
||||
}
|
||||
|
||||
export async function generateMprZipBuffer({
|
||||
config,
|
||||
modules = [],
|
||||
colors = {},
|
||||
orderReference = '',
|
||||
stackListKm = '01',
|
||||
defaultMaterialArticle = '',
|
||||
/** Index groupe commande dans le nom fichier (premier nombre). */
|
||||
groupIndex = 1
|
||||
}) {
|
||||
const cuttingList = generateCuttingList(config, colors, modules);
|
||||
const flat = flattenPiecesForMprExport(cuttingList.pieces);
|
||||
const withSeq = attachGlobalSeq(flat, flat);
|
||||
const records = buildMprFileRecords(withSeq, {
|
||||
groupIndex,
|
||||
orderReference,
|
||||
stackListKm,
|
||||
defaultMaterialArticle
|
||||
});
|
||||
|
||||
return {
|
||||
...(await packMprRecords(records, `apex-mpr-g${groupIndex}`)),
|
||||
fileCount: records.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export MPR pour une seule ligne de la liste de débitage (identifiée par pieceId).
|
||||
* @param {number|null|undefined} instanceIndex — exemplaire 0-based ; omis = tous les exemplaires de la ligne.
|
||||
*/
|
||||
export async function generateMprPieceDownload({
|
||||
config,
|
||||
modules = [],
|
||||
colors = {},
|
||||
pieceId,
|
||||
instanceIndex,
|
||||
orderReference = '',
|
||||
stackListKm = '01',
|
||||
defaultMaterialArticle = '',
|
||||
groupIndex = 1
|
||||
}) {
|
||||
if (!pieceId || typeof pieceId !== 'string') {
|
||||
throw new Error('pieceId requis.');
|
||||
}
|
||||
|
||||
const cuttingList = generateCuttingList(config, colors, modules);
|
||||
const piece = (cuttingList.pieces || []).find((p) => p.id === pieceId);
|
||||
if (!piece) {
|
||||
throw new Error('Pièce introuvable dans la liste de débitage.');
|
||||
}
|
||||
|
||||
const allFlat = flattenPiecesForMprExport(cuttingList.pieces);
|
||||
let pieceFlat = attachGlobalSeq(flattenPiecesForMprExport([piece]), allFlat);
|
||||
|
||||
if (instanceIndex != null && instanceIndex !== '') {
|
||||
const idx = Math.floor(Number(instanceIndex));
|
||||
if (!Number.isFinite(idx) || idx < 0) {
|
||||
throw new Error('instanceIndex invalide.');
|
||||
}
|
||||
pieceFlat = pieceFlat.filter((entry) => entry.qtyIndex === idx);
|
||||
if (pieceFlat.length === 0) {
|
||||
throw new Error('Exemplaire introuvable pour cette pièce.');
|
||||
}
|
||||
}
|
||||
|
||||
const records = buildMprFileRecords(pieceFlat, {
|
||||
groupIndex,
|
||||
orderReference,
|
||||
stackListKm,
|
||||
defaultMaterialArticle
|
||||
});
|
||||
|
||||
return packMprRecords(records, pieceId);
|
||||
}
|
||||
|
||||
function safeZipFileName(seg) {
|
||||
return String(seg).replace(/[^\w\-]+/g, '_').replace(/_+/g, '_');
|
||||
}
|
||||
|
||||
export default {
|
||||
generateMprZipBuffer,
|
||||
generateMprPieceDownload,
|
||||
countMprInstancesForPiece,
|
||||
flattenPiecesForMprExport
|
||||
};
|
||||
Reference in New Issue
Block a user