Reportlab Rechnungs-/Dokumentenerstellung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
tekkadan
User
Beiträge: 6
Registriert: Dienstag 4. Juni 2024, 16:41

Hallöle zusammen,

ich schlage mich schon seit einiger Zeit mit ReportLab rum, leider erfolglos.
Die Dokumentation von ReportLab online finde ich persönlich sehr einfach gehalten und bietet mir keine Hilfe, wenn es um komplexere PDFs geht.
Ich versuche seit 2 Wochen eine gescheite, mehrseitige Rechnung zu generieren (Erstmal mit Dummy Variablen und später mit User Input)...

Mein Ziel ist folgendes: Ich möchte eine Rechnung erstellen, die sich an die DIN 5008 Form B hält. D.h. fixe Positionen für Absender, Empfänger, Rechnungsdetails, Footer und "Mainblock" (Textfeld). Das sollte auch bei längeren Rechnungen/Lieferscheinen/Dokumenten klappen.

Mein Problem: Ich schaffe es einfach nicht die Positionierung von den o.g. Positionen bei BaseDocTemplate, oder SimpleDocTemplate einzuhalten. Mehrseitige Dokumente zersprengen mir meine PDF. Ich habe schon versucht über Frames die Geschichte zu lösen, was auch nicht richtig klappt und stehe nun richtig auf dem Schlauch. Ich bräuchte ein Template oder eine Vorgehensweise von euch, wie ich es schaffe fixe ("statische) Elemente auf der ersten Seite, wie Absender, Empfänger, Rechnungsdetails und Footer exakt positioniert zu erstellen und da zwischen eine Tabelle generiere, die automatisch Zeilenumbrüche und PageBreaks macht. Wobei der nächsten Seite dann der Mainblock weitergeht mit der Tabelle und die Footer + evtl. Seitenanzahl. Für Tipps, Snippets oder Hinweise auf Ressourcen wäre ich euch sehr dankbar.

Beste Grüße

Tekkadan
Benutzeravatar
grubenfox
User
Beiträge: 601
Registriert: Freitag 2. Dezember 2022, 15:49

Mojn,

ich kann zwar nicht wirklich helfen, werde aber interessiert mitlesen...

Dieses
tekkadan hat geschrieben: Dienstag 4. Juni 2024, 16:55 Die Dokumentation von ReportLab online finde ich persönlich sehr einfach gehalten und bietet mir keine Hilfe, wenn es um komplexere PDFs geht.
kann ich nämlich voll und ganz unterschreiben!
Benutzeravatar
snafu
User
Beiträge: 6850
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das hier ist speziell für Fedex und auf englisch, aber bestimmt kannst du dich am Code orientieren für etwas Eigenes.
https://github.com/radzhome/fedex-commercial-invoice

Dort findest du im "sample"-Ordner auch ein PDF mit einer Beispiel-Ausgabe.

Ich habe allerdings auf die Schnelle nicht nachgeschaut, inwieweit intelligente Seitenumbrüche unterstützt werden.

Und der Code ist halt 8 Jahre alt. Möglich dass er auf einem aktuellen System nicht auf Anhieb laufen wird...
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Muss es denn reportlab sein? Mit weasyprint kann man das Seitenlayout einfach per CSS definieren.
Hier zum Beispiel: https://github.com/Xiphe/din-5008-css
tekkadan
User
Beiträge: 6
Registriert: Dienstag 4. Juni 2024, 16:41

Sirius3 hat geschrieben: Mittwoch 5. Juni 2024, 05:40 Muss es denn reportlab sein? Mit weasyprint kann man das Seitenlayout einfach per CSS definieren.
Hier zum Beispiel: https://github.com/Xiphe/din-5008-css
Vielen Dank euch erstmal für die Antworten. Weasyprint hatte ich auch so einige Probleme mit fontconfig und die Installation auf Windowsmaschinen ist nicht so einfach..
Ich habe mit FPDF rumhantiert und bin schon viel weiter gekommen. Ich schaue mir das FEDEX Beispiel mal an und gebe nochmal Rückmeldung.

EDIT: Das ist schonmal ein guter Ansatz für weasyprint. schaue ich mir auch an. vielen dank :)
Zuletzt geändert von tekkadan am Mittwoch 5. Juni 2024, 13:45, insgesamt 1-mal geändert.
tekkadan
User
Beiträge: 6
Registriert: Dienstag 4. Juni 2024, 16:41

