From 9df6424a20bd3391509e0fa33b40d26f441f76fd Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 21 Jul 2025 14:22:05 +0000 Subject: [PATCH 01/10] fix(regional-uae): mark export items as zero rated (cherry picked from commit b8224693c49a21ddfa4a57a0a5ed2a8a963e06aa) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 4 ++++ .../patches/v15_0/update_uae_zero_rated_fetch.py | 9 +++++++++ erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- erpnext/regional/united_arab_emirates/setup.py | 1 + erpnext/regional/united_arab_emirates/utils.py | 13 +++++++++---- 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 erpnext/patches/v15_0/update_uae_zero_rated_fetch.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 74b899555c0..bf3326cd976 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -416,6 +416,10 @@ erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes erpnext.patches.v15_0.rename_price_list_to_buying_price_list erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice +<<<<<<< HEAD erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-04 execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1) erpnext.patches.v15_0.add_company_payment_gateway_account +======= +erpnext.patches.v15_0.update_uae_zero_rated_fetch +>>>>>>> b8224693c4 (fix(regional-uae): mark export items as zero rated) diff --git a/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py b/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py new file mode 100644 index 00000000000..a769cc0a85e --- /dev/null +++ b/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py @@ -0,0 +1,9 @@ +import frappe +from frappe.custom.doctype.property_setter.property_setter import make_property_setter + + +def execute(): + if not frappe.db.get_value("Company", {"country": "United Arab Emirates"}): + return + + make_property_setter("Sales Invoice Item", "is_zero_rated", "fetch_if_empty", 1, "Check") diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 1a68d7dec6a..7cf86adbe01 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -143,7 +143,7 @@ def get_total_emiratewise(filters): on i.parent = s.name where - s.docstatus = 1 and i.is_exempt != 1 and i.is_zero_rated != 1 + s.docstatus = 1 and i.is_exempt != 1 and i.is_zero_rated != 1 {conditions} group by s.vat_emirate; diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 36a079546e5..6a8c7b9438b 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -20,6 +20,7 @@ def make_custom_fields(): label="Is Zero Rated", fieldtype="Check", fetch_from="item_code.is_zero_rated", + fetch_if_empty=1, insert_after="description", print_hide=1, ) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index d71b87bd903..1fa4d070d12 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -7,10 +7,6 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax def update_itemised_tax_data(doc): - # maybe this should be a standard function rather than a regional one - if not doc.taxes: - return - if not doc.items: return @@ -19,6 +15,14 @@ def update_itemised_tax_data(doc): return itemised_tax = get_itemised_tax(doc.taxes) + is_export = 0 + + if doc.customer_address and doc.company_address: + company_country = frappe.get_cached_value("Address", doc.company_address, "country") + customer_country = frappe.db.get_value("Address", doc.customer_address, "country") + + if company_country != customer_country: + is_export = 1 for row in doc.items: tax_rate, tax_amount = 0.0, 0.0 @@ -30,6 +34,7 @@ def update_itemised_tax_data(doc): tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) tax_rate += _tax_rate + row.is_zero_rated = is_export row.tax_rate = flt(tax_rate, row.precision("tax_rate")) row.tax_amount = flt(tax_amount, row.precision("tax_amount")) row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) From 106b83e9f94c5bcd3725d957070a6aff7e3f8dc0 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 21 Jul 2025 14:47:14 +0000 Subject: [PATCH 02/10] fix(regional-uae): split export determination (cherry picked from commit dc72e6cf36f5b144b092efc60b2ebb4947a0d197) --- .../regional/united_arab_emirates/utils.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 1fa4d070d12..0f7336af596 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -15,14 +15,25 @@ def update_itemised_tax_data(doc): return itemised_tax = get_itemised_tax(doc.taxes) - is_export = 0 + is_export = False - if doc.customer_address and doc.company_address: - company_country = frappe.get_cached_value("Address", doc.company_address, "country") - customer_country = frappe.db.get_value("Address", doc.customer_address, "country") + def determine_if_export(doc): + if doc.flags.export_determined is not None: + return doc.flags.export_determined - if company_country != customer_country: - is_export = 1 + doc.flags.export_determined = False + if doc.customer_address and doc.company_address: + company_country = frappe.get_cached_value("Address", doc.company_address, "country") + customer_country = frappe.db.get_value("Address", doc.customer_address, "country") + + if company_country != customer_country: + doc.flags.export_determined = True + return doc.flags.export_determined + else: + frappe.msgprint( + _("Please set Customer and Company Address to determine if the transaction is an export."), + alert=True, + ) for row in doc.items: tax_rate, tax_amount = 0.0, 0.0 @@ -34,11 +45,16 @@ def update_itemised_tax_data(doc): tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) tax_rate += _tax_rate - row.is_zero_rated = is_export + if not row.is_zero_rated and not tax_rate: + is_export = is_export or determine_if_export(doc) + row.is_zero_rated = is_export + row.tax_rate = flt(tax_rate, row.precision("tax_rate")) row.tax_amount = flt(tax_amount, row.precision("tax_amount")) row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) + doc.flags.export_determined = None + def get_account_currency(account): """Helper function to get account currency.""" From 62db42cf2f65341be2d71e706b5ff7e5bbcaa5bb Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 28 Jul 2025 05:32:04 +0000 Subject: [PATCH 03/10] fix(regional-uae): restrict zero rated export to invoice (cherry picked from commit 1170e4fb2c067f7c44a06cac89f80b667ffe0e49) --- erpnext/regional/united_arab_emirates/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 0f7336af596..ecf8226b2db 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -10,14 +10,22 @@ def update_itemised_tax_data(doc): if not doc.items: return + has_zero_rated_field = False meta = frappe.get_meta(doc.items[0].doctype) if not meta.has_field("tax_rate"): return + if meta.has_field("is_zero_rated"): + has_zero_rated_field = True + itemised_tax = get_itemised_tax(doc.taxes) + doc.flags.export_determined = None is_export = False def determine_if_export(doc): + if doc.doctype != "Sales Invoice": + return False + if doc.flags.export_determined is not None: return doc.flags.export_determined @@ -45,7 +53,7 @@ def update_itemised_tax_data(doc): tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) tax_rate += _tax_rate - if not row.is_zero_rated and not tax_rate: + if not tax_rate and has_zero_rated_field and not row.is_zero_rated: is_export = is_export or determine_if_export(doc) row.is_zero_rated = is_export From 68c65866bf6e56fcdc4b55a3b49d3ae0c4feddf8 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 4 Aug 2025 15:45:43 +0530 Subject: [PATCH 04/10] fix: simplify export determination logic (cherry picked from commit d25846f383affe7ca4dfeba444cb5cc9daaec5ea) --- .../regional/united_arab_emirates/utils.py | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index ecf8226b2db..504651882cf 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -10,38 +10,32 @@ def update_itemised_tax_data(doc): if not doc.items: return - has_zero_rated_field = False meta = frappe.get_meta(doc.items[0].doctype) if not meta.has_field("tax_rate"): return - if meta.has_field("is_zero_rated"): - has_zero_rated_field = True - itemised_tax = get_itemised_tax(doc.taxes) - doc.flags.export_determined = None - is_export = False def determine_if_export(doc): if doc.doctype != "Sales Invoice": return False - if doc.flags.export_determined is not None: - return doc.flags.export_determined - - doc.flags.export_determined = False - if doc.customer_address and doc.company_address: - company_country = frappe.get_cached_value("Address", doc.company_address, "country") - customer_country = frappe.db.get_value("Address", doc.customer_address, "country") - - if company_country != customer_country: - doc.flags.export_determined = True - return doc.flags.export_determined - else: + if not doc.customer_address: frappe.msgprint( - _("Please set Customer and Company Address to determine if the transaction is an export."), + _("Please set Customer Address to determine if the transaction is an export."), alert=True, ) + return False + + company_country = frappe.get_cached_value("Company", doc.company, "country") + customer_country = frappe.db.get_value("Address", doc.customer_address, "country") + + if company_country != customer_country: + return True + + return False + + is_export = determine_if_export(doc) for row in doc.items: tax_rate, tax_amount = 0.0, 0.0 @@ -53,16 +47,13 @@ def update_itemised_tax_data(doc): tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) tax_rate += _tax_rate - if not tax_rate and has_zero_rated_field and not row.is_zero_rated: - is_export = is_export or determine_if_export(doc) - row.is_zero_rated = is_export + if not tax_rate and is_export: + row.is_zero_rated = 1 row.tax_rate = flt(tax_rate, row.precision("tax_rate")) row.tax_amount = flt(tax_amount, row.precision("tax_amount")) row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount")) - doc.flags.export_determined = None - def get_account_currency(account): """Helper function to get account currency.""" From faae7347971c0515044e8004e380c80fb8296a77 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 4 Aug 2025 15:52:22 +0530 Subject: [PATCH 05/10] fix: avoid property setter for custom field (cherry picked from commit 0c15b65756b2c3dac09a705cf2a08bc8cd459c20) --- erpnext/patches/v15_0/update_uae_zero_rated_fetch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py b/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py index a769cc0a85e..4ccdbb4229f 100644 --- a/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py +++ b/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py @@ -1,9 +1,9 @@ import frappe -from frappe.custom.doctype.property_setter.property_setter import make_property_setter +from erpnext.regional.united_arab_emirates.setup import make_custom_fields def execute(): if not frappe.db.get_value("Company", {"country": "United Arab Emirates"}): return - make_property_setter("Sales Invoice Item", "is_zero_rated", "fetch_if_empty", 1, "Check") + make_custom_fields() From 534b27afa52d4ee0c9068fe4245ab5044e44db44 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 6 Aug 2025 11:40:33 +0530 Subject: [PATCH 06/10] chore: linters (cherry picked from commit eb6c8d8938226f12d517a59e2c9777f479001888) --- erpnext/patches/v15_0/update_uae_zero_rated_fetch.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py b/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py index 4ccdbb4229f..57b8db59f97 100644 --- a/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py +++ b/erpnext/patches/v15_0/update_uae_zero_rated_fetch.py @@ -1,4 +1,5 @@ import frappe + from erpnext.regional.united_arab_emirates.setup import make_custom_fields From 614d38d0e63f3f4eee838af0faa8248187119817 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Fri, 8 Aug 2025 09:58:16 +0530 Subject: [PATCH 07/10] fix: show message only if no tax is applied (cherry picked from commit 38471995e76313b31cc37a52c8b2834744e2c46f) --- erpnext/regional/united_arab_emirates/utils.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 504651882cf..a975c1539a5 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -21,10 +21,12 @@ def update_itemised_tax_data(doc): return False if not doc.customer_address: - frappe.msgprint( - _("Please set Customer Address to determine if the transaction is an export."), - alert=True, - ) + if not doc.total_taxes_and_charges: + frappe.msgprint( + _("Please set Customer Address to determine if the transaction is an export."), + alert=True, + ) + return False company_country = frappe.get_cached_value("Company", doc.company, "country") @@ -47,8 +49,10 @@ def update_itemised_tax_data(doc): tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) tax_rate += _tax_rate - if not tax_rate and is_export: - row.is_zero_rated = 1 + if not tax_rate: + row.is_zero_rated = ( + is_export or frappe.get_cached_value("Item", row.item_code, "is_zero_rated") + ) row.tax_rate = flt(tax_rate, row.precision("tax_rate")) row.tax_amount = flt(tax_amount, row.precision("tax_amount")) From d47c25287d5485525bbfe73fe887dd813cd211f6 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Fri, 8 Aug 2025 10:02:35 +0530 Subject: [PATCH 08/10] chore: code styling (cherry picked from commit c8940a39b357836e36095d07ef91f53789d5eff5) --- erpnext/regional/united_arab_emirates/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index a975c1539a5..6a89944c4ba 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -50,9 +50,7 @@ def update_itemised_tax_data(doc): tax_rate += _tax_rate if not tax_rate: - row.is_zero_rated = ( - is_export or frappe.get_cached_value("Item", row.item_code, "is_zero_rated") - ) + row.is_zero_rated = is_export or frappe.get_cached_value("Item", row.item_code, "is_zero_rated") row.tax_rate = flt(tax_rate, row.precision("tax_rate")) row.tax_amount = flt(tax_amount, row.precision("tax_amount")) From 90913c66aeaf0d6de4ce18fe1e33e07d84a68f88 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Fri, 8 Aug 2025 11:01:59 +0530 Subject: [PATCH 09/10] fix: handle case where taxes is added invoice changed to non-export later (cherry picked from commit 29c3ef8280a36376cbb4ecc07d8467ecdd85ec41) --- erpnext/regional/united_arab_emirates/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/united_arab_emirates/utils.py b/erpnext/regional/united_arab_emirates/utils.py index 6a89944c4ba..19c00a6c798 100644 --- a/erpnext/regional/united_arab_emirates/utils.py +++ b/erpnext/regional/united_arab_emirates/utils.py @@ -49,7 +49,7 @@ def update_itemised_tax_data(doc): tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount")) tax_rate += _tax_rate - if not tax_rate: + if not tax_rate or row.get("is_zero_rated"): row.is_zero_rated = is_export or frappe.get_cached_value("Item", row.item_code, "is_zero_rated") row.tax_rate = flt(tax_rate, row.precision("tax_rate")) From faba5230862d02131c5ad6cf60e5b500e7daa2cb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 11 Aug 2025 11:41:10 +0530 Subject: [PATCH 10/10] chore: resolve conflict --- erpnext/patches.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bf3326cd976..1087f64276d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -416,10 +416,7 @@ erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes erpnext.patches.v15_0.rename_price_list_to_buying_price_list erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice -<<<<<<< HEAD erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-04 execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1) erpnext.patches.v15_0.add_company_payment_gateway_account -======= erpnext.patches.v15_0.update_uae_zero_rated_fetch ->>>>>>> b8224693c4 (fix(regional-uae): mark export items as zero rated)