Datevz for Craft CMS

Usage

Preview first, always

Datevz → Export, pick a period, press Preview. Nothing is written.

You get every posting the batch would contain — account, contra account, Soll/Haben, gross, BU key, rate, document number — grouped by order, with the VAT treatment named on each one. Under each order sit the problems Datevz found on it.

The preview and the export run the same code. There is no second implementation that could drift, so what you read is what the file contains.

What a posting looks like

One order, €100 of goods at 19%, exported:

119,00;"S";"EUR";;;;1400;8400;;1503;"RE-1042";"8556";;"Shop RE-1042";…

Gross against the debtor in Soll, revenue in Gegenkonto, Belegdatum as TTMM, the invoice number in Belegfeld 1.

The direction is never in the amount. Umsatz is always positive and unsigned; a credit is a posting with H in the Soll/Haben column. A negative Umsatz is a rejected import, not a refund.

Because the debtor is always Konto on an invoice posting, the batch reconciles against the order total by addition — which is exactly the check Datevz runs before it will write anything.

Creating the batch

When the preview is clean, Create the batch. If it is not clean, the button is still there but you have to tick Export anyway — and that decision is written to the log with the problems it overrode.

Every batch is stored whole, with a sha256 of the delivered file. Months later you can prove that the copy the practice holds is the copy Datevz produced.

An order that has been exported is left out of the next batch. Delete a batch and its orders are released again — the archive is the record of what went, so deleting it is the only honest way to say "that never happened".

Debtor master data

If you allocate a personal account per customer, Datevz → Debtors lists every number issued and exports them as a Debitoren/Kreditoren file for the practice to import.

Numbers are allocated once and never reused, even if the customer is deleted. A personal account number that comes back under a different name turns a year of bookkeeping into a puzzle.

From the command line

php craft datevz/export/run --from=2026-01-01 --to=2026-01-31
php craft datevz/export/run --from=2026-01-01 --to=2026-01-31 --dryRun
php craft datevz/export/run --from=2026-01-01 --to=2026-01-31 --path=/tmp/january.csv
php craft datevz/export/list
php craft datevz/debtors/export --path=/tmp/debtors.csv
php craft datevz/debtors/list
php craft datevz/log/prune

A dry run exits non-zero when it finds errors, so a monthly cron job fails loudly instead of mailing a broken batch:

0 6 1 * * cd /var/www/site && php craft datevz/export/run \
    --from="$(date -d 'last month' +%Y-%m-01)" \
    --to="$(date -d 'last month' +%Y-%m-%d -d "$(date +%Y-%m-01) -1 day")" \
    --path=/var/backups/datev/$(date +%Y-%m).csv

Plugin console commands are not listed by craft help datevz — they appear under craft help.

On the order screen

Commerce's own order edit screen grows a DATEV panel: the treatment, the debtor account, the postings that order produces, and whether it has been exported — with a link to the batch it went in and a warning if it has gone out more than once.

It is built live, so it reflects your current settings rather than what was true when the order was placed.

Twig

{% set state = craft.datevz.orderState(order) %}
{% if craft.datevz.isExported(order) %}
    Booked on {{ state.dateExported|date('d.m.Y') }} in batch {{ state.batchId }}
{% endif %}

{% set set = craft.datevz.postings(order) %}
{{ craft.datevz.treatmentLabel(set.treatment) }}

{% for posting in set.postings %}
    {{ posting.account }} / {{ posting.contraAccount }}
    {{ posting.debitCredit }} {{ posting.amount }}
{% endfor %}

Nothing in the Twig API writes. Rendering a template is not a place to allocate a debtor number or mark an order exported.