BR-O-01Out-of-scope supplies are on the invoice but there is no out-of-scope VAT breakdown
Mutabiq checks Saudi ZATCA Phase 2 e-invoice XML before you submit it: pre-validation against published ZATCA requirements, not an approval by the authority. This page explains one rule, BR-O-01.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[BR-O-01]-An Invoice that contains an Invoice line (BG-25), a Document level allowance (BG-20) or a Document level charge (BG-21) where the VAT category code (BT-151 or BT-95 or BT-102) is "Not subject to VAT" shall contain exactly at least one VAT breakdown group (BG-23) with the VAT category code (BT-118) equal to "Not subject to VAT".
What Mutabiq reports
One entry for each way this rule can be reported.
Reported as
What kind of value was found — empty, not_15_digits and the like — never the value itself.
Out-of-scope supplies are on the invoice but there is no out-of-scope VAT breakdown
Reported asbreakdown_missingthe VAT breakdown is missing
What is wrong
The out-of-scope category code O is used in the document under the VAT tax scheme - on an invoice line's cac:ClassifiedTaxCategory, or on the cac:TaxCategory of an allowance or a charge - and there is no cac:TaxSubtotal for it directly under the invoice's own cac:TaxTotal. EN 16931 rule BR-O-01, which ZATCA runs, requires at least one out-of-scope breakdown on any invoice that uses the category. SDK 3.4.8 flags it as a warning, not a rejection: the finding is reported without failing the document by itself (EN:800). The count is of breakdowns directly under the document root, so a cac:TaxSubtotal written inside an invoice line does not satisfy it. SDK 3.0.8 asked for exactly one breakdown, at error, and counted a breakdown's own category as a use of the category; SDK 3.4.8 asks for at least one, and only a line, an allowance or a charge in the category triggers the rule.
What to check in your ERP
The VAT breakdown needs one cac:TaxSubtotal per category used, and an out-of-scope category still needs its block: cbc:TaxableAmount, a cbc:TaxAmount of zero, the category code O and an exemption reason. Under rule packs before 2.0.0 the out-of-scope category was difficult to use at all on a Saudi invoice, because EN 16931 rule BR-O-02 forbade a seller VAT registration number on any invoice carrying an out-of-scope line while the Kingdom's own rule BR-KSA-39 requires one; SDK 3.4.8 removed BR-O-02. If the supply is untaxed rather than genuinely outside VAT, the zero-rated (Z) or exempt (E) category is very likely the right one.
More than one out-of-scope VAT breakdown on the same invoice
Reported asbreakdown_duplicatedthe same VAT breakdown appears more than once
What is wrong
The invoice carries more than one cac:TaxSubtotal coded O - out of scope - under the VAT tax scheme directly under its own cac:TaxTotal. Only rule packs before 2.0.0 report this: SDK 3.0.8's BR-O-01 required exactly one breakdown per category, and SDK 3.4.8, which rule pack 2.0.0 reads, requires at least one (EN:800), so a report produced under 2.0.0 never carries this note. EN 16931 still describes the VAT breakdown as one block per category.
What to check in your ERP
Nothing needs changing for rule pack 2.0.0 on this account. If you do merge the blocks, group by the category code alone and sum the taxable amounts into one cac:TaxSubtotal, and watch for a duplicated document-level cac:TaxTotal, which produces two blocks even when each is internally correct.
The clause that runs
Quoted from the validation-rule stylesheet ZATCA publishes, at the file and line named below. The executed guard is what the check runs; the reported test is what it prints when it fails, and the two are not always the same.
Executed guard
not(((exists(//cac:AllowanceCharge/cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']) or exists(//cac:ClassifiedTaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O'])) and (count(cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']) > 0)) or (not(//cac:AllowanceCharge/cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']) and not(//cac:ClassifiedTaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O'])))
Reported test
((exists(//cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']) or exists(//cac:ClassifiedTaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O'])) and (count(cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']) = 1)) or (not(//cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']) and not(//cac:ClassifiedTaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'O']))
Evaluated against
/ubl:Invoice | /cn:CreditNote
Source
vendor/zatca/schematron/CEN-EN16931-UBL.xsl:800
As shipped in ZATCA SDK 3.4.8: the copy quoted here is, byte for byte, the file that release ships. The date in the file name is the name ZATCA gives the file; the rules in it are those of SDK 3.4.8.
A worked example
A synthetic document from the measured test corpus that trips this rule, shown where it differs from one that passed. Mutabiq's report names the class of the value, never the value itself.