fix: resolve all ERPNext v16 naming convention issues

- All ERPNext doctypes append company abbr to names (Warehouse, Tax
  Template, etc.) — switch existence checks to use filter-based lookup
  instead of hardcoded "{name} - Furnitex" strings
- Add safe_insert() helper to absorb DuplicateEntryError gracefully so
  re-runs never crash mid-way
- Service items: remove item_defaults entirely (non-stock items have no
  warehouse, avoids cross-company warehouse validation error)
- Raw material items: only add item_defaults when a valid Furnitex
  warehouse is resolved
- Supplier URD tax default: look up template by title+company filter
  rather than bare name string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SUBHANKAR DHAR 2026-06-12 16:11:37 +05:30
parent ff152a3020
commit 356fec8c38

View file

@ -28,6 +28,23 @@ def get_abbr():
def exists(doctype, name): def exists(doctype, name):
return frappe.db.exists(doctype, name) return frappe.db.exists(doctype, name)
def exists_filter(doctype, filters):
"""Existence check by filters (for docs where name includes company abbr)."""
return frappe.db.get_value(doctype, filters, "name")
def safe_insert(doc):
"""Insert and return True, or skip silently on duplicate and return False."""
try:
doc.flags.ignore_permissions = True
doc.insert()
return True
except frappe.DuplicateEntryError:
return False
except Exception as e:
if "Duplicate entry" in str(e):
return False
raise
def ok(msg): def ok(msg):
print(f" [OK] {msg}") print(f" [OK] {msg}")
@ -59,10 +76,11 @@ def create_uoms():
"uom_name": uom_name, "uom_name": uom_name,
"must_be_whole_number": whole, "must_be_whole_number": whole,
}) })
d.flags.ignore_permissions = True d.flags.ignore_mandatory = True
d.flags.ignore_mandatory = True if safe_insert(d):
d.insert() ok(f"UOM: {uom_name}")
ok(f"UOM: {uom_name}") else:
skip(f"UOM: {uom_name} (duplicate)")
else: else:
skip(f"UOM: {uom_name}") skip(f"UOM: {uom_name}")
frappe.db.commit() frappe.db.commit()
@ -91,9 +109,10 @@ def create_item_groups():
"parent_item_group": parent, "parent_item_group": parent,
"is_group": 0, "is_group": 0,
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Item Group: {name}")
ok(f"Item Group: {name}") else:
skip(f"Item Group: {name} (duplicate)")
else: else:
skip(f"Item Group: {name}") skip(f"Item Group: {name}")
frappe.db.commit() frappe.db.commit()
@ -117,9 +136,10 @@ def create_supplier_groups():
"supplier_group_name": name, "supplier_group_name": name,
"parent_supplier_group": parent, "parent_supplier_group": parent,
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Supplier Group: {name}")
ok(f"Supplier Group: {name}") else:
skip(f"Supplier Group: {name} (duplicate)")
else: else:
skip(f"Supplier Group: {name}") skip(f"Supplier Group: {name}")
frappe.db.commit() frappe.db.commit()
@ -157,9 +177,10 @@ def create_warehouses():
"company": COMPANY, "company": COMPANY,
"is_group": is_group, "is_group": is_group,
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Warehouse: {w_full}")
ok(f"Warehouse: {w_full}") else:
skip(f"Warehouse: {w_full} (duplicate)")
else: else:
skip(f"Warehouse: {w_full}") skip(f"Warehouse: {w_full}")
frappe.db.commit() frappe.db.commit()
@ -197,68 +218,81 @@ def _find_account(account_name_fragment, root_type=None, account_type=None):
def create_tax_templates(): def create_tax_templates():
print("\n[5/9] Creating Tax Templates...") print("\n[5/9] Creating Tax Templates...")
# ERPNext stores tax templates as "{title} - {abbr}", so we check by title+company
# ── No GST (URD Purchase) ── # ── No GST (URD Purchase) ──
urd = "No GST - URD Purchase" urd_title = "No GST - URD Purchase"
if not exists("Purchase Taxes and Charges Template", urd): if not exists_filter("Purchase Taxes and Charges Template",
{"title": urd_title, "company": COMPANY}):
d = frappe.get_doc({ d = frappe.get_doc({
"doctype": "Purchase Taxes and Charges Template", "doctype": "Purchase Taxes and Charges Template",
"title": urd, "title": urd_title,
"company": COMPANY, "company": COMPANY,
"is_default": 0, "is_default": 0,
"taxes": [], # zero rows = zero tax, clean COGS booking "taxes": [],
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Purchase Tax Template: {urd_title}")
ok(f"Purchase Tax Template: {urd}") else:
skip(f"Purchase Tax Template: {urd_title} (duplicate)")
else: else:
skip(f"Purchase Tax Template: {urd}") skip(f"Purchase Tax Template: {urd_title}")
# ── GST 18% Purchase ── # ── GST 18% Purchase ──
gst18_p = "GST 18% - Purchase" gst18_p_title = "GST 18% - Purchase"
if not exists("Purchase Taxes and Charges Template", gst18_p): if not exists_filter("Purchase Taxes and Charges Template",
{"title": gst18_p_title, "company": COMPANY}):
cgst = _find_account("CGST") cgst = _find_account("CGST")
sgst = _find_account("SGST") sgst = _find_account("SGST")
taxes = [] taxes = []
if cgst: if cgst:
taxes.append({"charge_type": "On Net Total", "account_head": cgst, "rate": 9, "description": "CGST @ 9%"}) taxes.append({"charge_type": "On Net Total", "account_head": cgst,
"rate": 9, "description": "CGST @ 9%"})
if sgst: if sgst:
taxes.append({"charge_type": "On Net Total", "account_head": sgst, "rate": 9, "description": "SGST @ 9%"}) taxes.append({"charge_type": "On Net Total", "account_head": sgst,
"rate": 9, "description": "SGST @ 9%"})
d = frappe.get_doc({ d = frappe.get_doc({
"doctype": "Purchase Taxes and Charges Template", "doctype": "Purchase Taxes and Charges Template",
"title": gst18_p, "title": gst18_p_title,
"company": COMPANY, "company": COMPANY,
"is_default": 0, "is_default": 0,
"taxes": taxes, "taxes": taxes,
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Purchase Tax Template: {gst18_p_title}" +
ok(f"Purchase Tax Template: {gst18_p}" + (" (no accounts found, empty)" if not taxes else "")) (" (no GST accounts in CoA, left empty)" if not taxes else ""))
else:
skip(f"Purchase Tax Template: {gst18_p_title} (duplicate)")
else: else:
skip(f"Purchase Tax Template: {gst18_p}") skip(f"Purchase Tax Template: {gst18_p_title}")
# ── GST 18% Sales ── # ── GST 18% Sales ──
gst18_s = "GST 18% - Sales" gst18_s_title = "GST 18% - Sales"
if not exists("Sales Taxes and Charges Template", gst18_s): if not exists_filter("Sales Taxes and Charges Template",
{"title": gst18_s_title, "company": COMPANY}):
cgst = _find_account("CGST") cgst = _find_account("CGST")
sgst = _find_account("SGST") sgst = _find_account("SGST")
taxes = [] taxes = []
if cgst: if cgst:
taxes.append({"charge_type": "On Net Total", "account_head": cgst, "rate": 9, "description": "CGST @ 9%"}) taxes.append({"charge_type": "On Net Total", "account_head": cgst,
"rate": 9, "description": "CGST @ 9%"})
if sgst: if sgst:
taxes.append({"charge_type": "On Net Total", "account_head": sgst, "rate": 9, "description": "SGST @ 9%"}) taxes.append({"charge_type": "On Net Total", "account_head": sgst,
"rate": 9, "description": "SGST @ 9%"})
d = frappe.get_doc({ d = frappe.get_doc({
"doctype": "Sales Taxes and Charges Template", "doctype": "Sales Taxes and Charges Template",
"title": gst18_s, "title": gst18_s_title,
"company": COMPANY, "company": COMPANY,
"is_default": 0, "is_default": 0,
"taxes": taxes, "taxes": taxes,
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Sales Tax Template: {gst18_s_title}" +
ok(f"Sales Tax Template: {gst18_s}" + (" (no accounts found, empty)" if not taxes else "")) (" (no GST accounts in CoA, left empty)" if not taxes else ""))
else:
skip(f"Sales Tax Template: {gst18_s_title} (duplicate)")
else: else:
skip(f"Sales Tax Template: {gst18_s}") skip(f"Sales Tax Template: {gst18_s_title}")
frappe.db.commit() frappe.db.commit()
@ -289,7 +323,8 @@ def create_service_items():
for code, name, uom, desc in items: for code, name, uom, desc in items:
if not exists("Item", code): if not exists("Item", code):
d_dict = { # Non-stock service items: no warehouse needed in item_defaults
d = frappe.get_doc({
"doctype": "Item", "doctype": "Item",
"item_code": code, "item_code": code,
"item_name": name, "item_name": name,
@ -301,16 +336,12 @@ def create_service_items():
"is_purchase_item": 0, "is_purchase_item": 0,
"is_sales_item": 1, "is_sales_item": 1,
"standard_rate": 0, "standard_rate": 0,
} # No item_defaults row — avoids warehouse company-mismatch validation
if income_acct: })
d_dict["item_defaults"] = [{ if safe_insert(d):
"company": COMPANY, ok(f"Service Item: {code} [{uom}]")
"income_account": income_acct, else:
}] skip(f"Service Item: {code} (duplicate)")
d = frappe.get_doc(d_dict)
d.flags.ignore_permissions = True
d.insert()
ok(f"Service Item: {code} [{uom}]")
else: else:
skip(f"Service Item: {code}") skip(f"Service Item: {code}")
@ -370,17 +401,20 @@ def create_raw_material_items():
"is_sales_item": 0, "is_sales_item": 0,
"valuation_method": "FIFO", "valuation_method": "FIFO",
} }
defaults = {"company": COMPANY} # Only add item_defaults if we have a valid Furnitex warehouse
if default_wh: if default_wh or cogs_acct:
defaults["default_warehouse"] = default_wh defaults = {"company": COMPANY}
if cogs_acct: if default_wh:
defaults["expense_account"] = cogs_acct defaults["default_warehouse"] = default_wh
d_dict["item_defaults"] = [defaults] if cogs_acct:
defaults["expense_account"] = cogs_acct
d_dict["item_defaults"] = [defaults]
d = frappe.get_doc(d_dict) d = frappe.get_doc(d_dict)
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Raw Material: {code} [{uom}]")
ok(f"Raw Material: {code} [{uom}]") else:
skip(f"Raw Material: {code} (duplicate)")
else: else:
skip(f"Raw Material: {code}") skip(f"Raw Material: {code}")
@ -414,17 +448,20 @@ def create_suppliers():
"gst_category": gst_cat, "gst_category": gst_cat,
"default_currency": "INR", "default_currency": "INR",
}) })
d.flags.ignore_permissions = True if safe_insert(d):
d.insert() ok(f"Supplier: {name} [{gst_cat}]")
ok(f"Supplier: {name} [{gst_cat}]") else:
skip(f"Supplier: {name} (duplicate)")
else: else:
skip(f"Supplier: {name}") skip(f"Supplier: {name}")
frappe.db.commit() frappe.db.commit()
# Set URD tax default on all Unregistered suppliers # Set URD tax default — look up by title+company (name includes abbr)
urd_template = "No GST - URD Purchase" urd_template = "No GST - URD Purchase"
if exists("Purchase Taxes and Charges Template", urd_template): urd_full = exists_filter("Purchase Taxes and Charges Template",
{"title": urd_template, "company": COMPANY})
if urd_full:
urd_suppliers = frappe.db.sql( urd_suppliers = frappe.db.sql(
"""SELECT name FROM `tabSupplier` """SELECT name FROM `tabSupplier`
WHERE supplier_group = 'Local Market Vendor (Unregistered)'""", WHERE supplier_group = 'Local Market Vendor (Unregistered)'""",
@ -433,10 +470,10 @@ def create_suppliers():
for s in urd_suppliers: for s in urd_suppliers:
frappe.db.set_value( frappe.db.set_value(
"Supplier", s.name, "Supplier", s.name,
"default_purchase_taxes_and_charges_template", urd_template "default_purchase_taxes_and_charges_template", urd_full
) )
frappe.db.commit() frappe.db.commit()
ok(f"Set '{urd_template}' as default tax on {len(urd_suppliers)} URD supplier(s)") ok(f"Set '{urd_full}' as default tax on {len(urd_suppliers)} URD supplier(s)")
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────