style: fix black/isort formatting and codespell typo to pass pre-commit lint

- black: auto-reformatted all 4 Furnitex scripts to PEP 8 style
- isort: sorted imports in setup_furnitex.py and delete_streetwok.py
- codespell: renamed loop variable `ot` → `opp_type` in create_crm_stages()
  (codespell flagged `ot` as a misspelling)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SUBHANKAR DHAR 2026-06-16 15:41:54 +05:30
parent 4678d517ae
commit c40e7992cb
4 changed files with 1008 additions and 586 deletions

View file

@ -6,7 +6,6 @@ Run via: bench --site frontend execute frappe.delete_streetwok.run
import frappe import frappe
COMPANY = "streetwok (Demo)" COMPANY = "streetwok (Demo)"
@ -47,7 +46,8 @@ def run():
# Get submitted docs for this company # Get submitted docs for this company
docs = frappe.db.sql( docs = frappe.db.sql(
f"SELECT name FROM `tab{dt}` WHERE company=%s AND docstatus=1", f"SELECT name FROM `tab{dt}` WHERE company=%s AND docstatus=1",
COMPANY, as_dict=1 COMPANY,
as_dict=1,
) )
if docs: if docs:
print(f" Cancelling {len(docs)} submitted {dt}(s)...") print(f" Cancelling {len(docs)} submitted {dt}(s)...")
@ -99,9 +99,9 @@ def run():
# ── STEP 5: Delete child tables that reference the company ──── # ── STEP 5: Delete child tables that reference the company ────
child_cleanups = [ child_cleanups = [
("Sales Invoice Item", "company"), ("Sales Invoice Item", "company"),
("Purchase Invoice Item", "company"), ("Purchase Invoice Item", "company"),
("Payment Entry Reference", None), # handled via parent delete ("Payment Entry Reference", None), # handled via parent delete
] ]
# ── STEP 6: Delete Warehouses ───────────────────────────────── # ── STEP 6: Delete Warehouses ─────────────────────────────────
@ -116,27 +116,28 @@ def run():
for wh in wh_list: for wh in wh_list:
try: try:
frappe.delete_doc("Warehouse", wh.name, frappe.delete_doc(
ignore_permissions=True, force=True, "Warehouse",
ignore_on_trash=True) wh.name,
except Exception as e: ignore_permissions=True,
frappe.db.sql( force=True,
"DELETE FROM `tabWarehouse` WHERE name=%s", wh.name ignore_on_trash=True,
) )
except Exception as e:
frappe.db.sql("DELETE FROM `tabWarehouse` WHERE name=%s", wh.name)
frappe.db.commit() frappe.db.commit()
print(f" Deleted {len(wh_list)} Warehouse(s)") print(f" Deleted {len(wh_list)} Warehouse(s)")
# ── STEP 7: Delete Cost Centers ─────────────────────────────── # ── STEP 7: Delete Cost Centers ───────────────────────────────
cc_list = frappe.db.sql( cc_list = frappe.db.sql(
"SELECT name FROM `tabCost Center` WHERE company=%s ORDER BY lft DESC", "SELECT name FROM `tabCost Center` WHERE company=%s ORDER BY lft DESC",
COMPANY, as_dict=1 COMPANY,
as_dict=1,
) )
if cc_list: if cc_list:
for cc in cc_list: for cc in cc_list:
try: try:
frappe.db.sql( frappe.db.sql("DELETE FROM `tabCost Center` WHERE name=%s", cc.name)
"DELETE FROM `tabCost Center` WHERE name=%s", cc.name
)
except Exception: except Exception:
pass pass
frappe.db.commit() frappe.db.commit()
@ -149,21 +150,21 @@ def run():
frappe.db.sql( frappe.db.sql(
"DELETE FROM `tabAccount` WHERE company=%s AND is_group=0", COMPANY "DELETE FROM `tabAccount` WHERE company=%s AND is_group=0", COMPANY
) )
frappe.db.sql( frappe.db.sql("DELETE FROM `tabAccount` WHERE company=%s", COMPANY)
"DELETE FROM `tabAccount` WHERE company=%s", COMPANY
)
frappe.db.commit() frappe.db.commit()
print(f" Deleted {acct_count} Account(s)") print(f" Deleted {acct_count} Account(s)")
# ── STEP 9: Delete Fiscal Years linked only to this company ─── # ── STEP 9: Delete Fiscal Years linked only to this company ───
fy_links = frappe.db.sql( fy_links = frappe.db.sql(
"""SELECT parent FROM `tabFiscal Year Company` """SELECT parent FROM `tabFiscal Year Company`
WHERE company=%s""", COMPANY, as_dict=1 WHERE company=%s""",
COMPANY,
as_dict=1,
) )
for fy in fy_links: for fy in fy_links:
frappe.db.sql( frappe.db.sql(
"DELETE FROM `tabFiscal Year Company` WHERE company=%s AND parent=%s", "DELETE FROM `tabFiscal Year Company` WHERE company=%s AND parent=%s",
(COMPANY, fy.parent) (COMPANY, fy.parent),
) )
# If this fiscal year has no other company links, delete it too # If this fiscal year has no other company links, delete it too
remaining = frappe.db.count("Fiscal Year Company", {"parent": fy.parent}) remaining = frappe.db.count("Fiscal Year Company", {"parent": fy.parent})
@ -186,7 +187,8 @@ def run():
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 FROM `tabSales Order` SELECT 1 FROM `tabSales Order`
WHERE customer=c.name AND company='Furnitex' AND docstatus < 2 WHERE customer=c.name AND company='Furnitex' AND docstatus < 2
)""", as_dict=1 )""",
as_dict=1,
) )
if stale_customers: if stale_customers:
for c in stale_customers: for c in stale_customers:
@ -199,9 +201,13 @@ def run():
# ── STEP 11: Delete the Company record itself ───────────────── # ── STEP 11: Delete the Company record itself ─────────────────
try: try:
frappe.delete_doc("Company", COMPANY, frappe.delete_doc(
ignore_permissions=True, force=True, "Company",
ignore_on_trash=True) COMPANY,
ignore_permissions=True,
force=True,
ignore_on_trash=True,
)
except Exception: except Exception:
frappe.db.sql("DELETE FROM `tabCompany` WHERE name=%s", COMPANY) frappe.db.sql("DELETE FROM `tabCompany` WHERE name=%s", COMPANY)
frappe.db.commit() frappe.db.commit()
@ -219,7 +225,8 @@ def run():
"""SELECT name FROM `tabItem` """SELECT name FROM `tabItem`
WHERE item_name LIKE '%streetwok%' WHERE item_name LIKE '%streetwok%'
OR item_code LIKE '%streetwok%' OR item_code LIKE '%streetwok%'
OR description LIKE '%streetwok%'""", as_dict=1 OR description LIKE '%streetwok%'""",
as_dict=1,
) )
if stale_items: if stale_items:
for item in stale_items: for item in stale_items:

