JET-Design-Wortmarke im Kopf und auf der Rechnung
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>
This commit is contained in:
12
AGENTS.md
12
AGENTS.md
@@ -8,6 +8,8 @@
|
|||||||
- `templates/settings.html` — Einstellungsseite (Druckerabschreibung, Arbeitskosten, Marge, MwSt., Firmendaten, Logo)
|
- `templates/settings.html` — Einstellungsseite (Druckerabschreibung, Arbeitskosten, Marge, MwSt., Firmendaten, Logo)
|
||||||
- `static/style.css` — Light-/Dark-Mode-Theming
|
- `static/style.css` — Light-/Dark-Mode-Theming
|
||||||
- `static/script.js` — Live-Berechnung + Auto-Save beim Tippen
|
- `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)
|
- `druckkalkulation_config.yaml` — auto-created/auto-saved state (in .gitignore)
|
||||||
- `proxmox-setup.sh` — Proxmox LXC-Ersteinrichtung
|
- `proxmox-setup.sh` — Proxmox LXC-Ersteinrichtung
|
||||||
- `proxmox-update.sh` — Proxmox LXC-Update
|
- `proxmox-update.sh` — Proxmox LXC-Update
|
||||||
@@ -100,4 +102,14 @@ Kein Test, Lint, Typecheck oder CI vorhanden. README.md und AGENTS.md enthalten
|
|||||||
`_migriere()` in eine einzelne Position und entfernt die Altfelder.
|
`_migriere()` in eine einzelne Position und entfernt die Altfelder.
|
||||||
- **Saeamtliche UI-Texte sind Deutsch** — Labels, Fehlermeldungen, Rechnungsinhalte.
|
- **Saeamtliche UI-Texte sind Deutsch** — Labels, Fehlermeldungen, Rechnungsinhalte.
|
||||||
- Nur `.venv/`, `druckkalkulation_config.yaml` und `static/logo.*` werden ignoriert.
|
- 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).
|
- `3dDruckKalkulator.py` ist die alte Tkinter-Version und wird nicht mehr verwendet (nicht im Tracking).
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ sich ueber `HOST` und `PORT` ueberschreiben.
|
|||||||
- *Regelbesteuert* — MwSt. mit 19 % oder 7 %
|
- *Regelbesteuert* — MwSt. mit 19 % oder 7 %
|
||||||
- **Rechnungspflichtangaben** nach §14 UStG (Steuernummer/USt-IdNr.,
|
- **Rechnungspflichtangaben** nach §14 UStG (Steuernummer/USt-IdNr.,
|
||||||
Leistungsdatum) — nur in den beiden Unternehmer-Modi
|
Leistungsdatum) — nur in den beiden Unternehmer-Modi
|
||||||
- **Logo-Upload** (PNG/JPEG/WebP) für die Rechnung
|
- **JET-Design-Wortmarke** im Seitenkopf (SVG, hell/dunkel) und auf der
|
||||||
|
Rechnung (JPEG). Ein eigenes **Logo-Upload** (PNG/JPEG/WebP) ersetzt sie.
|
||||||
|
|
||||||
## Installation auf Proxmox (LXC-Container)
|
## Installation auf Proxmox (LXC-Container)
|
||||||
|
|
||||||
@@ -151,3 +152,5 @@ druckkalkulation_projekte.yaml # Gespeicherte Projekte (ignoriert)
|
|||||||
- Alle UI-Texte sind Deutsch
|
- Alle UI-Texte sind Deutsch
|
||||||
- Die YAML-Config wird automatisch erstellt und bei jedem Tastendruck gespeichert
|
- Die YAML-Config wird automatisch erstellt und bei jedem Tastendruck gespeichert
|
||||||
- `druckkalkulation_config.yaml` und `static/logo.*` sind in `.gitignore` ausgeschlossen
|
- `druckkalkulation_config.yaml` und `static/logo.*` sind in `.gitignore` ausgeschlossen
|
||||||
|
- `static/img/logo/` gehört dagegen ins Repo: die beiden SVG-Varianten für den
|
||||||
|
Seitenkopf und `jet-design.jpg` für den Rechnungsdruck (fpdf2 setzt nur Pixelbilder)
|
||||||
|
|||||||
26
app.py
26
app.py
@@ -118,6 +118,20 @@ def as_stueckzahl(value):
|
|||||||
return max(1, zahl)
|
return max(1, zahl)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def logo_datei(config):
|
||||||
|
"""Pfad des Rechnungslogos relativ zu static/.
|
||||||
|
|
||||||
|
Ein hochgeladenes Logo hat Vorrang, sonst greift die mitgelieferte
|
||||||
|
Wortmarke. Fehlen beide Dateien, kommt '' zurueck und der PDF-Kopf
|
||||||
|
setzt den Firmennamen als Text.
|
||||||
|
"""
|
||||||
|
for name in (config.get('logo', ''), STANDARD_LOGO):
|
||||||
|
if name and os.path.exists(os.path.join('static', name)):
|
||||||
|
return name
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def pdf_text(value):
|
def pdf_text(value):
|
||||||
"""Macht beliebigen Text fuer die latin-1-Kernfonts von fpdf sicher.
|
"""Macht beliebigen Text fuer die latin-1-Kernfonts von fpdf sicher.
|
||||||
|
|
||||||
@@ -147,6 +161,11 @@ MAX_PROJEKTE = 500
|
|||||||
|
|
||||||
LOGO_EXTS = ('png', 'jpg', 'webp')
|
LOGO_EXTS = ('png', 'jpg', 'webp')
|
||||||
|
|
||||||
|
# Mitgelieferte JET-Design-Wortmarke fuer den Rechnungskopf. Aus der
|
||||||
|
# SVG-Vorlage des Shops gerastert, weil fpdf2 nur Pixelbilder setzt.
|
||||||
|
# Greift, solange kein eigenes Logo hochgeladen wurde.
|
||||||
|
STANDARD_LOGO = 'img/logo/jet-design.jpg'
|
||||||
|
|
||||||
# Obergrenze, damit ein fehlerhafter Client die Config nicht aufblaeht.
|
# Obergrenze, damit ein fehlerhafter Client die Config nicht aufblaeht.
|
||||||
MAX_POSITIONEN = 50
|
MAX_POSITIONEN = 50
|
||||||
|
|
||||||
@@ -589,7 +608,8 @@ def index():
|
|||||||
def settings():
|
def settings():
|
||||||
"""Einstellungsseite fuer Druckerabschreibung, Arbeitskosten und Marge."""
|
"""Einstellungsseite fuer Druckerabschreibung, Arbeitskosten und Marge."""
|
||||||
config = load_config()
|
config = load_config()
|
||||||
return render_template('settings.html', config=config)
|
return render_template('settings.html', config=config,
|
||||||
|
logo_anzeige=logo_datei(config))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/calculate', methods=['POST'])
|
@app.route('/calculate', methods=['POST'])
|
||||||
@@ -843,9 +863,9 @@ def invoice_pdf():
|
|||||||
col_w = 45
|
col_w = 45
|
||||||
|
|
||||||
# Logo rechts oben
|
# Logo rechts oben
|
||||||
logo_name = config.get('logo', '')
|
logo_name = logo_datei(config)
|
||||||
logo_path = os.path.join('static', logo_name) if logo_name else ''
|
logo_path = os.path.join('static', logo_name) if logo_name else ''
|
||||||
if logo_name and os.path.exists(logo_path):
|
if logo_name:
|
||||||
pdf.image(logo_path, x=175, y=12, w=20)
|
pdf.image(logo_path, x=175, y=12, w=20)
|
||||||
img_w, img_h = image_dimensions(logo_path)
|
img_w, img_h = image_dimensions(logo_path)
|
||||||
if img_w and img_h:
|
if img_w and img_h:
|
||||||
|
|||||||
7
static/img/logo/jet-design-weiss.svg
Normal file
7
static/img/logo/jet-design-weiss.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.1 KiB |
BIN
static/img/logo/jet-design.jpg
Normal file
BIN
static/img/logo/jet-design.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
7
static/img/logo/jet-design.svg
Normal file
7
static/img/logo/jet-design.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.1 KiB |
@@ -11,3 +11,25 @@
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wortmarke im Kopf. Die Schrift ist im SVG fest eingefaerbt, deshalb
|
||||||
|
liegen beide Varianten im Markup und die zum Theme passende wird
|
||||||
|
eingeblendet -- der Dark-Mode-Schalter tauscht nur das Attribut am
|
||||||
|
<html>, ohne die Seite neu zu laden. */
|
||||||
|
.marke {
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.marke-dunkel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .marke-hell {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .marke-dunkel {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
<header class="navbar navbar-expand-md">
|
<header class="navbar navbar-expand-md">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="navbar-brand navbar-brand-autodark pe-0 pe-md-3 py-2 d-flex align-items-center gap-2">
|
<div class="navbar-brand pe-0 pe-md-3 py-2 d-flex align-items-center gap-2">
|
||||||
<span class="fw-bold">JET Design</span>
|
<img src="{{ url_for('static', filename='img/logo/jet-design.svg') }}" alt="JET Design" class="marke marke-hell">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo/jet-design-weiss.svg') }}" alt="JET Design" class="marke marke-dunkel">
|
||||||
<span class="text-secondary" style="opacity:0.5;">|</span>
|
<span class="text-secondary" style="opacity:0.5;">|</span>
|
||||||
<span class="fw-semibold">3D Druck Kostenkalkulator</span>
|
<span class="fw-semibold">3D Druck Kostenkalkulator</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
<header class="navbar navbar-expand-md">
|
<header class="navbar navbar-expand-md">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="navbar-brand navbar-brand-autodark pe-0 pe-md-3 py-2 d-flex align-items-center gap-2">
|
<div class="navbar-brand pe-0 pe-md-3 py-2 d-flex align-items-center gap-2">
|
||||||
<span class="fw-bold">JET Design</span>
|
<img src="{{ url_for('static', filename='img/logo/jet-design.svg') }}" alt="JET Design" class="marke marke-hell">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo/jet-design-weiss.svg') }}" alt="JET Design" class="marke marke-dunkel">
|
||||||
<span class="text-secondary" style="opacity:0.5;">|</span>
|
<span class="text-secondary" style="opacity:0.5;">|</span>
|
||||||
<span class="fw-semibold"><i class="ti ti-file-invoice me-1"></i>Rechnung erstellen</span>
|
<span class="fw-semibold"><i class="ti ti-file-invoice me-1"></i>Rechnung erstellen</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
<header class="navbar navbar-expand-md">
|
<header class="navbar navbar-expand-md">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div class="navbar-brand navbar-brand-autodark pe-0 pe-md-3 py-2 d-flex align-items-center gap-2">
|
<div class="navbar-brand pe-0 pe-md-3 py-2 d-flex align-items-center gap-2">
|
||||||
<span class="fw-bold">JET Design</span>
|
<img src="{{ url_for('static', filename='img/logo/jet-design.svg') }}" alt="JET Design" class="marke marke-hell">
|
||||||
|
<img src="{{ url_for('static', filename='img/logo/jet-design-weiss.svg') }}" alt="JET Design" class="marke marke-dunkel">
|
||||||
<span class="text-secondary" style="opacity:0.5;">|</span>
|
<span class="text-secondary" style="opacity:0.5;">|</span>
|
||||||
<span class="fw-semibold">Einstellungen</span>
|
<span class="fw-semibold">Einstellungen</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,13 +83,17 @@
|
|||||||
<div class="card-header"><i class="ti ti-photo me-2"></i>Logo</div>
|
<div class="card-header"><i class="ti ti-photo me-2"></i>Logo</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{% if config.logo %}
|
{% if logo_anzeige %}
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<img src="{{ url_for('static', filename=config.logo) }}" style="max-height:80px;max-width:240px" class="border rounded p-1">
|
<img src="{{ url_for('static', filename=logo_anzeige) }}" style="max-height:80px;max-width:240px" class="border rounded p-1">
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if config.logo %}
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm mb-2" onclick="deleteLogo()">
|
<button type="button" class="btn btn-outline-danger btn-sm mb-2" onclick="deleteLogo()">
|
||||||
<i class="ti ti-trash me-1"></i>Logo entfernen
|
<i class="ti ti-trash me-1"></i>Eigenes Logo entfernen
|
||||||
</button>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<div class="form-text mb-2">Mitgelieferte JET-Design-Wortmarke. Ein hochgeladenes Logo ersetzt sie.</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="file" class="form-control" id="logo-upload" accept="image/png,image/jpeg,image/webp">
|
<input type="file" class="form-control" id="logo-upload" accept="image/png,image/jpeg,image/webp">
|
||||||
<button type="button" class="btn btn-primary btn-sm mt-2" onclick="uploadLogo()">
|
<button type="button" class="btn btn-primary btn-sm mt-2" onclick="uploadLogo()">
|
||||||
|
|||||||
Reference in New Issue
Block a user