snafu hat geschrieben: Mittwoch 5. Juni 2024, 04:53 Das hier ist speziell für Fedex und auf englisch, aber bestimmt kannst du dich am Code orientieren für etwas Eigenes.
https://github.com/radzhome/fedex-commercial-invoice

Dort findest du im "sample"-Ordner auch ein PDF mit einer Beispiel-Ausgabe.

Ich habe allerdings auf die Schnelle nicht nachgeschaut, inwieweit intelligente Seitenumbrüche unterstützt werden.

Und der Code ist halt 8 Jahre alt. Möglich dass er auf einem aktuellen System nicht auf Anhieb laufen wird...
Das sieht doch auf den ersten Blick sehr hilfreich aus. Vielen Dank! Ich checke es ab und melde mich zurück :)
tekkadan
User
Beiträge: 6
Registriert: Dienstag 4. Juni 2024, 16:41

Nach rumprobieren belasse ich es die reportlab library zu nutzen. Ich versuche mich jetzt an fpdf2. Ich bin auch ein gutes Stück weitergekommen, nur habe ich folgendes Problem.
Diese Methode ist für die Rechnungstabelle zuständig. Wie man auf meinem Bild sehen kann, wird sobald ein Seitenumbruch stattfindet, der Rest der Zeile ab der letzten y-Position fortgesetzt, aber auf der NEUEN Seite. Das möchte ich natürlich nicht, da es nicht hineinpasst. Der Seitenumbruch erfolgt, nachdem die y-Position einen Schwellenwert erreicht hat. Ich weiß nicht, wie man Folgendes implementieren kann:

Zeile 1 Spalte 1 erreicht den Schwellenwert nicht.
Zeile 1 Spalte 2 erreicht ihn -> Seitenumbruch, setzt die multicell mit bestehenden Logik auf der nächsten Seite fort. (korrekt)
Zeile 1 Spalte 3 prüft den maximalen y-Wert -> erreicht nicht den Schwellenwert -> kein Seitenumbruch erforderlich. Die Zelle sollte auf der VORHERIGEN Seite gedruckt werden (funktioniert nicht)
Zeile 1 Spalte 4 erreicht wieder den Schwellenwert -> KEIN neuer Seitenumbruch (es sei denn, er ist so lang, dass wir nach der nächsten Seite einen weiteren Seitenumbruch brauchen), da wir bereits einen haben. Mit der multicell auf dem bereits eingefügten Seitenumbruch fortfahren. (funktioniert nicht)

Es muss die anderen Spalten auf der Seite vor dem Seitenumbruch drucken. Wenn es den Schwellenwert erreicht, sollte es keinen zusätzlichen neuen Seitenumbruch hinzufügen, sondern einfach auf der neuen Seite fortfahren. Siehe Bild für Problemstellung.

Der Code:

Code: Alles auswählen

def add_table_row(pdf, product, widths, alignments):
        pdf.set_font('Helvetica', '', 9)
        x_start = 25
        y_before = pdf.get_y()
        max_y = y_before

        for width, field, align in zip(widths, product, alignments):
            # Set x position to 25mm for the first column
            pdf.set_xy(x_start, y_before)
            pdf.multi_cell(width, 5, field, border=1, align=align)
            x_start += width  # Move x position for the next column
            max_y = max(max_y, pdf.get_y())

        pdf.set_y(max_y)

    
 # Add table rows
for idx, product in enumerate(product_lines, start=1):
        current_y = pdf.get_y()
        if current_y + 10 > pdf.h - 25:
            pdf.add_page()
            current_y = 25  # Set Y position to top margin for new page

        pdf.set_xy(25, current_y)  # Reset X position for the new row
        formatted_menge = f"{locale.format_string('%.2f', product['menge'], grouping=True)} {product['einheit']}"
        formatted_preis_netto = f"{locale.format_string('%.2f', product['preis_netto'], grouping=True)} EUR"
        formatted_betrag_netto = f"{product['betrag_netto']} EUR"
        product_data = [str(idx) + '.', product['produktname'], formatted_menge,
                        formatted_preis_netto, product['ust'] + ' %', formatted_betrag_netto]

        add_table_row(pdf, product_data, widths, alignments)


