Changes to the Buchungs Processing for the AGB Accepting and the AVV also the the email and workflow was changed
This commit is contained in:
+115
-4
@@ -83,6 +83,8 @@ BOOKING_PRICE_MAP = {
|
||||
"testversion": 0.0,
|
||||
}
|
||||
BOOKING_ADMIN_EMAIL_FALLBACK = os.environ.get("BOOKING_ADMIN_EMAIL", "").strip()
|
||||
AGB_FILENAME = "AGB Invario.pdf"
|
||||
AVV_FILENAME = "AVV Invario.pdf"
|
||||
|
||||
|
||||
@app.after_request
|
||||
@@ -97,6 +99,18 @@ def set_security_headers(response):
|
||||
return response
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def _get_avv_version() -> str:
|
||||
path = os.path.join(BASE_DIR, "static", "downloads", AVV_FILENAME)
|
||||
try:
|
||||
with open(path, "rb") as handle:
|
||||
digest = hashlib.sha256(handle.read()).hexdigest()
|
||||
return f"{AVV_FILENAME} | SHA-256: {digest}"
|
||||
except OSError:
|
||||
return AVV_FILENAME
|
||||
|
||||
|
||||
INVOICE_UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads", "invoices")
|
||||
TUTORIAL_UPLOAD_DIR = os.path.join(BASE_DIR, "static", "uploads", "tutorials")
|
||||
LEGAL_DOWNLOAD_DIR = os.path.join(BASE_DIR, "static", "downloads", "legal")
|
||||
@@ -276,6 +290,11 @@ def _build_legal_pdf_bundle() -> BytesIO:
|
||||
with zipfile.ZipFile(bundle, mode="w", compression=zipfile.ZIP_DEFLATED) as archive:
|
||||
for filename, _, _ in LEGAL_DOWNLOAD_FILES:
|
||||
archive.write(os.path.join(LEGAL_DOWNLOAD_DIR, filename), arcname=filename)
|
||||
static_download_dir = os.path.join(BASE_DIR, "static", "downloads")
|
||||
for filename in (AGB_FILENAME, AVV_FILENAME):
|
||||
source = os.path.join(static_download_dir, filename)
|
||||
if os.path.isfile(source):
|
||||
archive.write(source, arcname=filename)
|
||||
bundle.seek(0)
|
||||
return bundle
|
||||
|
||||
@@ -1940,11 +1959,21 @@ def register():
|
||||
password = request.form.get("password") or ""
|
||||
password_repeat = request.form.get("password_repeat") or ""
|
||||
marketing_opt_in = True if request.form.get("marketing_opt_in") == "on" else False
|
||||
legal_consents = {
|
||||
"agb": request.form.get("accept_agb") == "on",
|
||||
"privacy": request.form.get("accept_privacy") == "on",
|
||||
"contract": request.form.get("accept_contract") == "on",
|
||||
"avv": request.form.get("accept_avv") == "on",
|
||||
}
|
||||
|
||||
if not username or not school_name or not contact_person or not email or not password or not password_repeat:
|
||||
flash("Bitte alle Felder ausfüllen.", "error")
|
||||
return redirect(url_for("register"))
|
||||
|
||||
if not all(legal_consents.values()):
|
||||
flash("Bitte bestätigen Sie AGB, Datenschutzerklärung, Vertragsbedingungen und AVV einzeln.", "error")
|
||||
return redirect(url_for("register"))
|
||||
|
||||
if not _validate_username(username):
|
||||
flash("Benutzername muss 3-30 Zeichen lang sein und nur Buchstaben, Zahlen, - und _ enthalten.", "error")
|
||||
return redirect(url_for("register"))
|
||||
@@ -1982,7 +2011,9 @@ def register():
|
||||
username, password, school_name, contact_person, email,
|
||||
marketing_opt_in=marketing_opt_in,
|
||||
verified=False,
|
||||
verification_token=token
|
||||
verification_token=token,
|
||||
legal_consents=legal_consents,
|
||||
legal_consents_accepted_at=_utc_now_iso(),
|
||||
):
|
||||
flash("Benutzer konnte nicht erstellt werden.", "error")
|
||||
return redirect(url_for("register"))
|
||||
@@ -2344,6 +2375,7 @@ def background_booking_provisioning(
|
||||
# 7. Bestätigungs-E-Mail versenden
|
||||
try:
|
||||
if user_email:
|
||||
_ensure_legal_pdf_files()
|
||||
formatted_date = datetime.today().strftime('%d.%m.%Y')
|
||||
|
||||
send_accreditation_email(
|
||||
@@ -2727,6 +2759,39 @@ def _get_user_trial_instance(username: str) -> dict | None:
|
||||
"is_active": bool(instance.get("is_trial") and instance.get("trial_state") in {"active", "in_progress", "expired"}),
|
||||
}
|
||||
|
||||
|
||||
def _get_booking_form_defaults(username: str) -> dict:
|
||||
defaults = {}
|
||||
if not username:
|
||||
return defaults
|
||||
|
||||
try:
|
||||
user = user_store.get_user(username) or {}
|
||||
defaults.update({
|
||||
"school_name": user.get("school_name") or "",
|
||||
"contact_person": user.get("contact_person") or user.get("name") or "",
|
||||
"contact_email": user.get("email") or user.get("Email") or "",
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
client, collection = _get_collection("instance_requests")
|
||||
try:
|
||||
request_doc = collection.find_one(
|
||||
{"username": username, "booking_data": {"$exists": True}},
|
||||
sort=[("created_at", -1)],
|
||||
)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
if request_doc and isinstance(request_doc.get("booking_data"), dict):
|
||||
defaults.update(request_doc["booking_data"])
|
||||
return {
|
||||
key: _sanitize_text(value, 600)
|
||||
for key, value in defaults.items()
|
||||
if value not in (None, "") and key not in {"agreements_accepted", "confirm_subdomain_change"}
|
||||
}
|
||||
|
||||
@app.route('/booking/payment', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def booking_payment():
|
||||
@@ -2799,8 +2864,30 @@ def booking_payment():
|
||||
"confirm_subdomain_change": (request.values.get("confirm_subdomain_change") or "").strip().lower() in {"1", "on", "true", "yes"},
|
||||
"notes": _sanitize_text(request.values.get("notes") or "", 600),
|
||||
"agreements_accepted": (request.values.get("agreements_accepted") or "").strip().lower() in {"1", "on", "true", "yes"},
|
||||
"accept_agb": (request.values.get("accept_agb") or "").strip().lower() in {"1", "on", "true", "yes"},
|
||||
"accept_privacy": (request.values.get("accept_privacy") or "").strip().lower() in {"1", "on", "true", "yes"},
|
||||
"accept_contract": (request.values.get("accept_contract") or "").strip().lower() in {"1", "on", "true", "yes"},
|
||||
"accept_avv": (request.values.get("accept_avv") or "").strip().lower() in {"1", "on", "true", "yes"},
|
||||
"avv_version": _get_avv_version(),
|
||||
}
|
||||
|
||||
booking_defaults = _get_booking_form_defaults(session.get("username") or "")
|
||||
for field in (
|
||||
"school_name",
|
||||
"contact_person",
|
||||
"contact_email",
|
||||
"contact_phone",
|
||||
"school_number",
|
||||
"billing_organization",
|
||||
"billing_street",
|
||||
"billing_zip",
|
||||
"billing_city",
|
||||
"billing_reference",
|
||||
"notes",
|
||||
):
|
||||
if not form_data[field] and booking_defaults.get(field):
|
||||
form_data[field] = booking_defaults[field]
|
||||
|
||||
if not form_data["tenant_slug"] and trial_instance:
|
||||
form_data["tenant_slug"] = trial_instance.get("subdomain") or ""
|
||||
|
||||
@@ -2851,8 +2938,8 @@ def booking_payment():
|
||||
flash(err_msg, "error")
|
||||
return render_template("schnell_buchung.html", **review_data)
|
||||
|
||||
if not form_data["agreements_accepted"]:
|
||||
err_msg = "Bitte Nutzungsbedingungen, Lizenzvereinbarung und Datenschutzerklärung bestätigen."
|
||||
if not all(form_data[field] for field in ("accept_agb", "accept_privacy", "accept_contract", "accept_avv")):
|
||||
err_msg = "Bitte bestätigen Sie AGB, Datenschutzerklärung, Vertragsbedingungen und die konkrete AVV-Fassung einzeln."
|
||||
if is_json_request:
|
||||
return jsonify({"success": False, "error": err_msg}), 400
|
||||
flash(err_msg, "error")
|
||||
@@ -2865,6 +2952,10 @@ def booking_payment():
|
||||
flash(err_msg, "error")
|
||||
return render_template("schnell_buchung.html", **review_data)
|
||||
|
||||
form_data["agreements_accepted"] = True
|
||||
form_data["legal_consents_accepted_at"] = _utc_now_iso()
|
||||
form_data["legal_consent_username"] = session.get("username") or ""
|
||||
|
||||
assigned_admin = _select_booking_admin()
|
||||
assigned_admin_username = assigned_admin.get("username") if assigned_admin else ""
|
||||
assigned_admin_display_name = assigned_admin.get("display_name") if assigned_admin else ""
|
||||
@@ -3878,7 +3969,7 @@ def vertrag():
|
||||
|
||||
@app.route('/avv')
|
||||
def avv():
|
||||
return render_template("avv.html")
|
||||
return render_template("avv.html", avv_version=_get_avv_version())
|
||||
|
||||
@app.route('/instance_request/check_subdomain', methods=['GET'])
|
||||
def check_subdomain():
|
||||
@@ -3940,6 +4031,26 @@ def download_legal_pdfs_bundle():
|
||||
download_name="invario-rechtliche-dokumente.zip",
|
||||
)
|
||||
|
||||
|
||||
@app.route('/downloads/agb-invario.pdf')
|
||||
def download_agb_pdf():
|
||||
return send_from_directory(
|
||||
os.path.join(BASE_DIR, "static", "downloads"),
|
||||
AGB_FILENAME,
|
||||
as_attachment=True,
|
||||
download_name=AGB_FILENAME,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/downloads/avv-invario.pdf')
|
||||
def download_avv_pdf():
|
||||
return send_from_directory(
|
||||
os.path.join(BASE_DIR, "static", "downloads"),
|
||||
AVV_FILENAME,
|
||||
as_attachment=True,
|
||||
download_name=AVV_FILENAME,
|
||||
)
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('username', None)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ avv_title|default('Auftragsverarbeitungsvertrag') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="panel legal-document">
|
||||
<h1>Auftragsverarbeitungsvertrag</h1>
|
||||
<p><strong>Verbindliche Fassung: {{ avv_version }}</strong></p>
|
||||
<p>Diese Fassung wird als Anlage zum Nutzungsvertrag zwischen der Invario UG und der jeweiligen Schule vereinbart.</p>
|
||||
|
||||
<h2>1. Gegenstand und Dauer</h2>
|
||||
<p>Der Auftragsverarbeiter verarbeitet personenbezogene Daten im Auftrag der Schule zur Bereitstellung der Invario-Software. Die Verarbeitung beginnt mit der Bereitstellung und endet mit Beendigung des Nutzungsvertrags.</p>
|
||||
|
||||
<h2>2. Art und Zweck der Verarbeitung</h2>
|
||||
<p>Die Verarbeitung umfasst insbesondere Speicherung, Abruf, Organisation und Verwaltung von Schul-, Nutzer-, Termin- und Inventardaten. Eine Verarbeitung zu eigenen Zwecken des Auftragsverarbeiters findet nicht statt.</p>
|
||||
|
||||
<h2>3. Weisungen und Vertraulichkeit</h2>
|
||||
<p>Der Auftragsverarbeiter verarbeitet Daten nur auf dokumentierte Weisung der Schule und verpflichtet eingesetztes Personal zur Vertraulichkeit.</p>
|
||||
|
||||
<h2>4. Technische und organisatorische Maßnahmen</h2>
|
||||
<p>Der Auftragsverarbeiter schützt die Daten durch Zugriffskontrollen, Verschlüsselung der Transportwege, Datensicherungen und Protokollierung. Die konkreten Maßnahmen werden regelmäßig auf ihre Wirksamkeit überprüft.</p>
|
||||
|
||||
<h2>5. Unterauftragsverarbeiter und Löschung</h2>
|
||||
<p>Unterauftragsverarbeiter werden nur im Rahmen der gesetzlichen Anforderungen eingesetzt. Nach Vertragsende werden die Daten nach Weisung der Schule gelöscht oder herausgegeben.</p>
|
||||
|
||||
<h2>6. Version und Änderungen</h2>
|
||||
<p>Maßgeblich ist ausschließlich die oben bezeichnete PDF-Fassung. Änderungen werden als neue, eindeutig erkennbare Datei mit neuer Prüfsumme bereitgestellt und nicht stillschweigend durch Austausch dieses Dokuments vorgenommen.</p>
|
||||
|
||||
<p><a href="{{ url_for('download_avv_pdf') }}">Diese konkrete AVV-Fassung als PDF herunterladen</a></p>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<h2>2. Datenschutzbeauftragter</h2>
|
||||
<p>Unseren Datenschutzbeauftragten erreichen Sie unter:</p>
|
||||
<p>
|
||||
<strong>Simon V. Brieger</strong><br>
|
||||
info@invario-software.de
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -140,14 +140,14 @@
|
||||
<h2>12. Geltendes Recht</h2>
|
||||
<p>
|
||||
Für alle Rechtsbeziehungen zwischen dem Nutzer und uns gilt ausschließlich deutsches Recht.
|
||||
Es gilt das TMG und das MDStV in ihrer jeweils aktuellen Fassung. Erfüllungsort ist Musterstadt.
|
||||
Es gilt das TMG und das MDStV in ihrer jeweils aktuellen Fassung. Erfüllungsort ist Bruckmühl.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>13. Kontakt zum Datenschutzbeauftragten</h2>
|
||||
<p>
|
||||
<strong>Datenschutzbeauftragter:</strong><br>
|
||||
<strong>Datenschutzbeauftragter: Simon V. Brieger</strong><br>
|
||||
E-Mail: support@invario-software.de<br>
|
||||
Telefon: 08061 / 3497799<br>
|
||||
</p>
|
||||
|
||||
@@ -69,6 +69,24 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.legal-consents {
|
||||
margin: 1.2rem 0 1.3rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid #d4e0e9;
|
||||
border-radius: 12px;
|
||||
background: #f7fafc;
|
||||
}
|
||||
|
||||
.legal-consents h2 {
|
||||
margin: 0 0 0.7rem;
|
||||
font-size: 1rem;
|
||||
color: #0f344d;
|
||||
}
|
||||
|
||||
.legal-consents .checkbox-line {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.auth-btn {
|
||||
width: 100%;
|
||||
border: none;
|
||||
@@ -127,6 +145,26 @@
|
||||
<span>Ja, ich möchte E-Mails zu Produktupdates, Neuigkeiten und Marketinginformationen erhalten. (Abmeldung jederzeit möglich)</span>
|
||||
</label>
|
||||
|
||||
<fieldset class="legal-consents">
|
||||
<h2>Verbindliche Zustimmung zur Registrierung</h2>
|
||||
<label class="checkbox-line">
|
||||
<input id="accept_agb" name="accept_agb" type="checkbox" required>
|
||||
<span>Ich habe die <a href="{{ url_for('nutzungsbedingungen') }}" target="_blank" rel="noopener noreferrer">Allgemeinen Geschäftsbedingungen (AGB) und Lizenzbedingungen</a> gelesen und akzeptiere sie verbindlich.</span>
|
||||
</label>
|
||||
<label class="checkbox-line">
|
||||
<input id="accept_privacy" name="accept_privacy" type="checkbox" required>
|
||||
<span>Ich habe die <a href="{{ url_for('datenschutz') }}" target="_blank" rel="noopener noreferrer">Datenschutzerklärung</a> gelesen und akzeptiere die dort beschriebene Verarbeitung meiner personenbezogenen Daten.</span>
|
||||
</label>
|
||||
<label class="checkbox-line">
|
||||
<input id="accept_contract" name="accept_contract" type="checkbox" required>
|
||||
<span>Ich habe die <a href="{{ url_for('vertrag') }}" target="_blank" rel="noopener noreferrer">Vertragsbedingungen</a> gelesen und akzeptiere sie für die Nutzung der angebotenen Leistungen.</span>
|
||||
</label>
|
||||
<label class="checkbox-line">
|
||||
<input id="accept_avv" name="accept_avv" type="checkbox" required>
|
||||
<span>Ich habe den <a href="{{ url_for('avv') }}" target="_blank" rel="noopener noreferrer">Auftragsverarbeitungsvertrag (AVV)</a> gelesen und stimme seiner Geltung zu, soweit er auf meine Nutzung Anwendung findet.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<button class="auth-btn" type="submit">Konto erstellen</button>
|
||||
</form>
|
||||
<p class="auth-meta">Bereits registriert? <a href="{{ url_for('login') }}">Zum Login</a></p>
|
||||
|
||||
@@ -402,13 +402,21 @@
|
||||
</div>
|
||||
|
||||
<div class="booking-review__field booking-review__field--full booking-review__consent">
|
||||
<label for="agreements_accepted">
|
||||
<input id="agreements_accepted" name="agreements_accepted" type="checkbox" required>
|
||||
<span>
|
||||
Ich habe die <a href="{{ url_for('nutzungsbedingungen') }}" target="_blank" rel="noopener" style="color: #1a68d0; font-weight: 700;">AGB (PDF)</a>,
|
||||
die <a href="{{ url_for('vertrag') }}" target="_blank" rel="noopener" style="color: #1a68d0; font-weight: 700;">Vertragsbedingungen</a> sowie den gesetzlich erforderlichen
|
||||
<a href="{{ url_for('avv') }}" target="_blank" rel="noopener" style="color: #1a68d0; font-weight: 700;">Auftragsverarbeitungsvertrag (AVV)</a> gelesen und stimme diesen Dokumenten verbindlich zu.
|
||||
</span>
|
||||
<label for="accept_agb">
|
||||
<input id="accept_agb" name="accept_agb" type="checkbox" required {% if form_data.accept_agb %}checked{% endif %}>
|
||||
<span>Ich habe die <a href="{{ url_for('download_agb_pdf') }}" target="_blank" rel="noopener">AGB und Lizenzbedingungen als PDF</a> gelesen und akzeptiere sie verbindlich.</span>
|
||||
</label>
|
||||
<label for="accept_privacy">
|
||||
<input id="accept_privacy" name="accept_privacy" type="checkbox" required {% if form_data.accept_privacy %}checked{% endif %}>
|
||||
<span>Ich habe die <a href="{{ url_for('datenschutz') }}" target="_blank" rel="noopener">Datenschutzerklärung</a> gelesen und akzeptiere die dort beschriebene Verarbeitung.</span>
|
||||
</label>
|
||||
<label for="accept_contract">
|
||||
<input id="accept_contract" name="accept_contract" type="checkbox" required {% if form_data.accept_contract %}checked{% endif %}>
|
||||
<span>Ich habe die <a href="{{ url_for('vertrag') }}" target="_blank" rel="noopener">Vertragsbedingungen</a> gelesen und akzeptiere sie verbindlich.</span>
|
||||
</label>
|
||||
<label for="accept_avv">
|
||||
<input id="accept_avv" name="accept_avv" type="checkbox" required {% if form_data.accept_avv %}checked{% endif %}>
|
||||
<span>Ich schließe den <a href="{{ url_for('download_avv_pdf') }}" target="_blank" rel="noopener">Auftragsverarbeitungsvertrag in der Fassung {{ form_data.avv_version }}</a> als Anlage zum Nutzungsvertrag ab. Ich kann diese Fassung herunterladen.</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+3
-1
@@ -42,7 +42,7 @@ def check_nm_pwd(username, password):
|
||||
client.close()
|
||||
|
||||
|
||||
def add_user(username, password, school_name="", contact_person="", email="", marketing_opt_in=False, verified=False, verification_token=None):
|
||||
def add_user(username, password, school_name="", contact_person="", email="", marketing_opt_in=False, verified=False, verification_token=None, legal_consents=None, legal_consents_accepted_at=None):
|
||||
"""
|
||||
Legt einen neuen Benutzer mit allen Verifizierungsdaten in MongoDB an.
|
||||
"""
|
||||
@@ -62,6 +62,8 @@ def add_user(username, password, school_name="", contact_person="", email="", ma
|
||||
'contact_person': contact_person,
|
||||
'email': (email or '').strip().lower(),
|
||||
'marketing_opt_in': marketing_opt_in,
|
||||
'legal_consents': legal_consents or {},
|
||||
'legal_consents_accepted_at': legal_consents_accepted_at,
|
||||
'verified': verified,
|
||||
'verification_token': hashing(verification_token) if verification_token else None,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user