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( "", 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('', 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()