Project H - Maak PDF-facturen

01 - Projectoverzicht
  • In dit project maak je een Python-script dat eenvoudige PDF-facturen genereert.
  • Het script lost het probleem op dat je dezelfde factuurgegevens niet steeds handmatig in een document wilt typen.
  • Je gebruikt invoergegevens zoals klantnaam, factuurnummer, datum, omschrijving, bedrag en btw-percentage.
  • Het script berekent het totaalbedrag en zet de gegevens overzichtelijk in een PDF-bestand.
  • Het resultaat is een PDF-factuur met een herkenbare bestandsnaam zoals factuur_2026_001_jansen.pdf.
  • Je gebruikt AI om een eerste versie te maken en vraagt expliciet om eenvoudige code met duidelijke variabelen.
  • Je voert de code uit in VS Code en opent daarna de PDF om de layout te controleren.
  • Als de PDF rommelig is, verbeter je de prompt met vaste marges, kolombreedtes en voorbeeldtekst.
02 - Projectmap en Python-omgeving

Voorkennis: doorloop de voorgaande projecten. Het doel en gebruik van .venv zijn uitgelegd in project A, blok 02. Voor ieder nieuw project maak je een eigen omgeving; hieronder herhaal je die bekende stappen.

  • Maak een eigen projectmap project_h en open die map in VS Code. Maak daarin straks factuur.py.
  • Deze oefeningen voer je lokaal uit in de terminal van VS Code, omdat ze bestanden op je computer lezen of schrijven. Gebruik hiervoor de Python-installatie uit de basiscursus.
  • Controleer je Python-versie. Werkt python op macOS of Linux niet, gebruik dan python3 voor het aanmaken van de omgeving.
python --version
python -m venv .venv

Activeer de omgeving. In Windows PowerShell:

.venv\Scripts\Activate.ps1

In de Windows-opdrachtprompt (cmd):

.venv\Scripts\activate.bat

Op macOS of Linux:

source .venv/bin/activate

Kies in VS Code via Python: Select Interpreter de interpreter uit .venv. Gebruik vanaf nu de geactiveerde terminal. Je verlaat de omgeving later met deactivate.

Een package is een extra pakket met Python-modules. pip is het installatiehulpmiddel; python -m pip gebruikt het bij de actieve Python. Installeer reportlab nu in de projectomgeving. De werking van het pakket volgt vóór de opdracht in blok 04.

python -m pip install reportlab
03 - Testgegevens voorbereiden

We gebruiken fictieve gegevens voor een oefenfactuur. Klant: Voorbeeldklant Jansen, factuurnummer 2026-001, datum 2026-05-01. De factuur heeft twee regels: 2 uur ondersteuning à EUR 50.00 en 1 handleiding à EUR 25.00.

Voor de rekenoefening kiezen we 21% btw. Dit percentage en de velden zijn voorbeeldinvoer; het document is een oefenmodel. Alle gegevens staan bovenaan het script en de PDF komt in uitvoer.

04 - Begrippen en hulpmiddelen vóór de opdracht
  • Je gebruikt de geldberekeningen uit F en G. ROUND_HALF_UP kiest voor het afronden van halve centen naar boven; we passen één vaste afrondingsregel toe.
  • ReportLab is een extra package om PDF’s op te bouwen. Een SimpleDocTemplate beheert pagina’s; een Paragraph is tekst met regelafbreking, een Spacer is witruimte en een Table is een tabel.
  • A4 kiest het papierformaat en mm rekent millimeters om naar de eenheid van de bibliotheek. TableStyle stelt lijnen, achtergrond en uitlijning in.
  • escape uit xml.sax.saxutils maakt tekst veilig voor de beperkte opmaaktaal van Paragraph. De inhoud wordt niet als HTML-pagina uitgevoerd.
  • Geldbedragen komen uit strings voor Decimal. We ronden eerst de regelbedragen af en berekenen daarna de btw over het subtotaal.
  • SimpleDocTemplate plaatst tekst en tabellen op pagina’s. Een Paragraph breekt lange omschrijvingen af binnen de kolom.
  • escape voorkomt dat tekens zoals & als PDF-opmaak worden gelezen. Bij opnieuw uitvoeren wordt dezelfde oefen-PDF vervangen.
05 - Een gerichte AI-prompt maken

Geef AI eerst een concrete opdracht. Kopieer bijvoorbeeld deze prompt en vergelijk het antwoord met de voorbeeldcode in het volgende blok:

Maak met ReportLab een eenvoudige PDF-oefenfactuur op A4 met 20 mm marges,
een tabel met omschrijving/aantal/prijs/bedrag en subtotaal, btw en totaal.
Gebruik Decimal uit strings, rond btw op twee decimalen met ROUND_HALF_UP.
Gebruik fictieve klantgegevens, nummer 2026-001, datum 2026-05-01 en regels
2x50.00 ondersteuning en 1x25.00 handleiding. Zet 21 procent als instelbare
voorbeeldwaarde. Gebruik Paragraph voor afbreken van omschrijvingen en
escape tekst. Bewaar in uitvoer/factuur_2026_001_jansen.pdf. Leg uit welke
gegevens ik kan aanpassen.

