BR-KSA-DEC-02VAT rate is below zero or above one hundred
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-KSA-DEC-02.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[BR-KSA-DEC-02]-The VAT rates (BT-96, BT-103, BT-119, BT-152, KSA-34) must be from 0.00 to 100.00, with maximum two decimals.Only numerals are accepted, the percentage symbol (%) is not allowed.
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.
VAT rate is below zero or above one hundred
Reported asrate_out_of_rangethe rate is below zero or above one hundred
What is wrong
A cbc:Percent read by this rule holds a value outside the range from zero to one hundred inclusive. ZATCA's rule BR-KSA-DEC-02 requires every VAT rate it reads to be within that range and to carry no more than two decimal places. The rule reads two places in the document: an invoice line's cac:Item/cac:ClassifiedTaxCategory/cbc:Percent and an allowance's cac:TaxCategory/cbc:Percent. It does not read the VAT breakdown's rate - the branch of ZATCA's own expression that would have done so asks for a tax category inside cbc:TaxAmount, and cbc:TaxAmount holds a number and can have no elements inside it, so no valid document can match it. That means a breakdown's rate is genuinely unconstrained here, and Mutabiq mirrors the rule as it is written rather than as it appears to be meant. Note also that the allowance branch has no charge indicator condition on it, so a charge's rate is read as well as a discount's.
What to check in your ERP
A rate outside this range is almost always a units problem rather than a tax problem. Look for a rate held as a fraction and then multiplied - 0.15 becoming 15 in one place and staying 0.15 in another is the common shape, and a negative value usually comes from a credit note where the sign was applied to the rate instead of to the amounts. A rate above one hundred points to a value that is not a rate at all, such as an amount or a quantity written into the rate field by a mis-aligned mapping.
VAT rate is written with more than two decimal places
Reported astoo_many_decimalsmore decimal places than allowed
What is wrong
A cbc:Percent read by this rule is written with more than two digits after the decimal point. ZATCA's rule BR-KSA-DEC-02 counts the digits as they appear in the file rather than judging the value, so a rate written with three decimals fails even when the third digit is a nought and the value itself is an ordinary one. The rule reads an invoice line's cac:Item/cac:ClassifiedTaxCategory/cbc:Percent and an allowance's cac:TaxCategory/cbc:Percent. It does not read the VAT breakdown's rate, because the branch of ZATCA's expression that would have done so asks for a tax category inside cbc:TaxAmount, which holds a number and can have no elements inside it - so a breakdown rate with three decimals is not reported by this rule, and Mutabiq mirrors that rather than tidying it. The rule says nothing about how you round, only about how many digits you print.
What to check in your ERP
This is a formatting setting rather than a calculation one. Look for a decimal or numeric field serialised with its full stored precision - a rate held with four decimals in the database will print all four unless the export formats it - and for a locale or template setting that fixes every number in the file to three decimals. Format the rate to at most two decimals on the way out. Note that the same value written with two digits or with none is equally acceptable, so an integer rate needs no decimal point at all.
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(xs:decimal(.) >= 0 and xs:decimal(.) <= 100 and string-length(substring-after(.,'.')) <=2 and not (contains('%',(.))) )
Reported test
xs:decimal(.) >= 0 and xs:decimal(.) <= 100 and string-length(substring-after(.,'.')) <=2
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.