Note (2026-04-24): After this document was written, legal_entity was renamed to tenant and the old tenant was renamed to organization. Read references to these terms with the pre-rename meaning.

DATEV REWE Integration Design

Import historical journal entries from DATEV Rechnungswesen via Maesn API.

Context

These are separate integrations for separate personas. Tax advisors typically have REWE access while CFOs have UO access.

Requirements

Database Schema

New enum value

ALTER TYPE integration_type ADD VALUE 'datev_rewe';

New enum for booking history source

CREATE TYPE booking_history_source AS ENUM ('csv_upload', 'datev_rewe');
CREATE TABLE datev_rewe_link (
    id               UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    legal_entity_id  UUID NOT NULL REFERENCES legal_entity(id) ON DELETE CASCADE,
    token            TEXT NOT NULL UNIQUE,
    expires_at       TIMESTAMPTZ NOT NULL,
    created_by       UUID REFERENCES identity(id) ON DELETE SET NULL,
    used_at          TIMESTAMPTZ,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_datev_rewe_link_token ON datev_rewe_link(token);
CREATE INDEX idx_datev_rewe_link_legal_entity ON datev_rewe_link(legal_entity_id);

Extend booking_history_upload

ALTER TABLE booking_history_upload
    ADD COLUMN source booking_history_source NOT NULL DEFAULT 'csv_upload';

Connection storage

Stored in existing legal_entity_integration table:

  1. CFO (in settings) clicks "Generate DATEV REWE Link"
  2. Backend creates datev_rewe_link record:
  3. Returns URL: https://app.getorcha.com/connect/datev-rewe/{token}

Landing page (public, no auth, HTMX)

  1. Tax advisor visits link
  2. Backend validates token: exists, not used, not expired
  3. Shows minimal page: "Connect DATEV REWE for [Legal Entity Name]" with "Continue to DATEV" button
  4. If invalid/expired: show error message, no action possible

OAuth redirect

  1. Tax advisor clicks "Continue"
  2. Backend redirects to Maesn: https://api.maesn.dev/auth/datev-rewe-sandbox-longtoken?environmentSelection=true
  3. Tax advisor authenticates with DATEV credentials, selects company

OAuth callback

  1. Maesn redirects back with account key
  2. Backend validates state, retrieves link record
  3. If legal entity already has datev_rewe integration → replace (update credentials)
  4. Otherwise → create new legal_entity_integration record
  5. Mark link as used (used_at = now())
  6. Trigger immediate sync in async virtual thread
  7. HTMX partial update shows success message: "Connected successfully. You can close this window."

IMPORTANT (temporary): The sync is triggered directly in the ERP handler via virtual thread, not via SQS. This is intentional for V1 simplicity. Code should include a prominent TODO comment indicating this will be migrated to a queued process handled by workers in the future.

Data Sync

Initial sync (triggered on OAuth callback)

  1. Fetch company info from Maesn to get company_id
  2. Determine fiscal years: current year + previous year (e.g., 2026, 2025)
  3. For each fiscal year, paginate through GET /accounting/journalEntries:
  4. Create booking_history_upload record with source = 'datev_rewe', filename like "DATEV REWE Sync 2026-02-18"
  5. Map journal entry line items to booking_history_item (see mapping below)
  6. Update legal_entity_integration.config with {:last_sync_at ...}

Field mapping

Maesn journal entry field booking_history_item field
Line item description or parent description supplier_name, supplier_name_normalized
Line item description description, description_normalized
accountNumber (DEBIT indicator) debit_account
accountNumber (CREDIT indicator) credit_account
First dimension code cost_center
totalNetAmount net_amount

Normalized fields derived using same logic as CSV upload.

Monthly sync

Deferred to future work. When implemented:

UI Touchpoints

User settings (ERP)

New section in integrations: "DATEV REWE (Historical Data)"

States:

Disconnect button follows same pattern as existing DATEV UO disconnect (soft-deactivates integration).

Public pages (HTMX)

Error Handling

OAuth errors

Sync errors

Data mapping errors

Maesn API Reference

Authentication endpoint

GET https://api.maesn.dev/auth/datev-rewe-sandbox-longtoken?environmentSelection=true
Header: X-API-KEY: <api-key>

Get journal entries

GET https://api.maesn.dev/accounting/journalEntries
Headers:
  X-API-KEY: <api-key>
  X-ACCOUNT-KEY: <account-key>
Query params:
  fiscalYear (required for DATEV REWE)
  page, limit (pagination)
  lastModifiedAt (for incremental sync)

Response includes paginated journal entries with line items containing account numbers, amounts, dimensions (cost centers), tax info.

Implementation Notes