Bild

Danke im Voraus!
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt habe ich auch einen Windows-Rechner gefunden, wo ich weasyprint installieren konnte. Mit pip war das kein Problem, nur dass GTK3 extra installiert werden muß, ist ein bißchen mehr Aufwand. Wenn Du also Probleme hast, solltest Du hier die konkrete Fehlermeldung posten.

Alles weitere ist deutlich angenehmer. Bei Deinem Beispiel reicht der css-Style `page-break-inside:avoid`:

Code: Alles auswählen

import random
import jinja2
from weasyprint import HTML

environment = jinja2.Environment(autoescape=True)
template = environment.from_string("""
  <style>
    @page {
      @bottom-center {
        content: 'Seite ' counter(page) ' von ' counter(pages);
     }
    }  
    
    tr {page-break-inside:avoid;}
    td {vertical-align: top;}
  </style>
  <table>
    <thead>
      <tr><th>#</th><th>Beschreibung</th><th>Anzahl</th><th>Preis</th><th>USt</th><th>EUR</th></tr>
    </thead>
    <tbody>
      {%for row in data%}
      <tr>
        {%for cell in row%}
          <td>{{cell}}</td>
        {%endfor%}
      </tr>
      {%endfor%}
    </tbody>
  </table>
""")

data = [
    (
        n,
        jinja2.utils.generate_lorem_ipsum(n=1, html=False),
        random.randint(1,4000),
        f"{random.random()*100:.2f}",
        "19%",
        "EUR"
    )
    for n in range(1, 21)
]
html_text = template.render(data=data)
html = HTML(string=html_text)
html.write_pdf('tabelle.pdf')
tekkadan
User
Beiträge: 6
Registriert: Dienstag 4. Juni 2024, 16:41

Sirius3 hat geschrieben: Donnerstag 6. Juni 2024, 08:56 Jetzt habe ich auch einen Windows-Rechner gefunden, wo ich weasyprint installieren konnte. Mit pip war das kein Problem, nur dass GTK3 extra installiert werden muß, ist ein bißchen mehr Aufwand. Wenn Du also Probleme hast, solltest Du hier die konkrete Fehlermeldung posten.

Alles weitere ist deutlich angenehmer. Bei Deinem Beispiel reicht der css-Style `page-break-inside:avoid`:

Code: Alles auswählen

import random
import jinja2
from weasyprint import HTML

environment = jinja2.Environment(autoescape=True)
template = environment.from_string("""
  <style>
    @page {
      @bottom-center {
        content: 'Seite ' counter(page) ' von ' counter(pages);
     }
    }  
    
    tr {page-break-inside:avoid;}
    td {vertical-align: top;}
  </style>
  <table>
    <thead>
      <tr><th>#</th><th>Beschreibung</th><th>Anzahl</th><th>Preis</th><th>USt</th><th>EUR</th></tr>
    </thead>
    <tbody>
      {%for row in data%}
      <tr>
        {%for cell in row%}
          <td>{{cell}}</td>
        {%endfor%}
      </tr>
      {%endfor%}
    </tbody>
  </table>
""")

data = [
    (
        n,
        jinja2.utils.generate_lorem_ipsum(n=1, html=False),
        random.randint(1,4000),
        f"{random.random()*100:.2f}",
        "19%",
        "EUR"
    )
    for n in range(1, 21)
]
html_text = template.render(data=data)
html = HTML(string=html_text)
html.write_pdf('tabelle.pdf')
Hi, ich wurde zu weasyprint bekehrt. Funktioniert alles super, nur habe ich deinen gtk3 part nicht verstanden. Das sind meine Errors:

Code: Alles auswählen

Fontconfig error: Cannot load default config file: No such file: (null)
Fontconfig error: Cannot load default config file: No such file: (null)
Fontconfig error: Cannot load default config file: No such file: (null)
Ist das ein pip paket oder wie kann ich das verstehen?
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie hast Du denn weasyprint installiert?
tekkadan
User
Beiträge: 6
Registriert: Dienstag 4. Juni 2024, 16:41

Sirius3 hat geschrieben: Samstag 8. Juni 2024, 13:26 Wie hast Du denn weasyprint installiert?
Ich nutze VSC und ganz einfach über pip install weasyprint.
Antworten