Automation MCP Server Features Blog Pricing Contact
Validation rule XRechnung CIUS

BR-DE-18

German cash discounts travel as text with machine-grade formatting rules: BR-DE-18 requires every Skonto entry in the payment terms (BT-20) to match the strict #SKONTO#TAGE=n#PROZENT=n.nn# pattern, upper case, hash-delimited, line break at the end. Free-text Skonto wording fails.

What BR-DE-18 checks

BR-DE-18 validates the format of Skonto (cash discount) entries in the payment terms, BT-20. Any line in the payment terms note that begins with # is treated as a Skonto declaration and must match the KoSIT pattern exactly: #SKONTO#TAGE=n#PROZENT=n.nn#, optionally extended by #BASISBETRAG=n.nn# when the discount applies to a partial base, written in upper case, ending with a line break, with no extra whitespace. The receiving portals parse these entries to schedule discounted payment, which is why "2% Skonto bei Zahlung binnen 14 Tagen" as prose satisfies a human but not the rule's regular expression.

Layer and formats

The finding carries "layer": "cius" and fires when validating against the XRechnung profile, in UBL and CII alike, including hybrids declaring the XRechnung CustomizationID. It is a fatal rule: a malformed Skonto line fails validation outright.

Why it fails in practice

  • Free-text discount wording. The ERP's payment terms text mentions Skonto in prose, or mixes prose onto the same line as a structured entry.
  • Format drift. Lower-case #skonto#, a comma instead of the decimal dot, one decimal instead of two, a missing trailing #, or a missing final line break.
  • Multiple entries glued together. Staggered terms written on one line instead of one entry per line, each closed by a line break.

How to fix it

Emit each Skonto tier as its own strictly formatted line in the first payment terms note:

<cac:PaymentTerms>
  <cbc:Note>#SKONTO#TAGE=14#PROZENT=2.00#
#SKONTO#TAGE=30#PROZENT=1.00#
</cbc:Note>
</cac:PaymentTerms>

In an InvoiceXML create request the value is invoice.paymentDetails.paymentTerms; put the formatted entries there, one per line. Keep any human-readable terms in separate lines that do not start with #.

The raw rejection vs the InvoiceXML finding

The Schematron report answers with a regular expression and a paragraph of German formatting law (abbreviated here; the original runs well past a hundred words):

<svrl:failed-assert id="BR-DE-18" flag="fatal"
    location="/*:Invoice[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'][1]">
  <svrl:text>[BR-DE-18] Skonto Zeilen in ubl:Invoice müssen diesem
    regulärem Ausdruck entsprechen: #(SKONTO)#TAGE=([0-9]+
    #PROZENT=[0-9]+\.[0-9]{2})(#BASISBETRAG=-?[0-9]+\.[0-9]{2})?#$ ...
    Die Informationen zur Gewährung von Skonto müssen wie folgt im
    Element "Payment terms" (BT-20) übermittelt werden: Anzugeben ist
    im ersten Segment "SKONTO", im zweiten "TAGE=n", im dritten
    "PROZENT=n". ... Alle Angaben zur Gewährung von Skonto müssen in
    Großbuchstaben gemacht werden.</svrl:text>
</svrl:failed-assert>

The InvoiceXML finding compresses that into one actionable sentence:

{
  "rule": "BR-DE-18",
  "layer": "cius",
  "line": null,
  "message": "A Skonto (cash discount) entry in the payment terms does not match the required XRechnung format. Each entry must be an upper-case line like #SKONTO#TAGE=n#PROZENT=n.nn#, ending with a line break.",
  "btCodes": ["BT-20"],
  "fields": ["paymentDetails.paymentTerms"],
  "raw": "[BR-DE-18] CIUS: Skonto Zeilen in ubl:Invoice müssen diesem regulärem Ausdruck entsprechen: ..."
}

Your end customer sees the expected shape immediately, your UI highlights the payment terms input via fields, and the full German original stays in raw for the curious. Clients localize by the stable rule key.

Check or generate XRechnung documents

Validate Skonto-carrying documents in the XRechnung validator, or generate with POST /v1/create/xrechnung and pass the formatted entries in paymentDetails.paymentTerms; the document is validated against the full KoSIT rule set before delivery. The XRechnung API toolkit covers the wider integration.

Frequently asked questions

What does a valid Skonto entry look like?

#SKONTO#TAGE=14#PROZENT=2.00# followed by a line break: 2% discount when paid within 14 days. Percentages carry exactly two decimals with a dot. If the discount base is only part of the amount due, a fourth segment #BASISBETRAG=n.nn# names it. Several entries, one per line, express staggered terms.

Why does XRechnung encode discounts in text at all?

EN 16931's core model has no structured cash-discount fields, and Germany's B2G processes need machine-readable Skonto. The compromise is a rigid micro-format inside the existing payment terms text (BT-20): human-readable, yet parseable by the receiving portals. BR-DE-18 polices exactly that parseability.

Does BR-DE-18 fire when I have no Skonto?

Only lines starting with # are checked. Ordinary prose payment terms ("Zahlbar innerhalb 30 Tagen") pass untouched. The rule bites when something looks like a Skonto entry but deviates: lower case, missing segments, wrong decimals, missing final hash or line break.

Where do the payment terms live in the XML?

BT-20, cac:PaymentTerms/cbc:Note in UBL and ram:SpecifiedTradePaymentTerms/ram:Description in CII. All Skonto entries belong in the first payment terms note, each on its own line.