feat: MVP 3D-Druck Kostenkalkulator

- Single-Page HTML-App mit allen 18 Eingabefeldern
- 12 Berechnungen live (calc.js, reine Funktionen)
- LocalStorage-Persistenz, Mehrfach-Projekte via Sidebar
- Excel Im-/Export ueber SheetJS (vendored, MIT)
- Drag&Drop + File-Picker-Import
- Apple-Swiss-Styling, responsive
- Vorlagen-Excel mit 3 Reitern (Eingabe/Kalkulation/Angebot), Formeln referenzieren Eingabe
- openpyxl-Script fuer reproduzierbaren Template-Build
- 5 Test-Szenarien validiert

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 21:15:25 +02:00
commit 7507f768a3
12 changed files with 1308 additions and 0 deletions

68
assets/calc.js Normal file
View File

@@ -0,0 +1,68 @@
/* Berechnungslogik — 12 Formeln laut Spec */
/* global window */
/**
* @typedef {Object} Inputs
* @property {number} materialCostPerKg
* @property {number} materialUsageG
* @property {number} printTimeH
* @property {number} machineRate
* @property {number} powerKwh
* @property {number} powerPrice
* @property {number} postMin
* @property {number} postRate
* @property {number} packagingCost
* @property {number} shippingCost
* @property {number} setupCost
* @property {number} scrapPct
* @property {number} marginPct
* @property {number} quantity
* @property {number} individualAdjustment
* @property {number} vatPct
*/
const num = (v) => {
const n = parseFloat(v);
return Number.isFinite(n) ? n : 0;
};
const round2 = (v) => Math.round(v * 100) / 100;
/**
* Alle 12 Berechnungen.
* @param {Object} s State-Objekt
* @returns {Object} Ergebnisse
*/
const calculate = (s) => {
const materialCost = num(s.materialUsageG) * (num(s.materialCostPerKg) / 1000);
const machineCost = num(s.printTimeH) * num(s.machineRate);
const energyCost = num(s.printTimeH) * num(s.powerKwh) * num(s.powerPrice);
const postCost = (num(s.postMin) / 60) * num(s.postRate);
const totalProduction = materialCost + machineCost + energyCost + postCost
+ num(s.setupCost) + num(s.packagingCost) + num(s.shippingCost);
const scrapSurcharge = totalProduction * (num(s.scrapPct) / 100);
const subtotalNet = totalProduction + scrapSurcharge;
const margin = subtotalNet * (num(s.marginPct) / 100);
const customerNet = subtotalNet + margin + num(s.individualAdjustment);
const qty = Math.max(1, num(s.quantity) || 1);
const unitNet = customerNet / qty;
const unitGross = unitNet * (1 + num(s.vatPct) / 100);
const totalGross = unitGross * qty;
return {
materialCost: round2(materialCost),
machineCost: round2(machineCost),
energyCost: round2(energyCost),
postCost: round2(postCost),
totalProduction: round2(totalProduction),
scrapSurcharge: round2(scrapSurcharge),
subtotalNet: round2(subtotalNet),
margin: round2(margin),
customerNet: round2(customerNet),
unitNet: round2(unitNet),
unitGross: round2(unitGross),
totalGross: round2(totalGross),
};
};
window.Calc = { calculate, round2, num };