Rule reference

BR-DE-18 A line of the payment terms (BT-20) starts with "#", which marks it as a Skonto (early-payment discount) entry, but "#SKONTO#TAGE=14#PROZENT=2#" does not match the grammar XRechnung prescribes

Line 1 of the payment terms (BT-20) starts with "#", which marks it as a Skonto (early-payment discount) entry, but "#SKONTO#TAGE=14#PROZENT=2#" does not match the grammar XRechnung prescribes. The format is rigid on purpose — it is the one place the standard turns free-text payment terms into something machine-readable — and it is: #SKONTO#TAGE=n#PROZENT=n.nn# with an optional fourth segment #BASISBETRAG=n.nn# before the closing #. Every segment is separated and terminated by "#", everything is upper case, the percentage carries exactly two decimals with a dot (never a comma) and no sign, and no whitespace of any kind is allowed inside the entry.

This rule is implemented and its error payload below is real, but the long-form write-up — normative text, worked example, divergence note — is not written yet. Everything the library knows about this rule is on this page. Ask and we will prioritise it.

Business term
BT-20
Severity
fatal

What the library returns

The exact object in result.errors when this rule fires. Generated by running @attestwire/en16931, not transcribed:

TeachingError
{
  "rule": "BR-DE-18",
  "field": "BT-20",
  "severity": "fatal",
  "message": "Line 1 of the payment terms (BT-20) starts with \"#\", which marks it as a Skonto (early-payment discount) entry, but \"#SKONTO#TAGE=14#PROZENT=2#\" does not match the grammar XRechnung prescribes. The format is rigid on purpose — it is the one place the standard turns free-text payment terms into something machine-readable — and it is: #SKONTO#TAGE=n#PROZENT=n.nn# with an optional fourth segment #BASISBETRAG=n.nn# before the closing #. Every segment is separated and terminated by \"#\", everything is upper case, the percentage carries exactly two decimals with a dot (never a comma) and no sign, and no whitespace of any kind is allowed inside the entry.",
  "fix": "Write each discount as its own line, e.g. \"#SKONTO#TAGE=14#PROZENT=2.00#\". Add the base amount only when the discount applies to part of the invoice rather than to the full amount due (BT-115): \"#SKONTO#TAGE=14#PROZENT=2.00#BASISBETRAG=1000.00#\". Each entry must end with a newline. Human-readable prose is fine in BT-20 as well — it just must not start a line with \"#\".",
  "example": "\"paymentTerms\": \"Zahlbar innerhalb von 30 Tagen.\\n#SKONTO#TAGE=14#PROZENT=2.00#\\n\"",
  "xpath": "/ubl:Invoice/cac:PaymentTerms/cbc:Note",
  "docsUrl": "https://attestwire.com/rules/BR-DE-18"
}

xpath locates the element in the generated UBL document, which is where a KoSIT or Peppol validator will report the same problem.

A passing value

the shape this field expects
"paymentTerms": "Zahlbar innerhalb von 30 Tagen.\n#SKONTO#TAGE=14#PROZENT=2.00#\n"

The fix

Write each discount as its own line, e.g. "#SKONTO#TAGE=14#PROZENT=2.00#". Add the base amount only when the discount applies to part of the invoice rather than to the full amount due (BT-115): "#SKONTO#TAGE=14#PROZENT=2.00#BASISBETRAG=1000.00#". Each entry must end with a newline. Human-readable prose is fine in BT-20 as well — it just must not start a line with "#".

Arrived from a stack trace? The docsUrl on every error links straight here. Something wrong on this page — tell us.