Markus Michalski FAQ

PDF Invoices: Technical Details

Technical details of the PDF Invoices module for OXID eShop 7.x: invoice creation flow, components, number assignment, storage, database tables and snapshot layout.

Deutsche Version

Back to overview

PDF Invoices: Technical Details

Live Demo: Try this and other plugins live — no installation, no risk: demo.markus-michalski.net

This documentation always describes the latest published version.

Invoice creation flow

The invoice is not triggered by an order event. It is created either when the order email is sent or manually in the admin area. Both paths call the same service (InvoiceService.createForOrder).

graph TD
    A["Mail path: order email, missing invoice is created automatically"] --> E["InvoiceService.createForOrder"]
    M["Admin path: Create Invoice (InvoiceController.createInvoice)"] --> E
    E --> N["Assign invoice number"]
    N --> S["Build snapshot (ExtendedOrder.createInvoiceSnapshot)"]
    S --> P["Render PDF (Twig template and Dompdf)"]
    P --> F["Store PDF and compute SHA-256"]
    F --> R["Save record in mmd_pdfinvoice"]

The order inside createForOrder is: check for an existing invoice, assign the number, build the snapshot, create the PDF, store the file, compute the hash, save the record. The number is therefore fixed before the PDF exists.

The admin path ends with the stored PDF. It attaches nothing to an order email and sets no sent status. Attaching and setting the sent status belong only to the mail path in ExtendedEmail.send:

graph TD
    A["Order email is sent (ExtendedEmail.send)"] --> B{"Attachment mode matches recipient?"}
    B -->|"No"| X["Email without invoice"]
    B -->|"Yes"| C{"Invoice exists for the order?"}
    C -->|"Yes"| G["Read PDF from storage and verify hash"]
    C -->|"No"| D{"Auto-generate enabled?"}
    D -->|"No"| X
    D -->|"Yes"| E["InvoiceService.createForOrder (see above)"]
    E --> G
    G --> H["Attach PDF to the email and set sent status"]

Components

Class Purpose
ExtendedEmail Extends Email, remembers order emails and attaches the PDF in send()
ExtendedOrder Extends Order, provides the invoice and builds the snapshot
InvoiceService Orchestrates creation, loading, hash verification and regeneration
InvoiceNumberGenerator Assigns invoice numbers through the table mmd_pdfinvoice_seq
PdfGenerator Prepares the template variables, renders the Twig template and creates the PDF with Dompdf
InvoiceStorage Writes, reads and deletes the PDF files
InvoiceRepository Reads and writes the table mmd_pdfinvoice
InvoiceConfiguration Reads the module settings
DebugLogger Writes debug messages only when debug logging is enabled
InvoiceController Admin controller mmd_pdfinvoice_admin with the actions createInvoice, downloadPdf, sendInvoiceEmail, regeneratePdf
ModuleEvents Creates tables and directory on activation

Module data

Property Value
Module ID mmd_pdfinvoice
Composer package mmd/oxid7-pdf-invoice
Install directory source/modules/mmd/pdf-invoice
Admin controller mmd_pdfinvoice_admin
Admin tab tbclorder_pdfinvoice (Orders > Order)
Templates @mmd_pdfinvoice/pdf/invoice, @mmd_pdfinvoice/admin/invoice_main
Events onActivate (create tables and directory), onDeactivate (deletes nothing)

Invoice numbering

Format: {prefix}{year}-{number} with a five-digit number with leading zeros, for example RE-2026-00001.

The admin area shows the setting "Reset Numbering Yearly" (mmdPdfInvoiceResetYearly). It has no effect in the code. Numbering always runs per shop and year, whether the option is switched on or off.

PDF file storage

Path: {storage path}/{shop ID}/{year}/{month}/{invoice number}.pdf, relative to the shop directory. In the invoice number every character except letters, digits, - and _ is replaced by _. Year and month come from the invoice date.

On reading, the module compares the file's SHA-256 hash with the value stored in PDF_HASH. If it differs, the file is not delivered until the PDF has been regenerated.

Dompdf keeps its font cache in source/tmp/dompdf. The PDF is created in A4 portrait with the default font DejaVu Sans.

Database

mmd_pdfinvoice

Column Description
OXID Primary key
OXORDERID Order ID
OXSHOPID Shop ID
INVOICE_NUMBER Formatted invoice number
INVOICE_DATE Invoice date
PDF_PATH Relative path of the PDF file
PDF_HASH SHA-256 hash of the PDF file
SENT_TO_CUSTOMER Flag: sent to customer
SENT_AT_CUSTOMER Time of sending to the customer
SENT_TO_OWNER Flag: sent to shop owner
SENT_AT_OWNER Time of sending to the shop owner
SNAPSHOT_DATA JSON snapshot of the order data
CREATED_AT Time of creation
OXTIMESTAMP Timestamp of the last change (automatic)

Unique keys: OXORDERID + OXSHOPID (one invoice per order and shop) and INVOICE_NUMBER + OXSHOPID (one invoice number per shop).

mmd_pdfinvoice_seq

Column Description
OXSHOPID Shop ID (part of the primary key)
YEAR Year (part of the primary key)
CURRENT_NUMBER Last assigned number
PREFIX Prefix at the time the row was created

The PREFIX column is filled when the row is created. The number itself is always built with the current setting.

Data snapshot

When the invoice is created, the module stores the snapshot in SNAPSHOT_DATA. It is built from the order and contains:

Key Content
version Format version of the snapshot, currently the value 1.0
created_at Time of creation
order Order number, order date, shipping date, tracking code, currency, rate, order remark
billing Billing address including phone, fax, email and VAT ID
delivery Delivery address if the order has a delivery last name stored, otherwise null (no comparison with the billing address)
items Items with article number, title, quantity, prices, VAT and weight
totals Net, gross and total sums, shipping, payment, wrapping and gift card costs, discount, VAT breakdown
payment Payment ID, name and, for direct debit, the bank details

The shop master data and the logo are not part of the snapshot. When a PDF is regenerated they are read as they are now.

Behavior on errors