feat(ui): PDF-Text back under PDF preview; one-click export+download
- Extracted-text expander moved back to left column under PDF preview (PDF-Text tab removed, now 3 tabs) - Export button now generates and downloads in a single click via st.download_button - ZUGFeRD generation cached (st.cache_data) so it doesn't rerun on every keystroke Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c07eb860fa
commit
1992c30991
108
main.py
108
main.py
|
|
@ -89,6 +89,30 @@ def get_cached_llm():
|
|||
return st.session_state.llm_instance
|
||||
|
||||
|
||||
@st.cache_data(show_spinner=False)
|
||||
def build_zugferd_pdf(invoice_json: str, original_pdf_bytes: bytes) -> bytes:
|
||||
"""Erzeugt die ZUGFeRD-Hybrid-PDF (factur-x + PDF/A-3). Gecacht ueber (Rechnungsdaten-JSON,
|
||||
Original-PDF-Bytes), damit diese vergleichsweise teure Erzeugung nur laeuft, wenn sich die
|
||||
Rechnung tatsaechlich aendert - so kann der Export-Button direkt herunterladen, ohne bei
|
||||
jedem Rerun neu zu generieren."""
|
||||
invoice_dict = json.loads(invoice_json)
|
||||
cii_xml_string = generate_compliant_zugferd_cii(invoice_dict)
|
||||
# factur-x: XSD-Validierung der CII-XML, korrektes Einbetten und PDF/A-3-konforme XMP-Struktur.
|
||||
pdf_bytes = facturx_generate_from_binary(
|
||||
original_pdf_bytes,
|
||||
cii_xml_string.encode("utf-8"),
|
||||
flavor="factur-x",
|
||||
level="en16931",
|
||||
check_xsd=True,
|
||||
check_schematron=False, # benötigt optional 'saxonche' für Schematron-Regeln
|
||||
afrelationship="data",
|
||||
)
|
||||
# Zusätzliches OutputIntent (sRGB-ICC) + XRechnung-Konformitaets-Fix in den XMP-Metadaten.
|
||||
pdf_bytes = add_pdfa_output_intent(pdf_bytes)
|
||||
pdf_bytes = fix_xrechnung_xmp_conformance(pdf_bytes)
|
||||
return pdf_bytes
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 5. STREAMLIT FRONTEND
|
||||
# ==========================================
|
||||
|
|
@ -109,6 +133,7 @@ with col1:
|
|||
st.success("PDF eingelesen!")
|
||||
uploaded_file.seek(0)
|
||||
st.pdf(uploaded_file)
|
||||
with st.expander("Gefundener Text"): st.text(pdf_text)
|
||||
|
||||
with col2:
|
||||
st.subheader("2. Strukturierte Daten validieren")
|
||||
|
|
@ -203,8 +228,8 @@ with col2:
|
|||
|
||||
# Reiter-Navigation: gruppiert die Eingabefelder, damit man nicht die ganze Seite
|
||||
# runterscrollen muss. Summen & Export stehen bewusst UNTERHALB der Tabs (immer sichtbar).
|
||||
tab_basis, tab_parteien, tab_positionen, tab_pdftext = st.tabs(
|
||||
["Basis", "Parteien", "Positionen", "PDF-Text"]
|
||||
tab_basis, tab_parteien, tab_positionen = st.tabs(
|
||||
["Basis", "Parteien", "Positionen"]
|
||||
)
|
||||
|
||||
with tab_basis:
|
||||
|
|
@ -359,9 +384,6 @@ with col2:
|
|||
with c_disc4:
|
||||
skonto_days = st.text_input("Skonto-Frist (Tage)", value="")
|
||||
|
||||
with tab_pdftext:
|
||||
st.text(pdf_text if pdf_text.strip() else "(Kein PDF-Text verfügbar)")
|
||||
|
||||
# --- SUMMEN & EXPORT: bewusst AUSSERHALB der Tabs, damit sie unabhängig vom aktiven
|
||||
# Reiter immer sichtbar und ohne Scrollen erreichbar sind. ---
|
||||
st.markdown("---")
|
||||
|
|
@ -439,51 +461,53 @@ with col2:
|
|||
|
||||
export_disabled = len(blocker) > 0
|
||||
|
||||
# Ein-Klick-Export: die Exportdatei wird vorab erzeugt und direkt in den Download-Button
|
||||
# gelegt - ein Klick startet also sofort den Download, ohne separaten Zwischen-Button.
|
||||
if "UBL XML" in export_format:
|
||||
if st.button("🔒 XRechnung exportieren", type="primary", width="stretch", disabled=export_disabled):
|
||||
xml_data = generate_compliant_xrechnung(final_invoice_dict)
|
||||
st.download_button(label="📥 Download XRechnung XML", data=xml_data, file_name=f"XRechnung_{inv_num}.xml", mime="text/xml")
|
||||
xml_data = generate_compliant_xrechnung(final_invoice_dict) if not export_disabled else ""
|
||||
st.download_button(
|
||||
"🔒 XRechnung exportieren & herunterladen",
|
||||
data=xml_data,
|
||||
file_name=f"XRechnung_{inv_num}.xml",
|
||||
mime="text/xml",
|
||||
type="primary",
|
||||
width="stretch",
|
||||
disabled=export_disabled,
|
||||
)
|
||||
else:
|
||||
if st.button("🔒 ZUGFeRD PDF exportieren", type="primary", width="stretch", disabled=export_disabled):
|
||||
zugferd_pdf_bytes = None
|
||||
zugferd_error = None
|
||||
if not export_disabled and uploaded_file is not None:
|
||||
try:
|
||||
cii_xml_string = generate_compliant_zugferd_cii(final_invoice_dict)
|
||||
|
||||
uploaded_file.seek(0)
|
||||
original_pdf_bytes = uploaded_file.read()
|
||||
|
||||
# 1) factur-x übernimmt: XSD-Validierung der CII-XML, korrektes Einbetten
|
||||
# (Filespec mit AFRelationship=/Data, /F + /UF, Subtype text/xml) und die
|
||||
# komplette PDF/A-3-konforme XMP-Metadatenstruktur (pdfaid:part/conformance,
|
||||
# fx:-Schema-Erweiterung). Bricht die XML-Validierung fehl, wird hier ein
|
||||
# klarer Fehler geworfen statt einer stillen, ungültigen PDF.
|
||||
zugferd_pdf_bytes = facturx_generate_from_binary(
|
||||
zugferd_pdf_bytes = build_zugferd_pdf(
|
||||
json.dumps(final_invoice_dict, default=str, sort_keys=True),
|
||||
original_pdf_bytes,
|
||||
cii_xml_string.encode("utf-8"),
|
||||
flavor="factur-x",
|
||||
level="en16931",
|
||||
check_xsd=True,
|
||||
check_schematron=False, # benötigt optional 'saxonche' für Schematron-Regeln
|
||||
afrelationship="data",
|
||||
)
|
||||
|
||||
# 2) Zusätzliches OutputIntent (sRGB-ICC-Profil) ergänzen. Das ist für echte
|
||||
# PDF/A-3-Konformität zwingend, wird von factur-x aber nicht selbst gesetzt.
|
||||
zugferd_pdf_bytes = add_pdfa_output_intent(zugferd_pdf_bytes)
|
||||
zugferd_pdf_bytes = fix_xrechnung_xmp_conformance(zugferd_pdf_bytes)
|
||||
|
||||
st.download_button(
|
||||
label="📥 Download ZUGFeRD Hybrid-PDF",
|
||||
data=zugferd_pdf_bytes,
|
||||
file_name=f"ZUGFeRD_{inv_num}.pdf",
|
||||
mime="application/pdf"
|
||||
)
|
||||
st.info(
|
||||
"ℹ️ Hinweis: Die eingebettete CII-XML wurde gegen das offizielle EN16931-XSD-Schema "
|
||||
"validiert. Für eine vollständige PDF/A-3-Zertifizierung (z. B. Schriftarten-Einbettung "
|
||||
"der Ursprungs-PDF) empfiehlt sich zusätzlich eine Prüfung mit veraPDF."
|
||||
)
|
||||
except Exception as e:
|
||||
st.error(f"Fehler bei der ZUGFeRD-Generierung: {e}")
|
||||
zugferd_error = str(e)
|
||||
elif not export_disabled and uploaded_file is None:
|
||||
zugferd_error = "Original-PDF nicht mehr geladen. Bitte die PDF erneut hochladen."
|
||||
|
||||
if zugferd_error:
|
||||
st.error(f"Fehler bei der ZUGFeRD-Generierung: {zugferd_error}")
|
||||
|
||||
st.download_button(
|
||||
"🔒 ZUGFeRD PDF exportieren & herunterladen",
|
||||
data=zugferd_pdf_bytes if zugferd_pdf_bytes else b"",
|
||||
file_name=f"ZUGFeRD_{inv_num}.pdf",
|
||||
mime="application/pdf",
|
||||
type="primary",
|
||||
width="stretch",
|
||||
disabled=export_disabled or zugferd_pdf_bytes is None,
|
||||
)
|
||||
if zugferd_pdf_bytes:
|
||||
st.info(
|
||||
"ℹ️ Hinweis: Die eingebettete CII-XML wurde gegen das offizielle EN16931-XSD-Schema "
|
||||
"validiert. Für eine vollständige PDF/A-3-Zertifizierung (z. B. Schriftarten-Einbettung "
|
||||
"der Ursprungs-PDF) empfiehlt sich zusätzlich eine Prüfung mit veraPDF."
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# FOOTER
|
||||
|
|
|
|||
Loading…
Reference in New Issue