Files
3ddruckkalkulation/AGENTS.md
Erik Thiele 1b3ef82db4 Knopf "Neues Projekt" zum Leeren des Arbeitsstands
Bisher liess sich ein neuer Auftrag nur von Hand beginnen: Namen
ueberschreiben, jede Position neu ausfuellen und auf der Rechnungsseite
Kundendaten und Rechnungsnummer leeren. Wurde der letzte Schritt
vergessen, stand der alte Kunde auf der neuen Rechnung.

- POST /projekte/neu setzt nur PROJEKT_FELDER zurueck (Name, Positionen,
  Kundendaten, Rechnungsnummer, Datum auf heute). Betriebswerte wie
  Marge, Stundenlohn, Drucker, Steuer und Firmendaten bleiben.
- GET /projekte liefert zusaetzlich 'ungespeichert'. Der Knopf fragt nur
  nach, wenn der offene Stand wirklich nicht als Projekt gesichert ist.
- Der Vergleich ignoriert 'datum' und 'geaendert', sonst waere ein
  gespeichertes Projekt am naechsten Tag faelschlich "ungespeichert".

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

104 lines
5.3 KiB
Markdown

# 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.
- `/projekte/neu` setzt nur `PROJEKT_FELDER` zurueck. `GET /projekte`
liefert zusaetzlich `ungespeichert` -- der Vergleich laeuft ueber
`_vergleichbar()` und ignoriert bewusst `datum` und `geaendert`, sonst
waere ein Projekt am naechsten Tag faelschlich "ungespeichert".
- 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).