diff --git a/schadenprotokoll/api/processors.py b/schadenprotokoll/api/processors.py index 8eabd18..e502f78 100644 --- a/schadenprotokoll/api/processors.py +++ b/schadenprotokoll/api/processors.py @@ -1,6 +1,3 @@ -""" -Schadenprotokoll Processors - PDF/DOCX Verarbeitung -""" from docx import Document from docx.oxml.ns import qn from pypdf import PdfReader, PdfWriter @@ -8,128 +5,95 @@ import zipfile import xml.etree.ElementTree as ET -def parse_laufzettel(docx_path: str) -> dict: - """Extrahiert Schadensdaten aus dem Laufzettel.docx""" +def parse_laufzettel(docx_path): data = {} - - with zipfile.ZipFile(docx_path, "r") as z: - xml_content = z.read("word/document.xml") + with zipfile.ZipFile(docx_path, 'r') as z: + xml_content = z.read('word/document.xml') root = ET.fromstring(xml_content) - ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} + ns = {'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'} - for sdt in root.findall(".//w:sdt", ns): - alias = sdt.find(".//w:alias", ns) + for sdt in root.findall('.//w:sdt', ns): + alias = sdt.find('.//w:alias', ns) if alias is not None: - alias_val = alias.get(qn("w:val")) - texts = sdt.findall(".//w:t", ns) - content = " ".join([t.text for t in texts if t.text]).strip() + alias_val = alias.get(qn('w:val')) + texts = sdt.findall('.//w:t', ns) + content = ' '.join([t.text for t in texts if t.text]).strip() field_map = { - "Schadennummer": "Schadennummer", - "Versicherungsnehmer": "Versicherungsnehmer:", - "Versicherer": "Versicherer:", - "Datum": "Datum:", - "Schadenort": "Schadenort:", - "Schadenart": "Schadenart:", - "e-Mail": "e-Mail VN:", + 'Schadennummer': 'Schadennummer', + 'Versicherungsnehmer': 'Versicherungsnehmer', + 'Versicherer': 'Versicherer', + 'Schadenort': 'Schadenort', + 'Schadenart': 'Schadenart', } if alias_val in field_map: data[field_map[alias_val]] = content - elif alias_val == "Straße": - data["Adresse:"] = content - elif alias_val == "Ort": - if "Adresse:" in data: - data["Adresse:"] += ", " + content + elif alias_val == 'Straße': + data['Adresse'] = content + elif alias_val == 'Ort': + if 'Adresse' in data: + data['Adresse'] += ', ' + content else: - data["Adresse:"] = content + data['Adresse'] = content return data -def fill_pdf(template_path: str, output_path: str, data: dict) -> str: - """Befuellt das PDF-Template mit den Schadensdaten""" +def fill_pdf(template_path, output_path, data): reader = PdfReader(template_path) writer = PdfWriter() - - for page in reader.pages: - writer.add_page(page) - if "/Annots" in page: - writer.update_page_form_field_values( - writer.pages[-1], data, auto_regenerate=False - ) - - with open(output_path, "wb") as f: + writer.append(reader) + writer.update_page_form_field_values(writer.pages[0], data) + with open(output_path, 'wb') as f: writer.write(f) - return output_path -def analyze_pdf(pdf_path: str) -> dict: - """Liest alle Formularfelder aus dem ausgefuellten PDF""" +def analyze_pdf(pdf_path): reader = PdfReader(pdf_path) - result = {"textfields": {}, "dropdowns": {}, "checkboxes": {}} + result = {'textfields': {}, 'dropdowns': {}, 'checkboxes': {}} - for page_num, page in enumerate(reader.pages): - if "/Annots" not in page: - continue + fields = reader.get_fields() + if not fields: + return result + + for name, field in fields.items(): + ft = field.get('/FT', '') + value = field.get('/V', '') - for annot in page["/Annots"]: - obj = annot.get_object() - field_type = obj.get("/FT", "") - field_name = obj.get("/T", f"Feld_{page_num}") - - if field_type == "/Tx": - value = obj.get("/V", "") - result["textfields"][field_name] = str(value) if value else "" - - elif field_type == "/Ch": - value = obj.get("/V", "") - options = [] - if "/Opt" in obj: - for opt in obj["/Opt"]: - if isinstance(opt, list): - options.append(str(opt[1]) if len(opt) > 1 else str(opt[0])) - else: - options.append(str(opt)) - result["dropdowns"][field_name] = { - "selected": str(value) if value else "", - "options": options - } - - elif field_type == "/Btn": - value = obj.get("/V", "/Off") - result["checkboxes"][field_name] = value != "/Off" + if ft == '/Tx': + result['textfields'][name] = str(value) if value else '' + elif ft == '/Ch': + opts = field.get('/Opt', []) + options = [str(o[1]) if isinstance(o, list) else str(o) for o in opts] + result['dropdowns'][name] = { + 'selected': str(value) if value else '', + 'options': options + } + elif ft == '/Btn': + result['checkboxes'][name] = value != '/Off' return result -def generate_vorbericht(pdf_data: dict, template_path: str, output_path: str) -> str: - """Generiert Vorbericht.docx aus PDF-Daten""" +def generate_vorbericht(pdf_data, template_path, output_path): doc = Document(template_path) + tf = pdf_data.get('textfields', {}) - tf = pdf_data.get("textfields", {}) replacements = { - "Schadennummer": tf.get("Schadennummer", "xx"), - "Versicherer": tf.get("Versicherer:", "xx"), - "Versicherungsnehmer": tf.get("Versicherungsnehmer:", "xx"), - "Adresse": tf.get("Adresse:", "xx"), + 'Schadennummer': tf.get('Schadennummer', 'xx'), + 'Versicherer': tf.get('Versicherer', 'xx'), + 'Versicherungsnehmer': tf.get('Versicherungsnehmer', 'xx'), + 'Adresse': tf.get('Adresse', 'xx'), } - # Dropdown-Antworten fuer Sachverhalt sammeln - dropdowns = pdf_data.get("dropdowns", {}) - sachverhalt = [] - for key in sorted(dropdowns.keys()): - val = dropdowns[key].get("selected", "") - if val and val not in ["Was ist beschädigt?", "Schadenursache?", ""]: - sachverhalt.append(val) - for para in doc.paragraphs: text = para.text for key, value in replacements.items(): - if key.lower() in text.lower() and "xx" in text: - para.text = text.replace("xx", value, 1) + if key.lower() in text.lower() and 'xx' in text: + para.text = text.replace('xx', value, 1) break doc.save(output_path) diff --git a/schadenprotokoll/app.js b/schadenprotokoll/app.js index f38ebfd..ed59170 100644 --- a/schadenprotokoll/app.js +++ b/schadenprotokoll/app.js @@ -5,15 +5,16 @@ document.querySelectorAll(".tab").forEach(tab => { 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(); }); }); -// Upload Zone Setup 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"); }); @@ -30,9 +31,9 @@ function setupUploadZone(zoneId, inputId, infoId, btnId) { } function updateFileInfo(input, info, btn) { - if (input.files.length) { + if (input.files.length && info && btn) { const file = input.files[0]; - info.innerHTML = `✓ ${file.name} (${(file.size / 1024).toFixed(1)} KB)`; + info.innerHTML = "OK " + file.name + " (" + (file.size/1024).toFixed(1) + " KB)"; info.classList.add("visible"); btn.disabled = false; } @@ -40,211 +41,131 @@ function updateFileInfo(input, info, btn) { function showStatus(id, type, message) { const status = document.getElementById(id); - status.className = `status visible ${type}`; - status.textContent = message; + if (status) { + status.className = "status visible " + type; + status.textContent = message; + } } -// Setup all upload zones +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"); -// API Base URL -const API_BASE = "/schadenprotokoll/api"; - -// Generate Button document.getElementById("generate-btn").addEventListener("click", async () => { - showStatus("generate-status", "loading", "⏳ Verarbeite Laufzettel..."); - + 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) { - const err = await response.json(); - throw new Error(err.error || "Unbekannter Fehler"); - } - + 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 url = URL.createObjectURL(blob); const a = document.createElement("a"); - a.href = url; + a.href = URL.createObjectURL(blob); a.download = "Schadenprotokoll_vorbefuellt.pdf"; + document.body.appendChild(a); a.click(); - URL.revokeObjectURL(url); - - showStatus("generate-status", "success", "✓ PDF erfolgreich generiert und heruntergeladen"); + document.body.removeChild(a); + showStatus("generate-status", "success", "PDF erfolgreich generiert!"); } catch (err) { - showStatus("generate-status", "error", `❌ Fehler: ${err.message}`); + showStatus("generate-status", "error", "Fehler: " + err.message); } }); -// Analyze Button document.getElementById("analyze-btn").addEventListener("click", async () => { - showStatus("analyze-status", "loading", "⏳ Analysiere PDF..."); - + 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) { - const err = await response.json(); - throw new Error(err.error || "Unbekannter Fehler"); - } - + 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"); + showStatus("analyze-status", "success", "Analyse abgeschlossen"); } catch (err) { - showStatus("analyze-status", "error", `❌ Fehler: ${err.message}`); + showStatus("analyze-status", "error", "Fehler: " + err.message); } }); function displayAnalysisResult(data) { const container = document.getElementById("analyze-data"); let html = ""; - - // Textfelder - if (Object.keys(data.textfields).length > 0) { - html += `
Textfelder
`; - for (const [key, value] of Object.entries(data.textfields)) { - if (value) html += ``; - } - html += `
`; + for (const [key, value] of Object.entries(data.textfields || {})) { + if (value) html += ""; } - - // Dropdowns - if (Object.keys(data.dropdowns).length > 0) { - html += `
Dropdown-Auswahlen (Sachverhalt)
`; - for (const [key, val] of Object.entries(data.dropdowns)) { - if (val.selected) { - html += ``; - } - } - html += `
`; + for (const [key, val] of Object.entries(data.dropdowns || {})) { + if (val.selected) html += ""; } - - container.innerHTML = html || "

Keine ausgefüllten Felder gefunden.

"; + container.innerHTML = html || "

Keine Felder gefunden.

"; document.getElementById("analyze-result").classList.add("visible"); } -// Vorbericht Button document.getElementById("vorbericht-btn").addEventListener("click", async () => { - showStatus("vorbericht-status", "loading", "⏳ Erstelle Vorbericht..."); - + 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) { - const err = await response.json(); - throw new Error(err.error || "Unbekannter Fehler"); - } - + 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 url = URL.createObjectURL(blob); const a = document.createElement("a"); - a.href = url; + a.href = URL.createObjectURL(blob); a.download = "Vorbericht.docx"; + document.body.appendChild(a); a.click(); - URL.revokeObjectURL(url); - - showStatus("vorbericht-status", "success", "✓ Vorbericht erfolgreich erstellt und heruntergeladen"); + document.body.removeChild(a); + showStatus("vorbericht-status", "success", "Vorbericht erstellt!"); } catch (err) { - showStatus("vorbericht-status", "error", `❌ Fehler: ${err.message}`); + 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 = "

Keine Templates vorhanden.

"; - return; - } - - container.innerHTML = data.templates.map(t => ` -
- ${t.type === "pdf" ? "📄" : "📝"} ${t.name} - ${(t.size / 1024).toFixed(1)} KB -
- `).join(""); - } catch (err) { - document.getElementById("templates-container").innerHTML = - `

Fehler: ${err.message}

`; - } -} - -// Template Upload 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) { - input.files = e.dataTransfer.files; - uploadTemplate(input.files[0], templateType); - } - }); - input.addEventListener("change", () => { - if (input.files.length) uploadTemplate(input.files[0], templateType); + 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 => + "
" + t.name + "" + (t.size/1024).toFixed(0) + " KB
" + ).join(""); + } catch (err) { container.innerHTML = "Fehler"; } } async function uploadTemplate(file, type) { - showStatus("admin-status", "loading", `⏳ Lade ${type.toUpperCase()} Template hoch...`); - + showStatus("admin-status", "loading", "Lade 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"); + const response = await fetch(API_BASE + "/templates/upload", { method: "POST", body: formData }); + if (!response.ok) throw new Error((await response.json()).error); + showStatus("admin-status", "success", "Hochgeladen!"); loadTemplates(); - } catch (err) { - showStatus("admin-status", "error", `❌ Fehler: ${err.message}`); - } + } 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);