Die Logos stammen aus der Shop-Webseite (assets/img/logo). Im Seitenkopf stehen beide SVG-Varianten im Markup, CSS blendet die zum Theme passende ein -- der Dark-Mode-Schalter laedt die Seite nicht neu. Fuer den Rechnungsdruck liegt die Wortmarke zusaetzlich als JPEG bei, weil fpdf2 kein SVG setzen kann. logo_datei() waehlt aus: hochgeladenes Logo vor mitgelieferter Marke, Textkopf nur noch als Notnagel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
116 lines
6.1 KiB
Markdown
116 lines
6.1 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
|
|
- `static/img/logo/` — JET-Design-Wortmarke: `jet-design.svg` (dunkle Schrift),
|
|
`jet-design-weiss.svg` (Dark Mode), `jet-design.jpg` (Rechnungs-PDF)
|
|
- `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.
|
|
`static/img/logo/` ist davon nicht betroffen und wird versioniert.
|
|
- Das Rechnungslogo liefert `logo_datei()`: hochgeladenes Logo vor
|
|
`STANDARD_LOGO`, und erst wenn beide Dateien fehlen, setzt der PDF-Kopf
|
|
den Namen als Text. Nie direkt `config['logo']` fuer die Ausgabe lesen.
|
|
- Die Wortmarke ist im SVG fest eingefaerbt. Deshalb stehen im Kopf beide
|
|
Varianten im Markup und `static/style.css` blendet ueber
|
|
`[data-bs-theme="dark"]` die passende ein -- `toggleDarkMode()` laedt die
|
|
Seite nicht neu, eine serverseitige Auswahl waere danach falsch. Am
|
|
`navbar-brand` darf `navbar-brand-autodark` nicht stehen: Tabler invertiert
|
|
damit Bilder im Dark Mode und wuerde die weisse Variante schwarz faerben.
|
|
- `3dDruckKalkulator.py` ist die alte Tkinter-Version und wird nicht mehr verwendet (nicht im Tracking).
|