Seite 1 von 1

Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 10:08
von grubenfox
Sirius3 hat geschrieben: Freitag 21. April 2023, 08:57 Ich persönlich finde ja https://weasyprint.org/ ganz angenehm, weil man damit relativ einfach per HTML und CSS die Seiten formatieren kann.
ich suche ja immer gerne nach Möglichkeiten mittels Python Berichte (z.B. Rechnungen) zu erzeugen. Der letzte Versuch mich mit ReportLab anzufreunden ist zugegebenermaßen schon einige Jahre her. War erfolglos....
Aus gegebenem Anlass habe ich eben mal kurz in die Doku von weasyprint reingeschaut. Scheint leider auch nur für einfache Fälle von Rechnungslayout geeignet zu sein.

Aber wie ist es mit "mehrseitiger Rechnung mit ausgewiesener Zwischensumme am Ende einer Seite und ausgewiesenem Übertrag am Anfang der Folgeseiten"?
Also benötige ich eine laufende Summe und muss irgendwie erkennen können ob eine Rechnungsposition noch auf Seite X gedruckt wird oder ob sie schon auf Seite X+1 rutscht.

Geht das bei weasyprint?

Falls das geht, hier die Steigerung: jede Rechnungsposition besteht aus mehreren Druckzeilen (sagen wir mal zwischen 3-7) und muss immer zusammenhängend an einem Stück auf der Seite gedruckt sein. Also kein Seitenumbruch innerhalb einer mehrzeiligen Rechnungsposition! Und das eben mit korrekter Zwischensumme bzw. Übertrag.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 10:28
von geraldfo
Weasyprint konvertiert HTML-Files in PDF-Files.
Ich glaube, du musst dich beim Erzeugen des HTML-Files selbst darum kümmern, dass Zeilenumbrüche an der richtigen Stelle sind.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 10:40
von geraldfo
Möglichkeiten:
* Zeilen zählen bei der Ausgabe
* Absolut positionieren mit (z. B.) Maßeinheit mm müßte auch gehen

Wenn du längere Texte ausgeben willst kann es kompliziert werden.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 11:34
von grubenfox
geraldfo hat geschrieben: Freitag 21. April 2023, 10:40 * Absolut positionieren mit (z. B.) Maßeinheit mm müßte auch gehen

Wenn du längere Texte ausgeben willst kann es kompliziert werden.
Ja... und "absolut positionieren" und mit A4-Format hantieren: will man das bei HTML? Wenn ich bisher etwas mit HTML gemacht habe, dann war es für die Webseite bzw. den Bildschirm (und damit beliebig lang).

P.S.
geraldfo hat geschrieben: Freitag 21. April 2023, 10:28 Ich glaube, du musst dich beim Erzeugen des HTML-Files selbst darum kümmern, dass Zeilenumbrüche an der richtigen Stelle sind.
ich glaube das Problem ist nicht so sehr der Zeilenumbruch, sondern der Seitenumbruch.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 11:44
von __deets__
Du kannst nicht alles haben. Entweder ueberlaesst du das ganze einer Layout-Engine, aber dann hast du keine Zwischensumme. Oder du kuemmerst dich selbst. Denn ein Automatismus kann notgedrungenerweise nicht funktionieren. HTML kennt keine Zwischensummen, und auch weasyprint nicht. Wenn der Layouter an eine Seitengrenze stoesst, dann kann er nicht rueckfragen, und da eine Zeile einfuehren.

Der Weg hier kann also nur iterativ sein: du platzierst Rechnungszeilen, mit der jeweiligen Zwischensumme bis zu dem Moment. Und wenn das Ergebnis auf eine Seite passt, dann versuchst du eine Zeile dazu zu nehmen. Bis es "kracht" - das letzte funktionierende Layout stellt dann die aktuelle Seite dar, und weiter geht's auf der naechsten.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 13:39
von Sirius3
@grubenfox: damit man eine Zwischensumme bilden kann, muß ja das Layout-System wissen, was es da inhaltlich darstellt. Das ist nicht Aufgabe eines allgemeinen Satzsystems.

Aber man kann sich das ja einfach selbst erzeugen, indem man z.B. abfragt, wo die Tabelle auf eine neue Seite umgebrochen wird.