Vraag om uitleg bij onbekende regels en controleer welke bestanden de code leest en schrijft voordat je haar uitvoert. Gebruik de oefengegevens uit dit project.

06 - Een werkende basisversie

Dit is een complete basisversie voor factuur.py. Neem de code over of vergelijk haar met het antwoord van AI.

from pathlib import Path
from decimal import Decimal, ROUND_HALF_UP
from xml.sax.saxutils import escape
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle

KLANT = "Voorbeeldklant Jansen"
NUMMER = "2026-001"
DATUM = "2026-05-01"
BTW = Decimal("0.21")
REGELS = [("Ondersteuning", 2, Decimal("50.00")), ("Handleiding", 1, Decimal("25.00"))]
UITVOER = Path(__file__).resolve().parent / "uitvoer" / "factuur_2026_001_jansen.pdf"

def geld(bedrag):
    return f"EUR {bedrag:.2f}"

def main():
    UITVOER.parent.mkdir(exist_ok=True)
    stijlen = getSampleStyleSheet()
    inhoud = [Paragraph("Oefenfactuur", stijlen["Title"])]
    for tekst in [f"Klant: {KLANT}", f"Factuurnummer: {NUMMER}", f"Datum: {DATUM}"]:
        inhoud.append(Paragraph(escape(tekst), stijlen["Normal"]))
    inhoud.append(Spacer(1, 8 * mm))
    tabel = [["Omschrijving", "Aantal", "Prijs", "Bedrag"]]
    subtotaal = Decimal("0.00")
    for omschrijving, aantal, prijs in REGELS:
        bedrag = (aantal * prijs).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
        subtotaal += bedrag
        tabel.append([Paragraph(escape(omschrijving), stijlen["Normal"]), str(aantal), geld(prijs), geld(bedrag)])
    btw = (subtotaal * BTW).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    tabel.extend([
        ["Subtotaal", "", "", geld(subtotaal)],
        [f"Btw {BTW * 100:.0f}%", "", "", geld(btw)],
        ["Totaal", "", "", geld(subtotaal + btw)],
    ])
    overzicht = Table(tabel, colWidths=[75 * mm, 20 * mm, 35 * mm, 40 * mm], repeatRows=1)
    overzicht.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
        ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
        ("VALIGN", (0, 0), (-1, -1), "TOP"),
        ("ALIGN", (1, 1), (-1, -1), "RIGHT"),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
    ]))
    inhoud.append(overzicht)
    document = SimpleDocTemplate(str(UITVOER), pagesize=A4, leftMargin=20*mm,
                                 rightMargin=20*mm, topMargin=20*mm, bottomMargin=20*mm)
    document.build(inhoud)
    print(f"Subtotaal {geld(subtotaal)}, btw {geld(btw)}, totaal {geld(subtotaal + btw)}")
    print(f"PDF gemaakt: {UITVOER}")

if __name__ == "__main__":
    main()
07 - Het script uitvoeren
  • Sla de volledige code op als factuur.py in je projectmap. Zet eventuele invoerbestanden op de plek die in blok 03 staat.
  • Open de geïntegreerde terminal in die projectmap en activeer .venv als dat nog niet is gebeurd.
  • Voer het script uit met:
python factuur.py

Lees eerst de terminaluitvoer en open daarna eventuele uitvoerbestanden. Controleer het resultaat met blok 08 voordat je met eigen gegevens verdergaat.

08 - Het resultaat controleren
  • Het subtotaal is EUR 125.00, de btw EUR 26.25 en het totaal EUR 151.25. Controleer dit in de terminal én in de PDF.
  • Open de PDF en controleer klant, nummer, datum, marges en uitlijning van de bedragen.
  • Maak een omschrijving langer en voeg meerdere regels toe. Tekst moet binnen de tabel blijven; bij meerdere pagina’s wordt de kopregel herhaald.
09 - Fouten herkennen en oplossen
  • ModuleNotFoundError voor reportlab? Controleer de actieve virtual environment en installeer met python -m pip install reportlab.
  • PermissionError bij opslaan? Sluit de PDF-lezer als deze het bestand vergrendelt. Onbekende lettertekens vragen om een passend ingesloten lettertype als uitbreiding.
10 - Uitbreiden en afronden

Probeer deze vervolgstap pas als de basis werkt:

JSON ken je uit hoofdstuk 10: vraag AI om factuurgegevens uit een JSON-bestand te lezen en te controleren op ontbrekende velden. Laat bestandsnamen uit het factuurnummer opbouwen en voorkom het vervangen van een bestaande factuur.

Bewaar je werkende basisversie en verander één onderdeel tegelijk. Geef AI bij een fout de foutmelding, het relevante stukje code, je testinvoer en de verwachte uitkomst.

Het project is afgerond wanneer de basiscontrole uit blok 08 slaagt en je in eigen woorden kunt uitleggen welke invoer wordt gebruikt, hoe de verwerking werkt en waar de uitvoer terechtkomt.

Naslag: ReportLab: tabellen.