/** * Task CAD-9 – Stueckliste (BOM) fuer Traversen-Systeme. * * buildBOM gruppiert Traversen + Eck-Traversen nach Typ + Mass * (Laenge x Breite), zaehlt Stueckzahlen und summiert die * Gesamt-Traversemeter (Boxcorner zaehlt BEIDE Arme). * exportBOMCsv: Semikolon-separierte CSV (Excel-DE-freundlich). */ import type { CADElement } from '../types/cad.types'; export interface BOMRow { pos: number; type: 'Traverse' | 'Eck-Traverse' | 'Kreis-Traverse'; /** Laenge in cm (Traverse: length; Eck-Traverse: armLength; Kreis: Durchmesser). */ length: number; /** Breite in cm (trussWidth). */ width: number; count: number; /** Gesamtlaenge dieser Position in cm. */ totalCm: number; /** Bezeichnung. */ label: string; /** Gurtrohr-Anzahl (optional, CAD-11). */ chordCount?: number; /** Eck-Winkel in Grad (optional, CAD-11). */ angle?: number; } export interface BOMResult { rows: BOMRow[]; /** Gesamte Traversenlaenge in Metern (Ecken zaehlen beide Arme). */ totalLengthM: number; } interface GroupKey { type: 'Traverse' | 'Eck-Traverse' | 'Kreis-Traverse'; length: number; width: number; chordCount?: number; angle?: number; } /** * Baut die Stueckliste aus allen Zeichnungselementen. * Nur truss- und boxcorner-Elemente fliessen ein; alles andere * wird still ignoriert. */ export function buildBOM(elements: CADElement[]): BOMResult { const groups = new Map(); for (const el of elements) { if (String(el.type) === 'truss' && el.properties.truss === true) { const length = (el.properties.length as number | undefined) ?? el.width ?? 0; const width = (el.properties.trussWidth as number | undefined) ?? el.height ?? 29; const chordCount = (el.properties.chordCount as number | undefined) ?? 2; const key: GroupKey = { type: 'Traverse', length, width, chordCount }; const id = `T|${length}|${width}|${chordCount}`; const g = groups.get(id) ?? { key, count: 0 }; g.count++; groups.set(id, g); } else if (String(el.type) === 'boxcorner' && el.properties.boxcorner === true) { const length = (el.properties.armLength as number | undefined) ?? 50; const width = (el.properties.trussWidth as number | undefined) ?? 29; const angle = (el.properties.angle as number | undefined) ?? 90; const key: GroupKey = { type: 'Eck-Traverse', length, width, angle }; const id = `C|${length}|${width}|${angle}`; const g = groups.get(id) ?? { key, count: 0 }; g.count++; groups.set(id, g); } else if (String(el.type) === 'circletruss' && el.properties.circletruss === true) { const diameter = (el.properties.diameter as number | undefined) ?? el.width; const width = (el.properties.trussWidth as number | undefined) ?? 29; const chordCount = (el.properties.chordCount as number | undefined) ?? 2; const key: GroupKey = { type: 'Kreis-Traverse', length: diameter, width, chordCount }; const id = `K|${diameter}|${width}|${chordCount}`; const g = groups.get(id) ?? { key, count: 0 }; g.count++; groups.set(id, g); } } // Sortierung: Traversen vor Ecken, innerhalb Typ nach Laenge absteigend const entries = Array.from(groups.values()); entries.sort((a, b) => { if (a.key.type !== b.key.type) { return a.key.type === 'Traverse' ? -1 : 1; } return b.key.length - a.key.length; }); const rows: BOMRow[] = entries.map((g, i) => { // Ecken haben 2 Arme: Gesamtlaenge = count * length * 2 // Kreis-Traversen: Umfang = Pi * Durchmesser const isCircle = g.key.type === 'Kreis-Traverse'; const armFactor = g.key.type === 'Eck-Traverse' ? 2 : 1; const perPieceCm = isCircle ? Math.PI * g.key.length : g.key.length * armFactor; const totalCm = g.count * perPieceCm; const detailParts: string[] = []; if (g.key.chordCount !== undefined && g.key.chordCount > 0) detailParts.push(`${g.key.chordCount}-rohr`); if (g.key.angle !== undefined && g.key.angle !== 90) detailParts.push(`${g.key.angle}°`); const detailSuffix = detailParts.length > 0 ? ` (${detailParts.join(', ')})` : ''; return { pos: i + 1, type: g.key.type, length: g.key.length, width: g.key.width, count: g.count, totalCm, label: `${g.key.type} ${isCircle ? '⌀' : ''}${g.key.length} × ${g.key.width}${detailSuffix}`, chordCount: g.key.chordCount, angle: g.key.angle, }; }); const totalCmAll = rows.reduce((s, r) => s + r.totalCm, 0); return { rows, totalLengthM: totalCmAll / 100, }; } /** * Exportiert die Stueckliste als Semikolon-CSV (Excel-DE oeffnet * das direkt mehrspaltig). Spalten: Pos;Typ;Laenge_cm;Breite_cm; * Anzahl;Gesamt_cm;Bezeichnung + Summenzeile am Ende. */ export function exportBOMCsv(bom: BOMResult): string { const lines: string[] = ['Pos;Typ;Laenge_cm;Breite_cm;Anzahl;Gesamt_cm;Bezeichnung']; for (const r of bom.rows) { lines.push( `${r.pos};${r.type};${r.length};${r.width};${r.count};${r.totalCm};${r.label}`, ); } if (bom.rows.length > 0) { lines.push(`;;;SUMME;;;${bom.totalLengthM.toFixed(2)} m`); } return lines.join('\n'); }