# 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 ```bash source .venv/bin/activate ``` **Dependencies**: gepinnt in `requirements.txt` (`pip install -r requirements.txt`). `flask`, `fpdf2` (imported as `fpdf`), `PyYAML`. Alle in `.venv`. ## Run ```bash 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).