Files
3ddruckkalkulation/AGENTS.md
Erik Thiele c42fecacb9 Mehrere Positionen pro Auftrag und Projektverwaltung
Bisher liess sich pro Rechnung nur ein Teil kalkulieren. Ein Objekt aus
mehreren Teilen musste in getrennten Rechnungen abgebildet werden.

Positionen:
- Die Kalkulation arbeitet mit einer Positionsliste statt einem
  Einzelteil. Bezeichnung, Material, Preis/kg, Druckzeit, Arbeitszeit
  und Stueckzahl gelten je Position -- verschiedene Teile nutzen oft
  unterschiedliche Filamente und brauchen unterschiedlich viel Nacharbeit.
- Drucker, Strom, Stundenlohn, Marge und Steuer bleiben auftragsweit.
- Jede Position wird eine eigene Rechnungszeile mit Menge, Einzelpreis
  und Zeilensumme. Bei vielen Positionen bricht das PDF auf weitere
  Seiten um.
- Die Auftragssumme ist die Summe der bereits gerundeten Zeilen, damit
  die Rechnung zeilenweise nachrechenbar bleibt.
- saubere_positionen() normalisiert jede Liste aus Config oder Client
  (Typen, Wertebereiche, Obergrenze 50); es bleibt immer mindestens
  eine Position bestehen.
- Alte Configs mit den Flachfeldern werden in eine einzelne Position
  migriert, die Altfelder entfernt.
- sammlePositionen() liefert null auf Seiten ohne Positionsliste, damit
  ein Tastendruck auf der Einstellungs- oder Rechnungsseite die
  Positionen nicht loescht.

Projekte:
- Benannte Kalkulationen lassen sich speichern und wieder laden,
  Ablage in druckkalkulation_projekte.yaml (ein Mapping fuer alle
  Projekte, Schluessel ist der Projektname).
- Gesichert werden nur PROJEKT_FELDER: Name, Positionen, Kundendaten,
  Rechnungsnummer, Rechnungs- und Leistungsdatum. Betriebswerte gehoeren
  bewusst nicht dazu -- sonst wuerde das Laden eines alten Projekts den
  aktuellen Stundenlohn oder das Steuerregime ueberschreiben.
- Neue Routen: GET /projekte sowie POST /projekte/{speichern,laden,loeschen}.
- Eigenes Lock, gleiche atomare Schreibtechnik wie die Config.
- Ueberschreiben, Laden bei ungespeicherten Aenderungen und Loeschen
  fragen jeweils nach.
- Der offene Autosave-Debounce wird vor dem Speichern durchgedrueckt,
  damit wirklich der aktuelle Stand ins Projekt geht.

Die beiden Teile stehen in einem Commit, weil die Aenderungen in app.py,
script.js und index.html ineinandergreifen und ein aufgetrennter
Zwischenstand nicht als solcher getestet wurde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 11:38:26 +02:00

5.0 KiB

AGENTS.md — 3D-Druck Kostenkalkulation

Project structure

  • app.py — Flask web app
  • templates/index.html — Hauptseite mit Formular und Live-Ergebnissen
  • templates/invoice.html — Rechnungsseite mit PDF-Download
  • templates/settings.html — Einstellungsseite (Druckerabschreibung, Arbeitskosten, Marge, MwSt., Firmendaten, Logo)
  • static/style.css — Light-/Dark-Mode-Theming
  • static/script.js — Live-Berechnung + Auto-Save beim Tippen
  • druckkalkulation_config.yaml — auto-created/auto-saved state (in .gitignore)
  • proxmox-setup.sh — Proxmox LXC-Ersteinrichtung
  • proxmox-update.sh — Proxmox LXC-Update
  • .venv/ — virtual environment with all deps

Setup

source .venv/bin/activate

Dependencies: gepinnt in requirements.txt (pip install -r requirements.txt). flask, fpdf2 (imported as fpdf), PyYAML. Alle in .venv.

Run

python app.py            # Debug aus, lauscht auf 0.0.0.0:5001
FLASK_DEBUG=1 python app.py   # Debug an, dann nur 127.0.0.1

HOST und PORT ueberschreiben die Defaults.

Kein Test, Lint, Typecheck oder CI vorhanden. README.md und AGENTS.md enthalten die Projektdokumentation.

Routes

