diff --git a/schadenprotokoll/api/app.py b/schadenprotokoll/api/app.py index 54ef662..58d06d8 100644 --- a/schadenprotokoll/api/app.py +++ b/schadenprotokoll/api/app.py @@ -1,8 +1,4 @@ #!/usr/bin/env python3 -""" -Schadenprotokoll API - Flask Backend -Endpunkte: /generate, /analyze, /vorbericht, /templates -""" from flask import Flask, request, jsonify, send_file from flask_cors import CORS import tempfile @@ -12,126 +8,92 @@ from processors import parse_laufzettel, fill_pdf, analyze_pdf, generate_vorberi app = Flask(__name__) CORS(app) -TEMPLATES_DIR = "/opt/stacks/spa-hosting/html/schadenprotokoll/templates" -TEMPLATE_PDF = os.path.join(TEMPLATES_DIR, "protokoll.pdf") -TEMPLATE_DOCX = os.path.join(TEMPLATES_DIR, "vorbericht.docx") +TEMPLATES_DIR = '/opt/stacks/spa-hosting/html/schadenprotokoll/templates' -@app.route("/health", methods=["GET"]) +@app.route('/health', methods=['GET']) def health(): - return jsonify({"status": "ok"}) + return jsonify({'status': 'ok'}) -@app.route("/templates", methods=["GET"]) -def list_templates(): - """Listet alle verfuegbaren Templates""" - templates = [] - for f in os.listdir(TEMPLATES_DIR): - path = os.path.join(TEMPLATES_DIR, f) - if os.path.isfile(path) and not f.endswith(".bak"): - templates.append({ - "name": f, - "size": os.path.getsize(path), - "type": "pdf" if f.endswith(".pdf") else "docx" - }) - return jsonify({"templates": templates}) - - -@app.route("/templates/upload", methods=["POST"]) -def upload_template(): - """Laedt ein neues Template hoch""" - if "file" not in request.files: - return jsonify({"error": "Keine Datei hochgeladen"}), 400 - - file = request.files["file"] - ttype = request.form.get("type", "pdf") - - if ttype == "pdf" and not file.filename.endswith(".pdf"): - return jsonify({"error": "PDF-Template muss .pdf sein"}), 400 - if ttype == "docx" and not file.filename.endswith(".docx"): - return jsonify({"error": "Word-Template muss .docx sein"}), 400 - - target = TEMPLATE_PDF if ttype == "pdf" else TEMPLATE_DOCX - backup = target + ".bak" - - if os.path.exists(target): - if os.path.exists(backup): - os.remove(backup) - os.rename(target, backup) - - file.save(target) - return jsonify({"success": True, "message": f"Template erfolgreich hochgeladen"}) - - -@app.route("/generate", methods=["POST"]) +@app.route('/generate', methods=['POST']) def generate(): - """Laufzettel.docx -> vorausgefuelltes Protokoll.pdf""" - if "file" not in request.files: - return jsonify({"error": "Keine Datei hochgeladen"}), 400 - - file = request.files["file"] - if not file.filename.endswith(".docx"): - return jsonify({"error": "Nur .docx Dateien erlaubt"}), 400 - - with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp_in: - file.save(tmp_in.name) - try: - data = parse_laufzettel(tmp_in.name) - with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_out: - fill_pdf(TEMPLATE_PDF, tmp_out.name, data) - os.unlink(tmp_in.name) - return send_file(tmp_out.name, mimetype="application/pdf", - as_attachment=True, download_name="Schadenprotokoll_vorbefuellt.pdf") - except Exception as e: - os.unlink(tmp_in.name) - return jsonify({"error": str(e)}), 500 + # Akzeptiert template (PDF) und laufzettel (DOCX) + if 'template' not in request.files or 'laufzettel' not in request.files: + return jsonify({'error': 'template und laufzettel erforderlich'}), 400 + + template_file = request.files['template'] + laufzettel_file = request.files['laufzettel'] + + if not template_file.filename.endswith('.pdf'): + return jsonify({'error': 'Template muss PDF sein'}), 400 + if not laufzettel_file.filename.endswith('.docx'): + return jsonify({'error': 'Laufzettel muss DOCX sein'}), 400 + + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_tpl: + template_file.save(tmp_tpl.name) + with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_lz: + laufzettel_file.save(tmp_lz.name) + try: + data = parse_laufzettel(tmp_lz.name) + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_out: + fill_pdf(tmp_tpl.name, tmp_out.name, data) + os.unlink(tmp_tpl.name) + os.unlink(tmp_lz.name) + return send_file(tmp_out.name, mimetype='application/pdf', + as_attachment=True, download_name='Schadenprotokoll_vorbefuellt.pdf') + except Exception as e: + os.unlink(tmp_tpl.name) + os.unlink(tmp_lz.name) + return jsonify({'error': str(e)}), 500 -@app.route("/analyze", methods=["POST"]) +@app.route('/analyze', methods=['POST']) def analyze(): - """Ausgefuelltes PDF -> JSON mit allen Feldern""" - if "file" not in request.files: - return jsonify({"error": "Keine Datei hochgeladen"}), 400 - - file = request.files["file"] - if not file.filename.endswith(".pdf"): - return jsonify({"error": "Nur .pdf Dateien erlaubt"}), 400 - - with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp: + if 'file' not in request.files: + return jsonify({'error': 'Keine Datei'}), 400 + file = request.files['file'] + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp: file.save(tmp.name) try: result = analyze_pdf(tmp.name) os.unlink(tmp.name) - return jsonify({"success": True, "data": result}) + return jsonify({'success': True, 'data': result}) except Exception as e: os.unlink(tmp.name) - return jsonify({"error": str(e)}), 500 + return jsonify({'error': str(e)}), 500 -@app.route("/vorbericht", methods=["POST"]) +@app.route('/vorbericht', methods=['POST']) def vorbericht(): - """Ausgefuelltes PDF -> Vorbericht.docx""" - if "file" not in request.files: - return jsonify({"error": "Keine Datei hochgeladen"}), 400 - - file = request.files["file"] - if not file.filename.endswith(".pdf"): - return jsonify({"error": "Nur .pdf Dateien erlaubt"}), 400 - - with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_in: + if 'file' not in request.files: + return jsonify({'error': 'Keine Datei'}), 400 + file = request.files['file'] + template_docx = os.path.join(TEMPLATES_DIR, 'vorbericht.docx') + with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_in: file.save(tmp_in.name) try: pdf_data = analyze_pdf(tmp_in.name) - with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp_out: - generate_vorbericht(pdf_data, TEMPLATE_DOCX, tmp_out.name) + with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_out: + generate_vorbericht(pdf_data, template_docx, tmp_out.name) os.unlink(tmp_in.name) return send_file(tmp_out.name, - mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document", - as_attachment=True, download_name="Vorbericht.docx") + mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document', + as_attachment=True, download_name='Vorbericht.docx') except Exception as e: os.unlink(tmp_in.name) - return jsonify({"error": str(e)}), 500 + return jsonify({'error': str(e)}), 500 -if __name__ == "__main__": - app.run(host="0.0.0.0", port=5050, debug=False) +@app.route('/templates', methods=['GET']) +def list_templates(): + templates = [] + for f in os.listdir(TEMPLATES_DIR): + if not f.endswith('.bak'): + path = os.path.join(TEMPLATES_DIR, f) + templates.append({'name': f, 'size': os.path.getsize(path)}) + return jsonify({'templates': templates}) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5050, debug=False) diff --git a/schadenprotokoll/app.js b/schadenprotokoll/app.js index ed59170..04ac15b 100644 --- a/schadenprotokoll/app.js +++ b/schadenprotokoll/app.js @@ -1,171 +1,142 @@ -// Tab Navigation -document.querySelectorAll(".tab").forEach(tab => { - tab.addEventListener("click", () => { - document.querySelectorAll(".tab").forEach(t => t.classList.remove("active")); - document.querySelectorAll(".tab-content").forEach(c => c.classList.remove("active")); - tab.classList.add("active"); - document.getElementById(tab.dataset.tab).classList.add("active"); - if (tab.dataset.tab === "admin") loadTemplates(); - }); -}); - -function setupUploadZone(zoneId, inputId, infoId, btnId) { - const zone = document.getElementById(zoneId); - const input = document.getElementById(inputId); - const info = document.getElementById(infoId); - const btn = document.getElementById(btnId); - if (!zone || !input) return; - - zone.addEventListener("click", () => input.click()); - zone.addEventListener("dragover", (e) => { e.preventDefault(); zone.classList.add("dragover"); }); - zone.addEventListener("dragleave", () => zone.classList.remove("dragover")); - zone.addEventListener("drop", (e) => { - e.preventDefault(); - zone.classList.remove("dragover"); - if (e.dataTransfer.files.length) { - input.files = e.dataTransfer.files; - updateFileInfo(input, info, btn); - } - }); - input.addEventListener("change", () => updateFileInfo(input, info, btn)); -} - -function updateFileInfo(input, info, btn) { - if (input.files.length && info && btn) { - const file = input.files[0]; - info.innerHTML = "OK " + file.name + " (" + (file.size/1024).toFixed(1) + " KB)"; - info.classList.add("visible"); - btn.disabled = false; - } -} - -function showStatus(id, type, message) { - const status = document.getElementById(id); - if (status) { - status.className = "status visible " + type; - status.textContent = message; - } -} - -const API_BASE = "/schadenprotokoll/api"; - -setupUploadZone("laufzettel-zone", "laufzettel-input", "laufzettel-info", "generate-btn"); -setupUploadZone("pdf-zone", "pdf-input", "pdf-info", "analyze-btn"); -setupUploadZone("vorbericht-zone", "vorbericht-input", "vorbericht-info", "vorbericht-btn"); - -document.getElementById("generate-btn").addEventListener("click", async () => { - showStatus("generate-status", "loading", "Verarbeite Laufzettel..."); - const file = document.getElementById("laufzettel-input").files[0]; - const formData = new FormData(); - formData.append("file", file); - - try { - const response = await fetch(API_BASE + "/generate", { method: "POST", body: formData }); - if (!response.ok) throw new Error((await response.json()).error); - const blob = await response.blob(); - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = "Schadenprotokoll_vorbefuellt.pdf"; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - showStatus("generate-status", "success", "PDF erfolgreich generiert!"); - } catch (err) { - showStatus("generate-status", "error", "Fehler: " + err.message); - } -}); - -document.getElementById("analyze-btn").addEventListener("click", async () => { - showStatus("analyze-status", "loading", "Analysiere PDF..."); - const file = document.getElementById("pdf-input").files[0]; - const formData = new FormData(); - formData.append("file", file); - - try { - const response = await fetch(API_BASE + "/analyze", { method: "POST", body: formData }); - if (!response.ok) throw new Error((await response.json()).error); - const result = await response.json(); - displayAnalysisResult(result.data); - showStatus("analyze-status", "success", "Analyse abgeschlossen"); - } catch (err) { - showStatus("analyze-status", "error", "Fehler: " + err.message); - } -}); - -function displayAnalysisResult(data) { - const container = document.getElementById("analyze-data"); - let html = ""; - for (const [key, value] of Object.entries(data.textfields || {})) { - if (value) html += "
Keine Felder gefunden.
"; - document.getElementById("analyze-result").classList.add("visible"); -} - -document.getElementById("vorbericht-btn").addEventListener("click", async () => { - showStatus("vorbericht-status", "loading", "Erstelle Vorbericht..."); - const file = document.getElementById("vorbericht-input").files[0]; - const formData = new FormData(); - formData.append("file", file); - - try { - const response = await fetch(API_BASE + "/vorbericht", { method: "POST", body: formData }); - if (!response.ok) throw new Error((await response.json()).error); - const blob = await response.blob(); - const a = document.createElement("a"); - a.href = URL.createObjectURL(blob); - a.download = "Vorbericht.docx"; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - showStatus("vorbericht-status", "success", "Vorbericht erstellt!"); - } catch (err) { - showStatus("vorbericht-status", "error", "Fehler: " + err.message); - } -}); - -function setupTemplateUpload(zoneId, inputId, templateType) { - const zone = document.getElementById(zoneId); - const input = document.getElementById(inputId); - if (!zone || !input) return; - zone.addEventListener("click", () => input.click()); - zone.addEventListener("dragover", (e) => { e.preventDefault(); zone.classList.add("dragover"); }); - zone.addEventListener("dragleave", () => zone.classList.remove("dragover")); - zone.addEventListener("drop", (e) => { - e.preventDefault(); - zone.classList.remove("dragover"); - if (e.dataTransfer.files.length) uploadTemplate(e.dataTransfer.files[0], templateType); - }); - input.addEventListener("change", () => { if (input.files.length) uploadTemplate(input.files[0], templateType); }); -} - -async function loadTemplates() { - const container = document.getElementById("templates-container"); - if (!container) return; - try { - const response = await fetch(API_BASE + "/templates"); - const data = await response.json(); - container.innerHTML = data.templates.map(t => - "Laedt Daten aus dem Schadenlaufzettel.docx und fuellt das Schadenprotokoll-PDF vor.
+Waehle beide Dateien aus und klicke auf Generieren.
Datei hierher ziehen oder klicken
+ +Schadenprotokoll-Vorlage.pdf
+Schadenlaufzettel.docx
+Liest alle Formularfelder und Dropdown-Auswahlen aus dem Schadenprotokoll.
+Liest alle Formularfelder und Dropdown-Auswahlen aus.
Das ausgefuellte PDF-Formular
Generiert einen strukturierten Vorbericht aus dem ausgefuellten Schadenprotokoll.
+Generiert einen strukturierten Vorbericht aus dem ausgefuellten PDF.
Das vollstaendig ausgefuellte PDF-Formular
+Das vollstaendig ausgefuellte PDF
Hier kannst du die PDF- und Word-Templates hochladen, die fuer die Generierung verwendet werden.
-Schadenprotokoll-Vorlage
-Vorbericht-Vorlage
-