# DATEV payroll export (LODAS / Lohn und Gehalt)

> Produce DATEV LODAS or Lohn und Gehalt movement data (Bewegungsdaten) from approved TimeTracking hours, with an export mapping plus a small post-processing step.

URL: https://timetracking.tech/developers/integrations/payroll/datev
Last updated: 2026-10-24

## 1. Summary

| | |
|---|---|
| What syncs | Approved hours per hour type (Lohnart), allowances, approved expenses, approved leave |
| Direction | TimeTracking → DATEV LODAS or DATEV Lohn und Gehalt (usually via the tax adviser / Steuerberater) |
| Mechanism | Export mapping (`format: "txt"`, `;`, decimal comma) + post-processing (ANSI encoding, LODAS header block) → ASCII import |
| Trigger | Monthly payroll |

**Status:** Recipe — works today with a custom mapping and a post-processing script. No native DATEV connector, and TimeTracking cannot yet write the DATEV header block or ANSI encoding by itself.

## 2. Mechanism

1. Use the German rule pack `de-basic` (ArbZG breaks, 11 h rest, daily overtime after 8 h → `overtime_125`, night 23:00–06:00 → `night`, Sunday → `sunday`, public holidays → `holiday`) or your own pack aligned with the Tarifvertrag.
2. A custom export mapping writes one line per employee per Lohnart per month.
3. The committed file is downloaded with `GET /exports/runs/{id}/file`, which delivers a `windows-1252` mapping as Windows-1252 (ANSI). For LODAS, a small post-processor prepends the required header sections.
4. The tax adviser or payroll office imports the file in LODAS or Lohn und Gehalt (ASCII import of Bewegungsdaten).

Assumption — DATEV LODAS ASCII import files consist of sections such as `[Allgemein]` (Ziel, Version, Berater-/Mandantennummer, Abrechnungszeitraum), `[Satzbeschreibung]` (record layout definition) and `[Bewegungsdaten]` (data lines), in ANSI encoding with `;` separators and decimal comma. Lohn und Gehalt uses a similar ASCII import with a different record definition. The exact record numbers and field lists must be agreed with the tax adviser; verify with your DATEV consultant / Steuerberater.

## 3. Prerequisites

- TimeTracking admin; API key with `write` scope.
- Org country `DE` (public holidays) and rule pack `de-basic` or a custom pack.
- `employee_number` = DATEV Personalnummer (numeric, up to 5 digits in many setups — assumption, verify).
- DATEV Beraternummer, Mandantennummer and the Lohnart numbers per hour type.

## 4. Entity mapping

| DATEV field | TimeTracking | Notes / external ID |
|---|---|---|
| Beraternummer / Mandantennummer | Not in TimeTracking | Header block (post-processing). |
| Abrechnungszeitraum | Export range | Header block, e.g. `01/2027`. |
| Personalnummer | `employee_number` → `employeeNumber` | Only stable key today. |
| Lohnart | `hourType` via `map` | `normal`, `overtime_125`, `night`, `sunday`, `holiday`, `leave_*`, `expense_*`. |
| Stunden | `minutes` → `hours_decimal` with `,` | |
| Betrag | `amountCents` → `cents_decimal` with `,` | Expenses, allowances. |
| Kostenstelle | `placementRef` or `clientName` | Optional. |
| Tage (days) | Not available | PayLine has no day count. |

External IDs: upsert people, clients and placements idempotently with `PUT /{people|clients|placements}/by-external/{source}/{ref}` (or `POST /…/batch` with `externalSource` + `externalRef` per row, up to 500 rows). The response includes `external_source`, `external_ref` and `created: true|false`. Use for example source `datev` with the Personalnummer as `ref`.

## 5. Step-by-step setup

```sh
export TT=https://app.timetracking.tech/api/v1
export KEY=tt_live_…        # write scope
```

1. Select rule pack `de-basic` in **Regels & koppelingen** (admin).

2. Create the mapping yourself (admin, dashboard): open the export mappings screen (`/export/mappings`, linked from **Export**), copy `preset-generic` or `preset-sdworx`, edit columns, code tables and validation, and optionally set it as the org default. Saved mappings are validated; max 50 per org. The mapping id is the UUID in its URL (`/export/mappings/<uuid>`): pass it as `mappingId` to `POST /exports` and `tt export --mapping`. Example JSON for the editor (assumed layout — verify with your DATEV / Steuerberater consultant):

```json
{
  "id": "datev-lodas",
  "name": "DATEV LODAS — Bewegungsdaten",
  "format": "txt",
  "delimiter": ";",
  "includeHeader": false,
  "encoding": "windows-1252",
  "lineEnding": "crlf",
  "groupBy": ["employeeNumber", "hourType"],
  "columns": [
    { "header": "Personalnummer", "source": "employeeNumber" },
    { "header": "Lohnart", "source": "hourType", "transform": { "kind": "map", "table": "lohnart" } },
    { "header": "Stunden", "source": "minutes", "transform": { "kind": "hours_decimal", "decimals": 2, "decimalSeparator": "," } },
    { "header": "Betrag", "source": "amountCents", "transform": { "kind": "cents_decimal", "decimalSeparator": "," } },
    { "header": "Kostenstelle", "source": "placementRef" }
  ],
  "tables": { "lohnart": { "normal": "100", "overtime_125": "200", "night": "300", "sunday": "310", "holiday": "320",
                           "leave_vacation": "400", "leave_sick": "410", "expense_km": "500" } },
  "validation": [
    { "column": "Personalnummer", "required": true, "pattern": "^\\d{1,5}$" },
    { "column": "Lohnart", "required": true }
  ]
}
```

Lohnart numbers are placeholders.