View file

@ -9,10 +9,10 @@ Or directly:
import frappe import frappe
import frappe.defaults import frappe.defaults
COMPANY = "Furnitex"
SITE = "frontend"
ABBR = None # resolved at runtime via get_abbr()
COMPANY = "Furnitex"
SITE = "frontend"
ABBR = None # resolved at runtime via get_abbr()
def get_abbr(): def get_abbr():
global ABBR global ABBR
@ -25,13 +25,16 @@ def get_abbr():
# HELPERS # HELPERS
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
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): def exists_filter(doctype, filters):
"""Existence check by filters (for docs where name includes company abbr).""" """Existence check by filters (for docs where name includes company abbr)."""
return frappe.db.get_value(doctype, filters, "name") return frappe.db.get_value(doctype, filters, "name")
def safe_insert(doc): def safe_insert(doc):
"""Insert and return True, or skip silently on duplicate and return False.""" """Insert and return True, or skip silently on duplicate and return False."""
try: try:
@ -45,12 +48,15 @@ def safe_insert(doc):
return False return False
raise raise
def ok(msg): def ok(msg):
print(f" [OK] {msg}") print(f" [OK] {msg}")
def skip(msg): def skip(msg):
print(f" [SKIP] {msg}") print(f" [SKIP] {msg}")
def warn(msg): def warn(msg):
print(f" [WARN] {msg}") print(f" [WARN] {msg}")
@ -59,23 +65,26 @@ def warn(msg):
# 1. UOMs # 1. UOMs
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_uoms(): def create_uoms():
print("\n[1/9] Creating UOMs...") print("\n[1/9] Creating UOMs...")
uoms = [ uoms = [
("SqFt", 0), ("SqFt", 0),
("Rft", 0), ("Rft", 0),
("Bag", 1), ("Bag", 1),
("Sheet", 1), ("Sheet", 1),
("Bundle", 1), ("Bundle", 1),
("Cubic Ft", 0), ("Cubic Ft", 0),
] ]
for uom_name, whole in uoms: for uom_name, whole in uoms:
if not exists("UOM", uom_name): if not exists("UOM", uom_name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "UOM", {
"uom_name": uom_name, "doctype": "UOM",
"must_be_whole_number": whole, "uom_name": uom_name,
}) "must_be_whole_number": whole,
}
)
d.flags.ignore_mandatory = True d.flags.ignore_mandatory = True
if safe_insert(d): if safe_insert(d):
ok(f"UOM: {uom_name}") ok(f"UOM: {uom_name}")
@ -90,25 +99,28 @@ def create_uoms():
# 2. ITEM GROUPS # 2. ITEM GROUPS
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_item_groups(): def create_item_groups():
print("\n[2/9] Creating Item Groups...") print("\n[2/9] Creating Item Groups...")
groups = [ groups = [
("Raw Materials - Furnitex", "All Item Groups"), ("Raw Materials - Furnitex", "All Item Groups"),
("Plywood & Board", "Raw Materials - Furnitex"), ("Plywood & Board", "Raw Materials - Furnitex"),
("Laminates & Veneer", "Raw Materials - Furnitex"), ("Laminates & Veneer", "Raw Materials - Furnitex"),
("Hardware & Fittings", "Raw Materials - Furnitex"), ("Hardware & Fittings", "Raw Materials - Furnitex"),
("Civil & Surface Materials", "Raw Materials - Furnitex"), ("Civil & Surface Materials", "Raw Materials - Furnitex"),
("Execution Services", "All Item Groups"), ("Execution Services", "All Item Groups"),
("Loose Furniture", "All Item Groups"), ("Loose Furniture", "All Item Groups"),
] ]
for name, parent in groups: for name, parent in groups:
if not exists("Item Group", name): if not exists("Item Group", name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Item Group", {
"item_group_name": name, "doctype": "Item Group",
"parent_item_group": parent, "item_group_name": name,
"is_group": 0, "parent_item_group": parent,
}) "is_group": 0,
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Item Group: {name}") ok(f"Item Group: {name}")
else: else:
@ -122,20 +134,23 @@ def create_item_groups():
# 3. SUPPLIER GROUPS # 3. SUPPLIER GROUPS
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_supplier_groups(): def create_supplier_groups():
print("\n[3/9] Creating Supplier Groups...") print("\n[3/9] Creating Supplier Groups...")
groups = [ groups = [
("Local Market Vendor (Unregistered)", "All Supplier Groups"), ("Local Market Vendor (Unregistered)", "All Supplier Groups"),
("GST Registered Vendor", "All Supplier Groups"), ("GST Registered Vendor", "All Supplier Groups"),
("Labour Contractor", "All Supplier Groups"), ("Labour Contractor", "All Supplier Groups"),
] ]
for name, parent in groups: for name, parent in groups:
if not exists("Supplier Group", name): if not exists("Supplier Group", name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Supplier Group", {
"supplier_group_name": name, "doctype": "Supplier Group",
"parent_supplier_group": parent, "supplier_group_name": name,
}) "parent_supplier_group": parent,
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Supplier Group: {name}") ok(f"Supplier Group: {name}")
else: else:
@ -149,34 +164,35 @@ def create_supplier_groups():
# 4. WAREHOUSES # 4. WAREHOUSES
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_warehouses(): def create_warehouses():
print("\n[4/9] Creating Warehouses...") print("\n[4/9] Creating Warehouses...")
abbr = get_abbr() abbr = get_abbr()
# Find the company's root warehouse group # Find the company's root warehouse group
root_wh = frappe.db.get_value( root_wh = frappe.db.get_value(
"Warehouse", "Warehouse", {"company": COMPANY, "is_group": 1}, "name"
{"company": COMPANY, "is_group": 1},
"name"
) )
if not root_wh: if not root_wh:
root_wh = f"All Warehouses - {abbr}" root_wh = f"All Warehouses - {abbr}"
warehouses = [ warehouses = [
("Main Store", root_wh, 0), ("Main Store", root_wh, 0),
("Rejected Stock", root_wh, 0), ("Rejected Stock", root_wh, 0),
] ]
for w_short, parent, is_group in warehouses: for w_short, parent, is_group in warehouses:
# ERPNext appends company abbr: "Main Store - F" # ERPNext appends company abbr: "Main Store - F"
w_full = f"{w_short} - {abbr}" w_full = f"{w_short} - {abbr}"
if not exists("Warehouse", w_full): if not exists("Warehouse", w_full):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Warehouse", {
"warehouse_name": w_short, "doctype": "Warehouse",
"parent_warehouse": parent, "warehouse_name": w_short,
"company": COMPANY, "parent_warehouse": parent,
"is_group": is_group, "company": COMPANY,
}) "is_group": is_group,
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Warehouse: {w_full}") ok(f"Warehouse: {w_full}")
else: else:
@ -190,6 +206,7 @@ def create_warehouses():
# 5. TAX TEMPLATES # 5. TAX TEMPLATES
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def _find_account(account_name_fragment, root_type=None, account_type=None): def _find_account(account_name_fragment, root_type=None, account_type=None):
"""Find an account by partial name match under the company.""" """Find an account by partial name match under the company."""
filters = {"company": COMPANY, "is_group": 0} filters = {"company": COMPANY, "is_group": 0}
@ -199,8 +216,9 @@ def _find_account(account_name_fragment, root_type=None, account_type=None):
filters["account_type"] = account_type filters["account_type"] = account_type
# Try exact name match first # Try exact name match first
result = frappe.db.get_value("Account", result = frappe.db.get_value(
dict(filters, account_name=account_name_fragment), "name") "Account", dict(filters, account_name=account_name_fragment), "name"
)
if result: if result:
return result return result
@ -211,7 +229,8 @@ def _find_account(account_name_fragment, root_type=None, account_type=None):
WHERE company=%s AND is_group=0 WHERE company=%s AND is_group=0
AND account_name LIKE %s AND account_name LIKE %s
LIMIT 1""", LIMIT 1""",
(COMPANY, like_pattern), as_dict=0 (COMPANY, like_pattern),
as_dict=0,
) )
return result[0][0] if result else None return result[0][0] if result else None
@ -222,15 +241,18 @@ def create_tax_templates():
# ── No GST (URD Purchase) ── # ── No GST (URD Purchase) ──
urd_title = "No GST - URD Purchase" urd_title = "No GST - URD Purchase"
if not exists_filter("Purchase Taxes and Charges Template", if not exists_filter(
{"title": urd_title, "company": COMPANY}): "Purchase Taxes and Charges Template", {"title": urd_title, "company": COMPANY}
d = frappe.get_doc({ ):
"doctype": "Purchase Taxes and Charges Template", d = frappe.get_doc(
"title": urd_title, {
"company": COMPANY, "doctype": "Purchase Taxes and Charges Template",
"is_default": 0, "title": urd_title,
"taxes": [], "company": COMPANY,
}) "is_default": 0,
"taxes": [],
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Purchase Tax Template: {urd_title}") ok(f"Purchase Tax Template: {urd_title}")
else: else:
@ -240,27 +262,45 @@ def create_tax_templates():
# ── GST 18% Purchase ── # ── GST 18% Purchase ──
gst18_p_title = "GST 18% - Purchase" gst18_p_title = "GST 18% - Purchase"
if not exists_filter("Purchase Taxes and Charges Template", if not exists_filter(
{"title": gst18_p_title, "company": COMPANY}): "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, taxes.append(
"rate": 9, "description": "CGST @ 9%"}) {
"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, taxes.append(
"rate": 9, "description": "SGST @ 9%"}) {
d = frappe.get_doc({ "charge_type": "On Net Total",
"doctype": "Purchase Taxes and Charges Template", "account_head": sgst,
"title": gst18_p_title, "rate": 9,
"company": COMPANY, "description": "SGST @ 9%",
"is_default": 0, }
"taxes": taxes, )
}) d = frappe.get_doc(
{
"doctype": "Purchase Taxes and Charges Template",
"title": gst18_p_title,
"company": COMPANY,
"is_default": 0,
"taxes": taxes,
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Purchase Tax Template: {gst18_p_title}" + ok(
(" (no GST accounts in CoA, left empty)" if not taxes else "")) f"Purchase Tax Template: {gst18_p_title}"
+ (" (no GST accounts in CoA, left empty)" if not taxes else "")
)
else: else:
skip(f"Purchase Tax Template: {gst18_p_title} (duplicate)") skip(f"Purchase Tax Template: {gst18_p_title} (duplicate)")
else: else:
@ -268,27 +308,44 @@ def create_tax_templates():
# ── GST 18% Sales ── # ── GST 18% Sales ──
gst18_s_title = "GST 18% - Sales" gst18_s_title = "GST 18% - Sales"
if not exists_filter("Sales Taxes and Charges Template", if not exists_filter(
{"title": gst18_s_title, "company": COMPANY}): "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, taxes.append(
"rate": 9, "description": "CGST @ 9%"}) {
"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, taxes.append(
"rate": 9, "description": "SGST @ 9%"}) {
d = frappe.get_doc({ "charge_type": "On Net Total",
"doctype": "Sales Taxes and Charges Template", "account_head": sgst,
"title": gst18_s_title, "rate": 9,
"company": COMPANY, "description": "SGST @ 9%",
"is_default": 0, }
"taxes": taxes, )
}) d = frappe.get_doc(
{
"doctype": "Sales Taxes and Charges Template",
"title": gst18_s_title,
"company": COMPANY,
"is_default": 0,
"taxes": taxes,
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Sales Tax Template: {gst18_s_title}" + ok(
(" (no GST accounts in CoA, left empty)" if not taxes else "")) f"Sales Tax Template: {gst18_s_title}"
+ (" (no GST accounts in CoA, left empty)" if not taxes else "")
)
else: else:
skip(f"Sales Tax Template: {gst18_s_title} (duplicate)") skip(f"Sales Tax Template: {gst18_s_title} (duplicate)")
else: else:
@ -301,43 +358,92 @@ def create_tax_templates():
# 6. SERVICE ITEMS (Non-stock billing items) # 6. SERVICE ITEMS (Non-stock billing items)
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_service_items(): def create_service_items():
print("\n[6/9] Creating Service Items...") print("\n[6/9] Creating Service Items...")
income_acct = _find_account("Sales", root_type="Income") or \ income_acct = _find_account("Sales", root_type="Income") or _find_account(
_find_account("Service", root_type="Income") "Service", root_type="Income"
)
items = [ items = [
# (code, name, uom, description) # (code, name, uom, description)
("SVC-FC-EXEC", "False Ceiling Execution", "SqFt", "Labour + material for false ceiling. Bill per SqFt OR as lumpsum."), (
("SVC-WAR-LAM", "Laminate Wardrobe Fabrication", "SqFt", "Laminate wardrobe fabrication per SqFt."), "SVC-FC-EXEC",
("SVC-KIT-EXEC", "Modular Kitchen Execution", "SqFt", "Modular kitchen fabrication and installation."), "False Ceiling Execution",
("SVC-FLOOR", "Flooring Execution", "SqFt", "Vinyl/Wood/Tile flooring supply and installation."), "SqFt",
("SVC-LUMP", "Lumpsum Contract Work", "Nos", "Fixed-price lumpsum milestone. Change qty to 1 always."), "Labour + material for false ceiling. Bill per SqFt OR as lumpsum.",
("SVC-DESIGN", "Interior Design Consultation", "Nos", "Design + drawing fee — lumpsum."), ),
("SVC-CONVEY", "Site Conveyance & Transport", "Nos", "Transport charges to/from site."), (
("SVC-LABOUR", "Direct Site Labour", "Nos", "Daily-wage labour charges."), "SVC-WAR-LAM",
("SVC-ELECTRIC", "Electrical Work Execution", "Nos", "Electrical points, wiring, fitting."), "Laminate Wardrobe Fabrication",
("SVC-PAINT", "Painting & Polish Execution", "SqFt", "Wall painting / wood polish per SqFt."), "SqFt",
"Laminate wardrobe fabrication per SqFt.",
),
(
"SVC-KIT-EXEC",
"Modular Kitchen Execution",
"SqFt",
"Modular kitchen fabrication and installation.",
),
(
"SVC-FLOOR",
"Flooring Execution",
"SqFt",
"Vinyl/Wood/Tile flooring supply and installation.",
),
(
"SVC-LUMP",
"Lumpsum Contract Work",
"Nos",
"Fixed-price lumpsum milestone. Change qty to 1 always.",
),
(
"SVC-DESIGN",
"Interior Design Consultation",
"Nos",
"Design + drawing fee — lumpsum.",
),
(
"SVC-CONVEY",
"Site Conveyance & Transport",
"Nos",
"Transport charges to/from site.",
),
("SVC-LABOUR", "Direct Site Labour", "Nos", "Daily-wage labour charges."),
(
"SVC-ELECTRIC",
"Electrical Work Execution",
"Nos",
"Electrical points, wiring, fitting.",
),
(
"SVC-PAINT",
"Painting & Polish Execution",
"SqFt",
"Wall painting / wood polish per SqFt.",
),
] ]
for code, name, uom, desc in items: for code, name, uom, desc in items:
if not exists("Item", code): if not exists("Item", code):
# Non-stock service items: no warehouse needed in item_defaults # Non-stock service items: no warehouse needed in item_defaults
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Item", {
"item_code": code, "doctype": "Item",
"item_name": name, "item_code": code,
"item_group": "Execution Services", "item_name": name,
"description": desc, "item_group": "Execution Services",
"stock_uom": uom, "description": desc,
"sales_uom": uom, "stock_uom": uom,
"is_stock_item": 0, "sales_uom": uom,
"is_purchase_item": 0, "is_stock_item": 0,
"is_sales_item": 1, "is_purchase_item": 0,
"standard_rate": 0, "is_sales_item": 1,
# No item_defaults row — avoids warehouse company-mismatch validation "standard_rate": 0,
}) # No item_defaults row — avoids warehouse company-mismatch validation
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Service Item: {code} [{uom}]") ok(f"Service Item: {code} [{uom}]")
else: else:
@ -352,12 +458,15 @@ def create_service_items():
# 7. RAW MATERIAL ITEMS (Stock items) # 7. RAW MATERIAL ITEMS (Stock items)
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_raw_material_items(): def create_raw_material_items():
print("\n[7/9] Creating Raw Material Items...") print("\n[7/9] Creating Raw Material Items...")
cogs_acct = (_find_account("Cost of Goods Sold", root_type="Expense") or cogs_acct = (
_find_account("Stock Expenses", root_type="Expense") or _find_account("Cost of Goods Sold", root_type="Expense")
_find_account("Expenses Included", root_type="Expense")) or _find_account("Stock Expenses", root_type="Expense")
or _find_account("Expenses Included", root_type="Expense")
)
default_wh = f"Main Store - {get_abbr()}" default_wh = f"Main Store - {get_abbr()}"
if not exists("Warehouse", default_wh): if not exists("Warehouse", default_wh):
@ -367,39 +476,44 @@ def create_raw_material_items():
items = [ items = [
# (code, name, uom, group) # (code, name, uom, group)
("RM-PLY-19MM", "Plywood 19mm BWR 8x4", "Sheet", "Plywood & Board"), ("RM-PLY-19MM", "Plywood 19mm BWR 8x4", "Sheet", "Plywood & Board"),
("RM-PLY-12MM", "Plywood 12mm BWR 8x4", "Sheet", "Plywood & Board"), ("RM-PLY-12MM", "Plywood 12mm BWR 8x4", "Sheet", "Plywood & Board"),
("RM-PLY-6MM", "Plywood 6mm 8x4", "Sheet", "Plywood & Board"), ("RM-PLY-6MM", "Plywood 6mm 8x4", "Sheet", "Plywood & Board"),
("RM-MDF-18MM", "MDF Board 18mm 8x4", "Sheet", "Plywood & Board"), ("RM-MDF-18MM", "MDF Board 18mm 8x4", "Sheet", "Plywood & Board"),
("RM-HDF-3MM", "HDF 3mm 8x4", "Sheet", "Plywood & Board"), ("RM-HDF-3MM", "HDF 3mm 8x4", "Sheet", "Plywood & Board"),
("RM-LAM-1MM", "Laminate Sheet 1mm 8x4", "Sheet", "Laminates & Veneer"), ("RM-LAM-1MM", "Laminate Sheet 1mm 8x4", "Sheet", "Laminates & Veneer"),
("RM-VEN-NAT", "Veneer Sheet Natural Wood", "Sheet", "Laminates & Veneer"), ("RM-VEN-NAT", "Veneer Sheet Natural Wood", "Sheet", "Laminates & Veneer"),
("RM-LAM-ACRY", "Acrylic Laminate Sheet", "Sheet", "Laminates & Veneer"), ("RM-LAM-ACRY", "Acrylic Laminate Sheet", "Sheet", "Laminates & Veneer"),
("RM-HW-HINGE", "Concealed Hinge (pair)", "Nos", "Hardware & Fittings"), ("RM-HW-HINGE", "Concealed Hinge (pair)", "Nos", "Hardware & Fittings"),
("RM-HW-CHAN18", "Drawer Channel 18 inch", "Nos", "Hardware & Fittings"), ("RM-HW-CHAN18", "Drawer Channel 18 inch", "Nos", "Hardware & Fittings"),
("RM-HW-CHAN24", "Drawer Channel 24 inch", "Nos", "Hardware & Fittings"), ("RM-HW-CHAN24", "Drawer Channel 24 inch", "Nos", "Hardware & Fittings"),
("RM-HW-HANDLE", "Cabinet Handle", "Nos", "Hardware & Fittings"), ("RM-HW-HANDLE", "Cabinet Handle", "Nos", "Hardware & Fittings"),
("RM-HW-LOCK", "Drawer Lock", "Nos", "Hardware & Fittings"), ("RM-HW-LOCK", "Drawer Lock", "Nos", "Hardware & Fittings"),
("RM-HW-SCREW", "Wood Screw Assorted", "Bundle", "Hardware & Fittings"), ("RM-HW-SCREW", "Wood Screw Assorted", "Bundle", "Hardware & Fittings"),
("RM-CIV-CEM", "OPC Cement 53 Grade", "Bag", "Civil & Surface Materials"), ("RM-CIV-CEM", "OPC Cement 53 Grade", "Bag", "Civil & Surface Materials"),
("RM-CIV-PUTTY", "Wall Putty White", "Bag", "Civil & Surface Materials"), ("RM-CIV-PUTTY", "Wall Putty White", "Bag", "Civil & Surface Materials"),
("RM-CIV-PRIMER","Primer Interior", "Nos", "Civil & Surface Materials"), ("RM-CIV-PRIMER", "Primer Interior", "Nos", "Civil & Surface Materials"),
("RM-CIV-GYPS", "Gypsum Board 8x4 12.5mm", "Sheet", "Civil & Surface Materials"), (
("RM-CIV-GYPS-C","Gypsum Cornice / Grid", "Rft", "Civil & Surface Materials"), "RM-CIV-GYPS",
"Gypsum Board 8x4 12.5mm",
"Sheet",
"Civil & Surface Materials",
),
("RM-CIV-GYPS-C", "Gypsum Cornice / Grid", "Rft", "Civil & Surface Materials"),
] ]
for code, name, uom, group in items: for code, name, uom, group in items:
if not exists("Item", code): if not exists("Item", code):
d_dict = { d_dict = {
"doctype": "Item", "doctype": "Item",
"item_code": code, "item_code": code,
"item_name": name, "item_name": name,
"item_group": group, "item_group": group,
"stock_uom": uom, "stock_uom": uom,
"is_stock_item": 1, "is_stock_item": 1,
"is_purchase_item": 1, "is_purchase_item": 1,
"is_sales_item": 0, "is_sales_item": 0,
"valuation_method": "FIFO", "valuation_method": "FIFO",
} }
# Only add item_defaults if we have a valid Furnitex warehouse # Only add item_defaults if we have a valid Furnitex warehouse
if default_wh or cogs_acct: if default_wh or cogs_acct:
@ -425,29 +539,52 @@ def create_raw_material_items():
# 8. SAMPLE SUPPLIERS # 8. SAMPLE SUPPLIERS
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_suppliers(): def create_suppliers():
print("\n[8/9] Creating Sample Suppliers...") print("\n[8/9] Creating Sample Suppliers...")
suppliers = [ suppliers = [
# (name, group, gst_category) # (name, group, gst_category)
("Local Hardware Market - Kolkata", "Local Market Vendor (Unregistered)", "Unregistered"), (
("Burrabazar Plywood Supplier", "Local Market Vendor (Unregistered)", "Unregistered"), "Local Hardware Market - Kolkata",
("Fancy Laminates - Howrah", "Local Market Vendor (Unregistered)", "Unregistered"), "Local Market Vendor (Unregistered)",
("Modern Furniture Hardware - BBD Bag","Local Market Vendor (Unregistered)", "Unregistered"), "Unregistered",
("Registered Hardware Supplier Ltd", "GST Registered Vendor", "Registered Regular"), ),
("Site Labour Contractor - Ramesh", "Labour Contractor", "Unregistered"), (
"Burrabazar Plywood Supplier",
"Local Market Vendor (Unregistered)",
"Unregistered",
),
(
"Fancy Laminates - Howrah",
"Local Market Vendor (Unregistered)",
"Unregistered",
),
(
"Modern Furniture Hardware - BBD Bag",
"Local Market Vendor (Unregistered)",
"Unregistered",
),
(
"Registered Hardware Supplier Ltd",
"GST Registered Vendor",
"Registered Regular",
),
("Site Labour Contractor - Ramesh", "Labour Contractor", "Unregistered"),
] ]
for name, group, gst_cat in suppliers: for name, group, gst_cat in suppliers:
if not exists("Supplier", name): if not exists("Supplier", name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Supplier", {
"supplier_name": name, "doctype": "Supplier",
"supplier_group": group, "supplier_name": name,
"country": "India", "supplier_group": group,
"gst_category": gst_cat, "country": "India",
"default_currency": "INR", "gst_category": gst_cat,
}) "default_currency": "INR",
}
)
if safe_insert(d): if safe_insert(d):
ok(f"Supplier: {name} [{gst_cat}]") ok(f"Supplier: {name} [{gst_cat}]")
else: else:
@ -461,8 +598,9 @@ def create_suppliers():
# URD tax bypass is handled instead by the server script: # URD tax bypass is handled instead by the server script:
# "Furnitex - Clear GST on URD Purchase" (Before Save on Purchase Invoice) # "Furnitex - Clear GST on URD Purchase" (Before Save on Purchase Invoice)
# Tick the "URD Purchase (No GST)" checkbox on any invoice to auto-clear taxes. # Tick the "URD Purchase (No GST)" checkbox on any invoice to auto-clear taxes.
urd_suppliers_count = frappe.db.count("Supplier", urd_suppliers_count = frappe.db.count(
{"supplier_group": "Local Market Vendor (Unregistered)"}) "Supplier", {"supplier_group": "Local Market Vendor (Unregistered)"}
)
ok(f"URD tax via server script — {urd_suppliers_count} URD suppliers registered") ok(f"URD tax via server script — {urd_suppliers_count} URD suppliers registered")
@ -470,6 +608,7 @@ def create_suppliers():
# 9. CUSTOM FIELDS # 9. CUSTOM FIELDS
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_custom_fields(): def create_custom_fields():
print("\n[9/9] Creating Custom Fields...") print("\n[9/9] Creating Custom Fields...")
@ -481,25 +620,31 @@ def create_custom_fields():
# (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)", (
"Check", None, "supplier", 1), "Purchase Invoice",
("Journal Entry", "project", "Project", "is_urd_purchase",
"Link", "Project", "voucher_type", 0), "URD Purchase (No GST)",
"Check",
None,
"supplier",
1,
),
("Journal Entry", "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
@ -512,19 +657,22 @@ def create_custom_fields():
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") ok(
"Native 'project' field already present on PI, PO, Stock Entry, DN — no custom fields needed there"
)
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
# 10. SERVER SCRIPTS (Automation) # 10. SERVER SCRIPTS (Automation)
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def create_server_scripts(): def create_server_scripts():
print("\n[+] Creating Server Scripts...") print("\n[+] Creating Server Scripts...")
# Script 1: Auto-create OnSite WIP Warehouse on Project insert # Script 1: Auto-create OnSite WIP Warehouse on Project insert
wip_script_name = "Furnitex - Auto Create OnSite WIP Warehouse" wip_script_name = "Furnitex - Auto Create OnSite WIP Warehouse"
wip_script_code = """ wip_script_code = """
# Auto-fires after a new Project is saved # Auto-fires after a new Project is saved
# Creates "{Project Name} - OnSite WIP" warehouse automatically # Creates "{Project Name} - OnSite WIP" warehouse automatically
@ -560,15 +708,17 @@ if not frappe.db.exists("Warehouse", warehouse_name):
""" """
if not exists("Server Script", wip_script_name): if not exists("Server Script", wip_script_name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Server Script", {
"name": wip_script_name, "doctype": "Server Script",
"script_type": "DocType Event", "name": wip_script_name,
"reference_doctype": "Project", "script_type": "DocType Event",
"doctype_event": "After Insert", "reference_doctype": "Project",
"enabled": 1, "doctype_event": "After Insert",
"script": wip_script_code, "enabled": 1,
}) "script": wip_script_code,
}
)
d.flags.ignore_permissions = True d.flags.ignore_permissions = True
d.insert() d.insert()
ok(f"Server Script: {wip_script_name}") ok(f"Server Script: {wip_script_name}")
@ -597,15 +747,17 @@ if doc.is_urd_purchase:
""" """
if not exists("Server Script", urd_script_name): if not exists("Server Script", urd_script_name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Server Script", {
"name": urd_script_name, "doctype": "Server Script",
"script_type": "DocType Event", "name": urd_script_name,
"reference_doctype": "Purchase Invoice", "script_type": "DocType Event",
"doctype_event": "Before Save", "reference_doctype": "Purchase Invoice",
"enabled": 1, "doctype_event": "Before Save",
"script": urd_script_code, "enabled": 1,
}) "script": urd_script_code,
}
)
d.flags.ignore_permissions = True d.flags.ignore_permissions = True
d.insert() d.insert()
ok(f"Server Script: {urd_script_name}") ok(f"Server Script: {urd_script_name}")
@ -706,19 +858,22 @@ def get_data(filters):
return rows return rows
''' '''
def create_custom_report(): def create_custom_report():
print("\n[+] Creating Custom Report: Furnitex Project Profitability...") print("\n[+] Creating Custom Report: Furnitex Project Profitability...")
report_name = "Furnitex Project Profitability" report_name = "Furnitex Project Profitability"
if not exists("Report", report_name): if not exists("Report", report_name):
d = frappe.get_doc({ d = frappe.get_doc(
"doctype": "Report", {
"report_name": report_name, "doctype": "Report",
"ref_doctype": "Project", "report_name": report_name,
"report_type": "Script Report", "ref_doctype": "Project",
"is_standard": "No", "report_type": "Script Report",
"module": "Projects", "is_standard": "No",
"script": PROFIT_REPORT_SCRIPT, "module": "Projects",
}) "script": PROFIT_REPORT_SCRIPT,
}
)
d.flags.ignore_permissions = True d.flags.ignore_permissions = True
d.insert() d.insert()
ok(f"Custom Report: {report_name}") ok(f"Custom Report: {report_name}")
@ -731,6 +886,7 @@ def create_custom_report():
# MASTER RUNNER # MASTER RUNNER
# ───────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────
def run_all(): def run_all():
frappe.set_user("Administrator") frappe.set_user("Administrator")
print("\n" + "=" * 58) print("\n" + "=" * 58)
@ -759,6 +915,7 @@ def run_all():
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
frappe.init(site="frontend") frappe.init(site="frontend")
frappe.connect() frappe.connect()
run_all() run_all()