Code: Alles auswählen

import random
from weasyprint import HTML, CSS

rechnungsposten = [
    (f"Posten {i}", random.randint(1, 20), random.randint(1, 1000) * 0.01)
    for i in range(100)
]

rechnungsposten = [
    (name, anzahl, einzelpreis, anzahl*einzelpreis)
    for name, anzahl, einzelpreis in rechnungsposten
]


def generate_table(rechnungsposten, summe):
    table = "<table>"
    table += "<thead><tr><th>Bezeichnung</th><th>Anzahl</th><th>Einzelpreis</th><th>Gesamtpreis</th></tr></thead>"
    table += "<tbody>"
    gesamtsumme = 0
    for name, anzahl, einzelpreis, gesamtpreis in rechnungsposten:
        if einzelpreis is None:
            table += f"<tr><td>{name}</td><td></td><td></td><td>{gesamtpreis:.2f}</td></tr>"
        else:
            table += f"<tr><td>{name}</td><td>{anzahl}</td><td>{einzelpreis:.2f}</td><td>{gesamtpreis:.2f}</td></tr>"
        gesamtsumme += gesamtpreis
    table += "</tbody>"
    table += f"<tfoot><tr><th>{summe}</th><th></th><th></th><th>{gesamtsumme:.2f}</th></tr></thead>"
    return table


css = CSS(string='''
@page {
   @bottom-right {
    content: counter(page) " - " counter(pages);
   }
}
td, th { text-align:right }
td:first-child, th:first-child { text-align:left }
''')

pages = [rechnungsposten]
while True:
    tables = [
        generate_table(p, "Gesamtsumme" if p == pages[-1] else "Zwischensumme")
        for p in pages
    ]
    html = HTML(string=f'''<h1>Rechnung</h1>{''.join(tables)}''')
    doc = html.render(stylesheets=[css])
    if len(doc.pages) == len(pages):
        break
    last_page = pages.pop()
    body = doc.pages[len(pages)]._page_box.children[0].children[0]
    table = body.children[-1]
    tbody = table.children[0].children[1]
    rows = len(tbody.children)
    pages.append(last_page[:rows])
    summe = sum(p[-1] for p in last_page[:rows])
    pages.append([('Zwischensumme', None, None, summe)] + last_page[rows:])

doc.write_pdf('example.pdf')

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Freitag 21. April 2023, 14:33
von grubenfox
Sirius3 hat geschrieben: Freitag 21. April 2023, 13:39 @grubenfox: damit man eine Zwischensumme bilden kann, muß ja das Layout-System wissen, was es da inhaltlich darstellt. Das ist nicht Aufgabe eines allgemeinen Satzsystems.
Das natürlich nicht, aber es könnte mir über irgendwelche Callbacks mitteilen ob eine zu druckende Zeile noch auf die aktuelle Seite passt oder auf die nächste verschoben wird. Oder so ähnlich...
Ich hätte auch nichts gegen ein nicht ganz so allgemeines Satzsystem das verschiedene Arten von Felder kennt und dadurch dann z.b. numerische Felder eben zur laufenden Summe summieren könnte. So ganz allgemein muss es hier eigentlich nicht sein: "nur" Rechnungen, Frachtbriefe, Lieferscheine reichen mir ja. ;)

Aber möglicherweise ist weasyprint eine Basis um den nicht so ganz allgemeinen Teil vom Drucksystem dann selbst zu entwickeln.
Sirius3 hat geschrieben: Freitag 21. April 2023, 13:39 Aber man kann sich das ja einfach selbst erzeugen, indem man z.B. abfragt, wo die Tabelle auf eine neue Seite umgebrochen wird.
Hmmpfff ... "einfach"... :) Den Code muss ich mir mal in einer ruhigen Stunde zu Gemüte führen. Vielleicht am Wochenende.
Wie/wo spielen da noch die eingerichteten Seitenränder (oben und unten) mit rein?

