Merge pull request #57258 from mihir-kandoi/per-master-company-restriction

This commit is contained in:
Mihir Kandoi
2026-07-18 20:39:13 +05:30
committed by GitHub
12 changed files with 74 additions and 37 deletions

View File

@@ -2,6 +2,12 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Supplier", { frappe.ui.form.on("Supplier", {
restrict_to_companies(frm) {
if (!frm.doc.restrict_to_companies) {
frm.set_value("allowed_companies", []);
}
},
setup: function (frm) { setup: function (frm) {
frm.set_query("allowed_companies", () => ({ frm.set_query("allowed_companies", () => ({
query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query", query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",

View File

@@ -55,6 +55,7 @@
"tax_withholding_group", "tax_withholding_group",
"settings_tab", "settings_tab",
"company_restrictions_section", "company_restrictions_section",
"restrict_to_companies",
"allowed_companies", "allowed_companies",
"invoice_settings_section", "invoice_settings_section",
"is_transporter", "is_transporter",
@@ -430,16 +431,22 @@
{ {
"fieldname": "company_restrictions_section", "fieldname": "company_restrictions_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Company Restrictions", "label": "Company Restrictions"
"description": "If set, this Supplier is only available for transactions in the listed companies. Leave empty for no restriction.", },
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)" {
"default": "0",
"fieldname": "restrict_to_companies",
"fieldtype": "Check",
"label": "Restrict to Companies",
"description": "If checked, this Supplier is only available for transactions in the companies listed below."
}, },
{ {
"fieldname": "allowed_companies", "fieldname": "allowed_companies",
"fieldtype": "Table MultiSelect", "fieldtype": "Table MultiSelect",
"label": "Allowed Companies", "label": "Allowed Companies",
"options": "Company Restriction", "options": "Company Restriction",
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)" "depends_on": "eval:doc.restrict_to_companies",
"mandatory_depends_on": "eval:doc.restrict_to_companies"
}, },
{ {
"fieldname": "contact_and_address_tab", "fieldname": "contact_and_address_tab",
@@ -578,7 +585,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2026-07-14 21:00:00.000000", "modified": "2026-07-14 23:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",

View File

@@ -73,6 +73,7 @@ class Supplier(TransactionBase):
primary_address: DF.TextEditor | None primary_address: DF.TextEditor | None
release_date: DF.Date | None release_date: DF.Date | None
represents_company: DF.Link | None represents_company: DF.Link | None
restrict_to_companies: DF.Check
supplier_details: DF.Text | None supplier_details: DF.Text | None
supplier_group: DF.Link | None supplier_group: DF.Link | None
supplier_name: DF.Data supplier_name: DF.Data

View File

@@ -2,6 +2,12 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Customer", { frappe.ui.form.on("Customer", {
restrict_to_companies(frm) {
if (!frm.doc.restrict_to_companies) {
frm.set_value("allowed_companies", []);
}
},
setup: function (frm) { setup: function (frm) {
frm.set_query("allowed_companies", () => ({ frm.set_query("allowed_companies", () => ({
query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query", query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",

View File

@@ -66,6 +66,7 @@
"tax_withholding_category", "tax_withholding_category",
"settings_tab", "settings_tab",
"company_restrictions_section", "company_restrictions_section",
"restrict_to_companies",
"allowed_companies", "allowed_companies",
"section_break_ario", "section_break_ario",
"so_required", "so_required",
@@ -516,18 +517,24 @@
"label": "Settings" "label": "Settings"
}, },
{ {
"description": "If set, this Customer is only available for transactions in the listed companies. Leave empty for no restriction.",
"fieldname": "company_restrictions_section", "fieldname": "company_restrictions_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Company Restrictions", "label": "Company Restrictions"
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)" },
{
"default": "0",
"fieldname": "restrict_to_companies",
"fieldtype": "Check",
"label": "Restrict to Companies",
"description": "If checked, this Customer is only available for transactions in the companies listed below."
}, },
{ {
"fieldname": "allowed_companies", "fieldname": "allowed_companies",
"fieldtype": "Table MultiSelect", "fieldtype": "Table MultiSelect",
"label": "Allowed Companies", "label": "Allowed Companies",
"options": "Company Restriction", "options": "Company Restriction",
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)" "depends_on": "eval:doc.restrict_to_companies",
"mandatory_depends_on": "eval:doc.restrict_to_companies"
}, },
{ {
"collapsible": 1, "collapsible": 1,
@@ -717,7 +724,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2026-07-14 21:00:00.000000", "modified": "2026-07-14 23:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer", "name": "Customer",

View File

@@ -99,6 +99,7 @@ class Customer(TransactionBase):
primary_address: DF.TextEditor | None primary_address: DF.TextEditor | None
prospect_name: DF.Link | None prospect_name: DF.Link | None
represents_company: DF.Link | None represents_company: DF.Link | None
restrict_to_companies: DF.Check
sales_team: DF.Table[SalesTeam] sales_team: DF.Table[SalesTeam]
so_required: DF.Check so_required: DF.Check
supplier_numbers: DF.Table[SupplierNumberAtCustomer] supplier_numbers: DF.Table[SupplierNumberAtCustomer]

View File

@@ -17,8 +17,7 @@
"disable_rounded_total", "disable_rounded_total",
"disable_in_words", "disable_in_words",
"column_break_hnew", "column_break_hnew",
"use_posting_datetime_for_naming_documents", "use_posting_datetime_for_naming_documents"
"enable_company_wise_masters"
], ],
"fields": [ "fields": [
{ {
@@ -93,13 +92,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Use Posting Datetime for Naming Documents" "label": "Use Posting Datetime for Naming Documents"
}, },
{
"default": "0",
"description": "When enabled, Supplier, Customer, and Item records can be restricted to specific companies via their <b>Allowed Companies</b> table. Transactions will only show masters configured for the selected company.",
"fieldname": "enable_company_wise_masters",
"fieldtype": "Check",
"label": "Enable Company-wise Master Filtering"
},
{ {
"fieldname": "defaults_section", "fieldname": "defaults_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -121,7 +113,7 @@
"in_create": 1, "in_create": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2026-07-14 18:30:00.000000", "modified": "2026-07-14 23:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Global Defaults", "name": "Global Defaults",

View File

@@ -18,7 +18,6 @@ keydict = {
"account_url": "account_url", "account_url": "account_url",
"disable_rounded_total": "disable_rounded_total", "disable_rounded_total": "disable_rounded_total",
"disable_in_words": "disable_in_words", "disable_in_words": "disable_in_words",
"enable_company_wise_masters": "enable_company_wise_masters",
} }
ROUNDED_TOTAL_DOCTYPES = ( ROUNDED_TOTAL_DOCTYPES = (
@@ -52,7 +51,6 @@ class GlobalDefaults(Document):
demo_company: DF.Link | None demo_company: DF.Link | None
disable_in_words: DF.Check disable_in_words: DF.Check
disable_rounded_total: DF.Check disable_rounded_total: DF.Check
enable_company_wise_masters: DF.Check
hide_currency_symbol: DF.Check hide_currency_symbol: DF.Check
use_posting_datetime_for_naming_documents: DF.Check use_posting_datetime_for_naming_documents: DF.Check
# end: auto-generated types # end: auto-generated types

View File

@@ -26,9 +26,6 @@ class CompanyRestriction(Document):
def get_allowed_companies(user, doctype): def get_allowed_companies(user, doctype):
from frappe.permissions import get_allowed_docs_for_doctype, get_user_permissions from frappe.permissions import get_allowed_docs_for_doctype, get_user_permissions
if not frappe.get_single_value("Global Defaults", "enable_company_wise_masters"):
return None
user_permissions = get_user_permissions(user or frappe.session.user) user_permissions = get_user_permissions(user or frappe.session.user)
if "Company" not in user_permissions: if "Company" not in user_permissions:
return None return None
@@ -45,31 +42,39 @@ def get_permission_query_conditions(user, doctype=None):
parent = frappe.qb.DocType(doctype) parent = frappe.qb.DocType(doctype)
restriction = frappe.qb.DocType("Company Restriction") restriction = frappe.qb.DocType("Company Restriction")
restriction_rows = ( allowed_rows = (
frappe.qb.from_(restriction) frappe.qb.from_(restriction)
.select(restriction.name) .select(restriction.name)
.where( .where(
(restriction.parenttype == doctype) (restriction.parenttype == doctype)
& (restriction.parentfield == "allowed_companies") & (restriction.parentfield == "allowed_companies")
& (restriction.parent == parent.name) & (restriction.parent == parent.name)
& (restriction.company.isin(allowed_companies))
) )
) )
allowed_rows = restriction_rows.where(restriction.company.isin(allowed_companies)) return Bracket((parent.restrict_to_companies == 0) | ExistsCriterion(allowed_rows))
return Bracket(ExistsCriterion(allowed_rows) | ExistsCriterion(restriction_rows).negate())
def has_permission(doc, ptype=None, user=None): def has_permission(doc, ptype=None, user=None):
if not doc.get("restrict_to_companies"):
return True
allowed_companies = get_allowed_companies(user, doc.doctype) allowed_companies = get_allowed_companies(user, doc.doctype)
if not allowed_companies: if not allowed_companies:
return True return True
companies = [row.company for row in doc.get("allowed_companies") or []] return any(row.company in allowed_companies for row in doc.get("allowed_companies") or [])
if not companies:
return True
return any(company in allowed_companies for company in companies)
def validate_allowed_companies(doc): def validate_allowed_companies(doc):
if not doc.get("restrict_to_companies"):
doc.set("allowed_companies", [])
elif not doc.get("allowed_companies") and not doc.flags.ignore_mandatory:
frappe.throw(
_("Allowed Companies is required when Restrict to Companies is checked"),
frappe.MandatoryError,
)
if doc.flags.ignore_permissions: if doc.flags.ignore_permissions:
return return

View File

@@ -60,6 +60,12 @@ frappe.ui.form.on("Item", {
erpnext.utils.confirm_negative_stock(frm); erpnext.utils.confirm_negative_stock(frm);
}, },
restrict_to_companies(frm) {
if (!frm.doc.restrict_to_companies) {
frm.set_value("allowed_companies", []);
}
},
setup: function (frm) { setup: function (frm) {
frm.set_query("allowed_companies", () => ({ frm.set_query("allowed_companies", () => ({
query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query", query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",

View File

@@ -41,6 +41,7 @@
"column_break_wugd", "column_break_wugd",
"over_billing_allowance", "over_billing_allowance",
"company_restrictions_section", "company_restrictions_section",
"restrict_to_companies",
"allowed_companies", "allowed_companies",
"section_break_11", "section_break_11",
"brand", "brand",
@@ -1089,16 +1090,22 @@
{ {
"fieldname": "company_restrictions_section", "fieldname": "company_restrictions_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Company Restrictions", "label": "Company Restrictions"
"description": "If set, this Item is only available for transactions in the listed companies. Leave empty for no restriction.", },
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)" {
"default": "0",
"fieldname": "restrict_to_companies",
"fieldtype": "Check",
"label": "Restrict to Companies",
"description": "If checked, this Item is only available for transactions in the companies listed below."
}, },
{ {
"fieldname": "allowed_companies", "fieldname": "allowed_companies",
"fieldtype": "Table MultiSelect", "fieldtype": "Table MultiSelect",
"label": "Allowed Companies", "label": "Allowed Companies",
"options": "Company Restriction", "options": "Company Restriction",
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)" "depends_on": "eval:doc.restrict_to_companies",
"mandatory_depends_on": "eval:doc.restrict_to_companies"
} }
], ],
"icon": "fa fa-tag", "icon": "fa fa-tag",
@@ -1106,7 +1113,7 @@
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"make_attachments_public": 1, "make_attachments_public": 1,
"modified": "2026-07-14 21:00:00.000000", "modified": "2026-07-14 23:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -134,6 +134,7 @@ class Item(Document):
quality_inspection_template: DF.Link | None quality_inspection_template: DF.Link | None
reorder_levels: DF.Table[ItemReorder] reorder_levels: DF.Table[ItemReorder]
retain_sample: DF.Check retain_sample: DF.Check
restrict_to_companies: DF.Check
safety_stock: DF.Float safety_stock: DF.Float
sales_tax_withholding_category: DF.Link | None sales_tax_withholding_category: DF.Link | None
sales_uom: DF.Link | None sales_uom: DF.Link | None