Billing
Das Billing-Modul deckt den Sales-to-Cash- und Procurement-Flow ab: Rechnungen, Bestellungen sowie die zugrunde liegenden Produktstammdaten. Positionen, Zahlungen, Korrekturen und Versand-Logs liegen direkt am jeweiligen Beleg-Datensatz als typisierte Felder — kein eigenes Sub-Resource- Schema, keine separaten CRUD-Endpoints.
Resources
| Resource | Pfad | Permission-Präfix | Sequenz |
|---|---|---|---|
| Rechnungen | /api/v1/invoices | invoice | INV- |
| Bestellungen | /api/v1/purchase_orders | purchase_order | PO- |
| Produkte | /api/v1/products | product | — |
| Produkt-Kategorien | /api/v1/product_categories | product_category | — |
Schemas
Invoice
Schema for validating Rechnung (invoice) entities
partyId
Customer party UUID
invoiceNumber
Auto-generated sequence number (e.g. RE-2026-00001)
invoiceDate
Invoice date
dueDate
Payment due date
status
Invoice lifecycle status. POSTED = verbucht / revisionssicher festgeschrieben (lockedAt set). SENT = an Kunden versandt. Beide sind immutable.
DRAFTPOSTEDSENTPAIDPARTIALLY_PAIDOVERDUECANCELLEDCREDITED salesInvoiceType
Type of sales invoice (mirrors weclapp salesInvoiceOrigin). ADVANCE_PAYMENT=Anzahlung, PART_PAYMENT=Teilrechnung, FINAL=Schlussrechnung, RECURRING=wiederkehrend
STANDARDADVANCE_PAYMENTPART_PAYMENTPREPAYMENTFINALRECURRINGRETAIL bookingDate
Buchungsdatum für DATEV (may differ from invoiceDate). Set on POSTED, drives the booking period.
lockedAt
When the invoice was locked (POSTED). After this the invoice is immutable except for status changes (PAID/OVERDUE/CANCELLED) and dunning fields.
lockedBy
User UUID who posted (locked) the invoice
contentHash
SHA-256 hash of canonical invoice content at lock time (GoBD revisionssicher). Verified on every read after POSTED.
recordAddressSnapshot
Frozen invoice (recipient) address snapshot taken at POSTED. GoBD requires the address as printed on the original document.
name
addressLine1
addressLine2
postalCode
city
state
countryCode
vatRegistrationNumber
deliveryAddressSnapshot
Frozen delivery address snapshot (if different from invoice address)
name
addressLine1
addressLine2
postalCode
city
state
countryCode
exchangeRate
Exchange rate to EUR at invoice date. Only set when currency != EUR. Used for DATEV export.
source
How the invoice was created
MANUALPERFORMANCE_RECORDSALES_ORDERCONTRACTWECLAPPCREDIT_NOTEWHOLESALE_LICENSING customerTenantId
Customer tenant this invoice was generated for (only set when source=WHOLESALE_LICENSING).
invoiceType
INVOICECREDIT_NOTE creditedInvoiceId
Original invoice ID (for credit notes)
contractId
Source contract (if generated from recurring billing)
dunningLevel
lastDunningAt
dunningBlockedUntil
Dunning suspended until this date
performanceRecordId
Source performance record UUID (if generated from Leistungsnachweis)
title
Invoice title / subject
introText
Introductory text above positions
closingText
Closing text below positions
notes
Internal notes (not printed)
openTicketNotes
ADR 0146 — Snapshot copied from the source Leistungsnachweis: tickets that were still open at billing time but were unlocked via `ticket.billingOverride`. The invoice PDF renders a neutral "Hinweise"-section listing the affected ticket numbers; the internal reason is intentionally NOT propagated here.
items
Invoice line items
position
description
unit
quantity
unitPrice
Decimal string, e.g. "125.00"
vatRate
VAT rate as percent string
netAmount
Computed: quantity * unitPrice
articleId
Article reference (local UUID or weclapp article id)
articleNumber
Article SKU / weclapp articleNumber
itemType
Categorizes the line item origin
SERVICETRAVEL_DISTANCETRAVEL_TIMETRAVEL_OTHERFLAT_RATETICKETMATERIAL materialBucketId
Source material bucket UUID (if generated from Materialkorb)
contractId
contractItemId
Which contract position generated this invoice line
timeRecordId
Source time_record UUID — set when this line was generated from a single booking (perEntry, ungrouped perTicket, or travel linked via trip.timeRecordId).
timeRecordNumber
Human-readable record number (e.g. TR-2026-00042) of the source time record. Snapshotted at build time so the UI can deep-link without an extra fetch.
ticketId
Source ticket UUID — set when this line aggregates time records of one ticket (perTicket) or originates from a TR with referenceType=TICKET.
accountCode
DATEV revenue account (Erlöskonto, e.g. "8400"). Falls back to article.revenueAccountCode at posting time.
costCenterId
Cost center UUID (Kostenstelle) for DATEV
costTypeId
Cost type UUID (Kostenträger) for DATEV
taxKey
DATEV Steuerschlüssel (e.g. "9" for 19% USt., "8" for 7%)
netAmount
Net total (excl. VAT) as decimal string
vatAmount
VAT total as decimal string
grossAmount
Gross total (incl. VAT) as decimal string
vatRate
Default VAT rate for this invoice
currency
paymentTermsDays
Payment terms in days (e.g. 14, 30)
pdfFileId
Reference to stored PDF file in files collection
weclappId
External weclapp sales invoice ID (if synced from weclapp)
weclappBaseUrl
Base URL of the weclapp tenant (e.g. https://demo.weclapp.com) for deep-linking
externalVersion
External system version (e.g. weclapp `version` field). Used to detect changes without re-downloading the PDF.
syncedAt
Timestamp of the last successful sync from the external system
customerPaymentReportedAt
Set when the customer pressed "Bezahlt gemeldet" in the portal. Does NOT change `status` — the MSP confirms separately. UI surfaces this as a yellow "Zahlung gemeldet" pill until the MSP transitions to PAID.
customerPaymentReportedBy
Customer portal user who reported the payment
customerPaymentReference
Optional reference text from the customer (e.g. SEPA reference, IBAN-Last4)
groupingMode
Grouping mode used when generating line items from performance record
onePositionperTicketperEntry servicePeriodFrom
Service period start (from performance record)
servicePeriodTo
Service period end (from performance record)
salesOrderId
Source sales order UUID (if generated from order)
proposalId
Source proposal UUID (if traceable to a proposal)
Keine Felder passen zum Filter.
Purchase-Order
Einkaufsbestellung an einen Lieferanten mit Status-Workflow und Wareneingang.
purchaseOrderNumber
Auto-generierte Bestellnummer (PO-{YYYY}-{NNNNN}).
supplierId
Lieferanten-Party-UUID (party.isSupplier=true).
assignedToId
Verantwortlicher Einkäufer (User-UUID).
orgUnitId
Verantwortliche Org-Einheit.
status
Workflow: DRAFT → ORDERED → (PARTIALLY_RECEIVED →) RECEIVED. CANCELLED ist aus DRAFT/ORDERED erreichbar.
DRAFTORDEREDPARTIALLY_RECEIVEDRECEIVEDCANCELLED orderDate
Bestelldatum (gesetzt bei Übergang nach ORDERED).
expectedDeliveryDate
Erwartetes Liefertermin.
actualDeliveryDate
Tatsächlicher (vollständiger) Wareneingang.
deliveryOrgUnitId
Eigene Niederlassung als Lieferadresse (Verweis auf org_unit).
deliveryAddress
Eingebettete Lieferadresse, falls keine org_unit-Referenz.
items
positionNumber
articleName
articleNumber
supplierArticleNumber
description
quantityOrdered
quantityReceived
Bisher gebuchte Wareneingangsmenge.
unitPrice
discountPercent
taxRate
Steuersatz in Prozent (z.B. "19").
totalNet
Computed: quantityOrdered × unitPrice × (1 − discountPercent/100).
currencyCode
totalNet
totalTax
totalGross
note
Externe Notiz an den Lieferanten.
internalNote
Interne Notiz (nicht Lieferanten-sichtbar).
cancelReason
confirmedAt
confirmedBy
cancelledAt
cancelledBy
_id
schema
purchase_order schemaVersion
tenantId
version
createdAt
updatedAt
createdBy
updatedBy
deletedAt
deletedBy
Keine Felder passen zum Filter.
Product
Schema for validating product entities (product portfolio items)
name
Product name
description
Product description (plain text). SSoT for the long marketing text shown in the customer-portal shop across all linked articles. See ADR 0094c.
descriptionHtml
Rich text HTML description — SSoT for the cluster-wide marketing description shown in the customer-portal shop for all linked articles. See ADR 0094c.
productCategoryId
Product category UUID
manufacturer
Manufacturer or vendor name (e.g. "Microsoft", "Acronis"). SSoT — articles fall back to this when their own manufacturer is empty; articles may override for OEM cases (see ADR 0094a).
website
Product or manufacturer website URL
logoS3Key
Product logo S3 key
imageFileIds
Image file IDs (resolved via /api/v1/files/{fileId}/download-url). SSoT for the cluster gallery — Index 0 is the main image. Articles may override per-variant via article.imageFileIds (see ADR 0094a).
attachmentFileIds
Loose non-image attachments — primary source for the customer-portal Downloads tab. Variant-specific attachments live on article.attachmentFileIds and appear in a separate section. For richer metadata (category, visibility, version) use the documents field instead. See ADR 0094a.
tags
Tags for categorization
isActive
Whether this product is active
sortOrder
Display sort order
internalNote
Internal note (not visible to customers)
matchingKeywords
Keywords for AI-based article-to-product matching (e.g. "m365", "office 365")
articleCount
Number of articles assigned to this product (cached)
infoPages
Product info pages with rich content for proposals and customer portal
title
slug
contentHtml
sortOrder
isPublished
documents
Datasheets, manuals, certificates etc. — reference files via fileId. SSoT for cluster-wide structured documents shown in the customer-portal Downloads tab. Richer per-file metadata than attachmentFileIds (category, visibility, version, download counter). Variant-specific files belong on article.attachmentFileIds. See ADR 0094a.
title
category
datasheetmanualsafetycertificatemarketingother visibility
internalcustomers version
language
sizeBytes
mimeType
downloads
uploadedBy
uploadedAt
pricingCalculatorConfig
Pricing calculator configuration
enabled
basisPrice
Listenpreis (Basis) for the calculator KPI strip
margin
Marge in percent (0–100) — informational only, not applied to formula
formula
Pricing formula expression (e.g. "basePrice * users")
tiers
Volume pricing tiers (legacy — see discountTiers for the simpler factor-based model)
media
Product media gallery (images, videos, documents, brochures)
type
IMAGEVIDEODOCUMENTBROCHURE title
url
s3Key
mimeType
thumbnailS3Key
sortOrder
isPublished
demoConfig
Demo booking configuration
testimonials
Customer testimonials / case studies
quote
authorName
authorTitle
authorCompany
logoS3Key
sortOrder
Keine Felder passen zum Filter.
Product-Category
Schema for validating product category entities
name
Category name
description
Category description
parentCategoryId
Parent category UUID for hierarchical nesting
icon
MUI icon name
color
Hex color code
sortOrder
Display sort order
isActive
Whether this category is active
Keine Felder passen zum Filter.
Custom-Endpoints
Über das generische CRUD hinaus gibt es spezialisierte Routen unter
/api/v1/billing/.... Die exakten Bodies und die vollständige Liste
sehen Sie in der OpenAPI-Spec.
Lifecycle
Rechnungen
draft → issued → (paid | overdue) → (closed | void)
draft lässt sich beliebig editieren. Ab issued sind Positionen schreib-
geschützt; Korrekturen laufen über typisierte Korrektur-Felder am Datensatz
oder über eine Storno-Rechnung.
Zahlungen
Zahlungseingänge werden als typisierte Liste am invoice-Datensatz gepflegt
(siehe Schema oben). Übersteigt die Summe der Zahlungen den
Rechnungsbetrag, antwortet die API mit 422.
Currency, Steuer
Beträge sind decimal (typischerweise zwei Nachkommastellen, je nach
Currency mehr). currency ist ISO-4217. Steuersätze hängen an der
Position, nicht am Dokument — eine Rechnung kann mehrere Steuersätze
nebeneinander tragen (7 %, 19 %, reverse charge).
Verwandt
- Konventionen
- Verträge — Rechnungen können Verträgen zugeordnet sein
- Webhook-Ingress — Stripe-Webhooks
- Konzept: Sequenznummern — Format der Nummern