Skip to content

Fix: ZATCA validation BR-CO-15 by adding custom_grand_total_without_rounding #266

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

alaalsalam
Copy link

Summary

This pull request resolves the ZATCA validation error:

[BR-CO-15] - Invoice total amount with VAT (BT-112) must equal the sum of total without VAT (BT-109) and total VAT (BT-110)

Problem

ERPNext/Frappe by default applies rounding to the grand total amount (grand_total), which can lead to mismatch with ZATCA's expected precise values in the XML (UBL 2.1), especially in fields BT-109 + BT-110 ≠ BT-112.

Solution

A new custom field was added to Sales Invoice:

  • Fieldname: custom_grand_total_without_rounding
  • Label: Grand Total (Without Rounding)
  • Purpose: Stores the exact computed total (without any system rounding)
  • This value is now used when generating the UBL XML and reporting to ZATCA

Impact

✅ Fixes the BR-CO-15 error
✅ Ensures mathematical consistency in reported invoices
✅ Does not affect standard UI behavior or printed totals
✅ Transparent to end users

Ensure that BT-112 (total with VAT) equals BT-109 (total without VAT) plus BT-110 (VAT amount),
as required by ZATCA UBL validation rules. Adjusted computation logic to comply.
@mhaggag
Copy link
Collaborator

mhaggag commented Jul 6, 2025

Hello, and thank you for your contribution. Before I review it from a technical standpoint, I'd like @omarashrafsarhan to discuss the business side of things. Am I correct in understanding that this new custom grand total field is used in both printed invoices and reporting to ZATCA? That is, it is considered the actual amount that customers actually pay. Or is that still the regular grand total?

@omarashrafsarhan
Copy link
Collaborator

@alaalsalam
Hello, thank you for your contribution.

The rounding issue may occur in several scenarios, such as with the 'Total taxes and charges' and sum of "Invoice line tax amounts. Could you clarify how this fix addresses those cases?

Additionally, will this workaround be suitable for instances involving tax rates of 15% and 0% in the same invoice.

@mhaggag
Copy link
Collaborator

mhaggag commented Jul 6, 2025

I have set up a test instance containing frappe, erpnext, and ksa_compliance from Ala's fork and branch.

  • @omarashrafsarhan The credentials are available on Bitwarden under the Test collection
  • @alaalsalam I will share a Bitwarden Send with you through email so that you can access the instance as well.

I onboarded the sandbox environment and tried an example invoice without any rounding involved (10000 SAR, 11500 with VAT), but it failed validation for me with BR-CO-15:
Sales Invoice
Error Log

@alaalsalam
Copy link
Author

Hi @mhaggag @omarashrafsarhan 👋

I'd like to first clarify the core issue this PR addresses.

⚠️ Root Cause

In ERPNext, even when rounding is disabled in the Sales Invoice settings, the system still applies rounding to the Grand Total (i.e., grand_total, which maps to BT-112 in ZATCA's XML). This behavior persists due to internal design logic and is known to cause BR-CO-15 validation failures.

ERPNext rounds the total after tax calculation — which often results in a mismatch with:

BT-112BT-109 + BT-110

I understand this might be addressed in future ERPNext updates, but at the moment, it can result in false positives on valid invoices when reported to ZATCA.


✅ Why This Fix?

Rather than modifying ERPNext’s core behavior or overriding how grand_total is computed, I introduced a non-intrusive solution:

  • A new custom field: custom_grand_total_without_rounding
  • This field is calculated manually as:

Does it work with mixed tax rates (15% and 0%)?
Yes.

@alaalsalam
Copy link
Author

Hi @mhaggag 👋

After reviewing the test XML, I found that the issue was not with the underlying calculation — it was due to the fact that the XML template still used invoice.grand_total for the cbc:TaxInclusiveAmount (BT-112).

As discussed earlier, ERPNext applies internal rounding to grand_total, which sometimes results in minor mismatches between:

✅ The Fix
I updated the XML template to instead use:


<cbc:TaxInclusiveAmount currencyID="{{ invoice.currency_code }}">{{ rounded(invoice.grand_total, 2) }}</cbc:TaxInclusiveAmount>


<cbc:TaxInclusiveAmount currencyID="{{ invoice.currency_code }}">{{ rounded(invoice.custom_grand_total_without_rounding, 2) }}</cbc:TaxInclusiveAmount>

…Amount) in UBL XML

- Replaced invoice.grand_total with custom_grand_total_without_rounding in the XML template
- Prevents BR-CO-15 validation error by ensuring BT-112 = BT-109 + BT-110 exactly
- This ensures compatibility with mixed tax rates (15%, 0%) and avoids ERPNext's internal rounding
- Does not affect ERP logic or printed values — this is strictly for ZATCA compliance
@mhaggag
Copy link
Collaborator

mhaggag commented Jul 14, 2025

Is this ready for review/testing, or are you still working on further fixes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants