implementation of the upload

This commit is contained in:
2026-08-28 14:13:36 +02:00
parent 46478d5d1f
commit 9d600bc33a
3 changed files with 124 additions and 27 deletions
+91 -11
View File
@@ -23,6 +23,7 @@ from markupsafe import escape
from pymongo import MongoClient from pymongo import MongoClient
from pymongo.errors import PyMongoError from pymongo.errors import PyMongoError
from bson.objectid import ObjectId from bson.objectid import ObjectId
from bson.binary import Binary
import user as user_store import user as user_store
import buchungen import buchungen
import server_steering as steering import server_steering as steering
@@ -119,6 +120,7 @@ LEGAL_DOWNLOAD_FILES = (
], ],
), ),
) )
ALLOWED_CONTRACT_EXTENSIONS = {'pdf', 'png', 'jpg', 'jpeg', 'webp'}
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017") MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "Invario_Website") MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME", "Invario_Website")
INSTANCE_REPO_URL = os.environ.get("INSTANCE_REPO_URL", "https://git.invario-software.eu/Invario/Inventarsystem") INSTANCE_REPO_URL = os.environ.get("INSTANCE_REPO_URL", "https://git.invario-software.eu/Invario/Inventarsystem")
@@ -184,6 +186,10 @@ def _is_allowed_image_filename(filename: str) -> bool:
return lowered.endswith(".jpg") or lowered.endswith(".jpeg") or lowered.endswith(".png") or lowered.endswith(".webp") return lowered.endswith(".jpg") or lowered.endswith(".jpeg") or lowered.endswith(".png") or lowered.endswith(".webp")
def _allowed_contract_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_CONTRACT_EXTENSIONS
def _is_allowed_tutorial_video_filename(filename: str) -> bool: def _is_allowed_tutorial_video_filename(filename: str) -> bool:
if not filename: if not filename:
return False return False
@@ -2732,25 +2738,102 @@ def blog_post(post_id):
return render_template("blog_post.html", post=post) return render_template("blog_post.html", post=post)
@app.route('/my/invoices') @app.route('/invoices/<invoice_id>/hauptvertrag')
@login_required @login_required
def my_invoices(): def view_hauptvertrag(invoice_id):
invoices = []
client = None client = None
try: try:
client, col = _get_collection("invoices") client, col = _get_collection("invoices")
invoices = list(col.find({"username": session.get("username")}, {"_id": 0}).sort("created_at", -1)) invoice = col.find_one({"_id": ObjectId(invoice_id)})
# Beträge in Float umwandeln, da das Jinja-Template echte Zahlen erwartet if not invoice or "hauptvertrag_data" not in invoice:
for inv in invoices: flash("Kein Hauptvertrag für diese Rechnung vorhanden.", "error")
return redirect(url_for("my_invoices"))
# Sicherheitsprüfung: Darf der Nutzer die Datei sehen? (Nutzer selbst oder Admin)
current_user = session.get("username")
is_admin = session.get("is_admin")
if invoice.get("username") != current_user and not is_admin:
flash("Keine Berechtigung zum Anschauen dieses Hauptvertrags.", "error")
return redirect(url_for("my_invoices"))
# Datei-Streams aus den Binärdaten in MongoDB erzeugen
file_data = invoice["hauptvertrag_data"]
mimetype = invoice.get("hauptvertrag_mimetype", "application/pdf")
filename = invoice.get("hauptvertrag_filename", "hauptvertrag")
return send_file(
io.BytesIO(file_data),
mimetype=mimetype,
as_attachment=False,
download_name=filename
)
except Exception as e:
flash("Fehler beim Abrufen der Datei aus der Datenbank.", "error")
return redirect(url_for("my_invoices"))
finally:
if client:
client.close()
@app.route('/my/invoices', methods=['GET', 'POST'])
@login_required
def my_invoices():
client = None
# POST-Request: Datei in MongoDB speichern
if request.method == 'POST':
action = request.form.get("action")
invoice_id = request.form.get("invoice_id")
uploaded_file = request.files.get("hauptvertrag_file")
if action == "upload_hauptvertrag" and invoice_id and uploaded_file:
if _allowed_contract_file(uploaded_file.filename):
file_bytes = uploaded_file.read()
filename = uploaded_file.filename
mimetype = uploaded_file.mimetype or "application/octet-stream"
try:
client, col = _get_collection("invoices")
# Datei direkt als Binary in das Invoice-Dokument schreiben
col.update_one(
{"_id": ObjectId(invoice_id), "username": session.get("username")},
{"$set": {
"hauptvertrag_data": Binary(file_bytes),
"hauptvertrag_filename": filename,
"hauptvertrag_mimetype": mimetype,
"paperwork_status": "Hauptvertrag hochgeladen",
"updated_at": _utc_now_iso()
}}
)
flash("Hauptvertrag erfolgreich in der Datenbank gespeichert.", "success")
except Exception:
flash("Fehler beim Speichern in MongoDB.", "error")
finally:
if client:
client.close()
else:
flash("Ungültiges Format. Erlaubt sind PDF, JPG, PNG und WEBP.", "error")
return redirect(url_for("my_invoices"))
# GET-Request: Rechnungen laden
invoices = []
try:
client, col = _get_collection("invoices")
raw_invoices = list(col.find({"username": session.get("username")}).sort("created_at", -1))
for inv in raw_invoices:
inv["id"] = str(inv["_id"])
amt = inv.get("amount_eur", 0) amt = inv.get("amount_eur", 0)
if isinstance(amt, str): if isinstance(amt, str):
try: try:
# Entfernt Tausenderpunkte und wandelt das deutsche Komma in einen Punkt um
amt = float(amt.replace('.', '').replace(',', '.')) amt = float(amt.replace('.', '').replace(',', '.'))
except ValueError: except ValueError:
amt = 0.0 amt = 0.0
inv["amount_eur"] = amt inv["amount_eur"] = amt
# Flag setzen, ob Vertrag in MongoDB liegt (ohne die schweren Binärdaten ins Template zu laden)
inv["has_hauptvertrag"] = "hauptvertrag_data" in inv
invoices.append(inv)
except PyMongoError: except PyMongoError:
flash("Rechnungen konnten nicht geladen werden.", "error") flash("Rechnungen konnten nicht geladen werden.", "error")
@@ -2758,15 +2841,12 @@ def my_invoices():
if client: if client:
client.close() client.close()
# Load instance requests for this user
instance_requests = [] instance_requests = []
try: try:
req_client, req_col = _get_collection("instance_requests") req_client, req_col = _get_collection("instance_requests")
instance_requests = list(req_col.find({"username": session.get("username")}).sort("created_at", -1)) instance_requests = list(req_col.find({"username": session.get("username")}).sort("created_at", -1))
for item in instance_requests: for item in instance_requests:
item["id"] = str(item.get("_id")) item["id"] = str(item.get("_id"))
# Auch hier sicherstellen, dass der Betrag ein Float ist
amt = item.get("amount_eur", 0) amt = item.get("amount_eur", 0)
if isinstance(amt, str): if isinstance(amt, str):
try: try:
@@ -2774,7 +2854,6 @@ def my_invoices():
except ValueError: except ValueError:
amt = 0.0 amt = 0.0
item["amount_eur"] = amt item["amount_eur"] = amt
except PyMongoError: except PyMongoError:
flash("Instanz-Anfragen konnten nicht geladen werden.", "error") flash("Instanz-Anfragen konnten nicht geladen werden.", "error")
finally: finally:
@@ -2783,6 +2862,7 @@ def my_invoices():
return render_template("my_invoices.html", invoices=invoices, instance_requests=instance_requests) return render_template("my_invoices.html", invoices=invoices, instance_requests=instance_requests)
@app.route('/my/instance', methods=['GET', 'POST']) @app.route('/my/instance', methods=['GET', 'POST'])
@login_required @login_required
def my_instance_management(): def my_instance_management():
+15 -2
View File
@@ -44,11 +44,23 @@
<p><strong>Unterlagen:</strong> {{ item.paperwork_status or 'Offen' }}{% if item.consent_accepted %} | Einwilligung bestätigt{% endif %}</p> <p><strong>Unterlagen:</strong> {{ item.paperwork_status or 'Offen' }}{% if item.consent_accepted %} | Einwilligung bestätigt{% endif %}</p>
<p><strong>Kontaktdaten:</strong> {{ item.booking_data.get('contact_person', '-') }} | {{ item.booking_data.get('contact_email', '-') }} | {{ item.booking_data.get('billing_city', '-') }}</p> <p><strong>Kontaktdaten:</strong> {{ item.booking_data.get('contact_person', '-') }} | {{ item.booking_data.get('contact_email', '-') }} | {{ item.booking_data.get('billing_city', '-') }}</p>
{% endif %} {% endif %}
<div style="margin: 0.5rem 0; padding: 0.5rem; background: #f4f8fa; border-radius: 8px;">
{% if item.pdf_path %} {% if item.pdf_path %}
<p><strong>PDF:</strong> <a href="{{ url_for('static', filename=item.pdf_path) }}" target="_blank" rel="noopener">Ansehen</a> <p style="margin:0.2rem 0;"><strong>Rechnungs-PDF:</strong>
<button class="print-btn" type="button" onclick="printPdf('{{ url_for('static', filename=item.pdf_path) }}')">PDF drucken</button> <a href="{{ url_for('static', filename=item.pdf_path) }}" target="_blank" rel="noopener">Ansehen</a>
</p> </p>
{% endif %} {% endif %}
{% if item.hauptvertrag_data is defined or item.get('hauptvertrag_filename') %}
<p style="margin:0.2rem 0;"><strong>Hauptvertrag (aus MongoDB):</strong>
<a href="{{ url_for('view_hauptvertrag', invoice_id=item.id) }}" target="_blank" rel="noopener">
{{ item.hauptvertrag_filename or 'Hauptvertrag öffnen' }}
</a>
</p>
{% else %}
<p style="margin:0.2rem 0; color: #777;"><strong>Hauptvertrag:</strong> Noch nicht in MongoDB hochgeladen</p>
{% endif %}
</div>
<form method="post" class="inline-form" enctype="multipart/form-data"> <form method="post" class="inline-form" enctype="multipart/form-data">
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
<input type="hidden" name="invoice_id" value="{{ item.id }}"> <input type="hidden" name="invoice_id" value="{{ item.id }}">
@@ -82,6 +94,7 @@
<form method="post" style="display:inline-block; margin-right:0.6rem;"> <form method="post" style="display:inline-block; margin-right:0.6rem;">
<input type="hidden" name="action" value="create_from_provision"> <input type="hidden" name="action" value="create_from_provision">
<input type="hidden" name="prov_id" value="{{ prov.id }}"> <input type="hidden" name="prov_id" value="{{ prov.id }}">
<input type="file" name="contract_file" accept=".pdf,.png,.jpg,.jpeg,.webp">
<input type="text" name="period" placeholder="Zeitraum (optional)"> <input type="text" name="period" placeholder="Zeitraum (optional)">
<input type="text" name="due_date" placeholder="Fälligkeit (YYYY-MM-DD)"> <input type="text" name="due_date" placeholder="Fälligkeit (YYYY-MM-DD)">
<button type="submit">Rechnung erzeugen</button> <button type="submit">Rechnung erzeugen</button>
+16 -12
View File
@@ -6,7 +6,7 @@
<section class="panel" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;"> <section class="panel" style="display:flex; align-items:center; justify-content:space-between; gap:1rem;">
<div> <div>
<h1>Rechnungseinsicht</h1> <h1>Rechnungseinsicht</h1>
<p style="margin:0">Alle Rechnungen im Überblick. Der Admin prüft neue Rechnungen zuerst; der Status zeigt dir, ob die Rechnung noch geprüft wird oder bereits bearbeitet wurde.</p> <p style="margin:0">Alle Rechnungen im Überblick.</p>
</div> </div>
<div class="no-print"> <div class="no-print">
<button class="print-btn" type="button" onclick="printDocument()">Seite drucken</button> <button class="print-btn" type="button" onclick="printDocument()">Seite drucken</button>
@@ -22,7 +22,8 @@
<th>Betrag</th> <th>Betrag</th>
<th>Fälligkeit</th> <th>Fälligkeit</th>
<th>Status</th> <th>Status</th>
<th>PDF</th> <th>Rechnung PDF</th>
<th>Hauptvertrag</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -36,26 +37,29 @@
<td> <td>
{% if invoice.pdf_path %} {% if invoice.pdf_path %}
<a href="{{ url_for('static', filename=invoice.pdf_path) }}" target="_blank" rel="noopener">PDF ansehen</a> <a href="{{ url_for('static', filename=invoice.pdf_path) }}" target="_blank" rel="noopener">PDF ansehen</a>
<button class="print-btn" type="button" onclick="printPdf('{{ url_for('static', filename=invoice.pdf_path) }}')">PDF drucken</button>
{% else %} {% else %}
- -
{% endif %} {% endif %}
</td> </td>
<td>
{% if invoice.has_hauptvertrag %}
<a href="{{ url_for('view_hauptvertrag', invoice_id=invoice.id) }}" target="_blank" rel="noopener">Hauptvertrag ansehen</a>
{% else %}
<form method="post" enctype="multipart/form-data" style="display:flex; gap:0.3rem; align-items:center;">
<input type="hidden" name="action" value="upload_hauptvertrag">
<input type="hidden" name="invoice_id" value="{{ invoice.id }}">
<input type="file" name="hauptvertrag_file" accept="image/*,application/pdf" required style="font-size:0.8rem;">
<button type="submit" style="padding: 0.2rem 0.6rem; font-size:0.8rem;">Hochladen</button>
</form>
{% endif %}
</td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="6">Noch keine Rechnungen vorhanden.</td> <td colspan="7">Noch keine Rechnungen vorhanden.</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</section> </section>
<style>
.panel { margin-bottom: 1rem; }
.table-wrap { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 0.7rem; border-bottom: 1px solid #e8eef2; }
a { color: #0a4c74; font-weight: 700; }
</style>
{% endblock %} {% endblock %}