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>
104 lines
5.3 KiB
Markdown
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).
|