File diff suppressed because it is too large Load diff

View file

@ -8,20 +8,20 @@ import frappe
COMPANY = "Furnitex" COMPANY = "Furnitex"
ADDRESS = "6/A/108 Mukundapur" ADDRESS = "6/A/108 Mukundapur"
CITY = "Kolkata" CITY = "Kolkata"
STATE = "West Bengal" STATE = "West Bengal"
PINCODE = "700099" PINCODE = "700099"
COUNTRY = "India" COUNTRY = "India"
PHONE = "+91 62905 91422" PHONE = "+91 62905 91422"
EMAIL = "info.furnitex@gmail.com" EMAIL = "info.furnitex@gmail.com"
WEBSITE = "https://furnitex.co.in" WEBSITE = "https://furnitex.co.in"
INSTAGRAM = "https://www.instagram.com/frunitex" INSTAGRAM = "https://www.instagram.com/frunitex"
LEGAL_NAME = "Furnitex Atelier Pvt. Ltd." LEGAL_NAME = "Furnitex Atelier Pvt. Ltd."
TAGLINE = "Redefine What Surrounds You" TAGLINE = "Redefine What Surrounds You"
FULL_ADDR = f"{ADDRESS}, {CITY} - {PINCODE}, {STATE}, {COUNTRY}" FULL_ADDR = f"{ADDRESS}, {CITY} - {PINCODE}, {STATE}, {COUNTRY}"
REP_NAME = "Subhankar Dhar" REP_NAME = "Subhankar Dhar"
OFFICE_HOURS = "Monday Saturday, 10:00 AM 7:00 PM IST" OFFICE_HOURS = "Monday Saturday, 10:00 AM 7:00 PM IST"
def run(): def run():
@ -39,12 +39,13 @@ def run():
# ── 1. Company record ───────────────────────────────────────────────────────── # ── 1. Company record ─────────────────────────────────────────────────────────
def update_company(): def update_company():
co = frappe.get_doc("Company", COMPANY) co = frappe.get_doc("Company", COMPANY)
co.phone_no = PHONE co.phone_no = PHONE
co.email = EMAIL co.email = EMAIL
co.website = WEBSITE co.website = WEBSITE
co.company_name = COMPANY # keep short name as primary co.company_name = COMPANY # keep short name as primary
co.flags.ignore_permissions = True co.flags.ignore_permissions = True
co.save() co.save()
print(f" ✓ Company record updated: phone={PHONE}, email={EMAIL}") print(f" ✓ Company record updated: phone={PHONE}, email={EMAIL}")
@ -52,30 +53,26 @@ def update_company():
# ── 2. Address record ───────────────────────────────────────────────────────── # ── 2. Address record ─────────────────────────────────────────────────────────
def update_address(): def update_address():
# Check if a Furnitex address already exists # Check if a Furnitex address already exists
existing = frappe.db.get_value( existing = frappe.db.get_value(
"Address", "Address", {"address_title": COMPANY, "address_type": "Billing"}, "name"
{"address_title": COMPANY, "address_type": "Billing"},
"name"
) )
addr_doc = { addr_doc = {
"doctype": "Address", "doctype": "Address",
"address_title": COMPANY, "address_title": COMPANY,
"address_type": "Billing", "address_type": "Billing",
"address_line1": ADDRESS, "address_line1": ADDRESS,
"city": CITY, "city": CITY,
"state": STATE, "state": STATE,
"pincode": PINCODE, "pincode": PINCODE,
"country": COUNTRY, "country": COUNTRY,
"phone": PHONE, "phone": PHONE,
"email_id": EMAIL, "email_id": EMAIL,
"is_primary_address": 1, "is_primary_address": 1,
"links": [{ "links": [{"link_doctype": "Company", "link_name": COMPANY}],
"link_doctype": "Company",
"link_name": COMPANY
}]
} }
if existing: if existing:
@ -120,6 +117,7 @@ LETTER_HEAD_HTML = f"""
</div> </div>
""" """
def update_letter_head(): def update_letter_head():
lh_name = "Furnitex" lh_name = "Furnitex"
if frappe.db.exists("Letter Head", lh_name): if frappe.db.exists("Letter Head", lh_name):
@ -130,12 +128,14 @@ def update_letter_head():
doc.save() doc.save()
print(" ✓ Letter Head updated with real contact details") print(" ✓ Letter Head updated with real contact details")
else: else:
doc = frappe.get_doc({ doc = frappe.get_doc(
"doctype": "Letter Head", {
"letter_head_name": lh_name, "doctype": "Letter Head",
"content": LETTER_HEAD_HTML, "letter_head_name": lh_name,
"is_default": 1, "content": LETTER_HEAD_HTML,
}) "is_default": 1,
}
)
doc.flags.ignore_permissions = True doc.flags.ignore_permissions = True
doc.insert() doc.insert()
print(" ✓ Letter Head created") print(" ✓ Letter Head created")
@ -186,11 +186,12 @@ PO_TC = f"""<div style="font-size:10.5px; line-height:1.8; color:#333;">
</div>""" </div>"""
TC_MAP = { TC_MAP = {
"Furnitex - Quotation T&C": QUOTATION_TC, "Furnitex - Quotation T&C": QUOTATION_TC,
"Furnitex - Invoice T&C": INVOICE_TC, "Furnitex - Invoice T&C": INVOICE_TC,
"Furnitex - Purchase Order T&C": PO_TC, "Furnitex - Purchase Order T&C": PO_TC,
} }
def update_terms_conditions(): def update_terms_conditions():
for title, content in TC_MAP.items(): for title, content in TC_MAP.items():
if frappe.db.exists("Terms and Conditions", title): if frappe.db.exists("Terms and Conditions", title):
@ -200,14 +201,16 @@ def update_terms_conditions():
doc.save() doc.save()
print(f" ✓ Updated T&C: {title}") print(f" ✓ Updated T&C: {title}")
else: else:
doc = frappe.get_doc({ doc = frappe.get_doc(
"doctype": "Terms and Conditions", {
"title": title, "doctype": "Terms and Conditions",
"terms": content, "title": title,
"selling": 1, "terms": content,
"buying": 1, "selling": 1,
"hr": 0, "buying": 1,
}) "hr": 0,
}
)
doc.flags.ignore_permissions = True doc.flags.ignore_permissions = True
doc.insert() doc.insert()
print(f" ✓ Created T&C: {title}") print(f" ✓ Created T&C: {title}")