From 110d0a38a6728977e6d11207ad9fa3bca4590ac3 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Sun, 9 Aug 2026 16:20:32 +0530 Subject: [PATCH] fix(regional): rename Italy's duplicate Customer name fields The Italy regional setup created Custom Fields first_name/last_name on Customer. Since #46281 added standard quick-entry fields with the same names, every Italian site carries duplicate field definitions: - the setup wizard creates the duplicates silently because it skips validation, and any later Custom Field on Customer then raises UniqueFieldnameError (#50915) - without the duplicates, creating an Italian company aborts inside install_country_fixtures; on MariaDB an interrupted fixture run persists Custom Field documents whose columns were never added, after which every Company insert fails with "Unknown column 'fiscal_regime'" (#57215) Re-land the rename from #50921 (reverted in #53409): the fields become italy_customer_first_name/italy_customer_last_name and the e-invoice template reads the new names. The migration patch runs only on sites with Italy fixtures, re-runs them, explicitly syncs the schema of every affected doctype (create_custom_fields skips unchanged fields, so its own schema sync cannot restore missing columns), copies the old column values wherever the new field is empty (also on sites that removed the duplicate fields with the documented manual workaround), and deletes the duplicate Custom Fields last so an interrupted run stays resumable. The old insert_after anchor "salutation" no longer exists on Customer; the renamed fields anchor after customer_type. --- erpnext/patches.txt | 1 + .../rename_italy_customer_name_fields.py | 53 +++++++++++++++++++ erpnext/regional/italy/e-invoice.xml | 4 +- erpnext/regional/italy/setup.py | 16 +++--- 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 erpnext/patches/v16_0/rename_italy_customer_name_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 97cceebe5f7..fa1838c4345 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -509,3 +509,4 @@ erpnext.patches.v16_0.backfill_repost_accounting_ledger_status erpnext.patches.v16_0.merge_seeded_item_group_root erpnext.patches.v16_0.set_stock_uom_in_job_card erpnext.patches.v16_0.set_work_order_requested_and_picked_qty +erpnext.patches.v16_0.rename_italy_customer_name_fields diff --git a/erpnext/patches/v16_0/rename_italy_customer_name_fields.py b/erpnext/patches/v16_0/rename_italy_customer_name_fields.py new file mode 100644 index 00000000000..4e1b13a947b --- /dev/null +++ b/erpnext/patches/v16_0/rename_italy_customer_name_fields.py @@ -0,0 +1,53 @@ +import frappe + +RENAMED_FIELDS = { + "first_name": "italy_customer_first_name", + "last_name": "italy_customer_last_name", +} + + +def execute(): + """Rename Italy's Customer name fields, which clash with the standard quick-entry + first_name/last_name fields, and restore any Italy custom field columns that a + previously interrupted fixture run left missing.""" + if not has_italy_fixtures(): + return + + duplicate_fieldnames = [ + fieldname for fieldname in RENAMED_FIELDS if frappe.db.exists("Custom Field", f"Customer-{fieldname}") + ] + + from erpnext.regional.italy.setup import get_custom_fields, make_custom_fields + + make_custom_fields() + for doctype in get_custom_fields(): + frappe.clear_cache(doctype=doctype) + frappe.db.updatedb(doctype) + + for old_fieldname, new_fieldname in RENAMED_FIELDS.items(): + copy_customer_names(old_fieldname, new_fieldname) + + for old_fieldname in duplicate_fieldnames: + frappe.delete_doc("Custom Field", f"Customer-{old_fieldname}", force=True) + + if duplicate_fieldnames: + frappe.clear_cache(doctype="Customer") + + +def has_italy_fixtures(): + return bool( + frappe.db.exists("Company", {"country": "Italy"}) + or frappe.db.exists("Custom Field", "Company-fiscal_regime") + ) + + +def copy_customer_names(old_fieldname, new_fieldname): + customer = frappe.qb.DocType("Customer") + old_column = customer[old_fieldname] + new_column = customer[new_fieldname] + ( + frappe.qb.update(customer) + .set(new_column, old_column) + .where(old_column.isnotnull() & (old_column != "")) + .where(new_column.isnull() | (new_column == "")) + ).run() diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index ef1e94ff27b..713e85a556e 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -99,8 +99,8 @@ {%- if doc.customer_data.customer_type == "Individual" %} {{ doc.customer_data.fiscal_code }} - {{ doc.customer_data.first_name }} - {{ doc.customer_data.last_name }} + {{ doc.customer_data.italy_customer_first_name }} + {{ doc.customer_data.italy_customer_last_name }} {%- else %} diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 9f9115ca12d..a21be948650 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -23,6 +23,10 @@ def setup(company=None, patch=True): def make_custom_fields(update=True): + create_custom_fields(get_custom_fields(), ignore_validate=frappe.flags.in_patch, update=update) + + +def get_custom_fields(): invoice_item_fields = [ dict( fieldname="tax_rate", @@ -96,7 +100,7 @@ def make_custom_fields(update=True): ), ] - custom_fields = { + return { "Company": [ dict( fieldname="sb_e_invoicing", @@ -232,18 +236,18 @@ def make_custom_fields(update=True): depends_on='eval:doc.customer_type=="Company"', ), dict( - fieldname="first_name", + fieldname="italy_customer_first_name", label="First Name", fieldtype="Data", - insert_after="salutation", + insert_after="customer_type", print_hide=1, depends_on='eval:doc.customer_type!="Company"', ), dict( - fieldname="last_name", + fieldname="italy_customer_last_name", label="Last Name", fieldtype="Data", - insert_after="first_name", + insert_after="italy_customer_first_name", print_hide=1, depends_on='eval:doc.customer_type!="Company"', ), @@ -461,8 +465,6 @@ def make_custom_fields(update=True): ], } - create_custom_fields(custom_fields, ignore_validate=frappe.flags.in_patch, update=update) - def setup_report(): report_name = "Electronic Invoice Register"