fix: skip custom project fields that already exist natively in v16

ERPNext v16 ships project fields natively on Purchase Invoice, Purchase
Order, Stock Entry, and Delivery Note. Adding duplicate custom fields
caused UniqueFieldnameError. Now only two custom fields are added:
  - Purchase Invoice.is_urd_purchase (new URD toggle)
  - Journal Entry.project (only doctype missing it)

Also update profitability report queries to use native 'project' field
instead of 'furnitex_project' on PI, SE, and JE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SUBHANKAR DHAR 2026-06-12 16:14:16 +05:30
parent 1f5bb6d942
commit bdeb078f29

View file

@ -473,46 +473,46 @@ def create_suppliers():
def create_custom_fields(): def create_custom_fields():
print("\n[9/9] Creating Custom Fields...") print("\n[9/9] Creating Custom Fields...")
# NOTE: Purchase Invoice, Purchase Order, Stock Entry, and Delivery Note
# already have a native 'project' field in ERPNext v16 — skip those.
# We only add:
# 1. is_urd_purchase (Check) on Purchase Invoice
# 2. project (Link) on Journal Entry (only one missing it)
# (dt, fieldname, label, fieldtype, options, insert_after, in_list_view) # (dt, fieldname, label, fieldtype, options, insert_after, in_list_view)
fields = [ fields = [
("Purchase Invoice", "is_urd_purchase", "URD Purchase (No GST)", ("Purchase Invoice", "is_urd_purchase", "URD Purchase (No GST)",
"Check", None, "supplier", 1), "Check", None, "supplier", 1),
("Purchase Invoice", "furnitex_project", "Project", ("Journal Entry", "project", "Project",
"Link", "Project", "is_urd_purchase", 1), "Link", "Project", "voucher_type", 0),
("Purchase Order", "furnitex_project", "Project",
"Link", "Project", "supplier", 1),
("Stock Entry", "furnitex_project", "Project",
"Link", "Project", "purpose", 1),
("Delivery Note", "furnitex_project", "Project",
"Link", "Project", "customer", 0),
("Journal Entry", "furnitex_project", "Project",
"Link", "Project", "voucher_type", 0),
] ]
for dt, fn, label, ft, opts, after, in_list in fields: for dt, fn, label, ft, opts, after, in_list in fields:
cf_name = f"{dt}-{fn}" cf_name = f"{dt}-{fn}"
if not exists("Custom Field", cf_name): if not exists("Custom Field", cf_name):
d_dict = { d_dict = {
"doctype": "Custom Field", "doctype": "Custom Field",
"dt": dt, "dt": dt,
"fieldname": fn, "fieldname": fn,
"label": label, "label": label,
"fieldtype": ft, "fieldtype": ft,
"insert_after": after, "insert_after": after,
"in_list_view": in_list, "in_list_view": in_list,
"in_standard_filter": 1, "in_standard_filter": 1,
"search_index": 1, "search_index": 1,
} }
if opts: if opts:
d_dict["options"] = opts d_dict["options"] = opts
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"Custom Field: {dt}.{fn}")
ok(f"Custom Field: {dt}.{fn}") else:
skip(f"Custom Field: {dt}.{fn} (duplicate)")
else: else:
skip(f"Custom Field: {dt}.{fn}") skip(f"Custom Field: {dt}.{fn}")
frappe.db.commit() frappe.db.commit()
ok("Native 'project' field already present on PI, PO, Stock Entry, DN — no custom fields needed there")
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
@ -668,14 +668,14 @@ def get_data(filters):
pname, as_dict=1)[0].v or 0) pname, as_dict=1)[0].v or 0)
raw_mat = (frappe.db.sql( raw_mat = (frappe.db.sql(
"SELECT COALESCE(SUM(base_net_total),0) v FROM `tabPurchase Invoice` WHERE furnitex_project=%s AND docstatus=1", "SELECT COALESCE(SUM(base_net_total),0) v FROM `tabPurchase Invoice` WHERE project=%s AND docstatus=1",
pname, as_dict=1)[0].v or 0) pname, as_dict=1)[0].v or 0)
consumed = (frappe.db.sql( consumed = (frappe.db.sql(
"""SELECT COALESCE(SUM(sed.amount),0) v """SELECT COALESCE(SUM(sed.amount),0) v
FROM `tabStock Entry Detail` sed FROM `tabStock Entry Detail` sed
JOIN `tabStock Entry` se ON se.name=sed.parent JOIN `tabStock Entry` se ON se.name=sed.parent
WHERE se.furnitex_project=%s AND se.stock_entry_type='Material Issue' AND se.docstatus=1""", WHERE se.project=%s AND se.stock_entry_type='Material Issue' AND se.docstatus=1""",
pname, as_dict=1)[0].v or 0) pname, as_dict=1)[0].v or 0)
labour = (frappe.db.sql( labour = (frappe.db.sql(