Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
+5
-2
@@ -73,9 +73,9 @@ BOOKING_FLOW_MAP = {
|
||||
}
|
||||
|
||||
BOOKING_PRICE_MAP = {
|
||||
"startpaket": 420.0,
|
||||
"startpaket": 450.0,
|
||||
"advanced_package": 0.0,
|
||||
"inventarsystem": 390.0,
|
||||
"inventarsystem": 350.0,
|
||||
"buecherei": 120.0,
|
||||
"terminverwaltung": 120.0,
|
||||
"emailversand": 70.0,
|
||||
@@ -2681,6 +2681,7 @@ def booking_payment():
|
||||
"package": selected_package,
|
||||
"package_key": package_raw,
|
||||
"package_price": total_price if not is_trial else 0.0,
|
||||
"price": total_price if not is_trial else 0.0,
|
||||
"flow": booking_flow,
|
||||
"is_trial": is_trial,
|
||||
"title": flow_config["title"],
|
||||
@@ -3180,6 +3181,7 @@ def my_instance_management():
|
||||
user_id = session.get("user_id")
|
||||
|
||||
if request.method == 'POST':
|
||||
"""Implementierung für das Ändern von Buchungsoptionen"""
|
||||
flash("Nutzer können Instanzen nicht selbst erstellen oder ändern. Bitte den Administrator kontaktieren.", "error")
|
||||
return redirect(url_for("my_instance_management"))
|
||||
|
||||
@@ -3219,6 +3221,7 @@ def my_instance_management():
|
||||
return render_template(
|
||||
"my_instance.html",
|
||||
instance=instance_view,
|
||||
is_trial=instance_view.get("is_trial") if instance_view else False,
|
||||
pending_request=pending_request,
|
||||
suggested_subdomain=suggested_subdomain,
|
||||
parent_domain=INSTANCE_PARENT_DOMAIN,
|
||||
|
||||
@@ -404,107 +404,6 @@ def generate_main_contract_pdf(
|
||||
pdf.output(output_path)
|
||||
return output_path
|
||||
|
||||
def generate_invoice_fpdf(
|
||||
school_name: str,
|
||||
address: str,
|
||||
price: str,
|
||||
date: str,
|
||||
software_name: str,
|
||||
output_path: str,
|
||||
invoice_number: str = "RE-2026-08-001"
|
||||
):
|
||||
"""Erstellt eine rechtssichere Rechnung inkl. Dankessatz und SELLER_INFO Daten mittels FPDF2."""
|
||||
|
||||
# 1. Beträge berechnen (Annahme: übergebener Preis ist Brutto)
|
||||
try:
|
||||
brutto = float(price.replace('.', '').replace(',', '.'))
|
||||
except ValueError:
|
||||
brutto = 250.00
|
||||
|
||||
netto = brutto / 1.19
|
||||
mwst = brutto - netto
|
||||
|
||||
pdf = FPDF()
|
||||
pdf.add_page()
|
||||
|
||||
# 2. Kopfzeile: Aussteller-Informationen aus SELLER_INFO (Rechtsbündig)
|
||||
pdf.set_font("Helvetica", size=9)
|
||||
pdf.cell(0, 5, f"{SELLER_INFO['company_name']} - {SELLER_INFO['sub_title']}", new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
pdf.cell(0, 5, SELLER_INFO['street'], new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
pdf.cell(0, 5, SELLER_INFO['zip_city'], new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
pdf.cell(0, 5, f"St.-Nr.: {SELLER_INFO['tax_number']} | USt-IdNr.: {SELLER_INFO['vat_id']}", new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
pdf.ln(10)
|
||||
|
||||
# 3. Empfänger-Informationen (Linksbündig)
|
||||
pdf.set_font("Helvetica", size=11)
|
||||
pdf.cell(0, 5, school_name, new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.cell(0, 5, address, new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.ln(15)
|
||||
|
||||
# 4. Rechnungs-Metadaten
|
||||
pdf.set_font("Helvetica", style="B", size=16)
|
||||
pdf.cell(0, 10, "Rechnung", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.set_font("Helvetica", size=10)
|
||||
pdf.cell(0, 5, f"Rechnungsnummer: {invoice_number}", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.cell(0, 5, f"Rechnungsdatum: {date}", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.cell(0, 5, "Liefer-/Leistungszeitraum: Entspricht Rechnungsdatum", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.ln(8)
|
||||
|
||||
# Netter Dankessatz direkt über der Tabelle
|
||||
pdf.set_font("Helvetica", style="I", size=10)
|
||||
pdf.cell(0, 6, "Vielen Dank für Ihren Einkauf! Wir freuen uns sehr über das entgegengebrachte Vertrauen.", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.ln(6)
|
||||
|
||||
# 5. Positionen (Tabellenkopf)
|
||||
pdf.set_font("Helvetica", style="B", size=10)
|
||||
pdf.cell(20, 8, "Menge", border=1)
|
||||
pdf.cell(100, 8, "Bezeichnung / Art der Leistung", border=1)
|
||||
pdf.cell(35, 8, "Einzelpreis (Netto)", border=1, align="R")
|
||||
pdf.cell(35, 8, "Gesamt (Netto)", border=1, new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
|
||||
# Positionen (Zeile)
|
||||
pdf.set_font("Helvetica", size=10)
|
||||
pdf.cell(20, 8, "1", border=1)
|
||||
pdf.cell(100, 8, f"Nutzungs Lizenz für {software_name}", border=1)
|
||||
pdf.cell(35, 8, f"{netto:.2f} EUR", border=1, align="R")
|
||||
pdf.cell(35, 8, f"{netto:.2f} EUR", border=1, new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
pdf.ln(5)
|
||||
|
||||
# 6. Summenblock
|
||||
pdf.set_left_margin(120) # Einrücken für die Summen
|
||||
pdf.cell(35, 8, "Summe Netto:", align="R")
|
||||
pdf.cell(35, 8, f"{netto:.2f} EUR", new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
|
||||
pdf.cell(35, 8, "zzgl. 19% USt.:", align="R")
|
||||
pdf.cell(35, 8, f"{mwst:.2f} EUR", new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
|
||||
pdf.cell(35, 8, "Abzüglich Skonto:", align="R")
|
||||
pdf.cell(35, 8, "0.00 EUR", new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
|
||||
pdf.set_font("Helvetica", style="B", size=11)
|
||||
pdf.cell(35, 10, "Gesamtbetrag (Brutto):", align="R")
|
||||
pdf.cell(35, 10, f"{brutto:.2f} EUR", new_x="LMARGIN", new_y="NEXT", align="R")
|
||||
|
||||
# 7. Zahlungsbedingungen, Bankverbindung & Footer
|
||||
pdf.set_left_margin(10) # Rand zurücksetzen
|
||||
pdf.ln(10)
|
||||
pdf.set_font("Helvetica", size=10)
|
||||
pdf.cell(0, 5, "Bitte überweisen Sie den fälligen Bruttobetrag innerhalb von 14 Tagen unter Angabe der", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.cell(0, 5, f"Rechnungsnummer ({invoice_number}) auf unser unten angegebenes Bankkonto. Es wurde kein Skonto vereinbart.", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.ln(5)
|
||||
|
||||
# Bankinformationen Block aus SELLER_INFO
|
||||
pdf.set_font("Helvetica", style="B", size=10)
|
||||
pdf.cell(0, 5, "Bankverbindung für Ihre Überweisung:", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.set_font("Helvetica", size=10)
|
||||
pdf.cell(0, 5, f"Bankinstitut: {SELLER_INFO['bank_name']}", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.cell(0, 5, f"IBAN: {SELLER_INFO['iban']}", new_x="LMARGIN", new_y="NEXT")
|
||||
pdf.cell(0, 5, f"BIC: {SELLER_INFO['bic']}", new_x="LMARGIN", new_y="NEXT")
|
||||
|
||||
# Speichern
|
||||
pdf.output(output_path)
|
||||
|
||||
|
||||
def send_accreditation_email(
|
||||
recipient: str,
|
||||
domain: str,
|
||||
@@ -547,25 +446,7 @@ def send_accreditation_email(
|
||||
|
||||
# 3. Dauerhafte Rechnung im static-Ordner erzeugen
|
||||
invoice_filename = f"Rechnung_{current_invoice_number}_{safe_school_name}.pdf"
|
||||
full_invoice_path = os.path.join(invoices_dir, invoice_filename)
|
||||
relative_invoice_path = f"invoices/{invoice_filename}" # Für den Datenbank-Eintrag
|
||||
|
||||
if not invoice_path:
|
||||
generate_invoice_fpdf(
|
||||
school_name=school_name,
|
||||
address=address,
|
||||
price=price,
|
||||
date=date,
|
||||
software_name=software_name,
|
||||
output_path=full_invoice_path,
|
||||
invoice_number=current_invoice_number
|
||||
)
|
||||
invoice_to_attach = full_invoice_path
|
||||
db_pdf_path = relative_invoice_path
|
||||
else:
|
||||
invoice_to_attach = invoice_path
|
||||
db_pdf_path = relative_invoice_path
|
||||
|
||||
def get_valid_pdf_path(filename):
|
||||
possible_paths = [
|
||||
os.path.join(static_dir, "downloads", filename),
|
||||
@@ -620,8 +501,6 @@ def send_accreditation_email(
|
||||
attachments = []
|
||||
if os.path.exists(contract_pdf_path):
|
||||
attachments.append(contract_pdf_path)
|
||||
if invoice_to_attach and os.path.exists(invoice_to_attach):
|
||||
attachments.append(invoice_to_attach)
|
||||
if agb_pdf_path:
|
||||
attachments.append(agb_pdf_path)
|
||||
if avv_pdf_path:
|
||||
@@ -693,16 +572,6 @@ def send_accreditation_email(
|
||||
req_client, req_col = _get_collection("instance_requests")
|
||||
inv_client, col = _get_collection("invoices")
|
||||
|
||||
# 6. Die generierte PDF-Datei als Binär-Daten für MongoDB einlesen
|
||||
rechnung_bytes = None
|
||||
if invoice_to_attach and os.path.exists(invoice_to_attach):
|
||||
try:
|
||||
with open(invoice_to_attach, "rb") as f:
|
||||
rechnung_bytes = f.read()
|
||||
except Exception as e:
|
||||
# Im Falle eines Dateilesefehlers überspringen wir das Speichern des Binaries
|
||||
pass
|
||||
|
||||
# 7. Die neue Rechnung in die Datenbank (invoices-Collection) schreiben
|
||||
invoice_document = {
|
||||
"username": user_id,
|
||||
@@ -713,18 +582,14 @@ def send_accreditation_email(
|
||||
"amount_eur": price,
|
||||
"status": "Zu prüfen",
|
||||
"due_date": computed_due_date,
|
||||
"pdf_path": db_pdf_path,
|
||||
"pdf_path": "",
|
||||
"created_at": _utc_now_iso(),
|
||||
"source": "booking_payment",
|
||||
"software_name": software_name,
|
||||
"domain": domain
|
||||
}
|
||||
|
||||
# Binärdaten der Rechnung hinzufügen, falls erfolgreich eingelesen
|
||||
if rechnung_bytes:
|
||||
invoice_document["rechnung_data"] = Binary(rechnung_bytes)
|
||||
invoice_document["rechnung_filename"] = invoice_filename
|
||||
invoice_document["rechnung_mimetype"] = "application/pdf"
|
||||
send("info@invario-software.de", f"Neue Rechnung erstellt: {current_invoice_number}", text_body=f"Eine neue Rechnung wurde erstellt für {school_name} ({user_id}).\n\nRechnungsnummer: {current_invoice_number}\nBetrag: {price} EUR\nFälligkeitsdatum: {computed_due_date} \n\n Schuldaten:\n- Schule: {school_name}\n- Adresse: {address}\n- Domain: {domain}\nGebuchte Software: {software_name}\n\nBitte prüfen Sie die Rechnung und aktualisieren Sie den Status in der Datenbank.")
|
||||
|
||||
# In MongoDB einfügen
|
||||
col.insert_one(invoice_document)
|
||||
|
||||
@@ -795,7 +795,7 @@
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3>Preis‑Transparenz</h3>
|
||||
<p>Echte Flatrate statt versteckter Gebühren: 350 € Subscription pro Jahr für das Inventarsystem für alle Schulen – vergleichbare Enterprise-Produkte liegen bei weit über 1000 €.</p>
|
||||
<p>Echte Flatrate statt versteckter Gebühren: 350 € <small>(Netto)</small> Subscription pro Jahr für das Inventarsystem für alle Schulen – vergleichbare Enterprise-Produkte liegen bei weit über 1000 €.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<h3>Zero Maintenance</h3>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<p><strong>Zugang:</strong> <a href="https://{{ instance.domain }}" target="_blank" rel="noopener noreferrer">https://{{ instance.domain }}</a></p>
|
||||
{% if instance.is_trial %}
|
||||
<p><strong>Testversion:</strong> aktiv bis {{ instance.expires_at }}</p>
|
||||
{% if instance.trial_state == "active" and not instance.upgrade_requested %}
|
||||
{% if instance.trial_state == "active" or instance.trial_state == "in_progress" or instance.trial_state == "expired" and not instance.upgrade_requested %}
|
||||
<form method="post" action="{{ url_for('request_trial_upgrade') }}" style="margin-top: 1rem;">
|
||||
<button type="submit">Auf reguläre Lizenz upgraden</button>
|
||||
</form>
|
||||
|
||||
@@ -170,7 +170,7 @@
|
||||
Jetzt kostenlos starten
|
||||
</button>
|
||||
{% else %}
|
||||
<a class="btn secondary" href="{{ url_for('login') }}">Zum Testen einloggen</a>
|
||||
<a class="btn secondary" href="{{ url_for('login') }}">Zum Testen einloggen / registrieren</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
@@ -182,7 +182,7 @@
|
||||
<h2 class="package-card__name">Inventarsystem</h2>
|
||||
</div>
|
||||
<div class="package-card__price">
|
||||
<span>390 €</span><small>/ Jahr</small>
|
||||
<small>Netto</small><span>350 €</span><small>/ Jahr</small>
|
||||
</div>
|
||||
</div>
|
||||
<p class="package-card__description">Der stabile Einstieg in die Schulverwaltung mit Inventarverwaltung, Benutzerrechten und Berichten.</p>
|
||||
@@ -194,7 +194,7 @@
|
||||
</ul>
|
||||
<div class="package-card__actions">
|
||||
{% if 'username' in session %}
|
||||
<button class="btn add-to-cart-btn" data-id="inventarsystem" data-name="Inventarsystem" data-price="390" onclick="handleAddToCart(this)">
|
||||
<button class="btn add-to-cart-btn" data-id="inventarsystem" data-name="Inventarsystem" data-price="350" onclick="handleAddToCart(this)">
|
||||
In den Warenkorb
|
||||
</button>
|
||||
{% else %}
|
||||
@@ -211,7 +211,7 @@
|
||||
<h2 class="package-card__name">Bibliothekssystem</h2>
|
||||
</div>
|
||||
<div class="package-card__price">
|
||||
<span>120 €</span><small>/ Jahr</small>
|
||||
<small>Netto</small><span>120 €</span><small>/ Jahr</small>
|
||||
</div>
|
||||
</div>
|
||||
<p class="package-card__description">Spezialisiertes Modul für Bibliotheken und Medienzentren inkl. Ausleihprozessen und Historie.</p>
|
||||
@@ -239,7 +239,7 @@
|
||||
<h2 class="package-card__name">Terminplaner</h2>
|
||||
</div>
|
||||
<div class="package-card__price">
|
||||
<span>120 €</span><small>/ Jahr</small>
|
||||
<small>Netto</small><span>120 €</span><small>/ Jahr</small>
|
||||
</div>
|
||||
</div>
|
||||
<p class="package-card__description">Für flexible Terminplanung entwickelt (z.B. Elternsprechstunden), um Termine hürdenlos online anzubieten.</p>
|
||||
@@ -306,8 +306,8 @@
|
||||
<p>Sie sind dabei, ein Einzelmodul in den Warenkorb zu legen. Wussten Sie, dass Inventar- und Bibliothekssystem perfekt zusammenarbeiten?</p>
|
||||
|
||||
<div class="upsell-box">
|
||||
<h4>Das Startpaket (490 € / Jahr)</h4>
|
||||
<p style="font-size: 0.9rem; margin-bottom: 0;">Beinhaltet das Inventarsystem (regulär 390 €) und das Bibliothekssystem (regulär 120 €). Sie sparen 30 € jährlich!</p>
|
||||
<h4>Das Startpaket (<small>Netto</small> 450 € / Jahr)</h4>
|
||||
<p style="font-size: 0.9rem; margin-bottom: 0;">Beinhaltet das Inventarsystem (regulär 350 €) und das Bibliothekssystem (regulär 120 €). Sie sparen 20 € jährlich!</p>
|
||||
</div>
|
||||
|
||||
<div class="custom-modal-actions">
|
||||
@@ -329,7 +329,7 @@
|
||||
</div>
|
||||
|
||||
<div class="cart-total-row" id="cartTotalRow">
|
||||
<span>Gesamtsumme:</span>
|
||||
<span>Gesamtsumme: <small>(Netto)</small></span>
|
||||
<span id="cartTotalPrice">0 €</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -62,6 +62,21 @@
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* Neu: Klassen für die Preisaufstellung */
|
||||
.price-breakdown {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.95rem;
|
||||
color: #5e757d;
|
||||
}
|
||||
|
||||
.booking-review__note {
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: 18px;
|
||||
@@ -287,19 +302,42 @@
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<div class="booking-review__grid">
|
||||
|
||||
<!-- ANGEPASSTE PAKET-KARTE MIT BRUTTO/NETTO RECHNUNG -->
|
||||
<div class="booking-review__card">
|
||||
<span class="booking-review__label">Paket</span>
|
||||
<div class="booking-review__value">{{ package }}</div>
|
||||
<span class="booking-review__label">Gewähltes Paket</span>
|
||||
<div class="booking-review__value" style="font-size: 1.1rem;">{{ package }}</div>
|
||||
|
||||
<!-- Hier findet die automatische Berechnung statt -->
|
||||
<!-- Voraussetzung: backend übergibt 'price' als numerische Variable (z.B. 149.00) -->
|
||||
{% if price %}
|
||||
<div class="price-breakdown">
|
||||
<div class="price-row">
|
||||
<span>Netto:</span>
|
||||
<span>{{ ("%.2f"|format(price)) | replace('.', ',') }} €</span>
|
||||
</div>
|
||||
<div class="price-row">
|
||||
<span>zzgl. 19% USt:</span>
|
||||
<span>{{ ("%.2f"|format(price * 0.19)) | replace('.', ',') }} €</span>
|
||||
</div>
|
||||
<div class="price-row total">
|
||||
<span>Gesamt (Brutto):</span>
|
||||
<span>{{ ("%.2f"|format(price * 1.19)) | replace('.', ',') }} €</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- /ANGEPASSTE PAKET-KARTE -->
|
||||
|
||||
<div class="booking-review__card" style="border-left: 4px solid #1b8a3e;">
|
||||
<span class="booking-review__label">Zahlungsart</span>
|
||||
<div class="booking-review__value" style="color: #1b8a3e;">Kauf auf Rechnung</div>
|
||||
<small style="color: #5e757d; font-weight: 600;">30 Tage Zahlungsziel nach Bereitstellung</small>
|
||||
<small style="color: #5e757d; font-weight: 600;">14 Tage Zahlungsziel nach Bereitstellung</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="booking-review__note">
|
||||
Die Software-Instanz wird nach dem Absenden <strong>sofort für 30 Tage freigeschaltet</strong>. Die finale Laufzeit aktiviert sich automatisch nach Zahlungseingang durch Ihren Schulträger.
|
||||
Die Software-Instanz wird nach dem Absenden <strong>sofort für 14 Tage freigeschaltet</strong>. Die finale Laufzeit aktiviert sich automatisch nach Zahlungseingang durch Ihren Schulträger.
|
||||
</div>
|
||||
|
||||
<form id="checkout-form" method="POST" action="{{ url_for('booking_payment') }}">
|
||||
|
||||
Reference in New Issue
Block a user