revert of docker changes and use of the right function
This commit is contained in:
+4
-4
@@ -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
|
||||
CMD ["gunicorn", "-c", "gunicorn.conf.py", "main:app"]
|
||||
|
||||
@@ -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'<div class="item-details">{item["details"]}</div>' if item["details"] else ""
|
||||
items_html += f"""
|
||||
<tr>
|
||||
<td class="text-center">{item['pos']}</td>
|
||||
<td>
|
||||
<div class="item-title">{item['description']}</div>
|
||||
{details_html}
|
||||
</td>
|
||||
<td class="text-center">{item['quantity']}</td>
|
||||
<td class="text-right">{item['unit_price']} €</td>
|
||||
<td class="text-center">{item['tax_rate']}</td>
|
||||
<td class="text-right">{item['line_total']} €</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
# 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"""
|
||||
<tr>
|
||||
<td class="summary-label">zzgl. MwSt. ({rate_str}):</td>
|
||||
<td class="summary-value">{amt_str} €</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
notes_html = f'<div class="notes-box"><strong>Hinweis:</strong> {notes}</div>' if notes else ""
|
||||
|
||||
# HTML & CSS Template
|
||||
html_content = f"""<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Rechnung {invoice_number}</title>
|
||||
<style>
|
||||
@page {{
|
||||
size: A4;
|
||||
margin: 15mm 15mm 20mm 15mm;
|
||||
@bottom-right {{
|
||||
content: "Seite " counter(page) " von " counter(pages);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
color: #64748b;
|
||||
}}
|
||||
@bottom-left {{
|
||||
content: "{seller.get('company_name', '')} • {seller.get('website', '')}";
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 8pt;
|
||||
color: #64748b;
|
||||
}}
|
||||
}}
|
||||
|
||||
*, *::before, *::after {{
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
|
||||
body {{
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 9.5pt;
|
||||
color: #1e293b;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}}
|
||||
|
||||
.header-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 25px;
|
||||
}}
|
||||
.header-table td {{ vertical-align: top; }}
|
||||
.logo-title {{
|
||||
font-size: 22pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
letter-spacing: -0.5px;
|
||||
}}
|
||||
.subtitle {{
|
||||
font-size: 9pt;
|
||||
color: #2563eb;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 2px;
|
||||
}}
|
||||
.seller-info-top {{
|
||||
font-size: 8.5pt;
|
||||
color: #64748b;
|
||||
text-align: right;
|
||||
line-height: 1.4;
|
||||
}}
|
||||
|
||||
.meta-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 25px;
|
||||
}}
|
||||
.meta-table td {{ vertical-align: top; }}
|
||||
.sender-line {{
|
||||
font-size: 7.5pt;
|
||||
color: #64748b;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 8px;
|
||||
}}
|
||||
.recipient-address {{
|
||||
font-size: 10pt;
|
||||
line-height: 1.4;
|
||||
color: #0f172a;
|
||||
}}
|
||||
|
||||
.invoice-details-card {{
|
||||
background-color: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
width: 230px;
|
||||
float: right;
|
||||
}}
|
||||
.invoice-details-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 8.5pt;
|
||||
}}
|
||||
.invoice-details-table td {{ padding: 3px 0; }}
|
||||
.invoice-details-table .label {{ color: #64748b; }}
|
||||
.invoice-details-table .value {{
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
color: #0f172a;
|
||||
}}
|
||||
|
||||
.doc-title {{
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
margin-bottom: 6px;
|
||||
}}
|
||||
.doc-intro {{
|
||||
font-size: 9.5pt;
|
||||
color: #334155;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
|
||||
.items-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.items-table th {{
|
||||
background-color: #0f172a;
|
||||
color: #ffffff;
|
||||
font-size: 8.5pt;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 8px 10px;
|
||||
}}
|
||||
.items-table td {{
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
font-size: 9pt;
|
||||
vertical-align: top;
|
||||
}}
|
||||
.items-table tr:nth-child(even) td {{ background-color: #f8fafc; }}
|
||||
.item-title {{ font-weight: bold; color: #0f172a; }}
|
||||
.item-details {{ font-size: 8.5pt; color: #64748b; margin-top: 3px; }}
|
||||
|
||||
.text-center {{ text-align: center; }}
|
||||
.text-right {{ text-align: right; }}
|
||||
|
||||
.summary-wrapper {{
|
||||
width: 100%;
|
||||
margin-bottom: 25px;
|
||||
}}
|
||||
.summary-table {{
|
||||
width: 280px;
|
||||
margin-left: auto;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
}}
|
||||
.summary-table td {{ padding: 4px 8px; }}
|
||||
.summary-label {{ text-align: right; color: #64748b; }}
|
||||
.summary-value {{ text-align: right; font-weight: bold; color: #0f172a; }}
|
||||
.grand-total-row td {{
|
||||
border-top: 2px solid #0f172a;
|
||||
border-bottom: 2px double #0f172a;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 11pt;
|
||||
}}
|
||||
.grand-total-row .summary-label {{ color: #0f172a; font-weight: bold; }}
|
||||
.grand-total-row .summary-value {{ color: #2563eb; font-weight: bold; }}
|
||||
|
||||
.notes-box {{
|
||||
background-color: #f1f5f9;
|
||||
border-left: 3px solid #2563eb;
|
||||
padding: 10px 12px;
|
||||
font-size: 9pt;
|
||||
color: #334155;
|
||||
margin-bottom: 25px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}}
|
||||
|
||||
.payment-info {{
|
||||
font-size: 9.5pt;
|
||||
color: #0f172a;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 35px;
|
||||
}}
|
||||
|
||||
.footer-grid {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-top: 1px solid #cbd5e1;
|
||||
padding-top: 15px;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
.footer-grid td {{
|
||||
width: 33.33%;
|
||||
vertical-align: top;
|
||||
font-size: 8pt;
|
||||
color: #64748b;
|
||||
line-height: 1.4;
|
||||
}}
|
||||
.footer-heading {{
|
||||
font-weight: bold;
|
||||
color: #0f172a;
|
||||
margin-bottom: 4px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table class="header-table">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="logo-title">{seller.get('company_name', 'INVARIO')}</div>
|
||||
<div class="subtitle">{seller.get('sub_title', 'Software & Inventarsysteme')}</div>
|
||||
</td>
|
||||
<td class="seller-info-top">
|
||||
<strong>{seller.get('company_name', '')}</strong><br>
|
||||
{seller.get('street', '')}<br>
|
||||
{seller.get('zip_city', '')}<br>
|
||||
E-Mail: {seller.get('email', '')}<br>
|
||||
Web: {seller.get('website', '')}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="meta-table">
|
||||
<tr>
|
||||
<td style="width: 55%;">
|
||||
<div class="sender-line">
|
||||
{seller.get('company_name', '')} • {seller.get('street', '')} • {seller.get('zip_city', '')}
|
||||
</div>
|
||||
<div class="recipient-address">
|
||||
<strong>{customer.get('name', '')}</strong><br>
|
||||
{f"z. Hd. {customer['attn']}<br>" if customer.get('attn') else ""}
|
||||
{customer.get('street', '')}<br>
|
||||
{customer.get('zip_city', '')}
|
||||
</div>
|
||||
</td>
|
||||
<td style="width: 45%;">
|
||||
<div class="invoice-details-card">
|
||||
<table class="invoice-details-table">
|
||||
<tr>
|
||||
<td class="label">Rechnungs-Nr.:</td>
|
||||
<td class="value">{invoice_number}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Datum:</td>
|
||||
<td class="value">{invoice_date}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Fällig am:</td>
|
||||
<td class="value">{due_date}</td>
|
||||
</tr>
|
||||
{f'<tr><td class="label">Kunden-Nr.:</td><td class="value">{customer.get("customer_id")}</td></tr>' if customer.get("customer_id") else ''}
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="doc-title">Rechnung {invoice_number}</div>
|
||||
<div class="doc-intro">
|
||||
Vielen Dank für Ihren Auftrag. Wir stellen Ihnen die nachfolgend aufgeführten Leistungen in Rechnung:
|
||||
</div>
|
||||
|
||||
<table class="items-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 6%;" class="text-center">Pos.</th>
|
||||
<th style="width: 48%;">Beschreibung</th>
|
||||
<th style="width: 8%;" class="text-center">Menge</th>
|
||||
<th style="width: 14%;" class="text-right">Einzelpreis</th>
|
||||
<th style="width: 10%;" class="text-center">MwSt.</th>
|
||||
<th style="width: 14%;" class="text-right">Gesamt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items_html}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="summary-wrapper">
|
||||
<table class="summary-table">
|
||||
<tr>
|
||||
<td class="summary-label">Zwischensumme (netto):</td>
|
||||
<td class="summary-value">{formatted_subtotal} €</td>
|
||||
</tr>
|
||||
{tax_breakdown_html}
|
||||
<tr class="grand-total-row">
|
||||
<td class="summary-label">Gesamtbetrag:</td>
|
||||
<td class="summary-value">{formatted_grand_total} €</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{notes_html}
|
||||
|
||||
<div class="payment-info">
|
||||
Bitte überweisen Sie den Rechnungsbetrag von <strong>{formatted_grand_total} €</strong> bis zum <strong>{due_date}</strong> auf das unten stehende Bankkonto.<br>
|
||||
Verwendungszweck: <strong>Rechnung {invoice_number}</strong>
|
||||
</div>
|
||||
|
||||
<table class="footer-grid">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="footer-heading">Unternehmensangaben</div>
|
||||
{seller.get('company_name', '')}<br>
|
||||
Inhaber: {seller.get('owner', '')}<br>
|
||||
{seller.get('street', '')}<br>
|
||||
{seller.get('zip_city', '')}
|
||||
</td>
|
||||
<td>
|
||||
<div class="footer-heading">Bankverbindung</div>
|
||||
Bank: {seller.get('bank_name', '')}<br>
|
||||
IBAN: {seller.get('iban', '')}<br>
|
||||
BIC: {seller.get('bic', '')}<br>
|
||||
Verwendungszweck: {invoice_number}
|
||||
</td>
|
||||
<td>
|
||||
<div class="footer-heading">Steuerangaben</div>
|
||||
Steuernummer: {seller.get('tax_number', '')}<br>
|
||||
USt-IdNr.: {seller.get('vat_id', '')}<br>
|
||||
Amtsgericht: {seller.get('court', 'München')}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -4,5 +4,4 @@ bleach>=6.1,<7.0
|
||||
pymongo>=4.8,<5.0
|
||||
gunicorn>=22.0,<23.0
|
||||
requests
|
||||
fpdf2
|
||||
weasyprint
|
||||
fpdf2
|
||||
Reference in New Issue
Block a user