diff --git a/Website/Dockerfile b/Website/Dockerfile
index 7a2c5ad..98ad6c4 100644
--- a/Website/Dockerfile
+++ b/Website/Dockerfile
@@ -5,17 +5,17 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
WORKDIR /app
-# Removed docker.io and sudo to reduce image size and avoid conflicts
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
iproute2 \
+ docker.io \
+ sudo \
curl \
openssl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
-# Install specific Docker CLI and Compose plugin versions
RUN set -eu; \
arch="$(dpkg --print-architecture)"; \
case "$arch" in \
@@ -36,9 +36,9 @@ RUN set -eu; \
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
-# Copy application sources into the image
+# Copy application sources into the image (templates, static, main app, config)
COPY . /app
EXPOSE 4999
-CMD ["gunicorn", "-c", "gunicorn.conf.py", "main:app"]s
\ No newline at end of file
+CMD ["gunicorn", "-c", "gunicorn.conf.py", "main:app"]
diff --git a/Website/modules/emailservice/email.py b/Website/modules/emailservice/email.py
index 20d58fb..866e145 100644
--- a/Website/modules/emailservice/email.py
+++ b/Website/modules/emailservice/email.py
@@ -7,7 +7,6 @@ import os
import tempfile
from fpdf import FPDF
from flask import current_app
-from weasyprint import HTML
SELLER_INFO = {
@@ -352,457 +351,41 @@ def generate_main_contract_pdf(
pdf.output(output_path)
return output_path
-def generate_invoice_pdf(
- invoice_number: str,
- invoice_date: str,
- due_date: str,
- customer: dict,
- seller: dict,
- items: list,
- output_path: str,
- notes: str = None
-) -> str:
- """
- Generiert eine professionelle PDF-Rechnung mit WeasyPrint.
-
- :param invoice_number: Rechnungsnummer (z. B. "INV-20260827-100002")
- :param invoice_date: Rechnungsdatum (z. B. "27.08.2026")
- :param due_date: Fälligkeitsdatum (z. B. "10.09.2026")
- :param customer: Dict mit Kundeninformationen (name, attn, street, zip_city, customer_id)
- :param seller: Dict mit Verkäuferinformationen (company_name, street, zip_city, iban, bic, vat_id, etc.)
- :param items: Liste von Dicts mit Leistungspositionen (description, details, quantity, unit_price, tax_rate)
- :param output_path: Zielpfad für die generierte PDF-Datei
- :param notes: Optionaler Hinweistext (z. B. Zahlungsbedingungen oder Kleinunternehmerregelung)
- """
- # 1. Summen und Steuern berechnen
- subtotal = 0.0
- tax_totals = {}
-
- processed_items = []
- for item in items:
- qty = item.get("quantity", 1)
- unit_price = item.get("unit_price", 0.0)
- tax_rate = item.get("tax_rate", 19.0)
-
- line_total = qty * unit_price
- subtotal += line_total
-
- tax_amount = line_total * (tax_rate / 100.0)
- tax_totals[tax_rate] = tax_totals.get(tax_rate, 0.0) + tax_amount
-
- processed_items.append({
- "pos": len(processed_items) + 1,
- "description": item.get("description", ""),
- "details": item.get("details", ""),
- "quantity": qty,
- "unit_price": f"{unit_price:,.2f}".replace(",", "X").replace(".", ",").replace("X", "."),
- "tax_rate": f"{tax_rate:.0f}%" if tax_rate.is_integer() else f"{tax_rate:.1f}%",
- "line_total": f"{line_total:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
- })
-
- total_tax = sum(tax_totals.values())
- grand_total = subtotal + total_tax
-
- formatted_subtotal = f"{subtotal:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
- formatted_grand_total = f"{grand_total:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
-
- # Tabellenzeilen für Leistungen erzeugen
- items_html = ""
- for item in processed_items:
- details_html = f'
{item["details"]}
' if item["details"] else ""
- items_html += f"""
-
- | {item['pos']} |
-
- {item['description']}
- {details_html}
- |
- {item['quantity']} |
- {item['unit_price']} € |
- {item['tax_rate']} |
- {item['line_total']} € |
-
- """
-
- # Steuer-Aufschlüsselung erzeugen
- tax_breakdown_html = ""
- for rate, amt in tax_totals.items():
- rate_str = f"{rate:.0f}%" if rate.is_integer() else f"{rate:.1f}%"
- amt_str = f"{amt:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
- tax_breakdown_html += f"""
-
- | zzgl. MwSt. ({rate_str}): |
- {amt_str} € |
-
- """
-
- notes_html = f'Hinweis: {notes}
' if notes else ""
-
- # HTML & CSS Template
- html_content = f"""
-
-
-
- Rechnung {invoice_number}
-
-
-
-
-
-
-
-
- Rechnung {invoice_number}
-
- Vielen Dank für Ihren Auftrag. Wir stellen Ihnen die nachfolgend aufgeführten Leistungen in Rechnung:
-
-
-
-
-
- | Pos. |
- Beschreibung |
- Menge |
- Einzelpreis |
- MwSt. |
- Gesamt |
-
-
-
- {items_html}
-
-
-
-
-
-
- | Zwischensumme (netto): |
- {formatted_subtotal} € |
-
- {tax_breakdown_html}
-
- | Gesamtbetrag: |
- {formatted_grand_total} € |
-
-
-
-
- {notes_html}
-
-
- Bitte überweisen Sie den Rechnungsbetrag von {formatted_grand_total} € bis zum {due_date} auf das unten stehende Bankkonto.
- Verwendungszweck: Rechnung {invoice_number}
-
-
-
-
-
-
-"""
-
- # HTML zu PDF rendern
- HTML(string=html_content).write_pdf(output_path)
- return output_path
-
-def generate_dynamic_invoice(school_name, address, price_val=250.00):
- """Erzeugt eine dynamische Rechnungs-PDF für einen Neukunden."""
- now = datetime.now()
- inv_num = f"INV-{now.strftime('%Y%m%d')}-{now.strftime('%H%M%S')}"
- inv_date = now.strftime("%d.%m.%Y")
- due_date = (now + timedelta(days=14)).strftime("%d.%m.%Y")
-
- customer = {
- "name": school_name,
- "street": address.split(',')[0] if ',' in address else address,
- "zip_city": address.split(',')[1].strip() if ',' in address else "",
- }
-
- items = [
- {
- "description": f"Invario Inventarsystem – Jahreslizenz",
- "details": f"Softwarenutzung für {school_name} (12 Monate)",
- "quantity": 1,
- "unit_price": float(price_val),
- "tax_rate": 19.0
- }
- ]
-
- out_path = os.path.join(tempfile.gettempdir(), f"rechnung_{inv_num}.pdf")
- generate_invoice_pdf(
- invoice_number=inv_num,
- invoice_date=inv_date,
- due_date=due_date,
- customer=customer,
- seller=SELLER_INFO,
- items=items,
- output_path=out_path,
- notes="Zahlbar innerhalb von 14 Tagen ohne Abzug."
- )
- return out_path
+def generate_invoice_fpdf(school_name: str, address: str, price: str, date: str, software_name: str, output_path: str):
+ """Erstellt eine Rechnung als PDF mittels FPDF2."""
+ pdf = FPDF()
+ pdf.add_page()
+
+ # Header
+ pdf.set_font("Helvetica", style="B", size=18)
+ pdf.cell(0, 10, "Rechnung", new_x="LMARGIN", new_y="NEXT", align="L")
+ pdf.ln(10)
+
+ # Adress- und Datumsblock
+ pdf.set_font("Helvetica", size=12)
+ pdf.cell(0, 8, f"Datum: {date}", new_x="LMARGIN", new_y="NEXT", align="R")
+ pdf.cell(0, 8, school_name, new_x="LMARGIN", new_y="NEXT")
+ pdf.cell(0, 8, address, new_x="LMARGIN", new_y="NEXT")
+ pdf.ln(15)
+
+ # Leistung
+ pdf.set_font("Helvetica", style="B", size=12)
+ pdf.cell(0, 10, "Leistungsbeschreibung:", new_x="LMARGIN", new_y="NEXT")
+ pdf.set_font("Helvetica", size=12)
+ pdf.cell(0, 8, f"Nutzungslizenz / Akkreditierung für {software_name}", new_x="LMARGIN", new_y="NEXT")
+ pdf.ln(10)
+
+ # Preis
+ pdf.set_font("Helvetica", style="B", size=12)
+ pdf.cell(0, 10, f"Zu zahlender Betrag: {price} EUR", new_x="LMARGIN", new_y="NEXT")
+ pdf.ln(20)
+
+ # Footer
+ pdf.set_font("Helvetica", size=10)
+ pdf.cell(0, 8, "Bitte ueberweisen Sie den offenen Betrag innerhalb von 14 Tagen nach Rechnungserhalt.", new_x="LMARGIN", new_y="NEXT")
+
+ # Speichern
+ pdf.output(output_path)
def send_accreditation_email(
recipient: str,
@@ -816,10 +399,11 @@ def send_accreditation_email(
software_name: str = "Invario Inventarsystem",
invoice_path: str = None
) -> bool:
- """Generiert den Hauptvertrag und die Rechnung temporär und versendet sie zusammen mit AGB & AVV per E-Mail."""
+ """Generiert Hauptvertrag und Rechnung temporär als PDF und versendet sie."""
+
+ safe_school_name = "".join([c for c in school_name if c.isalnum() or c in (' ', '_', '-')]).rstrip()
# 1. Hauptvertrag dynamisch erzeugen
- safe_school_name = "".join([c for c in school_name if c.isalnum() or c in (' ', '_', '-')]).rstrip()
contract_filename = f"Hauptvertrag_Invario_{safe_school_name}.pdf"
contract_pdf_path = os.path.join(tempfile.gettempdir(), contract_filename)
@@ -832,31 +416,30 @@ def send_accreditation_email(
output_path=contract_pdf_path
)
- # 1b. Rechnung dynamisch erzeugen (falls nicht direkt übergeben)
+ # 1b. Rechnung dynamisch mit FPDF erzeugen
generated_invoice_path = None
if not invoice_path:
- # Konvertiert "250,00" zu 250.00 für die Rechnungsfunktion
- try:
- price_float = float(price.replace('.', '').replace(',', '.'))
- except ValueError:
- price_float = 250.00
-
- generated_invoice_path = generate_dynamic_invoice(
- school_name=school_name,
- address=address,
- price_val=price_float
+ invoice_filename = f"Rechnung_Invario_{safe_school_name}.pdf"
+ generated_invoice_path = os.path.join(tempfile.gettempdir(), invoice_filename)
+
+ generate_invoice_fpdf(
+ school_name=school_name,
+ address=address,
+ price=price,
+ date=date,
+ software_name=software_name,
+ output_path=generated_invoice_path
)
invoice_to_attach = generated_invoice_path
else:
invoice_to_attach = invoice_path
- # 2. Statischen Ordner aus Flask dynamisch ermitteln (verhindert Pfadfehler im Container)
+ # 2. Statischen Ordner aus Flask dynamisch ermitteln
if current_app:
static_dir = current_app.static_folder
else:
static_dir = os.path.join(os.getcwd(), "static")
- # Hilfsfunktion zur flexiblen Pfadfindung ('download' vs 'downloads')
def get_valid_pdf_path(filename):
possible_paths = [
os.path.join(static_dir, "downloads", filename),
@@ -933,14 +516,12 @@ def send_accreditation_email(
success = send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments)
return success
finally:
- # Hauptvertrag löschen
if os.path.exists(contract_pdf_path):
try:
os.remove(contract_pdf_path)
except OSError as e:
print(f"[WARNING] Konnte temporären Vertrag nicht löschen: {e}")
- # Temporär erstellte Rechnung löschen
if generated_invoice_path and os.path.exists(generated_invoice_path):
try:
os.remove(generated_invoice_path)
diff --git a/Website/requirements.txt b/Website/requirements.txt
index 0356a13..170fa45 100644
--- a/Website/requirements.txt
+++ b/Website/requirements.txt
@@ -4,5 +4,4 @@ bleach>=6.1,<7.0
pymongo>=4.8,<5.0
gunicorn>=22.0,<23.0
requests
-fpdf2
-weasyprint
\ No newline at end of file
+fpdf2
\ No newline at end of file