Files
3ddruckkalkulation/3dDruckKalkulator.py

645 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from datetime import datetime
from fpdf import FPDF
import os
import yaml
class DruckKalkulation:
def __init__(self, root):
self.root = root
self.root.title("3D-Druck Kostenkalkulation")
self.root.geometry("1200x800")
# Konfigurationsdatei
self.config_file = 'druckkalkulation_config.yaml'
# Dark Mode Status
self.dark_mode = tk.BooleanVar(value=False)
# Theme-Farben
self.themes = {
'light': {
'bg': '#f0f4f8',
'fg': '#1f2937',
'card_bg': 'white',
'card_fg': '#1f2937',
'section_bg': {
'projekt': '#e0e7ff',
'material': '#dbeafe',
'druck': '#d1fae5',
'abschreibung': '#e9d5ff',
'arbeit': '#fed7aa',
'gewinn': '#fef3c7'
},
'result_bg': '#4f46e5',
'result_fg': 'white',
'result_text_bg': '#6366f1',
'detail_bg': '#e5e7eb',
'detail_text_bg': '#f3f4f6',
'entry_bg': 'white',
'entry_fg': '#1f2937'
},
'dark': {
'bg': '#111827',
'fg': '#f9fafb',
'card_bg': '#1f2937',
'card_fg': '#f9fafb',
'section_bg': {
'projekt': '#312e81',
'material': '#1e3a8a',
'druck': '#064e3b',
'abschreibung': '#581c87',
'arbeit': '#92400e',
'gewinn': '#713f12'
},
'result_bg': '#6366f1',
'result_fg': 'white',
'result_text_bg': '#4f46e5',
'detail_bg': '#374151',
'detail_text_bg': '#1f2937',
'entry_bg': '#374151',
'entry_fg': '#f9fafb'
}
}
self.root.configure(bg=self.themes['light']['bg'])
# Variablen
self.vars = {
'projektName': tk.StringVar(),
'kundenName': tk.StringVar(),
'kundenAdresse': tk.StringVar(value=''),
'materialGewicht': tk.DoubleVar(value=0),
'materialPreisProKg': tk.DoubleVar(value=25),
'druckzeit': tk.DoubleVar(value=0),
'stromverbrauch': tk.DoubleVar(value=0.15),
'strompreis': tk.DoubleVar(value=0.35),
'druckerAnschaffung': tk.DoubleVar(value=500),
'druckerNutzungsdauer': tk.DoubleVar(value=3),
'druckerBetriebsstunden': tk.DoubleVar(value=2000),
'arbeitszeit': tk.DoubleVar(value=0),
'stundenlohn': tk.DoubleVar(value=25),
'gewinnmarge': tk.DoubleVar(value=20),
'rechnungsnummer': tk.StringVar(),
'datum': tk.StringVar(value=datetime.now().strftime('%Y-%m-%d'))
}
# Konfiguration laden
self.load_config()
self.create_widgets()
self.update_calculation()
# Automatisches Speichern bei Änderungen
for var in self.vars.values():
var.trace_add('write', lambda *args: self.save_config())
# Dark Mode Status speichern/laden
self.dark_mode.trace_add('write', lambda *args: self.save_config())
def get_current_theme(self):
"""Gibt das aktuelle Theme zurück"""
return self.themes['dark'] if self.dark_mode.get() else self.themes['light']
def toggle_dark_mode(self):
"""Wechselt zwischen Light und Dark Mode"""
self.dark_mode.set(not self.dark_mode.get())
# Button Text aktualisieren
if hasattr(self, 'dark_mode_btn'):
if self.dark_mode.get():
self.dark_mode_btn.config(text="☀️ Light Mode", bg='#1f2937', fg='white')
else:
self.dark_mode_btn.config(text="🌙 Dark Mode", bg='#4f46e5', fg='white')
self.apply_theme()
def apply_theme(self):
"""Wendet das aktuelle Theme auf alle Widgets an"""
theme = self.get_current_theme()
# Root
self.root.configure(bg=theme['bg'])
# Alle Frames durchgehen und Theme anwenden
for widget in self.root.winfo_children():
self.apply_theme_recursive(widget, theme)
self.update_calculation()
def apply_theme_recursive(self, widget, theme):
"""Wendet Theme rekursiv auf alle Widgets an"""
widget_type = widget.winfo_class()
try:
if widget_type in ['Frame', 'Labelframe']:
current_bg = widget.cget('bg')
# Bestimme welche Farbe das Widget haben sollte
if current_bg == 'white' or current_bg.startswith('#f') or current_bg.startswith('#e') or current_bg.startswith('#d'):
if hasattr(self, 'title_frame') and widget == self.title_frame:
widget.configure(bg=theme['card_bg'])
elif current_bg in ['#e0e7ff', '#312e81']:
widget.configure(bg=theme['section_bg']['projekt'])
elif current_bg in ['#dbeafe', '#1e3a8a']:
widget.configure(bg=theme['section_bg']['material'])
elif current_bg in ['#d1fae5', '#064e3b']:
widget.configure(bg=theme['section_bg']['druck'])
elif current_bg in ['#e9d5ff', '#581c87']:
widget.configure(bg=theme['section_bg']['abschreibung'])
elif current_bg in ['#fed7aa', '#92400e']:
widget.configure(bg=theme['section_bg']['arbeit'])
elif current_bg in ['#fef3c7', '#713f12']:
widget.configure(bg=theme['section_bg']['gewinn'])
elif current_bg in ['#e5e7eb', '#374151']:
widget.configure(bg=theme['detail_bg'])
else:
widget.configure(bg=theme['bg'])
elif current_bg in ['#4f46e5', '#6366f1']:
widget.configure(bg=theme['result_bg'])
elif current_bg in ['#111827', '#f0f4f8']:
widget.configure(bg=theme['bg'])
elif widget_type == 'Label':
current_bg = widget.cget('bg')
if current_bg in ['#4f46e5', '#6366f1']:
widget.configure(bg=theme['result_bg'], fg=theme['result_fg'])
elif current_bg == 'white' or current_bg.startswith('#f') and widget != self.title_frame:
widget.configure(bg=theme['card_bg'], fg=theme['card_fg'])
elif current_bg in ['#e0e7ff', '#312e81']:
widget.configure(bg=theme['section_bg']['projekt'], fg=theme['fg'])
elif current_bg in ['#dbeafe', '#1e3a8a']:
widget.configure(bg=theme['section_bg']['material'], fg=theme['fg'])
elif current_bg in ['#d1fae5', '#064e3b']:
widget.configure(bg=theme['section_bg']['druck'], fg=theme['fg'])
elif current_bg in ['#e9d5ff', '#581c87']:
widget.configure(bg=theme['section_bg']['abschreibung'], fg=theme['fg'])
elif current_bg in ['#fed7aa', '#92400e']:
widget.configure(bg=theme['section_bg']['arbeit'], fg=theme['fg'])
elif current_bg in ['#fef3c7', '#713f12']:
widget.configure(bg=theme['section_bg']['gewinn'], fg=theme['fg'])
elif current_bg in ['#e5e7eb', '#374151']:
widget.configure(bg=theme['detail_bg'], fg=theme['fg'])
else:
widget.configure(bg=theme['bg'], fg=theme['fg'])
elif widget_type == 'Entry':
widget.configure(bg=theme['entry_bg'], fg=theme['entry_fg'],
insertbackground=theme['entry_fg'])
elif widget_type == 'Text':
if hasattr(self, 'result_text') and widget == self.result_text:
widget.configure(bg=theme['result_text_bg'], fg=theme['result_fg'])
elif hasattr(self, 'detail_text') and widget == self.detail_text:
widget.configure(bg=theme['detail_text_bg'], fg=theme['fg'])
else:
widget.configure(bg=theme['entry_bg'], fg=theme['entry_fg'])
elif widget_type == 'Canvas':
widget.configure(bg=theme['bg'])
except tk.TclError:
pass
# Rekursiv für alle Kinder
for child in widget.winfo_children():
self.apply_theme_recursive(child, theme)
def create_widgets(self):
# Hauptcontainer
main_frame = tk.Frame(self.root, bg=self.get_current_theme()['bg'])
main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
# Titel mit Dark Mode Toggle
self.title_frame = tk.Frame(main_frame, bg=self.get_current_theme()['card_bg'], relief=tk.RAISED, bd=2)
self.title_frame.pack(fill=tk.X, pady=(0, 20))
title_content = tk.Frame(self.title_frame, bg=self.get_current_theme()['card_bg'])
title_content.pack(fill=tk.X, padx=15, pady=15)
tk.Label(title_content, text="🖨️ 3D-Druck Kostenkalkulation",
font=('Arial', 24, 'bold'), bg=self.get_current_theme()['card_bg'],
fg='#4f46e5').pack(side=tk.LEFT)
# Dark Mode Toggle Button
dark_mode_frame = tk.Frame(title_content, bg=self.get_current_theme()['card_bg'])
dark_mode_frame.pack(side=tk.RIGHT, padx=10)
self.dark_mode_btn = tk.Button(
dark_mode_frame,
text="☀️ Light Mode" if self.dark_mode.get() else "🌙 Dark Mode",
command=self.toggle_dark_mode,
font=('Arial', 11, 'bold'),
bg='#4f46e5',
fg='white',
padx=15,
pady=8,
relief=tk.RAISED,
cursor='hand2',
borderwidth=2
)
self.dark_mode_btn.pack()
# Container für linke und rechte Spalte
content_frame = tk.Frame(main_frame, bg=self.get_current_theme()['bg'])
content_frame.pack(fill=tk.BOTH, expand=True)
# Linke Spalte - Eingaben
left_frame = tk.Frame(content_frame, bg=self.get_current_theme()['bg'])
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
# Scrollbarer Bereich für Eingaben
canvas = tk.Canvas(left_frame, bg=self.get_current_theme()['bg'], highlightthickness=0)
scrollbar = ttk.Scrollbar(left_frame, orient="vertical", command=canvas.yview)
scrollable_frame = tk.Frame(canvas, bg=self.get_current_theme()['bg'])
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Projektinformationen
self.create_section(scrollable_frame, "Projektinformationen", 'projekt', [
('Projektname', 'projektName', 'entry')
])
# Material
self.create_section(scrollable_frame, "Material", 'material', [
('Gewicht (Gramm)', 'materialGewicht', 'number'),
('Preis pro kg (€)', 'materialPreisProKg', 'number')
])
# Druckparameter
self.create_section(scrollable_frame, "Druckparameter", 'druck', [
('Druckzeit (Minuten)', 'druckzeit', 'number'),
('Stromverbrauch (kW)', 'stromverbrauch', 'number'),
('Strompreis (€/kWh)', 'strompreis', 'number')
])
# Drucker Abschreibung
self.create_section(scrollable_frame, "Drucker Abschreibung", 'abschreibung', [
('Anschaffungspreis (€)', 'druckerAnschaffung', 'number'),
('Nutzungsdauer (Jahre)', 'druckerNutzungsdauer', 'number'),
('Betriebsstunden pro Jahr', 'druckerBetriebsstunden', 'number')
])
# Arbeitskosten
self.create_section(scrollable_frame, "Arbeitskosten", 'arbeit', [
('Arbeitszeit (Minuten)', 'arbeitszeit', 'number'),
('Stundenlohn (€)', 'stundenlohn', 'number')
])
# Gewinnmarge
self.create_section(scrollable_frame, "Gewinnmarge", 'gewinn', [
('Gewinnmarge (%)', 'gewinnmarge', 'number')
])
# Rechte Spalte - Ergebnisse
right_frame = tk.Frame(content_frame, bg=self.get_current_theme()['bg'])
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(10, 0))
# Ergebnisbereich
self.result_frame = tk.Frame(right_frame, bg=self.get_current_theme()['result_bg'], relief=tk.RAISED, bd=3)
self.result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
tk.Label(self.result_frame, text="Kostenaufstellung",
font=('Arial', 18, 'bold'), bg=self.get_current_theme()['result_bg'],
fg=self.get_current_theme()['result_fg']).pack(pady=15)
self.result_text = tk.Text(self.result_frame, height=20, width=40,
font=('Courier', 11), bg=self.get_current_theme()['result_text_bg'],
fg=self.get_current_theme()['result_fg'],
relief=tk.FLAT, padx=15, pady=10)
self.result_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
# Detaillierte Informationen
detail_frame = tk.Frame(right_frame, bg=self.get_current_theme()['detail_bg'], relief=tk.RAISED, bd=2)
detail_frame.pack(fill=tk.X, pady=(0, 10))
tk.Label(detail_frame, text="Detaillierte Informationen",
font=('Arial', 12, 'bold'), bg=self.get_current_theme()['detail_bg'],
fg=self.get_current_theme()['fg']).pack(pady=10)
self.detail_text = tk.Text(detail_frame, height=5, width=40,
font=('Arial', 10), bg=self.get_current_theme()['detail_text_bg'],
fg=self.get_current_theme()['fg'],
relief=tk.FLAT, padx=10, pady=5)
self.detail_text.pack(fill=tk.X, padx=10, pady=(0, 10))
# Buttons
button_frame = tk.Frame(right_frame, bg='#f0f4f8')
button_frame.pack(fill=tk.X)
tk.Button(button_frame, text="📄 Rechnung erstellen",
command=self.create_invoice, bg='#4f46e5', fg='white',
font=('Arial', 12, 'bold'), padx=20, pady=10,
cursor='hand2').pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
tk.Button(button_frame, text="💾 Export",
command=self.export_data, bg='#10b981', fg='white',
font=('Arial', 12, 'bold'), padx=20, pady=10,
cursor='hand2').pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(5, 0))
def save_config(self):
"""Speichert alle Parameter in YAML-Datei"""
try:
config_data = {}
for key, var in self.vars.items():
value = var.get()
config_data[key] = value
# Dark Mode Status speichern
config_data['dark_mode'] = self.dark_mode.get()
with open(self.config_file, 'w', encoding='utf-8') as f:
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
except Exception as e:
print(f"Fehler beim Speichern der Konfiguration: {e}")
def load_config(self):
"""Lädt alle Parameter aus YAML-Datei"""
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)
if config_data:
for key, value in config_data.items():
if key == 'dark_mode':
self.dark_mode.set(value)
elif key in self.vars:
self.vars[key].set(value)
print(f"✓ Konfiguration geladen aus: {self.config_file}")
else:
print(f" Keine Konfigurationsdatei gefunden. Standard-Werte werden verwendet.")
except Exception as e:
print(f"Fehler beim Laden der Konfiguration: {e}")
messagebox.showwarning("Warnung",
f"Konfiguration konnte nicht geladen werden.\nStandard-Werte werden verwendet.")
def create_section(self, parent, title, theme_key, fields):
theme = self.get_current_theme()
bg_color = theme['section_bg'][theme_key]
frame = tk.Frame(parent, bg=bg_color, relief=tk.RAISED, bd=2)
frame.pack(fill=tk.X, pady=(0, 10))
tk.Label(frame, text=title, font=('Arial', 12, 'bold'),
bg=bg_color, fg=theme['fg']).pack(anchor='w', padx=10, pady=(10, 5))
for label, var_name, field_type in fields:
field_frame = tk.Frame(frame, bg=bg_color)
field_frame.pack(fill=tk.X, padx=10, pady=5)
tk.Label(field_frame, text=label, bg=bg_color, fg=theme['fg'],
font=('Arial', 10)).pack(anchor='w')
if field_type == 'entry':
entry = tk.Entry(field_frame, textvariable=self.vars[var_name],
font=('Arial', 10), bg=theme['entry_bg'],
fg=theme['entry_fg'], insertbackground=theme['entry_fg'])
else:
entry = tk.Entry(field_frame, textvariable=self.vars[var_name],
font=('Arial', 10), bg=theme['entry_bg'],
fg=theme['entry_fg'], insertbackground=theme['entry_fg'])
entry.pack(fill=tk.X, pady=(2, 0))
entry.bind('<KeyRelease>', lambda e: self.update_calculation())
def get_calculations(self):
try:
# Umrechnung
druckzeit_stunden = self.vars['druckzeit'].get() / 60
arbeitszeit_stunden = self.vars['arbeitszeit'].get() / 60
material_kg = self.vars['materialGewicht'].get() / 1000
# Berechnungen
materialkosten = material_kg * self.vars['materialPreisProKg'].get()
stromkosten = druckzeit_stunden * self.vars['stromverbrauch'].get() * self.vars['strompreis'].get()
abschreibung_pro_stunde = self.vars['druckerAnschaffung'].get() / (
self.vars['druckerNutzungsdauer'].get() * self.vars['druckerBetriebsstunden'].get()
)
abschreibung = abschreibung_pro_stunde * druckzeit_stunden
arbeitskosten = arbeitszeit_stunden * self.vars['stundenlohn'].get()
gesamtkosten_netto = materialkosten + stromkosten + abschreibung + arbeitskosten
gewinn = gesamtkosten_netto * (self.vars['gewinnmarge'].get() / 100)
verkaufspreis = gesamtkosten_netto + gewinn
mwst = verkaufspreis * 0.19
gesamt_brutto = verkaufspreis + mwst
return {
'materialkosten': materialkosten,
'stromkosten': stromkosten,
'abschreibung': abschreibung,
'arbeitskosten': arbeitskosten,
'gesamtkosten_netto': gesamtkosten_netto,
'gewinn': gewinn,
'verkaufspreis': verkaufspreis,
'mwst': mwst,
'gesamt_brutto': gesamt_brutto,
'druckzeit_stunden': druckzeit_stunden,
'material_kg': material_kg,
'abschreibung_pro_stunde': abschreibung_pro_stunde
}
except:
return None
def update_calculation(self):
calc = self.get_calculations()
if not calc:
return
# Ergebnis-Text
self.result_text.delete(1.0, tk.END)
result_str = f"""
Materialkosten: {calc['materialkosten']:>10.2f}
Stromkosten: {calc['stromkosten']:>10.2f}
Abschreibung Drucker: {calc['abschreibung']:>10.2f}
Arbeitskosten: {calc['arbeitskosten']:>10.2f}
{'' * 40}
Gesamtkosten: {calc['gesamtkosten_netto']:>10.2f}
Gewinn ({self.vars['gewinnmarge'].get():.0f}%): {calc['gewinn']:>10.2f}
{'' * 40}
Verkaufspreis (netto): {calc['verkaufspreis']:>10.2f}
MwSt. (19%): {calc['mwst']:>10.2f}
{'' * 40}
GESAMT (brutto): {calc['gesamt_brutto']:>10.2f}
"""
self.result_text.insert(1.0, result_str)
# Detail-Text
self.detail_text.delete(1.0, tk.END)
detail_str = f"""Druckzeit: {calc['druckzeit_stunden']:.2f} Stunden
Materialgewicht: {calc['material_kg']:.3f} kg
Abschreibung pro Stunde: {calc['abschreibung_pro_stunde']:.4f}
Kosten pro Gramm: {self.vars['materialPreisProKg'].get()/1000:.3f}
Stromkosten pro Stunde: {self.vars['stromverbrauch'].get() * self.vars['strompreis'].get():.3f}"""
self.detail_text.insert(1.0, detail_str)
def export_data(self):
calc = self.get_calculations()
if not calc:
messagebox.showerror("Fehler", "Bitte füllen Sie alle Felder aus")
return
filename = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("Text Datei", "*.txt"), ("Alle Dateien", "*.*")]
)
if filename:
with open(filename, 'w', encoding='utf-8') as f:
f.write("3D-DRUCK KOSTENKALKULATION\n")
f.write("=" * 50 + "\n\n")
f.write(f"Projekt: {self.vars['projektName'].get()}\n")
f.write(f"Datum: {self.vars['datum'].get()}\n\n")
f.write("KOSTENAUFSTELLUNG:\n")
f.write(f"Materialkosten: {calc['materialkosten']:.2f}\n")
f.write(f"Stromkosten: {calc['stromkosten']:.2f}\n")
f.write(f"Abschreibung: {calc['abschreibung']:.2f}\n")
f.write(f"Arbeitskosten: {calc['arbeitskosten']:.2f}\n")
f.write(f"Gesamtkosten: {calc['gesamtkosten_netto']:.2f}\n")
f.write(f"Gewinn: {calc['gewinn']:.2f}\n")
f.write(f"Verkaufspreis (netto): {calc['verkaufspreis']:.2f}\n")
f.write(f"MwSt. (19%): {calc['mwst']:.2f}\n")
f.write(f"GESAMTPREIS (brutto): {calc['gesamt_brutto']:.2f}\n")
messagebox.showinfo("Erfolg", "Daten erfolgreich exportiert!")
def create_invoice(self):
# Rechnungsfenster
invoice_window = tk.Toplevel(self.root)
invoice_window.title("Rechnung erstellen")
invoice_window.geometry("600x700")
invoice_window.configure(bg='white')
frame = tk.Frame(invoice_window, bg='white', padx=20, pady=20)
frame.pack(fill=tk.BOTH, expand=True)
tk.Label(frame, text="RECHNUNG", font=('Arial', 24, 'bold'),
bg='white').pack(pady=(0, 20))
# Kundendaten
tk.Label(frame, text="Rechnungsnummer:", bg='white',
font=('Arial', 10)).pack(anchor='w')
tk.Entry(frame, textvariable=self.vars['rechnungsnummer'],
font=('Arial', 10)).pack(fill=tk.X, pady=(0, 10))
tk.Label(frame, text="Datum:", bg='white', font=('Arial', 10)).pack(anchor='w')
tk.Entry(frame, textvariable=self.vars['datum'],
font=('Arial', 10)).pack(fill=tk.X, pady=(0, 10))
tk.Label(frame, text="Kundenname:", bg='white', font=('Arial', 10)).pack(anchor='w')
tk.Entry(frame, textvariable=self.vars['kundenName'],
font=('Arial', 10)).pack(fill=tk.X, pady=(0, 10))
tk.Label(frame, text="Kundenadresse:", bg='white', font=('Arial', 10)).pack(anchor='w')
address_text = tk.Text(frame, height=3, font=('Arial', 10))
address_text.pack(fill=tk.X, pady=(0, 20))
address_text.insert(1.0, self.vars['kundenAdresse'].get())
calc = self.get_calculations()
if calc:
# Rechnungsvorschau
preview = tk.Text(frame, height=15, font=('Courier', 10), bg='#f9fafb')
preview.pack(fill=tk.BOTH, expand=True, pady=(0, 20))
preview_text = f"""
LEISTUNGSBESCHREIBUNG:
{'' * 50}
Position: 3D-Druck - {self.vars['projektName'].get()}
Menge: 1
Einzelpreis: {calc['verkaufspreis']:.2f}
Gesamt: {calc['verkaufspreis']:.2f}
{'' * 50}
Nettobetrag: {calc['verkaufspreis']:.2f}
MwSt. (19%): {calc['mwst']:.2f}
{'' * 50}
GESAMTBETRAG: {calc['gesamt_brutto']:.2f}
Zahlbar innerhalb von 14 Tagen ohne Abzug.
Vielen Dank für Ihren Auftrag!
"""
preview.insert(1.0, preview_text)
preview.config(state='disabled')
tk.Button(frame, text="💾 Rechnung als PDF speichern",
command=lambda: self.save_pdf_invoice(address_text.get(1.0, tk.END)),
bg='#1f2937', fg='white', font=('Arial', 12, 'bold'),
padx=20, pady=10, cursor='hand2').pack(fill=tk.X)
def save_pdf_invoice(self, address):
try:
calc = self.get_calculations()
if not calc:
messagebox.showerror("Fehler", "Berechnung fehlgeschlagen")
return
filename = filedialog.asksaveasfilename(
defaultextension=".pdf",
filetypes=[("PDF Datei", "*.pdf")]
)
if not filename:
return
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", "B", 24)
pdf.cell(0, 15, "RECHNUNG", ln=True, align='C')
pdf.ln(10)
pdf.set_font("Arial", "", 11)
pdf.cell(0, 6, f"Rechnungsnummer: {self.vars['rechnungsnummer'].get()}", ln=True)
pdf.cell(0, 6, f"Datum: {self.vars['datum'].get()}", ln=True)
pdf.ln(10)
pdf.set_font("Arial", "B", 12)
pdf.cell(0, 6, "Rechnungsempfänger:", ln=True)
pdf.set_font("Arial", "", 11)
pdf.cell(0, 6, self.vars['kundenName'].get(), ln=True)
pdf.multi_cell(0, 6, address)
pdf.ln(5)
pdf.set_font("Arial", "B", 12)
pdf.cell(0, 8, "Leistungsbeschreibung:", ln=True)
pdf.set_font("Arial", "", 11)
pdf.cell(100, 8, f"3D-Druck: {self.vars['projektName'].get()}")
pdf.cell(30, 8, "1", align='R')
pdf.cell(30, 8, f"{calc['verkaufspreis']:.2f} EUR", align='R')
pdf.cell(30, 8, f"{calc['verkaufspreis']:.2f} EUR", ln=True, align='R')
pdf.ln(10)
pdf.cell(130, 6, "Nettobetrag:")
pdf.cell(60, 6, f"{calc['verkaufspreis']:.2f} EUR", ln=True, align='R')
pdf.cell(130, 6, "MwSt. (19%):")
pdf.cell(60, 6, f"{calc['mwst']:.2f} EUR", ln=True, align='R')
pdf.set_font("Arial", "B", 12)
pdf.cell(130, 8, "GESAMTBETRAG:")
pdf.cell(60, 8, f"{calc['gesamt_brutto']:.2f} EUR", ln=True, align='R')
pdf.ln(10)
pdf.set_font("Arial", "", 10)
pdf.multi_cell(0, 5, "Zahlbar innerhalb von 14 Tagen ohne Abzug.\nVielen Dank für Ihren Auftrag!")
pdf.output(filename)
messagebox.showinfo("Erfolg", "PDF-Rechnung erfolgreich erstellt!")
except Exception as e:
messagebox.showerror("Fehler", f"PDF konnte nicht erstellt werden.\nBitte installieren Sie: pip install fpdf\n\nFehler: {str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = DruckKalkulation(root)
root.mainloop()