3. Preview, then commit the month:

```sh
tt export --from 2027-01-01 --to 2027-01-31 --mapping datev-lodas
tt export --from 2027-01-01 --to 2027-01-31 --mapping datev-lodas --commit -o datev-commit.txt   # UTF-8 copy
```

Then download the ANSI file of that run (run id from the commit output, `GET /exports/runs` or the `export.completed` webhook):

```sh
curl -s "$TT/exports/runs/<runId>/file" -H "Authorization: Bearer $KEY" -o datev-body.txt   # charset=windows-1252
```

4. Post-process: prepend the header block (plain ASCII, so the file stays Windows-1252). Example (header content is an assumption — get the real one from your Steuerberater):

```sh
{
  printf '[Allgemein]\r\nZiel=LODAS\r\nVersion_SST=1.0\r\nBeraterNr=12345\r\nMandantenNr=99999\r\nAbrechnungszeitraum=01/2027\r\n\r\n'
  printf '[Satzbeschreibung]\r\n1;u_lod_bwd_buchung_standard;pnr#bwd;la_eigene#bwd;bs_wert_butab#bwd;kostenstelle#bwd;\r\n\r\n'
  printf '[Bewegungsdaten]\r\n'
  LC_ALL=C sed 's/^/1;/' datev-body.txt
} > datev-lodas-2027-01.txt
```

If you start from the UTF-8 commit copy instead, pipe the result through `iconv -f UTF-8 -t WINDOWS-1252`. The `sed` prefixes each line with the record number defined in `[Satzbeschreibung]`. Field codes in the example are illustrative.

5. Send `datev-lodas-2027-01.txt` to the tax adviser or import it yourself. An admin locks January in the dashboard after payroll is final.

## 6. Sync schedule and triggering events

- Monthly, after all weeks of the month are closed. The dashboard works per week; the API accepts the month range.
- `export.completed` `{runId,startsOn,endsOn,rows,mapping}` and `period.locked` `{startsOn,endsOn,kind:"month"}` (admin creates webhooks). A receiver can download the committed body with `GET /exports/runs/{runId}/file`, run the post-processor and drop the result for the Steuerberater.
- Committed files stay retrievable: `GET /exports/runs` (paginated `{items, next_cursor}`), `GET /exports/runs/{id}` (metadata, `mapping_snapshot`, `pay_lines`, `warnings`) and `GET /exports/runs/{id}/file` (the exact committed file, `text/csv`). A read-only key is enough.

## 7. Corrections, conflicts and locked periods

- Only approved, conflict-free days are exported; `pendingCount` shows the rest.
- Exported/locked days are immutable (`423`). Book corrections in the current month; DATEV receives them in the next file ("Korrektur im Folgemonat"). Retroactive DATEV corrections (Rückrechnung) are done in DATEV.

## 8. Testing checklist

- [ ] File opens in a text editor as ANSI; umlauts (ä, ö, ü, ß) display correctly; no `?` where a name or note had a character outside Latin-1.
- [ ] Header values (Berater/Mandant/period) confirmed by the Steuerberater.
- [ ] Test import into a DATEV test client (Testmandant) succeeds.
- [ ] Night and Sunday hours appear as separate Lohnarten for a test week.
- [ ] `break_too_short` warnings reviewed (ArbZG §4).

## 9. Troubleshooting

| HTTP / code | Message | Fix |
|---|---|---|
| 403 `forbidden` | API key is read-only | `write` key. |
| 404 `not_found` | Export mapping not found | Check mapping id. |
| 409 `conflict` | This period was just exported; refresh before exporting again | Avoid double commit. |
| 409 `conflict` | N day(s) in this period are not exported yet | Lock refused; export remaining days. |
| 422 `invalid` | Export has N validation issue(s) | Personalnummer too long / non-numeric, unmapped Lohnart. |
| 422 `invalid` | Nothing approved to export in this period | Approve first. |
| 423 `locked` | This day is exported or locked; book a correction in an open period | Correct in an open month. |
| DATEV import error on characters | — | You used the UTF-8 commit `content` or dashboard download: use `GET /exports/runs/{id}/file` or run `iconv`. |

## 10. Limits and known gaps

- No header/trailer sections in mappings; the LODAS header block needs post-processing.
- No record-number prefix column besides `const:` (you can add `{ "header": "Satz", "source": "const:1" }` as first column instead of `sed`).
- No day-count (Tage) values; only hours and amounts.
- `groupBy` keeps the first line's date (irrelevant for monthly Bewegungsdaten without a date column).

- Encoding: a mapping with `encoding: "windows-1252"` is delivered as Windows-1252 (Latin-1) only by `GET /exports/runs/{id}/file` (`content-type: text/csv; charset=windows-1252`, no BOM). Characters outside Latin-1 (€, curly quotes, em dash) become `?`. The preview/commit JSON `content` and the dashboard download stay UTF-8.

> **Planned (not in API v1.1)** — export-mapping CRUD via the API.

## 11. Build it with Claude

```text
Help me produce a DATEV LODAS import from TimeTracking.
1. With the TimeTracking MCP tools, run export_preview for 2027-01-01..2027-01-31 (mappingId "preset-generic") and list
   distinct hour types and employees whose employee number is not 1–5 digits.
2. Draft a datev-lodas ExportMapping JSON (see docs/developers/data-out/export-mappings.md) with a lohnart table
   (TODO numbers) and a shell post-processor that adds the [Allgemein]/[Satzbeschreibung]/[Bewegungsdaten] header and
   relies on GET /api/v1/exports/runs/{id}/file for the WINDOWS-1252 bytes. Mark every DATEV field as "verify with Steuerberater".
3. Do not commit or lock anything.
```
