mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 15:11:52 +00:00
refactor: gate company restrictions per master via Restrict to Companies checkbox
Replaces the Global Defaults toggle. Each Item/Customer/Supplier now carries a Restrict to Companies checkbox: the Allowed Companies table only shows (and is mandatory) when checked, is cleared on uncheck, and permission filtering, read denial and write validation apply only to masters that have the checkbox set.
This commit is contained in:
@@ -2,6 +2,12 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Supplier", {
|
||||
restrict_to_companies(frm) {
|
||||
if (!frm.doc.restrict_to_companies) {
|
||||
frm.set_value("allowed_companies", []);
|
||||
}
|
||||
},
|
||||
|
||||
setup: function (frm) {
|
||||
frm.set_query("allowed_companies", () => ({
|
||||
query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
"tax_withholding_group",
|
||||
"settings_tab",
|
||||
"company_restrictions_section",
|
||||
"restrict_to_companies",
|
||||
"allowed_companies",
|
||||
"invoice_settings_section",
|
||||
"is_transporter",
|
||||
@@ -430,16 +431,22 @@
|
||||
{
|
||||
"fieldname": "company_restrictions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"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)"
|
||||
"label": "Company Restrictions"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Allowed Companies",
|
||||
"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",
|
||||
@@ -578,7 +585,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2026-07-14 21:00:00.000000",
|
||||
"modified": "2026-07-14 23:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -73,6 +73,7 @@ class Supplier(TransactionBase):
|
||||
primary_address: DF.TextEditor | None
|
||||
release_date: DF.Date | None
|
||||
represents_company: DF.Link | None
|
||||
restrict_to_companies: DF.Check
|
||||
supplier_details: DF.Text | None
|
||||
supplier_group: DF.Link | None
|
||||
supplier_name: DF.Data
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Customer", {
|
||||
restrict_to_companies(frm) {
|
||||
if (!frm.doc.restrict_to_companies) {
|
||||
frm.set_value("allowed_companies", []);
|
||||
}
|
||||
},
|
||||
|
||||
setup: function (frm) {
|
||||
frm.set_query("allowed_companies", () => ({
|
||||
query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"tax_withholding_category",
|
||||
"settings_tab",
|
||||
"company_restrictions_section",
|
||||
"restrict_to_companies",
|
||||
"allowed_companies",
|
||||
"section_break_ario",
|
||||
"so_required",
|
||||
@@ -516,18 +517,24 @@
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Company Restrictions",
|
||||
"depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)"
|
||||
"label": "Company Restrictions"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Allowed Companies",
|
||||
"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,
|
||||
@@ -717,7 +724,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2026-07-14 21:00:00.000000",
|
||||
"modified": "2026-07-14 23:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -99,6 +99,7 @@ class Customer(TransactionBase):
|
||||
primary_address: DF.TextEditor | None
|
||||
prospect_name: DF.Link | None
|
||||
represents_company: DF.Link | None
|
||||
restrict_to_companies: DF.Check
|
||||
sales_team: DF.Table[SalesTeam]
|
||||
so_required: DF.Check
|
||||
supplier_numbers: DF.Table[SupplierNumberAtCustomer]
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
"disable_rounded_total",
|
||||
"disable_in_words",
|
||||
"column_break_hnew",
|
||||
"use_posting_datetime_for_naming_documents",
|
||||
"enable_company_wise_masters"
|
||||
"use_posting_datetime_for_naming_documents"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -93,13 +92,6 @@
|
||||
"fieldtype": "Check",
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -121,7 +113,7 @@
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-07-14 18:30:00.000000",
|
||||
"modified": "2026-07-14 23:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Global Defaults",
|
||||
|
||||
@@ -18,7 +18,6 @@ keydict = {
|
||||
"account_url": "account_url",
|
||||
"disable_rounded_total": "disable_rounded_total",
|
||||
"disable_in_words": "disable_in_words",
|
||||
"enable_company_wise_masters": "enable_company_wise_masters",
|
||||
}
|
||||
|
||||
ROUNDED_TOTAL_DOCTYPES = (
|
||||
@@ -52,7 +51,6 @@ class GlobalDefaults(Document):
|
||||
demo_company: DF.Link | None
|
||||
disable_in_words: DF.Check
|
||||
disable_rounded_total: DF.Check
|
||||
enable_company_wise_masters: DF.Check
|
||||
hide_currency_symbol: DF.Check
|
||||
use_posting_datetime_for_naming_documents: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -26,9 +26,6 @@ class CompanyRestriction(Document):
|
||||
def get_allowed_companies(user, doctype):
|
||||
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)
|
||||
if "Company" not in user_permissions:
|
||||
return None
|
||||
@@ -45,31 +42,39 @@ def get_permission_query_conditions(user, doctype=None):
|
||||
|
||||
parent = frappe.qb.DocType(doctype)
|
||||
restriction = frappe.qb.DocType("Company Restriction")
|
||||
restriction_rows = (
|
||||
allowed_rows = (
|
||||
frappe.qb.from_(restriction)
|
||||
.select(restriction.name)
|
||||
.where(
|
||||
(restriction.parenttype == doctype)
|
||||
& (restriction.parentfield == "allowed_companies")
|
||||
& (restriction.parent == parent.name)
|
||||
& (restriction.company.isin(allowed_companies))
|
||||
)
|
||||
)
|
||||
allowed_rows = restriction_rows.where(restriction.company.isin(allowed_companies))
|
||||
return Bracket(ExistsCriterion(allowed_rows) | ExistsCriterion(restriction_rows).negate())
|
||||
return Bracket((parent.restrict_to_companies == 0) | ExistsCriterion(allowed_rows))
|
||||
|
||||
|
||||
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)
|
||||
if not allowed_companies:
|
||||
return True
|
||||
|
||||
companies = [row.company for row in doc.get("allowed_companies") or []]
|
||||
if not companies:
|
||||
return True
|
||||
return any(company in allowed_companies for company in companies)
|
||||
return any(row.company in allowed_companies for row in doc.get("allowed_companies") or [])
|
||||
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
|
||||
@@ -60,6 +60,12 @@ frappe.ui.form.on("Item", {
|
||||
erpnext.utils.confirm_negative_stock(frm);
|
||||
},
|
||||
|
||||
restrict_to_companies(frm) {
|
||||
if (!frm.doc.restrict_to_companies) {
|
||||
frm.set_value("allowed_companies", []);
|
||||
}
|
||||
},
|
||||
|
||||
setup: function (frm) {
|
||||
frm.set_query("allowed_companies", () => ({
|
||||
query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"column_break_wugd",
|
||||
"over_billing_allowance",
|
||||
"company_restrictions_section",
|
||||
"restrict_to_companies",
|
||||
"allowed_companies",
|
||||
"section_break_11",
|
||||
"brand",
|
||||
@@ -1089,16 +1090,22 @@
|
||||
{
|
||||
"fieldname": "company_restrictions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"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)"
|
||||
"label": "Company Restrictions"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Allowed Companies",
|
||||
"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",
|
||||
@@ -1106,7 +1113,7 @@
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2026-07-14 21:00:00.000000",
|
||||
"modified": "2026-07-14 23:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -134,6 +134,7 @@ class Item(Document):
|
||||
quality_inspection_template: DF.Link | None
|
||||
reorder_levels: DF.Table[ItemReorder]
|
||||
retain_sample: DF.Check
|
||||
restrict_to_companies: DF.Check
|
||||
safety_stock: DF.Float
|
||||
sales_tax_withholding_category: DF.Link | None
|
||||
sales_uom: DF.Link | None
|
||||
|
||||
Reference in New Issue
Block a user