mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-22 15:55:09 +00:00
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:
parent
4678d517ae
commit
c40e7992cb
4 changed files with 1008 additions and 586 deletions
|
|
@ -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)...")
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ Or directly:
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
|
|
||||||
|
|
||||||
COMPANY = "Furnitex"
|
COMPANY = "Furnitex"
|
||||||
SITE = "frontend"
|
SITE = "frontend"
|
||||||
ABBR = None # resolved at runtime via get_abbr()
|
ABBR = None # resolved at runtime via get_abbr()
|
||||||
|
|
||||||
|
|
||||||
def get_abbr():
|
def get_abbr():
|
||||||
global ABBR
|
global ABBR
|
||||||
if not ABBR:
|
if not 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,6 +65,7 @@ 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 = [
|
||||||
|
|
@ -71,11 +78,13 @@ def create_uoms():
|
||||||
]
|
]
|
||||||
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",
|
"doctype": "UOM",
|
||||||
"uom_name": uom_name,
|
"uom_name": uom_name,
|
||||||
"must_be_whole_number": whole,
|
"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,6 +99,7 @@ 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 = [
|
||||||
|
|
@ -103,12 +113,14 @@ def create_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",
|
"doctype": "Item Group",
|
||||||
"item_group_name": name,
|
"item_group_name": name,
|
||||||
"parent_item_group": parent,
|
"parent_item_group": parent,
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
if safe_insert(d):
|
if safe_insert(d):
|
||||||
ok(f"Item Group: {name}")
|
ok(f"Item Group: {name}")
|
||||||
else:
|
else:
|
||||||
|
|
@ -122,6 +134,7 @@ 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 = [
|
||||||
|
|
@ -131,11 +144,13 @@ def create_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",
|
"doctype": "Supplier Group",
|
||||||
"supplier_group_name": name,
|
"supplier_group_name": name,
|
||||||
"parent_supplier_group": parent,
|
"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,15 +164,14 @@ 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}"
|
||||||
|
|
@ -170,13 +184,15 @@ def create_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",
|
"doctype": "Warehouse",
|
||||||
"warehouse_name": w_short,
|
"warehouse_name": w_short,
|
||||||
"parent_warehouse": parent,
|
"parent_warehouse": parent,
|
||||||
"company": COMPANY,
|
"company": COMPANY,
|
||||||
"is_group": is_group,
|
"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({
|
):
|
||||||
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Purchase Taxes and Charges Template",
|
"doctype": "Purchase Taxes and Charges Template",
|
||||||
"title": urd_title,
|
"title": urd_title,
|
||||||
"company": COMPANY,
|
"company": COMPANY,
|
||||||
"is_default": 0,
|
"is_default": 0,
|
||||||
"taxes": [],
|
"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",
|
||||||
|
"account_head": sgst,
|
||||||
|
"rate": 9,
|
||||||
|
"description": "SGST @ 9%",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Purchase Taxes and Charges Template",
|
"doctype": "Purchase Taxes and Charges Template",
|
||||||
"title": gst18_p_title,
|
"title": gst18_p_title,
|
||||||
"company": COMPANY,
|
"company": COMPANY,
|
||||||
"is_default": 0,
|
"is_default": 0,
|
||||||
"taxes": taxes,
|
"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",
|
||||||
|
"account_head": sgst,
|
||||||
|
"rate": 9,
|
||||||
|
"description": "SGST @ 9%",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Sales Taxes and Charges Template",
|
"doctype": "Sales Taxes and Charges Template",
|
||||||
"title": gst18_s_title,
|
"title": gst18_s_title,
|
||||||
"company": COMPANY,
|
"company": COMPANY,
|
||||||
"is_default": 0,
|
"is_default": 0,
|
||||||
"taxes": taxes,
|
"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,30 +358,78 @@ 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-WAR-LAM",
|
||||||
|
"Laminate Wardrobe Fabrication",
|
||||||
|
"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-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."),
|
"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",
|
"doctype": "Item",
|
||||||
"item_code": code,
|
"item_code": code,
|
||||||
"item_name": name,
|
"item_name": name,
|
||||||
|
|
@ -337,7 +442,8 @@ def create_service_items():
|
||||||
"is_sales_item": 1,
|
"is_sales_item": 1,
|
||||||
"standard_rate": 0,
|
"standard_rate": 0,
|
||||||
# No item_defaults row — avoids warehouse company-mismatch validation
|
# 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):
|
||||||
|
|
@ -384,7 +493,12 @@ def create_raw_material_items():
|
||||||
("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",
|
||||||
|
"Gypsum Board 8x4 12.5mm",
|
||||||
|
"Sheet",
|
||||||
|
"Civil & Surface Materials",
|
||||||
|
),
|
||||||
("RM-CIV-GYPS-C", "Gypsum Cornice / Grid", "Rft", "Civil & Surface Materials"),
|
("RM-CIV-GYPS-C", "Gypsum Cornice / Grid", "Rft", "Civil & Surface Materials"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -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"),
|
),
|
||||||
|
(
|
||||||
|
"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"),
|
("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",
|
"doctype": "Supplier",
|
||||||
"supplier_name": name,
|
"supplier_name": name,
|
||||||
"supplier_group": group,
|
"supplier_group": group,
|
||||||
"country": "India",
|
"country": "India",
|
||||||
"gst_category": gst_cat,
|
"gst_category": gst_cat,
|
||||||
"default_currency": "INR",
|
"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,10 +620,16 @@ 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:
|
||||||
|
|
@ -512,13 +657,16 @@ 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...")
|
||||||
|
|
||||||
|
|
@ -560,7 +708,8 @@ 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",
|
"doctype": "Server Script",
|
||||||
"name": wip_script_name,
|
"name": wip_script_name,
|
||||||
"script_type": "DocType Event",
|
"script_type": "DocType Event",
|
||||||
|
|
@ -568,7 +717,8 @@ if not frappe.db.exists("Warehouse", warehouse_name):
|
||||||
"doctype_event": "After Insert",
|
"doctype_event": "After Insert",
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
"script": wip_script_code,
|
"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,7 +747,8 @@ 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",
|
"doctype": "Server Script",
|
||||||
"name": urd_script_name,
|
"name": urd_script_name,
|
||||||
"script_type": "DocType Event",
|
"script_type": "DocType Event",
|
||||||
|
|
@ -605,7 +756,8 @@ if doc.is_urd_purchase:
|
||||||
"doctype_event": "Before Save",
|
"doctype_event": "Before Save",
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
"script": urd_script_code,
|
"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,11 +858,13 @@ 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",
|
"doctype": "Report",
|
||||||
"report_name": report_name,
|
"report_name": report_name,
|
||||||
"ref_doctype": "Project",
|
"ref_doctype": "Project",
|
||||||
|
|
@ -718,7 +872,8 @@ def create_custom_report():
|
||||||
"is_standard": "No",
|
"is_standard": "No",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"script": PROFIT_REPORT_SCRIPT,
|
"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()
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,13 @@ import frappe
|
||||||
COMPANY = "Furnitex"
|
COMPANY = "Furnitex"
|
||||||
|
|
||||||
|
|
||||||
def ok(msg): print(f" [OK] {msg}")
|
def ok(msg):
|
||||||
def skip(msg): print(f" [SKIP] {msg}")
|
print(f" [OK] {msg}")
|
||||||
|
|
||||||
|
|
||||||
|
def skip(msg):
|
||||||
|
print(f" [SKIP] {msg}")
|
||||||
|
|
||||||
|
|
||||||
def safe_insert(doc):
|
def safe_insert(doc):
|
||||||
try:
|
try:
|
||||||
|
|
@ -25,9 +30,11 @@ def safe_insert(doc):
|
||||||
return False
|
return False
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def exists(dt, name):
|
def exists(dt, name):
|
||||||
return frappe.db.exists(dt, name)
|
return frappe.db.exists(dt, name)
|
||||||
|
|
||||||
|
|
||||||
def exists_f(dt, filters):
|
def exists_f(dt, filters):
|
||||||
return frappe.db.get_value(dt, filters, "name")
|
return frappe.db.get_value(dt, filters, "name")
|
||||||
|
|
||||||
|
|
@ -48,14 +55,18 @@ def create_customer_groups():
|
||||||
]
|
]
|
||||||
for name, parent in groups:
|
for name, parent in groups:
|
||||||
if not exists("Customer Group", name):
|
if not exists("Customer Group", name):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Customer Group",
|
"doctype": "Customer Group",
|
||||||
"customer_group_name": name,
|
"customer_group_name": name,
|
||||||
"parent_customer_group": parent,
|
"parent_customer_group": parent,
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Customer Group: {name}")
|
)
|
||||||
else: skip(f"{name} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Customer Group: {name}")
|
||||||
|
else:
|
||||||
|
skip(f"{name} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Customer Group: {name}")
|
skip(f"Customer Group: {name}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -80,14 +91,18 @@ def create_territories():
|
||||||
]
|
]
|
||||||
for name, parent in territories:
|
for name, parent in territories:
|
||||||
if not exists("Territory", name):
|
if not exists("Territory", name):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Territory",
|
"doctype": "Territory",
|
||||||
"territory_name": name,
|
"territory_name": name,
|
||||||
"parent_territory": parent,
|
"parent_territory": parent,
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Territory: {name}")
|
)
|
||||||
else: skip(f"{name} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Territory: {name}")
|
||||||
|
else:
|
||||||
|
skip(f"{name} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Territory: {name}")
|
skip(f"Territory: {name}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -96,7 +111,8 @@ def create_territories():
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
# 3. LEAD SOURCES (added as custom Select field on Lead)
|
# 3. LEAD SOURCES (added as custom Select field on Lead)
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
LEAD_SOURCES = "\n".join([
|
LEAD_SOURCES = "\n".join(
|
||||||
|
[
|
||||||
"Instagram",
|
"Instagram",
|
||||||
"Facebook",
|
"Facebook",
|
||||||
"Word of Mouth",
|
"Word of Mouth",
|
||||||
|
|
@ -112,17 +128,23 @@ LEAD_SOURCES = "\n".join([
|
||||||
"Exhibition / Home Fair",
|
"Exhibition / Home Fair",
|
||||||
"YouTube",
|
"YouTube",
|
||||||
"WhatsApp Broadcast",
|
"WhatsApp Broadcast",
|
||||||
])
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_lead_sources():
|
def create_lead_sources():
|
||||||
# Lead Source is a standalone CRM-app doctype not installed here.
|
# Lead Source is a standalone CRM-app doctype not installed here.
|
||||||
# We add it as a custom Select field on Lead + Opportunity instead.
|
# We add it as a custom Select field on Lead + Opportunity instead.
|
||||||
print("\n[3] Lead Sources (as custom Select field)...")
|
print("\n[3] Lead Sources (as custom Select field)...")
|
||||||
|
|
||||||
for dt, after_field in [("Lead", "qualification_status"), ("Opportunity", "opportunity_type")]:
|
for dt, after_field in [
|
||||||
|
("Lead", "qualification_status"),
|
||||||
|
("Opportunity", "opportunity_type"),
|
||||||
|
]:
|
||||||
cf_name = f"{dt}-furnitex_lead_source"
|
cf_name = f"{dt}-furnitex_lead_source"
|
||||||
if not exists("Custom Field", cf_name):
|
if not exists("Custom Field", cf_name):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Custom Field",
|
"doctype": "Custom Field",
|
||||||
"dt": dt,
|
"dt": dt,
|
||||||
"fieldname": "furnitex_lead_source",
|
"fieldname": "furnitex_lead_source",
|
||||||
|
|
@ -132,9 +154,12 @@ def create_lead_sources():
|
||||||
"insert_after": after_field,
|
"insert_after": after_field,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Lead Source Select field → {dt}")
|
)
|
||||||
else: skip(f"{dt}.furnitex_lead_source (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Lead Source Select field → {dt}")
|
||||||
|
else:
|
||||||
|
skip(f"{dt}.furnitex_lead_source (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Lead Source field exists on {dt}")
|
skip(f"Lead Source field exists on {dt}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -152,15 +177,19 @@ def create_sales_persons():
|
||||||
]
|
]
|
||||||
for name, parent in persons:
|
for name, parent in persons:
|
||||||
if not exists("Sales Person", name):
|
if not exists("Sales Person", name):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Sales Person",
|
"doctype": "Sales Person",
|
||||||
"sales_person_name": name,
|
"sales_person_name": name,
|
||||||
"parent_sales_person": parent,
|
"parent_sales_person": parent,
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Sales Person: {name}")
|
)
|
||||||
else: skip(f"{name} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Sales Person: {name}")
|
||||||
|
else:
|
||||||
|
skip(f"{name} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Sales Person: {name}")
|
skip(f"Sales Person: {name}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -181,10 +210,12 @@ def create_payment_modes():
|
||||||
]
|
]
|
||||||
|
|
||||||
# find default bank account
|
# find default bank account
|
||||||
bank_acct = frappe.db.get_value("Account",
|
bank_acct = frappe.db.get_value(
|
||||||
{"account_type": "Bank", "company": COMPANY, "is_group": 0}, "name")
|
"Account", {"account_type": "Bank", "company": COMPANY, "is_group": 0}, "name"
|
||||||
cash_acct = frappe.db.get_value("Account",
|
)
|
||||||
{"account_type": "Cash", "company": COMPANY, "is_group": 0}, "name")
|
cash_acct = frappe.db.get_value(
|
||||||
|
"Account", {"account_type": "Cash", "company": COMPANY, "is_group": 0}, "name"
|
||||||
|
)
|
||||||
|
|
||||||
for name, mtype in modes:
|
for name, mtype in modes:
|
||||||
if not exists("Mode of Payment", name):
|
if not exists("Mode of Payment", name):
|
||||||
|
|
@ -195,13 +226,17 @@ def create_payment_modes():
|
||||||
}
|
}
|
||||||
acct = bank_acct if mtype == "Bank" else cash_acct
|
acct = bank_acct if mtype == "Bank" else cash_acct
|
||||||
if acct:
|
if acct:
|
||||||
d_dict["accounts"] = [{
|
d_dict["accounts"] = [
|
||||||
|
{
|
||||||
"company": COMPANY,
|
"company": COMPANY,
|
||||||
"default_account": acct,
|
"default_account": acct,
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
d = frappe.get_doc(d_dict)
|
d = frappe.get_doc(d_dict)
|
||||||
if safe_insert(d): ok(f"Mode of Payment: {name}")
|
if safe_insert(d):
|
||||||
else: skip(f"{name} (dup)")
|
ok(f"Mode of Payment: {name}")
|
||||||
|
else:
|
||||||
|
skip(f"{name} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Mode of Payment: {name}")
|
skip(f"Mode of Payment: {name}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -214,62 +249,88 @@ def create_payment_terms():
|
||||||
print("\n[6] Payment Terms Templates...")
|
print("\n[6] Payment Terms Templates...")
|
||||||
|
|
||||||
templates = [
|
templates = [
|
||||||
|
|
||||||
# ── A: Standard Interior Project (milestone-based) ───────
|
# ── A: Standard Interior Project (milestone-based) ───────
|
||||||
{
|
{
|
||||||
"name": "Furnitex - Standard Interior Project",
|
"name": "Furnitex - Standard Interior Project",
|
||||||
"terms": [
|
"terms": [
|
||||||
{"payment_term_name": "30% Advance on Agreement",
|
{
|
||||||
"invoice_portion": 30, "credit_days": 0,
|
"payment_term_name": "30% Advance on Agreement",
|
||||||
"description": "Booking advance on signing agreement"},
|
"invoice_portion": 30,
|
||||||
{"payment_term_name": "30% on 50% Site Completion",
|
"credit_days": 0,
|
||||||
"invoice_portion": 30, "credit_days": 30,
|
"description": "Booking advance on signing agreement",
|
||||||
"description": "Second milestone: 50% work done"},
|
},
|
||||||
{"payment_term_name": "30% on Work Completion",
|
{
|
||||||
"invoice_portion": 30, "credit_days": 60,
|
"payment_term_name": "30% on 50% Site Completion",
|
||||||
"description": "Third milestone: work complete"},
|
"invoice_portion": 30,
|
||||||
{"payment_term_name": "10% on Final Handover",
|
"credit_days": 30,
|
||||||
"invoice_portion": 10, "credit_days": 75,
|
"description": "Second milestone: 50% work done",
|
||||||
"description": "Retention released at handover"},
|
},
|
||||||
|
{
|
||||||
|
"payment_term_name": "30% on Work Completion",
|
||||||
|
"invoice_portion": 30,
|
||||||
|
"credit_days": 60,
|
||||||
|
"description": "Third milestone: work complete",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payment_term_name": "10% on Final Handover",
|
||||||
|
"invoice_portion": 10,
|
||||||
|
"credit_days": 75,
|
||||||
|
"description": "Retention released at handover",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
# ── B: 50-50 (smaller projects) ──────────────────────────
|
# ── B: 50-50 (smaller projects) ──────────────────────────
|
||||||
{
|
{
|
||||||
"name": "Furnitex - 50-50 Advance",
|
"name": "Furnitex - 50-50 Advance",
|
||||||
"terms": [
|
"terms": [
|
||||||
{"payment_term_name": "50% Advance",
|
{
|
||||||
"invoice_portion": 50, "credit_days": 0,
|
"payment_term_name": "50% Advance",
|
||||||
"description": "Advance before work begins"},
|
"invoice_portion": 50,
|
||||||
{"payment_term_name": "50% on Delivery",
|
"credit_days": 0,
|
||||||
"invoice_portion": 50, "credit_days": 30,
|
"description": "Advance before work begins",
|
||||||
"description": "Balance on delivery / installation"},
|
},
|
||||||
|
{
|
||||||
|
"payment_term_name": "50% on Delivery",
|
||||||
|
"invoice_portion": 50,
|
||||||
|
"credit_days": 30,
|
||||||
|
"description": "Balance on delivery / installation",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
# ── C: 100% Advance (small orders / loose furniture) ─────
|
# ── C: 100% Advance (small orders / loose furniture) ─────
|
||||||
{
|
{
|
||||||
"name": "Furnitex - 100% Advance",
|
"name": "Furnitex - 100% Advance",
|
||||||
"terms": [
|
"terms": [
|
||||||
{"payment_term_name": "100% Advance",
|
{
|
||||||
"invoice_portion": 100, "credit_days": 0,
|
"payment_term_name": "100% Advance",
|
||||||
"description": "Full payment before production"},
|
"invoice_portion": 100,
|
||||||
|
"credit_days": 0,
|
||||||
|
"description": "Full payment before production",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
# ── D: Lumpsum 3-stage (commercial projects) ─────────────
|
# ── D: Lumpsum 3-stage (commercial projects) ─────────────
|
||||||
{
|
{
|
||||||
"name": "Furnitex - Commercial 3-Stage",
|
"name": "Furnitex - Commercial 3-Stage",
|
||||||
"terms": [
|
"terms": [
|
||||||
{"payment_term_name": "40% Advance - Commercial",
|
{
|
||||||
"invoice_portion": 40, "credit_days": 0,
|
"payment_term_name": "40% Advance - Commercial",
|
||||||
"description": "Mobilisation advance"},
|
"invoice_portion": 40,
|
||||||
{"payment_term_name": "40% Mid-Stage - Commercial",
|
"credit_days": 0,
|
||||||
"invoice_portion": 40, "credit_days": 45,
|
"description": "Mobilisation advance",
|
||||||
"description": "Mid-project milestone"},
|
},
|
||||||
{"payment_term_name": "20% Retention - Commercial",
|
{
|
||||||
"invoice_portion": 20, "credit_days": 90,
|
"payment_term_name": "40% Mid-Stage - Commercial",
|
||||||
"description": "Retention on final handover"},
|
"invoice_portion": 40,
|
||||||
|
"credit_days": 45,
|
||||||
|
"description": "Mid-project milestone",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"payment_term_name": "20% Retention - Commercial",
|
||||||
|
"invoice_portion": 20,
|
||||||
|
"credit_days": 90,
|
||||||
|
"description": "Retention on final handover",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
@ -282,30 +343,38 @@ def create_payment_terms():
|
||||||
for term in t["terms"]:
|
for term in t["terms"]:
|
||||||
pt_name = term["payment_term_name"]
|
pt_name = term["payment_term_name"]
|
||||||
if not exists("Payment Term", pt_name):
|
if not exists("Payment Term", pt_name):
|
||||||
pt = frappe.get_doc({
|
pt = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Payment Term",
|
"doctype": "Payment Term",
|
||||||
"payment_term_name": pt_name,
|
"payment_term_name": pt_name,
|
||||||
"invoice_portion": term["invoice_portion"],
|
"invoice_portion": term["invoice_portion"],
|
||||||
"credit_days_based_on": "Day(s) after invoice date",
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
"credit_days": term["credit_days"],
|
"credit_days": term["credit_days"],
|
||||||
"description": term["description"],
|
"description": term["description"],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
safe_insert(pt)
|
safe_insert(pt)
|
||||||
term_rows.append({
|
term_rows.append(
|
||||||
|
{
|
||||||
"payment_term": pt_name,
|
"payment_term": pt_name,
|
||||||
"invoice_portion": term["invoice_portion"],
|
"invoice_portion": term["invoice_portion"],
|
||||||
"credit_days_based_on": "Day(s) after invoice date",
|
"credit_days_based_on": "Day(s) after invoice date",
|
||||||
"credit_days": term["credit_days"],
|
"credit_days": term["credit_days"],
|
||||||
"description": term["description"],
|
"description": term["description"],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Payment Terms Template",
|
"doctype": "Payment Terms Template",
|
||||||
"template_name": tname,
|
"template_name": tname,
|
||||||
"terms": term_rows,
|
"terms": term_rows,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Payment Terms: {tname}")
|
)
|
||||||
else: skip(f"{tname} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Payment Terms: {tname}")
|
||||||
|
else:
|
||||||
|
skip(f"{tname} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Payment Terms: {tname}")
|
skip(f"Payment Terms: {tname}")
|
||||||
|
|
||||||
|
|
@ -319,7 +388,6 @@ def create_terms_conditions():
|
||||||
print("\n[7] Terms & Conditions Templates...")
|
print("\n[7] Terms & Conditions Templates...")
|
||||||
|
|
||||||
templates = [
|
templates = [
|
||||||
|
|
||||||
# ── Quotation T&C ─────────────────────────────────────────
|
# ── Quotation T&C ─────────────────────────────────────────
|
||||||
{
|
{
|
||||||
"title": "Furnitex - Quotation Terms",
|
"title": "Furnitex - Quotation Terms",
|
||||||
|
|
@ -337,7 +405,6 @@ def create_terms_conditions():
|
||||||
</ol>
|
</ol>
|
||||||
<p><em>Furnitex | Interior Design & Furniture | Kolkata</em></p>""",
|
<p><em>Furnitex | Interior Design & Furniture | Kolkata</em></p>""",
|
||||||
},
|
},
|
||||||
|
|
||||||
# ── Sales Invoice T&C ─────────────────────────────────────
|
# ── Sales Invoice T&C ─────────────────────────────────────
|
||||||
{
|
{
|
||||||
"title": "Furnitex - Invoice Terms",
|
"title": "Furnitex - Invoice Terms",
|
||||||
|
|
@ -352,7 +419,6 @@ def create_terms_conditions():
|
||||||
<p>Bank: [Your Bank] | A/c: [Account No] | IFSC: [IFSC] | UPI: [UPI ID]</p>
|
<p>Bank: [Your Bank] | A/c: [Account No] | IFSC: [IFSC] | UPI: [UPI ID]</p>
|
||||||
<p><em>Thank you for choosing Furnitex!</em></p>""",
|
<p><em>Thank you for choosing Furnitex!</em></p>""",
|
||||||
},
|
},
|
||||||
|
|
||||||
# ── Purchase Order T&C ────────────────────────────────────
|
# ── Purchase Order T&C ────────────────────────────────────
|
||||||
{
|
{
|
||||||
"title": "Furnitex - Purchase Order Terms",
|
"title": "Furnitex - Purchase Order Terms",
|
||||||
|
|
@ -368,13 +434,17 @@ def create_terms_conditions():
|
||||||
|
|
||||||
for t in templates:
|
for t in templates:
|
||||||
if not exists("Terms and Conditions", t["title"]):
|
if not exists("Terms and Conditions", t["title"]):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Terms and Conditions",
|
"doctype": "Terms and Conditions",
|
||||||
"title": t["title"],
|
"title": t["title"],
|
||||||
"terms": t["terms"],
|
"terms": t["terms"],
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"T&C: {t['title']}")
|
)
|
||||||
else: skip(f"T&C: {t['title']} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"T&C: {t['title']}")
|
||||||
|
else:
|
||||||
|
skip(f"T&C: {t['title']} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"T&C: {t['title']}")
|
skip(f"T&C: {t['title']}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -388,16 +458,20 @@ def create_price_list():
|
||||||
|
|
||||||
pl_name = "Furnitex Interior Rate Card"
|
pl_name = "Furnitex Interior Rate Card"
|
||||||
if not exists("Price List", pl_name):
|
if not exists("Price List", pl_name):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Price List",
|
"doctype": "Price List",
|
||||||
"price_list_name": pl_name,
|
"price_list_name": pl_name,
|
||||||
"currency": "INR",
|
"currency": "INR",
|
||||||
"selling": 1,
|
"selling": 1,
|
||||||
"buying": 0,
|
"buying": 0,
|
||||||
"enabled": 1,
|
"enabled": 1,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Price List: {pl_name}")
|
)
|
||||||
else: skip(f"{pl_name} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Price List: {pl_name}")
|
||||||
|
else:
|
||||||
|
skip(f"{pl_name} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Price List: {pl_name}")
|
skip(f"Price List: {pl_name}")
|
||||||
|
|
||||||
|
|
@ -425,17 +499,23 @@ def create_price_list():
|
||||||
for code, rate in item_prices:
|
for code, rate in item_prices:
|
||||||
if not exists("Item", code):
|
if not exists("Item", code):
|
||||||
continue
|
continue
|
||||||
if not exists_f("Item Price", {"item_code": code, "price_list": pl, "selling": 1}):
|
if not exists_f(
|
||||||
ip = frappe.get_doc({
|
"Item Price", {"item_code": code, "price_list": pl, "selling": 1}
|
||||||
|
):
|
||||||
|
ip = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Item Price",
|
"doctype": "Item Price",
|
||||||
"item_code": code,
|
"item_code": code,
|
||||||
"price_list": pl,
|
"price_list": pl,
|
||||||
"selling": 1,
|
"selling": 1,
|
||||||
"currency": "INR",
|
"currency": "INR",
|
||||||
"price_list_rate": rate,
|
"price_list_rate": rate,
|
||||||
})
|
}
|
||||||
if safe_insert(ip): ok(f"Item Price: {code} ₹{rate} [{pl}]")
|
)
|
||||||
else: skip(f"Item Price: {code} [{pl}] (dup)")
|
if safe_insert(ip):
|
||||||
|
ok(f"Item Price: {code} ₹{rate} [{pl}]")
|
||||||
|
else:
|
||||||
|
skip(f"Item Price: {code} [{pl}] (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Item Price: {code} [{pl}]")
|
skip(f"Item Price: {code} [{pl}]")
|
||||||
|
|
||||||
|
|
@ -449,81 +529,240 @@ def create_crm_billing_fields():
|
||||||
print("\n[9] CRM & Billing Custom Fields...")
|
print("\n[9] CRM & Billing Custom Fields...")
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
|
|
||||||
# ── CUSTOMER ─────────────────────────────────────────────
|
# ── CUSTOMER ─────────────────────────────────────────────
|
||||||
("Customer", "whatsapp_number", "WhatsApp Number",
|
(
|
||||||
"Data", None, "mobile_no", 0),
|
"Customer",
|
||||||
("Customer", "property_address", "Property / Site Address",
|
"whatsapp_number",
|
||||||
"Small Text", None, "whatsapp_number", 0),
|
"WhatsApp Number",
|
||||||
("Customer", "project_type", "Project Type",
|
"Data",
|
||||||
"Select", "Residential\nCommercial\nHospitality\nRetail", "property_address", 0),
|
None,
|
||||||
("Customer", "property_size_sqft", "Approx. Area (SqFt)",
|
"mobile_no",
|
||||||
"Float", None, "project_type", 0),
|
0,
|
||||||
("Customer", "budget_range", "Budget Range",
|
),
|
||||||
|
(
|
||||||
|
"Customer",
|
||||||
|
"property_address",
|
||||||
|
"Property / Site Address",
|
||||||
|
"Small Text",
|
||||||
|
None,
|
||||||
|
"whatsapp_number",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Customer",
|
||||||
|
"project_type",
|
||||||
|
"Project Type",
|
||||||
|
"Select",
|
||||||
|
"Residential\nCommercial\nHospitality\nRetail",
|
||||||
|
"property_address",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Customer",
|
||||||
|
"property_size_sqft",
|
||||||
|
"Approx. Area (SqFt)",
|
||||||
|
"Float",
|
||||||
|
None,
|
||||||
|
"project_type",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Customer",
|
||||||
|
"budget_range",
|
||||||
|
"Budget Range",
|
||||||
"Select",
|
"Select",
|
||||||
"Under ₹5 Lakhs\n₹5–10 Lakhs\n₹10–25 Lakhs\n₹25–50 Lakhs\nAbove ₹50 Lakhs",
|
"Under ₹5 Lakhs\n₹5–10 Lakhs\n₹10–25 Lakhs\n₹25–50 Lakhs\nAbove ₹50 Lakhs",
|
||||||
"property_size_sqft", 0),
|
"property_size_sqft",
|
||||||
("Customer", "referred_by", "Referred By",
|
0,
|
||||||
"Data", None, "budget_range", 0),
|
),
|
||||||
|
("Customer", "referred_by", "Referred By", "Data", None, "budget_range", 0),
|
||||||
# ── LEAD ─────────────────────────────────────────────────
|
# ── LEAD ─────────────────────────────────────────────────
|
||||||
("Lead", "whatsapp_number", "WhatsApp Number",
|
("Lead", "whatsapp_number", "WhatsApp Number", "Data", None, "mobile_no", 0),
|
||||||
"Data", None, "mobile_no", 0),
|
(
|
||||||
("Lead", "project_type_lead", "Project Type",
|
"Lead",
|
||||||
"Select", "Residential\nCommercial\nHospitality\nRetail",
|
"project_type_lead",
|
||||||
"whatsapp_number", 0),
|
"Project Type",
|
||||||
("Lead", "property_address", "Property / Site Address",
|
"Select",
|
||||||
"Small Text", None, "project_type_lead", 0),
|
"Residential\nCommercial\nHospitality\nRetail",
|
||||||
("Lead", "area_sqft", "Approx. Area (SqFt)",
|
"whatsapp_number",
|
||||||
"Float", None, "property_address", 0),
|
0,
|
||||||
("Lead", "budget_range", "Budget Range",
|
),
|
||||||
|
(
|
||||||
|
"Lead",
|
||||||
|
"property_address",
|
||||||
|
"Property / Site Address",
|
||||||
|
"Small Text",
|
||||||
|
None,
|
||||||
|
"project_type_lead",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Lead",
|
||||||
|
"area_sqft",
|
||||||
|
"Approx. Area (SqFt)",
|
||||||
|
"Float",
|
||||||
|
None,
|
||||||
|
"property_address",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Lead",
|
||||||
|
"budget_range",
|
||||||
|
"Budget Range",
|
||||||
"Select",
|
"Select",
|
||||||
"Under ₹5 Lakhs\n₹5–10 Lakhs\n₹10–25 Lakhs\n₹25–50 Lakhs\nAbove ₹50 Lakhs",
|
"Under ₹5 Lakhs\n₹5–10 Lakhs\n₹10–25 Lakhs\n₹25–50 Lakhs\nAbove ₹50 Lakhs",
|
||||||
"area_sqft", 0),
|
"area_sqft",
|
||||||
("Lead", "site_visit_done", "Site Visit Done",
|
0,
|
||||||
"Check", None, "budget_range", 0),
|
),
|
||||||
("Lead", "site_visit_date", "Site Visit Date",
|
(
|
||||||
"Date", None, "site_visit_done", 0),
|
"Lead",
|
||||||
("Lead", "estimated_value", "Estimated Project Value (₹)",
|
"site_visit_done",
|
||||||
"Currency", None, "site_visit_date", 0),
|
"Site Visit Done",
|
||||||
|
"Check",
|
||||||
|
None,
|
||||||
|
"budget_range",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Lead",
|
||||||
|
"site_visit_date",
|
||||||
|
"Site Visit Date",
|
||||||
|
"Date",
|
||||||
|
None,
|
||||||
|
"site_visit_done",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Lead",
|
||||||
|
"estimated_value",
|
||||||
|
"Estimated Project Value (₹)",
|
||||||
|
"Currency",
|
||||||
|
None,
|
||||||
|
"site_visit_date",
|
||||||
|
0,
|
||||||
|
),
|
||||||
# ── QUOTATION ────────────────────────────────────────────
|
# ── QUOTATION ────────────────────────────────────────────
|
||||||
("Quotation", "site_address", "Site / Delivery Address",
|
(
|
||||||
"Small Text", None, "customer_address", 0),
|
"Quotation",
|
||||||
("Quotation", "scope_of_work", "Scope of Work",
|
"site_address",
|
||||||
"Small Text", None, "site_address", 0),
|
"Site / Delivery Address",
|
||||||
("Quotation", "site_visit_date", "Site Visit Date",
|
"Small Text",
|
||||||
"Date", None, "scope_of_work", 0),
|
None,
|
||||||
("Quotation", "expected_start_date_q", "Expected Start Date",
|
"customer_address",
|
||||||
"Date", None, "site_visit_date", 0),
|
0,
|
||||||
("Quotation", "expected_handover_date","Expected Handover Date",
|
),
|
||||||
"Date", None, "expected_start_date_q", 0),
|
(
|
||||||
("Quotation", "design_style", "Design Style",
|
"Quotation",
|
||||||
|
"scope_of_work",
|
||||||
|
"Scope of Work",
|
||||||
|
"Small Text",
|
||||||
|
None,
|
||||||
|
"site_address",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Quotation",
|
||||||
|
"site_visit_date",
|
||||||
|
"Site Visit Date",
|
||||||
|
"Date",
|
||||||
|
None,
|
||||||
|
"scope_of_work",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Quotation",
|
||||||
|
"expected_start_date_q",
|
||||||
|
"Expected Start Date",
|
||||||
|
"Date",
|
||||||
|
None,
|
||||||
|
"site_visit_date",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Quotation",
|
||||||
|
"expected_handover_date",
|
||||||
|
"Expected Handover Date",
|
||||||
|
"Date",
|
||||||
|
None,
|
||||||
|
"expected_start_date_q",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Quotation",
|
||||||
|
"design_style",
|
||||||
|
"Design Style",
|
||||||
"Select",
|
"Select",
|
||||||
"Modern / Contemporary\nTraditional / Classic\nMinimalist\nIndustrial\nBohemian\nMediterranean",
|
"Modern / Contemporary\nTraditional / Classic\nMinimalist\nIndustrial\nBohemian\nMediterranean",
|
||||||
"expected_handover_date", 0),
|
"expected_handover_date",
|
||||||
|
0,
|
||||||
|
),
|
||||||
# ── SALES INVOICE ────────────────────────────────────────
|
# ── SALES INVOICE ────────────────────────────────────────
|
||||||
# 'project' field is native on Sales Invoice in v16 — no custom field needed.
|
# 'project' field is native on Sales Invoice in v16 — no custom field needed.
|
||||||
# Adding billing-specific extras after 'customer_name' instead.
|
# Adding billing-specific extras after 'customer_name' instead.
|
||||||
("Sales Invoice", "milestone_description", "Milestone Description",
|
(
|
||||||
"Small Text", None, "customer_name", 0),
|
"Sales Invoice",
|
||||||
("Sales Invoice", "payment_mode_note", "Payment Mode Note",
|
"milestone_description",
|
||||||
"Data", None, "milestone_description", 0),
|
"Milestone Description",
|
||||||
|
"Small Text",
|
||||||
|
None,
|
||||||
|
"customer_name",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Sales Invoice",
|
||||||
|
"payment_mode_note",
|
||||||
|
"Payment Mode Note",
|
||||||
|
"Data",
|
||||||
|
None,
|
||||||
|
"milestone_description",
|
||||||
|
0,
|
||||||
|
),
|
||||||
# ── OPPORTUNITY ──────────────────────────────────────────
|
# ── OPPORTUNITY ──────────────────────────────────────────
|
||||||
("Opportunity", "property_address", "Property / Site Address",
|
(
|
||||||
"Small Text", None, "customer_name", 0),
|
"Opportunity",
|
||||||
("Opportunity", "area_sqft", "Area (SqFt)",
|
"property_address",
|
||||||
"Float", None, "property_address", 0),
|
"Property / Site Address",
|
||||||
("Opportunity", "design_style", "Design Style",
|
"Small Text",
|
||||||
|
None,
|
||||||
|
"customer_name",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Opportunity",
|
||||||
|
"area_sqft",
|
||||||
|
"Area (SqFt)",
|
||||||
|
"Float",
|
||||||
|
None,
|
||||||
|
"property_address",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Opportunity",
|
||||||
|
"design_style",
|
||||||
|
"Design Style",
|
||||||
"Select",
|
"Select",
|
||||||
"Modern / Contemporary\nTraditional / Classic\nMinimalist\nIndustrial\nBohemian\nMediterranean",
|
"Modern / Contemporary\nTraditional / Classic\nMinimalist\nIndustrial\nBohemian\nMediterranean",
|
||||||
"area_sqft", 0),
|
"area_sqft",
|
||||||
("Opportunity", "site_visit_done", "Site Visit Done",
|
0,
|
||||||
"Check", None, "design_style", 0),
|
),
|
||||||
("Opportunity", "site_visit_date", "Site Visit Date",
|
(
|
||||||
"Date", None, "site_visit_done", 0),
|
"Opportunity",
|
||||||
|
"site_visit_done",
|
||||||
|
"Site Visit Done",
|
||||||
|
"Check",
|
||||||
|
None,
|
||||||
|
"design_style",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Opportunity",
|
||||||
|
"site_visit_date",
|
||||||
|
"Site Visit Date",
|
||||||
|
"Date",
|
||||||
|
None,
|
||||||
|
"site_visit_done",
|
||||||
|
0,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
for dt, fn, label, ft, opts, after, in_list in [
|
for dt, fn, label, ft, opts, after, in_list in [
|
||||||
|
|
@ -543,8 +782,10 @@ def create_crm_billing_fields():
|
||||||
if opts:
|
if opts:
|
||||||
d_dict["options"] = opts
|
d_dict["options"] = opts
|
||||||
d = frappe.get_doc(d_dict)
|
d = frappe.get_doc(d_dict)
|
||||||
if safe_insert(d): ok(f"Custom Field: {dt}.{fn}")
|
if safe_insert(d):
|
||||||
else: skip(f"{dt}.{fn} (dup)")
|
ok(f"Custom Field: {dt}.{fn}")
|
||||||
|
else:
|
||||||
|
skip(f"{dt}.{fn} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Custom Field: {dt}.{fn}")
|
skip(f"Custom Field: {dt}.{fn}")
|
||||||
|
|
||||||
|
|
@ -580,8 +821,10 @@ def create_sample_customers():
|
||||||
for c in customers:
|
for c in customers:
|
||||||
if not exists("Customer", c["customer_name"]):
|
if not exists("Customer", c["customer_name"]):
|
||||||
d = frappe.get_doc({"doctype": "Customer", **c})
|
d = frappe.get_doc({"doctype": "Customer", **c})
|
||||||
if safe_insert(d): ok(f"Customer: {c['customer_name']}")
|
if safe_insert(d):
|
||||||
else: skip(f"{c['customer_name']} (dup)")
|
ok(f"Customer: {c['customer_name']}")
|
||||||
|
else:
|
||||||
|
skip(f"{c['customer_name']} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Customer: {c['customer_name']}")
|
skip(f"Customer: {c['customer_name']}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
@ -639,12 +882,16 @@ def create_crm_stages():
|
||||||
for stage in furnitex_stages:
|
for stage in furnitex_stages:
|
||||||
if not exists("Sales Stage", stage):
|
if not exists("Sales Stage", stage):
|
||||||
try:
|
try:
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Sales Stage",
|
"doctype": "Sales Stage",
|
||||||
"stage_name": stage,
|
"stage_name": stage,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Sales Stage: {stage}")
|
)
|
||||||
else: skip(f"{stage} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Sales Stage: {stage}")
|
||||||
|
else:
|
||||||
|
skip(f"{stage} (dup)")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
skip(f"Sales Stage {stage}: {e}")
|
skip(f"Sales Stage {stage}: {e}")
|
||||||
else:
|
else:
|
||||||
|
|
@ -663,19 +910,23 @@ def create_crm_stages():
|
||||||
"Wardrobe / Storage Only",
|
"Wardrobe / Storage Only",
|
||||||
"Flooring Only",
|
"Flooring Only",
|
||||||
]
|
]
|
||||||
for ot in opp_types:
|
for opp_type in opp_types:
|
||||||
if not exists("Opportunity Type", ot):
|
if not exists("Opportunity Type", opp_type):
|
||||||
try:
|
try:
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Opportunity Type",
|
"doctype": "Opportunity Type",
|
||||||
"name": ot,
|
"name": opp_type,
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Opportunity Type: {ot}")
|
)
|
||||||
else: skip(f"{ot} (dup)")
|
if safe_insert(d):
|
||||||
except Exception as e:
|
ok(f"Opportunity Type: {opp_type}")
|
||||||
skip(f"Opportunity Type {ot}: {e}")
|
|
||||||
else:
|
else:
|
||||||
skip(f"Opportunity Type: {ot}")
|
skip(f"{opp_type} (dup)")
|
||||||
|
except Exception as e:
|
||||||
|
skip(f"Opportunity Type {opp_type}: {e}")
|
||||||
|
else:
|
||||||
|
skip(f"Opportunity Type: {opp_type}")
|
||||||
|
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
|
|
@ -706,7 +957,8 @@ def create_letter_head():
|
||||||
print("\n[14] Letter Head...")
|
print("\n[14] Letter Head...")
|
||||||
lh_name = "Furnitex"
|
lh_name = "Furnitex"
|
||||||
if not exists("Letter Head", lh_name):
|
if not exists("Letter Head", lh_name):
|
||||||
d = frappe.get_doc({
|
d = frappe.get_doc(
|
||||||
|
{
|
||||||
"doctype": "Letter Head",
|
"doctype": "Letter Head",
|
||||||
"letter_head_name": lh_name,
|
"letter_head_name": lh_name,
|
||||||
"is_default": 1,
|
"is_default": 1,
|
||||||
|
|
@ -734,9 +986,12 @@ def create_letter_head():
|
||||||
<div style="font-family: Arial, sans-serif; border-top: 1px solid #ccc; padding-top: 6px; font-size: 10px; color: #888; text-align: center;">
|
<div style="font-family: Arial, sans-serif; border-top: 1px solid #ccc; padding-top: 6px; font-size: 10px; color: #888; text-align: center;">
|
||||||
Furnitex | Interior Design & Furniture | Kolkata | GSTIN: [Your GSTIN] | CIN: [If applicable]
|
Furnitex | Interior Design & Furniture | Kolkata | GSTIN: [Your GSTIN] | CIN: [If applicable]
|
||||||
</div>""",
|
</div>""",
|
||||||
})
|
}
|
||||||
if safe_insert(d): ok(f"Letter Head: {lh_name}")
|
)
|
||||||
else: skip(f"Letter Head {lh_name} (dup)")
|
if safe_insert(d):
|
||||||
|
ok(f"Letter Head: {lh_name}")
|
||||||
|
else:
|
||||||
|
skip(f"Letter Head {lh_name} (dup)")
|
||||||
else:
|
else:
|
||||||
skip(f"Letter Head: {lh_name}")
|
skip(f"Letter Head: {lh_name}")
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ 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
|
||||||
|
|
@ -52,12 +53,11 @@ 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 = {
|
||||||
|
|
@ -72,10 +72,7 @@ def update_address():
|
||||||
"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",
|
"doctype": "Letter Head",
|
||||||
"letter_head_name": lh_name,
|
"letter_head_name": lh_name,
|
||||||
"content": LETTER_HEAD_HTML,
|
"content": LETTER_HEAD_HTML,
|
||||||
"is_default": 1,
|
"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")
|
||||||
|
|
@ -191,6 +191,7 @@ TC_MAP = {
|
||||||
"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",
|
"doctype": "Terms and Conditions",
|
||||||
"title": title,
|
"title": title,
|
||||||
"terms": content,
|
"terms": content,
|
||||||
"selling": 1,
|
"selling": 1,
|
||||||
"buying": 1,
|
"buying": 1,
|
||||||
"hr": 0,
|
"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}")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue