645 lines
28 KiB
Python
645 lines
28 KiB
Python
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() |