From f25cf026609bdc4fae32bc3feb1c5f14a7678f3b Mon Sep 17 00:00:00 2001 From: JbLb Date: Mon, 16 Mar 2026 23:16:23 +0100 Subject: [PATCH] =?UTF-8?q?correction=20pour=20conformit=C3=A9=20complete?= =?UTF-8?q?=20PDF/A-3=20cod=C3=A9=20avec=20l'aide=20de=20claude=20IA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/factures.py | 57 ++++++++++++++++++++++++++++++-------- templates/pdf/facture.html | 37 +++++++++++++++++-------- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/routers/factures.py b/routers/factures.py index 80a210b..9ed35ac 100644 --- a/routers/factures.py +++ b/routers/factures.py @@ -159,29 +159,64 @@ def changer_statut_facture( from generate_facturx_jinja2 import Invoice, Party, Address, InvoiceLine, filter_amount, filter_datefmt - -@router.get("/{facture_id}/pdf", response_class=HTMLResponse) +@router.get("/{facture_id}/pdf") def telecharger_pdf(request: Request, facture_id: int, db: Session = Depends(get_db)): - from weasyprint import HTML + from weasyprint import HTML, Attachment + from facturx import generate_from_binary + import hashlib + import pikepdf + from pikepdf import Name + import io + facture = db.query(Facture).get(facture_id) if not facture: raise HTTPException(status_code=404) + # 1. XML Factur-X + invoice = get_invoice_data(facture_id, db) + xml_str = generate_facturx_xml(invoice) + xml_bytes = xml_str.encode("utf-8") + + # 2. Rendu HTML → PDF simple (pas PDF/A-3 ici) html_content = templates.get_template("pdf/facture.html").render({ "facture": facture, "settings": settings, }) + pdf_bytes = HTML(string=html_content, base_url=".").write_pdf( + pdf_variant="pdf/a-3b", + ) - pdf_bytes = HTML(string=html_content, base_url=".").write_pdf() + # 3. generate_from_binary gère : + # - l'intégration du XML + # - les métadonnées XMP Factur-X (DocumentType, DocumentFileName, etc.) + # - la conformité PDF/A-3b + pdf_bytes = generate_from_binary( + pdf_bytes, + xml_bytes, + flavor="factur-x", + level="BASIC", + ) + + # 4. Corriger uniquement /EF/F/Subtype → /text/xml avec pikepdf + pdf_io = io.BytesIO(pdf_bytes) + with pikepdf.open(pdf_io) as pdf: + names = pdf.Root.Names.EmbeddedFiles.Names + i = 0 + while i < len(names): + i += 1 # sauter la clé (string) + if i < len(names): + obj = names[i] + try: + obj.EF.F.Subtype = Name("/text/xml") + except Exception: + pass + i += 1 + + output = io.BytesIO() + pdf.save(output) + pdf_bytes = output.getvalue() filename = f"facture-{facture.numero}.pdf" - - xml_bytes = generate_facturx_xml(get_invoice_data(facture_id, db)) - - pdf_bytes = generate_from_binary(pdf_bytes, xml_bytes) - - filename = f"facture-{facture.numero}.pdf" - return Response( content=pdf_bytes, media_type="application/pdf", diff --git a/templates/pdf/facture.html b/templates/pdf/facture.html index 80a787c..dd81ce1 100644 --- a/templates/pdf/facture.html +++ b/templates/pdf/facture.html @@ -4,10 +4,22 @@