sevDesk Integration

sevDesk is the external accounting system behind the Billing System. R1 creates and manages invoices internally; sevDesk handles the bookkeeping side — invoice publication, email dispatch, PDF rendering, payment state, and DATEV export. The integration is one-directional per concern: R1 pushes invoices out, and pulls payment status back on a schedule.

Architecture

ComponentPathPurpose
API clientbackend/src/services/sevdesk/client.tsTyped HTTP client for sevDesk REST API v1; retries with exponential backoff (max 3, 1–30 s)
Invoice bridgebackend/src/services/sevdesk/invoice-bridge.tsLifecycle orchestration: publish → finalize → PDF → email → payment sync → cancel → DATEV export; contains syncPaymentStatus()
Typesbackend/src/services/sevdesk/types.tssevDesk API interfaces (Contact, Invoice, Payment, DatevExport, …)
Service implbackend/src/services/billing/sevdesk-service-impl.tsDI-registered service consumed by routes
Routesbackend/src/routes/sevdesk.ts/api/sevdesk/* endpoints (see below)

The boundary inside billing: billing-bridge.ts handles internal task → invoice creation, while invoice-bridge.ts owns everything that crosses into sevDesk.

Configuration

  • SEVDESK_API_TOKEN — required secret (Infisical, per environment). Never persisted to DB and never sent to the client; the frontend only sees a tokenConfigured boolean. If the token is missing, the client lazy-init skips registering the payment-sync cron, but operator workflows keep working.
  • SEVDESK_BASE_URL — optional, defaults to the sevDesk API v1 base URL.

Operational settings live in the singleton table sevdesk_config: account name/ID (from the connection test), template mappings per deal type and document kind (templateEbAr, templateEbSr, templateKopAr, templateKopSr), paymentSyncIntervalMinutes (default 10), datevChartOfAccounts (SKR03/SKR04), autoTransferInvoices, isConnected, lastSyncAt, and connectedBy (FK → contacts.id, per the Human FK Rule). Template/config UI lives in the billing System tab.

API Routes

All routes require auth + internal-user RBAC, rate-limited at 20 req/60 s. Admin-only unless noted.

MethodPathPurpose
GET / PUT/api/sevdesk/configRead/update operational settings (token deliberately absent — env-only)
POST/api/sevdesk/test-connectionVerify API connectivity; updates sevdesk_config
GET/api/sevdesk/templatesList sevDesk templates for the mapping dropdowns
POST/api/sevdesk/invoices/:invoiceId/publishPublish R1 invoice to sevDesk (non-admin allowed)
POST/api/sevdesk/invoices/:invoiceId/sendSend invoice email via sevDesk (non-admin allowed)
POST/api/sevdesk/invoices/:invoiceId/cancelCancel invoice in sevDesk (non-admin allowed)
GET/api/sevdesk/invoices/:invoiceId/pdfDownload invoice PDF from sevDesk (non-admin allowed)
POST/api/sevdesk/sync/paymentsManual out-of-cycle payment sync
POST / GET/api/sevdesk/export/datev + /:jobId/status + /:jobId/downloadDATEV export batch: trigger, poll, download ZIP

Payment Sync (BullMQ)

Job sevdesk-payment-sync on the operations queue, scheduled every 10 minutes (*/10 * * * *), processed by processSevdeskPaymentSyncJob() in backend/src/lib/jobs/processors/billing-scheduled.ts, which calls syncPaymentStatus():

  1. Select invoices with accountingProvider = 'sevdesk', externalId set, documentStatus = 'sent', claimStatus != 'paid'.
  2. Fetch sevDesk payment state; compare paidAmount against grossAmount.
  3. Update claimStatus to paid (sevDesk status 1000 or fully paid; sets paidAt) or partially_paid.
  4. Log a payment_synced event to billing_invoice_event_log.

A manual trigger exists in the UI (payment-refresh button in the invoice detail panel) backed by useTriggerSevdeskPaymentSyncMutation().

billing_invoices carries the handoff state: accountingProvider, externalId (sevDesk invoice ID — gates the preview/download buttons in the UI), externalInvoiceNumber, accountingSyncStatus, accountingSyncedAt. billing_document_templates.sevdeskTemplateId maps R1 document templates to sevDesk templates. Invoice recipients resolve polymorphically (contact = natural person, company = legal entity) via the HubSpot association infrastructure with fallback to invoice.contactId.

Frontend

  • frontend/src/lib/queries/sevdeskPaymentSyncQueries.tsuseTriggerSevdeskPaymentSyncMutation() (invalidates the billing query tree on success).
  • frontend/src/lib/queries/billingQueries.tsuseSevdeskTemplatesQuery() (5-min stale time).
  • frontend/src/lib/api/sevdesk-settings.ts — config get/save, test-connection, templates, manual sync.
  • UI: BillingSettingsProvider.tsx (System tab: config, templates, connection test) and BillingSettingsApiMapping.tsx (R1 ↔ sevDesk field mapping display).

History: landed with the billing V3 rebase PR#1674 (foundation PR#1397), refined in PR#1751.

See Also