Und da wäre auch noch der Seitenfuss auf jeder Seite mit Bankinformationen oder anderen Texten und am Ende der Rechnung dann der Rechnungsfuß mit Gesamtsumme, Mehrwertsteuer und dem ganzen Rest. Mal schauen ob ich das da noch eingebaut bekomme. Seitenzähler ist ja schon vorhanden, da kann ich mich wohl dran orientieren.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Samstag 22. April 2023, 08:44
von geraldfo
ich glaube das Problem ist nicht so sehr der Zeilenumbruch, sondern der Seitenumbruch.
Stimmt. Seitenumbruch war gemeint.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Samstag 22. April 2023, 10:38
von snafu
Den schon probiert?

https://pypi.org/project/InvoiceGenerator/

Basiert wohl auf ReportLab. Habe ihn aber noch nicht benutzt.

Der ist wohl noch in einem frühen Entwicklungsstadium und hat anscheinend seit 2020 keine neuen Commits mehr, aber könnte dennoch als Grundlage dienen.

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Dienstag 25. April 2023, 23:54
von grubenfox
Ich hatte mir den Code von Sirius3 jetzt am Wochenende mal genauer angeschaut und einige Programmdurchläufe (mit Debugausgaben) später dann auch ungefähr verstanden was da warum und wie abläuft.
Eine auftauchende Frage "wie kommt man darauf, da über `_page_box` in den privaten Eingeweiden von weasyprint rum zu graben..."
Sirius3 hat geschrieben: Freitag 21. April 2023, 13:39 body = doc.pages[len(pages)]._page_box.children[0].children[0]
table = body.children[-1]
tbody = table.children[0].children[1]
rows = len(tbody.children)
Mangels Beispiel in der Doku die ich so gefunden hatte, wäre ich auf den Gedanken erst sehr spät gekommen.
Heute am Montag habe ich mir mal die Sourcen von weasyprint angeschaut. Bei anschauen der zwei Routinen im Ordner "formatting_structure" hatte ich kurz den Gedanken "wie kann ich da meine eigenen Elemente (ein Adressefeld zum Beispiel) einbauen" bis mir dann einfiel "hier werden die Komponenten nicht in Python gemacht, sondern das läuft über css. Da müssten die Details (Größe, Position) vom Adressfeld rein".

Bis jetzt hatte ich ccs immer nur in Verbindung mit Webseiten genutzt. Nun ist offenbar das Thema "ccs und Printausgabe" mein neues Thema... die ersten Infos von verschiedenen Webseite sind gerade sehr interessant... und der Code sowohl von weasyprint als auch der von Sirius3 ergibt immer mehr Sinn...

Das alles gefällt mir immer besser.... :D

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Mittwoch 26. April 2023, 00:18
von grubenfox
snafu hat geschrieben: Samstag 22. April 2023, 10:38 Den schon probiert?
Noch nicht probiert....
Von der Projektbeschreibung:
This is library to generate a simple invoices. Currently supported formats are PDF and XML for Pohoda accounting system. PDF invoice is based on ReportLab.
Das `simple`und `ReportLab` stören mich ein wenig. Mit ReportLab hatte ich das ganze vor einigen Jahren ja schon mal erfolglos probiert zu lösen... da hatte ich dann versucht irgendwie iterativ zu ermitteln ob die Rechnungsposition X noch auf die aktuelle Seite kommt, damit auch der Wert der Rechnungsposition zur laufenden Zwischensumme gezählt werden muss die dann am Seitenende aufgeführt wird.

Aber in den letzten Stunden habe ich die Erinnerung daran erfolgreich verdrängt und der Ansatz den Sirius3 da bei weasyprint verfolgt erscheint mir immer logischer und einfacher... :o außerdem kann ich mich da mal mit Print-css auseinandersetzen. Kenne ich bisher noch nicht, noch erscheint mir da alles sehr rosarot :lol: und weckt noch keine negativen Erinnerungen wie ReportLab. ;)

Re: Rechnungsdruck mit Python (weasyprint?)

Verfasst: Mittwoch 26. April 2023, 16:22
von Opa Hansi
Ich habe das Problem vor 20 Jahren für Xbase gelöst und bin jetzt dabei es auch für Python in den Griff zu kriegen.
Wenn ich fertig bin und ich Deine Mailadresse habe, kann ich Dir ja das Ergebnis schicken.