Sidhra_Audit_System/sidrah_business/setup_multitenancy.py
2026-10-09 12:16:48 +05:30

344 lines
13 KiB
Python

import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
MULTITENANT_DOCTYPES = [
"SB Organization", "SB Incident", "SB Idea", "SB Order", "SB Process",
"SB Cycle", "SB KPI Input", "Managements", "Department", "Branch"
]
def ensure_group_company_doctype():
"""Ensure SB Group Company doctype exists."""
if frappe.db.exists("DocType", "SB Group Company"):
return
doc = frappe.get_doc({
"doctype": "DocType",
"name": "SB Group Company",
"module": "Sidrah Business",
"custom": 1,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"quick_entry": 1,
"track_changes": 1,
"autoname": "field:company_name",
"fields": [
{
"fieldname": "company_name",
"fieldtype": "Data",
"label": "Company Name",
"reqd": 1,
"unique": 1,
"in_list_view": 1,
},
{
"fieldname": "acronym",
"fieldtype": "Data",
"label": "Acronym",
"in_list_view": 1,
},
{
"fieldname": "code",
"fieldtype": "Data",
"label": "Company Code",
"unique": 1,
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Active\nInactive",
"default": "Active",
"in_list_view": 1,
},
{
"fieldname": "owner_user",
"fieldtype": "Link",
"label": "Group Co Owner / Admin",
"options": "User",
},
{
"fieldname": "notes",
"fieldtype": "Small Text",
"label": "Notes",
},
{
"fieldname": "custom_is_deleted",
"fieldtype": "Check",
"label": "Is Deleted",
"default": "0",
"read_only": 1,
"hidden": 1,
},
{
"fieldname": "custom_deleted_by",
"fieldtype": "Link",
"label": "Deleted By",
"options": "User",
"read_only": 1,
"hidden": 1,
},
{
"fieldname": "custom_deleted_on",
"fieldtype": "Datetime",
"label": "Deleted On",
"read_only": 1,
"hidden": 1,
},
{
"fieldname": "custom_deleted_reason",
"fieldtype": "Small Text",
"label": "Deleted Reason",
"read_only": 1,
"hidden": 1,
},
],
"permissions": [
{
"role": "System Manager",
"read": 1, "write": 1, "create": 1, "delete": 1
},
{
"role": "Administrator",
"read": 1, "write": 1, "create": 1, "delete": 1
}
]
})
doc.insert(ignore_permissions=True)
frappe.db.commit()
DOCTYPES_INSERT_AFTER = {
"SB Organization": "organization_name",
"SB Incident": "organization",
"SB Idea": "organization",
"SB Order": "organization",
"SB Process": "organization",
"SB Cycle": "organization",
"SB KPI Input": "organization",
"Managements": "organization",
"Department": "custom_organization",
"Branch": "organization",
}
def sync_user_group_company_permission(user_doc, method=None):
"""
Synchronizes SB Group Company User Permissions for a user.
Rule:
- Skip Administrator, Guest, Platform Admin, and CEO (unrestricted across system).
- For regular users (Auditors, Managers, Employees):
Ensure User Permission exists with for_value = custom_group_company.
Remove any permissions with a different for_value.
- For Admins / CEOs:
Remove any restricting SB Group Company user permissions so they have full access.
"""
user_name = user_doc.name if hasattr(user_doc, "name") else str(user_doc)
if user_name in ("Administrator", "Guest"):
return
from sidrah_business.soft_delete import is_platform_admin
is_admin = is_platform_admin(user_name)
company = getattr(user_doc, "custom_group_company", None)
if company is None and frappe.db.exists("User", user_name):
company = frappe.db.get_value("User", user_name, "custom_group_company")
# Fetch all existing User Permission records for allow = 'SB Group Company' for this user
existing_perms = frappe.get_all(
"User Permission",
filters={"user": user_name, "allow": "SB Group Company"},
fields=["name", "for_value"]
)
if is_admin:
# Admins and CEOs should not have restricting user permissions
for perm in existing_perms:
frappe.delete_doc("User Permission", perm.name, ignore_permissions=True)
return
# Regular users (Auditors, Managers, Employees): create user permission for their company
if company:
has_correct = False
for perm in existing_perms:
if perm.for_value == company:
has_correct = True
else:
frappe.delete_doc("User Permission", perm.name, ignore_permissions=True)
if not has_correct:
new_perm = frappe.get_doc({
"doctype": "User Permission",
"user": user_name,
"allow": "SB Group Company",
"for_value": company,
"is_default": 1,
"apply_to_all_doctypes": 1,
})
new_perm.insert(ignore_permissions=True)
else:
for perm in existing_perms:
frappe.delete_doc("User Permission", perm.name, ignore_permissions=True)
# Synchronize category to Frappe Has Role
category = getattr(user_doc, "custom_category", None)
if category is None and frappe.db.exists("User", user_name):
category = frappe.db.get_value("User", user_name, "custom_category")
if category:
category_map = {
'Admin': ['SB Admin', 'System Manager'],
'Business Owner': ['SB Business Owner', 'Business Owner', 'SB Owner'],
'Manager': ['SB Manager'],
'Auditor': ['SB Auditor'],
'X-Auditor': ['SB X-Auditor', 'SB Auditor'],
'Employee': ['SB Employee'],
'CEO': ['Ceo', 'SB Admin', 'System Manager'],
}
target_roles = category_map.get(category, [])
user_roles = frappe.get_roles(user_name)
for r in target_roles:
if not frappe.db.exists("Has Role", {"parent": user_name, "role": r}) and frappe.db.exists("Role", r):
frappe.get_doc({
"doctype": "Has Role",
"parent": user_name,
"parenttype": "User",
"parentfield": "roles",
"role": r
}).insert(ignore_permissions=True)
# Auto pick Group Co Owner / Admin User
if category in ('Admin', 'CEO', 'Business Owner') and company and frappe.db.exists("SB Group Company", company):
gc_owner = frappe.db.get_value("SB Group Company", company, "owner_user")
if not gc_owner:
frappe.db.set_value("SB Group Company", company, "owner_user", user_name, update_modified=False)
def sync_all_user_group_company_permissions():
"""Batch-sync SB Group Company User Permissions for all users."""
users = frappe.get_all("User", filters={"name": ["not in", ["Administrator", "Guest"]]}, fields=["name", "custom_group_company"])
for u in users:
sync_user_group_company_permission(u)
frappe.db.commit()
def setup_multitenancy_schema():
"""Creates SB Group Company doctype and adds group_company custom fields to target doctypes."""
ensure_group_company_doctype()
# Create default Group Company if none exists
default_co = "Default Group Company"
if not frappe.db.exists("SB Group Company", default_co):
co_doc = frappe.new_doc("SB Group Company")
co_doc.company_name = default_co
co_doc.acronym = "DGC"
co_doc.code = "DEFAULT"
co_doc.status = "Active"
co_doc.insert(ignore_permissions=True)
frappe.db.commit()
custom_fields = {}
for dt in MULTITENANT_DOCTYPES:
if frappe.db.exists("DocType", dt):
custom_fields[dt] = [{
"fieldname": "group_company",
"label": "Group Company",
"fieldtype": "Link",
"options": "SB Group Company",
"in_list_view": 0,
"in_standard_filter": 1,
"hidden": 1,
"insert_after": DOCTYPES_INSERT_AFTER.get(dt, "organization"),
}]
# Add custom_group_company and custom_manager fields on User doctype
custom_fields["User"] = [
{
"fieldname": "custom_group_company",
"label": "Group Company",
"fieldtype": "Link",
"options": "SB Group Company",
"in_list_view": 1,
"in_standard_filter": 1,
"insert_after": "custom_category",
},
{
"fieldname": "custom_manager",
"label": "Reports To / Manager",
"fieldtype": "Link",
"options": "User",
"in_list_view": 1,
"in_standard_filter": 1,
"insert_after": "custom_group_company",
}
]
create_custom_fields(custom_fields, ignore_validate=True)
# Ensure existing Custom Field records have hidden=1 and right insert_after
for dt, insert_after in DOCTYPES_INSERT_AFTER.items():
cf_name = frappe.db.get_value("Custom Field", {"dt": dt, "fieldname": "group_company"})
if cf_name:
frappe.db.set_value("Custom Field", cf_name, {
"insert_after": insert_after,
"hidden": 1,
"in_list_view": 0,
}, update_modified=False)
frappe.clear_cache(doctype=dt)
user_cf = frappe.db.get_value("Custom Field", {"dt": "User", "fieldname": "custom_group_company"})
if user_cf:
frappe.db.set_value("Custom Field", user_cf, "insert_after", "custom_category", update_modified=False)
user_mgr_cf = frappe.db.get_value("Custom Field", {"dt": "User", "fieldname": "custom_manager"})
if user_mgr_cf:
frappe.db.set_value("Custom Field", user_mgr_cf, "insert_after", "custom_group_company", update_modified=False)
cat_cf = frappe.db.get_value("Custom Field", {"dt": "User", "fieldname": "custom_category"})
if cat_cf:
frappe.db.set_value("Custom Field", cat_cf, "options", "\nAdmin\nBusiness Owner\nCEO\nChairman\nAuditor\nX-Auditor\nManager\nEmployee", update_modified=False)
frappe.clear_cache(doctype="User")
# Sync Group Company owner_users as SB Admin
companies = frappe.get_all("SB Group Company", filters={"owner_user": ["is", "set"], "custom_is_deleted": 0}, fields=["name", "owner_user"])
for comp in companies:
if comp.owner_user and frappe.db.exists("User", comp.owner_user):
owner_doc = frappe.get_doc("User", comp.owner_user)
roles = [r.role for r in owner_doc.roles]
if "SB Admin" not in roles:
owner_doc.add_roles("SB Admin")
if not owner_doc.custom_group_company:
frappe.db.set_value("User", comp.owner_user, "custom_group_company", comp.name, update_modified=False)
# Backfill existing records to default Group Company if empty
orgs = frappe.db.get_all("SB Organization", filters={"group_company": ["is", "not set"]})
for o in orgs:
frappe.db.set_value("SB Organization", o.name, "group_company", default_co, update_modified=False)
users = frappe.db.get_all("User", filters={"custom_group_company": ["is", "not set"]})
for u in users:
if u.name != "Administrator":
frappe.db.set_value("User", u.name, "custom_group_company", default_co, update_modified=False)
# Backfill documents from organization's group_company
for dt in ["SB Incident", "SB Idea", "SB Order", "SB Process", "SB Cycle", "SB KPI Input", "Managements", "Department", "Branch"]:
if frappe.db.exists("DocType", dt):
docs = frappe.db.get_all(dt, filters={"group_company": ["is", "not set"]})
for d in docs:
org = None
if frappe.db.has_column(dt, "organization"):
org = frappe.db.get_value(dt, d.name, "organization")
elif frappe.db.has_column(dt, "custom_organization"):
org = frappe.db.get_value(dt, d.name, "custom_organization")
co = frappe.db.get_value("SB Organization", org, "group_company") if org else default_co
frappe.db.set_value(dt, d.name, "group_company", co or default_co, update_modified=False)
# Synchronize User Permissions for all non-default group companies
sync_all_user_group_company_permissions()
frappe.db.commit()
print("Multi-tenancy schema, insert_after positioning, & user permissions sync completed successfully.")