Files
web-cad/frontend/src/services/bomService.ts
T

139 lines
5.1 KiB
TypeScript
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.
/**
* 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<string, { key: GroupKey; count: number }>();
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');
}