Methode Pfad Beschreibung
GET / Hauptseite mit Formular
POST /calculate JSON-Berechnung (AJAX)
POST /save_config Speichert Werte in YAML
GET /invoice Rechnungsseite
POST /invoice/pdf PDF-Download
POST /export TXT-Download
GET /settings Einstellungsseite (Druckerabschreibung, Arbeitskosten, Marge, MwSt.)
POST /upload_logo Logo hochladen (PNG/JPEG/WebP)
POST /delete_logo Logo loschen

Config

druckkalkulation_config.yaml wird beim Start gelesen und bei jeder Eingabe automatisch gespeichert (debounced 500ms via AJAX). Enthaelt alle Formularwerte plus dark_mode. Wird automatisch erstellt — kein manuelles Setup.

Wichtige Fallstricke

  • fpdf kommt via fpdf2-Paket, Import ist from fpdf import FPDF. Die Legacy-API (ln=True, output(dest='S'), Font "Arial") ist abgeloest durch new_x/new_y, output() und "Helvetica" -- nicht zurueckbauen.
  • PDF-Kernfonts koennen nur latin-1. Jeder Freitext muss durch pdf_text(), sonst wirft die PDF-Route bei Sonderzeichen einen UnicodeEncodeError.
  • load_config() wirft ConfigError, wenn die YAML kaputt ist. Nicht abfangen und auf Defaults zurueckfallen -- das wuerde die Datei beim naechsten Autosave endgueltig ueberschreiben.
  • /save_config uebernimmt nur Keys aus DEFAULT_VALUES.
  • Steuerlogik: steuerregime ist die Quelle der Wahrheit und kennt drei Werte (STEUERREGIME): 'privat' | 'kleinunternehmer' | 'regel'. 'privat' ist rechtlich NICHT dasselbe wie 'kleinunternehmer': ein Privatverkaeufer ist kein Unternehmer nach §2 UStG, daher duerfen weder §19-Hinweis noch Steuernummer/USt-IdNr. auf den Beleg -- auch dann nicht, wenn die Felder in der Config gefuellt sind (calc['ist_unternehmer']). mwst_ausweisen ist davon abgeleitet und wird in _migriere() gesetzt -- nie direkt schreiben. _migriere() laeuft in load_config() VOR dem Auffuellen mit Defaults (sonst greift die Ableitung aus Alt-Configs nicht) und in /save_config nach dem Update (sonst steht Widerspruechliches auf der Platte).
  • Der MwSt.-Satz kommt aus mwst_satz, nicht mehr hartcodiert.
  • Positionen: config['positionen'] ist eine Liste von Dicts mit den Feldern aus DEFAULT_POSITION. calculate() liefert calc['positionen'] (je Position stueckpreis und zeilensumme) plus die Auftragssummen. Jede Liste aus Config oder Client geht durch saubere_positionen() -- nie ungeprueft uebernehmen. Es gibt immer mindestens eine Position.
  • Die Auftragssumme ist die Summe der bereits gerundeten Zeilen, damit die Rechnung zeilenweise nachrechenbar bleibt.
  • sammlePositionen() im Frontend liefert null auf Seiten ohne Positionsliste (Einstellungen, Rechnung). Dann darf positionen NICHT mitgesendet werden -- sonst wuerde ein Tastendruck dort die Positionen loeschen.
  • Projekte: druckkalkulation_projekte.yaml haelt benannte Staende, Schluessel ist der Projektname. Nur PROJEKT_FELDER werden gesichert -- Betriebswerte (Marge, Steuer, Drucker, Firmendaten) gehoeren bewusst NICHT dazu, sonst wuerde das Laden eines alten Projekts sie ueberschreiben. Eigenes Lock _projekte_lock, gleiche atomare Schreibtechnik wie die Config. Die Preise in der Projektliste werden mit den AKTUELLEN Betriebswerten gerechnet, nicht mit denen von damals.
  • druckkalkulation_config.yaml ist der Arbeitsstand, nicht das Archiv.
  • Alte Configs mit den Flachfeldern (materialGewicht etc.) migriert _migriere() in eine einzelne Position und entfernt die Altfelder.
  • Saeamtliche UI-Texte sind Deutsch — Labels, Fehlermeldungen, Rechnungsinhalte.
  • Nur .venv/, druckkalkulation_config.yaml und static/logo.* werden ignoriert.
  • 3dDruckKalkulator.py ist die alte Tkinter-Version und wird nicht mehr verwendet (nicht im Tracking).