Add template upload functionality to Schadenprotokoll
- New Templates tab for PDF and Word template management - API endpoints for listing and uploading templates - Automatic backup of old templates before replacement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Schadenprotokoll API - Flask Backend
|
||||
Endpunkte: /generate, /analyze, /vorbericht
|
||||
Endpunkte: /generate, /analyze, /vorbericht, /templates
|
||||
"""
|
||||
from flask import Flask, request, jsonify, send_file
|
||||
from flask_cors import CORS
|
||||
@@ -12,8 +12,9 @@ from processors import parse_laufzettel, fill_pdf, analyze_pdf, generate_vorberi
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
TEMPLATE_PDF = "/opt/stacks/spa-hosting/html/schadenprotokoll/templates/protokoll.pdf"
|
||||
TEMPLATE_DOCX = "/opt/stacks/spa-hosting/html/schadenprotokoll/templates/vorbericht.docx"
|
||||
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")
|
||||
|
||||
|
||||
@app.route("/health", methods=["GET"])
|
||||
@@ -21,6 +22,47 @@ def health():
|
||||
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"])
|
||||
def generate():
|
||||
"""Laufzettel.docx -> vorausgefuelltes Protokoll.pdf"""
|
||||
@@ -33,20 +75,13 @@ def generate():
|
||||
|
||||
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"
|
||||
)
|
||||
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
|
||||
@@ -64,7 +99,6 @@ def analyze():
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
|
||||
file.save(tmp.name)
|
||||
|
||||
try:
|
||||
result = analyze_pdf(tmp.name)
|
||||
os.unlink(tmp.name)
|
||||
@@ -86,20 +120,14 @@ def vorbericht():
|
||||
|
||||
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)
|
||||
|
||||
os.unlink(tmp_in.name)
|
||||
return send_file(
|
||||
tmp_out.name,
|
||||
return send_file(tmp_out.name,
|
||||
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
as_attachment=True,
|
||||
download_name="Vorbericht.docx"
|
||||
)
|
||||
as_attachment=True, download_name="Vorbericht.docx")
|
||||
except Exception as e:
|
||||
os.unlink(tmp_in.name)
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@@ -172,3 +172,79 @@ document.getElementById("vorbericht-btn").addEventListener("click", async () =>
|
||||
showStatus("vorbericht-status", "error", `❌ Fehler: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Admin Tab - Templates laden
|
||||
async function loadTemplates() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/templates`);
|
||||
const data = await response.json();
|
||||
const container = document.getElementById("templates-container");
|
||||
|
||||
if (data.templates.length === 0) {
|
||||
container.innerHTML = "<p>Keine Templates vorhanden.</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = data.templates.map(t => `
|
||||
<div class="template-item">
|
||||
<span class="name">${t.type === "pdf" ? "📄" : "📝"} ${t.name}</span>
|
||||
<span class="size">${(t.size / 1024).toFixed(1)} KB</span>
|
||||
</div>
|
||||
`).join("");
|
||||
} catch (err) {
|
||||
document.getElementById("templates-container").innerHTML =
|
||||
`<p style="color:red;">Fehler: ${err.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Template Upload
|
||||
function setupTemplateUpload(zoneId, inputId, templateType) {
|
||||
const zone = document.getElementById(zoneId);
|
||||
const input = document.getElementById(inputId);
|
||||
|
||||
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;
|
||||
uploadTemplate(input.files[0], templateType);
|
||||
}
|
||||
});
|
||||
input.addEventListener("change", () => {
|
||||
if (input.files.length) uploadTemplate(input.files[0], templateType);
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadTemplate(file, type) {
|
||||
showStatus("admin-status", "loading", `⏳ Lade ${type.toUpperCase()} Template hoch...`);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("type", type);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/templates/upload`, {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json();
|
||||
throw new Error(err.error);
|
||||
}
|
||||
|
||||
showStatus("admin-status", "success", "✓ Template erfolgreich hochgeladen");
|
||||
loadTemplates();
|
||||
} catch (err) {
|
||||
showStatus("admin-status", "error", `❌ Fehler: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
setupTemplateUpload("pdf-template-zone", "pdf-template-input", "pdf");
|
||||
setupTemplateUpload("docx-template-zone", "docx-template-input", "docx");
|
||||
|
||||
// Templates beim Tab-Wechsel laden
|
||||
document.querySelector([data-tab=admin]).addEventListener("click", loadTemplates);
|
||||
|
||||
@@ -17,26 +17,23 @@
|
||||
<button class="tab active" data-tab="generate">1. Generieren</button>
|
||||
<button class="tab" data-tab="analyze">2. Auswerten</button>
|
||||
<button class="tab" data-tab="vorbericht">3. Vorbericht</button>
|
||||
<button class="tab" data-tab="admin">⚙️ Templates</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab 1: Generieren -->
|
||||
<div id="generate" class="tab-content active">
|
||||
<div class="info-box">
|
||||
<h4>Schadenlaufzettel → Protokoll PDF</h4>
|
||||
<p>Lädt Daten aus dem Schadenlaufzettel.docx und füllt das Schadenprotokoll-PDF vor.</p>
|
||||
<p>Laedt Daten aus dem Schadenlaufzettel.docx und fuellt das Schadenprotokoll-PDF vor.</p>
|
||||
</div>
|
||||
|
||||
<div class="upload-zone" id="laufzettel-zone">
|
||||
<input type="file" id="laufzettel-input" accept=".docx">
|
||||
<h3>📄 Schadenlaufzettel.docx hochladen</h3>
|
||||
<p>Datei hierher ziehen oder klicken zum Auswählen</p>
|
||||
<p>Datei hierher ziehen oder klicken</p>
|
||||
</div>
|
||||
<div class="file-info" id="laufzettel-info"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" id="generate-btn" disabled>
|
||||
⚙️ PDF generieren
|
||||
</button>
|
||||
<button class="btn btn-primary" id="generate-btn" disabled>⚙️ PDF generieren</button>
|
||||
</div>
|
||||
<div class="status" id="generate-status"></div>
|
||||
</div>
|
||||
@@ -44,21 +41,17 @@
|
||||
<!-- Tab 2: Auswerten -->
|
||||
<div id="analyze" class="tab-content">
|
||||
<div class="info-box">
|
||||
<h4>Ausgefülltes PDF analysieren</h4>
|
||||
<h4>Ausgefuelltes PDF analysieren</h4>
|
||||
<p>Liest alle Formularfelder und Dropdown-Auswahlen aus dem Schadenprotokoll.</p>
|
||||
</div>
|
||||
|
||||
<div class="upload-zone" id="pdf-zone">
|
||||
<input type="file" id="pdf-input" accept=".pdf">
|
||||
<h3>📋 Schadenprotokoll.pdf hochladen</h3>
|
||||
<p>Das ausgefüllte PDF-Formular</p>
|
||||
<p>Das ausgefuellte PDF-Formular</p>
|
||||
</div>
|
||||
<div class="file-info" id="pdf-info"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" id="analyze-btn" disabled>
|
||||
🔍 Analysieren
|
||||
</button>
|
||||
<button class="btn btn-primary" id="analyze-btn" disabled>🔍 Analysieren</button>
|
||||
</div>
|
||||
<div class="status" id="analyze-status"></div>
|
||||
<div class="result" id="analyze-result">
|
||||
@@ -71,24 +64,50 @@
|
||||
<div id="vorbericht" class="tab-content">
|
||||
<div class="info-box">
|
||||
<h4>Protokoll → Vorbericht Word</h4>
|
||||
<p>Generiert einen strukturierten Vorbericht aus dem ausgefüllten Schadenprotokoll.</p>
|
||||
<p>Generiert einen strukturierten Vorbericht aus dem ausgefuellten Schadenprotokoll.</p>
|
||||
</div>
|
||||
|
||||
<div class="upload-zone" id="vorbericht-zone">
|
||||
<input type="file" id="vorbericht-input" accept=".pdf">
|
||||
<h3>📋 Ausgefülltes Schadenprotokoll.pdf</h3>
|
||||
<p>Das vollständig ausgefüllte PDF-Formular</p>
|
||||
<h3>📋 Ausgefuelltes Schadenprotokoll.pdf</h3>
|
||||
<p>Das vollstaendig ausgefuellte PDF-Formular</p>
|
||||
</div>
|
||||
<div class="file-info" id="vorbericht-info"></div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" id="vorbericht-btn" disabled>
|
||||
📝 Vorbericht erstellen
|
||||
</button>
|
||||
<button class="btn btn-primary" id="vorbericht-btn" disabled>📝 Vorbericht erstellen</button>
|
||||
</div>
|
||||
<div class="status" id="vorbericht-status"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 4: Admin/Templates -->
|
||||
<div id="admin" class="tab-content">
|
||||
<div class="info-box">
|
||||
<h4>Template-Verwaltung</h4>
|
||||
<p>Hier kannst du die PDF- und Word-Templates hochladen, die fuer die Generierung verwendet werden.</p>
|
||||
</div>
|
||||
|
||||
<div class="template-list" id="template-list">
|
||||
<h4>Aktuelle Templates:</h4>
|
||||
<div id="templates-container">Lade...</div>
|
||||
</div>
|
||||
|
||||
<div class="template-upload">
|
||||
<h4>Neues Template hochladen:</h4>
|
||||
<div class="upload-row">
|
||||
<div class="upload-zone small" id="pdf-template-zone">
|
||||
<input type="file" id="pdf-template-input" accept=".pdf">
|
||||
<h3>📄 PDF Template</h3>
|
||||
<p>Schadenprotokoll-Vorlage</p>
|
||||
</div>
|
||||
<div class="upload-zone small" id="docx-template-zone">
|
||||
<input type="file" id="docx-template-input" accept=".docx">
|
||||
<h3>📝 Word Template</h3>
|
||||
<p>Vorbericht-Vorlage</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="admin-status"></div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>Schadenprotokoll Tool v1.0 | artetui.de</p>
|
||||
</footer>
|
||||
|
||||
@@ -105,3 +105,17 @@ header p { color: var(--text-muted); }
|
||||
.field-list { display: grid; gap: 0.5rem; }
|
||||
|
||||
footer { text-align: center; margin-top: 3rem; color: var(--text-muted); font-size: 0.875rem; }
|
||||
|
||||
.template-list { margin-bottom: 2rem; }
|
||||
.template-list h4 { margin-bottom: 1rem; }
|
||||
.template-item {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 1rem; background: white; border: 1px solid var(--border);
|
||||
border-radius: 0.5rem; margin-bottom: 0.5rem;
|
||||
}
|
||||
.template-item .name { font-weight: 500; }
|
||||
.template-item .size { color: var(--text-muted); font-size: 0.875rem; }
|
||||
.template-upload h4 { margin-bottom: 1rem; }
|
||||
.upload-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
.upload-zone.small { padding: 1.5rem; }
|
||||
.upload-zone.small h3 { font-size: 1rem; }
|
||||
|
||||
Reference in New Issue
Block a user