diff --git a/.mergify.yml b/.mergify.yml
index 5e558062048..95763b27cb2 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -88,7 +88,6 @@ pull_request_rules:
actions:
merge:
method: squash
- commit_message_template: |
- {{ title }} (#{{ number }})
-
- {{ body }}
+ commit_message_format:
+ title: pr-title
+ body: pr-body
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/au_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/au_standard_chart_of_accounts.json
index 515a1e4de9d..a55dd3a183d 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/au_standard_chart_of_accounts.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/au_standard_chart_of_accounts.json
@@ -24,7 +24,8 @@
"account_number": "11530"
},
"account_number": "115",
- "is_group": 1
+ "is_group": 1,
+ "account_type": "Bank"
},
"Trade Receivables": {
"Trade Debtors": {
@@ -529,6 +530,13 @@
"account_number": "630",
"is_group": 1
},
+ "Accrued Manufacturing Expenses": {
+ "Accrued Expenses - Manufacturing": {
+ "account_number": "63510"
+ },
+ "account_number": "635",
+ "is_group": 1
+ },
"account_number": "63",
"is_group": 1
},
@@ -814,4 +822,4 @@
"root_type": "Expense"
}
}
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py
index 5a37bccaafb..3e5b08d069d 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py
@@ -348,10 +348,15 @@ class TestExchangeRateRevaluation(ERPNextTestSuite, AccountsTestMixin):
je.reload()
self.assertEqual(je.voucher_type, "Exchange Rate Revaluation")
self.assertEqual(len(je.accounts), 3)
+ # A gain is credited to the gain/loss account, a loss is debited. The current
+ # exchange rate (from master data) may sit either side of the booked rate, so
+ # derive the column from the sign instead of assuming a gain.
+ gain_loss_debit = abs(total_gain_loss) if total_gain_loss < 0 else 0.0
+ gain_loss_credit = total_gain_loss if total_gain_loss > 0 else 0.0
expected = [
(usd_account, new_balance, 0.0, 100.0, 0.0),
(usd_account, 0.0, old_balance, 0.0, 100.0),
- (gain_loss_account, 0.0, total_gain_loss, 0.0, total_gain_loss),
+ (gain_loss_account, gain_loss_debit, gain_loss_credit, gain_loss_debit, gain_loss_credit),
]
actual = []
for acc in je.accounts:
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 17afc03dde1..e60d3f4614c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -472,7 +472,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
pr = frappe.new_doc("Purchase Receipt")
pr.currency = "USD"
pr.company = "_Test Company with perpetual inventory"
- pr.conversion_rate = (70,)
+ pr.conversion_rate = 80
pr.supplier = "_Test Supplier USD"
pr.append(
"items",
@@ -491,7 +491,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
# Createing purchase invoice against Purchase Receipt
pi = create_purchase_invoice(pr.name)
- pi.conversion_rate = 80
+ pi.conversion_rate = 70
pi.credit_to = "_Test Payable USD - TCP1"
pi.insert()
pi.submit()
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index 3ab3986b013..b6fc77fd1c4 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -21,11 +21,21 @@ class TDSComputationSummaryReport(TaxWithholdingDetailsReport):
AGGREGATE_FIELDS = ("total_amount", "tax_amount")
def validate_filters(self):
- if self.filters.from_date > self.filters.to_date:
+ from_date = self.filters.from_date
+ to_date = self.filters.to_date
+ if not from_date or not to_date:
+ frappe.throw(
+ _("{0} and {1} are mandatory").format(
+ frappe.bold(_("From Date")),
+ frappe.bold(_("To Date")),
+ )
+ )
+
+ if from_date > to_date:
frappe.throw(_("From Date must be before To Date"))
- from_year = get_fiscal_year(self.filters.from_date)[0]
- to_year = get_fiscal_year(self.filters.to_date)[0]
+ from_year = get_fiscal_year(from_date)[0]
+ to_year = get_fiscal_year(to_date)[0]
if from_year != to_year:
frappe.throw(_("From Date and To Date lie in different Fiscal Year"))
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
new file mode 100644
index 00000000000..e7dcefb59f3
--- /dev/null
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -0,0 +1,735 @@
+{
+ "app": "erpnext",
+ "charts": [
+ {
+ "chart_name": "Profit and Loss",
+ "label": "Profit and Loss"
+ },
+ {
+ "chart_name": "Accounts Receivable Ageing",
+ "label": "Accounts Receivable Ageing"
+ },
+ {
+ "chart_name": "Accounts Payable Ageing",
+ "label": "Accounts Payable Ageing"
+ },
+ {
+ "chart_name": "Bank Balance",
+ "label": "Bank Balance"
+ },
+ {
+ "chart_name": "Budget Variance",
+ "label": "Budget Variance"
+ }
+ ],
+ "content": "[{\"id\":\"acc_ov_hdr1\",\"type\":\"header\",\"data\":{\"text\":\"Accounting Overview\",\"col\":12}},{\"id\":\"acc_ov_nc01\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Bills\",\"col\":3}},{\"id\":\"acc_ov_nc02\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Bills\",\"col\":3}},{\"id\":\"acc_ov_nc03\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Payment\",\"col\":3}},{\"id\":\"acc_ov_nc04\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Payment\",\"col\":3}},{\"id\":\"acc_ov_ch01\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"acc_ov_ch02\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Accounts Receivable Ageing\",\"col\":6}},{\"id\":\"acc_ov_ch03\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Accounts Payable Ageing\",\"col\":6}},{\"id\":\"acc_ov_ch04\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Bank Balance\",\"col\":6}},{\"id\":\"acc_ov_ch05\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Budget Variance\",\"col\":6}}]",
+ "creation": "2026-07-14 12:00:00",
+ "custom_blocks": [],
+ "docstatus": 0,
+ "doctype": "Workspace",
+ "for_user": "",
+ "hide_custom": 0,
+ "icon": "landmark",
+ "idx": 0,
+ "indicator_color": "green",
+ "is_hidden": 0,
+ "label": "Accounting",
+ "link_type": "DocType",
+ "links": [],
+ "modified": "2026-07-14 14:28:55.763394",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "module_onboarding": "Accounting Onboarding",
+ "name": "Accounting",
+ "number_cards": [
+ {
+ "label": "Outgoing Bills",
+ "number_card_name": "Total Outgoing Bills"
+ },
+ {
+ "label": "Incoming Bills",
+ "number_card_name": "Total Incoming Bills"
+ },
+ {
+ "label": "Incoming Payment",
+ "number_card_name": "Total Incoming Payment"
+ },
+ {
+ "label": "Outgoing Payment",
+ "number_card_name": "Total Outgoing Payment"
+ }
+ ],
+ "owner": "Administrator",
+ "public": 1,
+ "quick_lists": [],
+ "roles": [],
+ "sequence_id": 4.0,
+ "shortcuts": [],
+ "sidebar_items": [
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "house",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Home",
+ "link_to": "Accounting",
+ "link_type": "Workspace",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "database",
+ "indent": 1,
+ "keep_closed": 0,
+ "label": "Setup",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Chart of Accounts",
+ "link_to": "Account",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Chart of Cost Centers",
+ "link_to": "Cost Center",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Account Category",
+ "link_to": "Account Category",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Accounting Dimension",
+ "link_to": "Accounting Dimension",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Currency",
+ "link_to": "Currency",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Currency Exchange",
+ "link_to": "Currency Exchange",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Finance Book",
+ "link_to": "Finance Book",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Mode of Payment",
+ "link_to": "Mode of Payment",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Payment Term",
+ "link_to": "Payment Term",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Journal Entry Template",
+ "link_to": "Journal Entry Template",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Terms and Conditions",
+ "link_to": "Terms and Conditions",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Company",
+ "link_to": "Company",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Fiscal Year",
+ "link_to": "Fiscal Year",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "book-open-check",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Opening & Closing",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "COA Importer",
+ "link_to": "Chart of Accounts Importer",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Opening Invoice Tool",
+ "link_to": "Opening Invoice Creation Tool",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Accounting Period",
+ "link_to": "Accounting Period",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "FX Revaluation",
+ "link_to": "Exchange Rate Revaluation",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Period Closing Voucher",
+ "link_to": "Period Closing Voucher",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "coins",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Taxes",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "panel-bottom-close",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Sales Tax Template",
+ "link_to": "Sales Taxes and Charges Template",
+ "link_type": "DocType",
+ "navigate_to_tab": "",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "panel-top-close",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Purchase Tax Template",
+ "link_to": "Purchase Taxes and Charges Template",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "package",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Item Tax Template",
+ "link_to": "Item Tax Template",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "triangle",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Tax Category",
+ "link_to": "Tax Category",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "book-open-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Tax Rule",
+ "link_to": "Tax Rule",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "book-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Tax Withholding Category",
+ "link_to": "Tax Withholding Category",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Tax Withholding Group",
+ "link_to": "Tax Withholding Group",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "notebook-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Deduction Certificate",
+ "link_to": "Lower Deduction Certificate",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "wallet",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Budgeting",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "briefcase-business",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Budget",
+ "link_to": "Budget",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "notepad-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Cost Center Allocation",
+ "link_to": "Cost Center Allocation",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "coins",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Share Management",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "user",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Shareholder",
+ "link_to": "Shareholder",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "move-horizontal",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Share Transfer",
+ "link_to": "Share Transfer",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "repeat",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Subscriptions",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "circle-dollar-sign",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subscription",
+ "link_to": "Subscription",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "receipt-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subscription Plan",
+ "link_to": "Subscription Plan",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "settings",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subscription Settings",
+ "link_to": "Subscription Settings",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "sheet",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Reports",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "TDS Computation Summary",
+ "link_to": "TDS Computation Summary",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Tax Withholding Details",
+ "link_to": "Tax Withholding Details",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "sheet",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Budget Variance",
+ "link_to": "Budget Variance Report",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "list",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Share Ledger",
+ "link_to": "Share Ledger",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "notepad-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Share Balance",
+ "link_to": "Share Balance",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "wrench",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Settings",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Accounts Settings",
+ "link_to": "Accounts Settings",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Currency Exchange Settings",
+ "link_to": "Currency Exchange Settings",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ }
+ ],
+ "standard": 1,
+ "title": "Accounting",
+ "type": "Workspace"
+}
diff --git a/erpnext/accounts/workspace/accounts_setup/accounts_setup.json b/erpnext/accounts/workspace/accounts_setup/accounts_setup.json
deleted file mode 100644
index 88dd071b131..00000000000
--- a/erpnext/accounts/workspace/accounts_setup/accounts_setup.json
+++ /dev/null
@@ -1,329 +0,0 @@
-{
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-14 12:44:31.994274",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "database",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Accounts Setup",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-06-14 13:43:50.138704",
- "modified_by": "Administrator",
- "module": "Accounts",
- "module_onboarding": "Accounting Onboarding",
- "name": "Accounts Setup",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 55.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 0,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Chart of Accounts",
- "link_to": "Account",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Chart of Cost Centers",
- "link_to": "Cost Center",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Account Category",
- "link_to": "Account Category",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounting Dimension",
- "link_to": "Accounting Dimension",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Currency",
- "link_to": "Currency",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Currency Exchange",
- "link_to": "Currency Exchange",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Finance Book",
- "link_to": "Finance Book",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Mode of Payment",
- "link_to": "Mode of Payment",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Payment Term",
- "link_to": "Payment Term",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Journal Entry Template",
- "link_to": "Journal Entry Template",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Terms and Conditions",
- "link_to": "Terms and Conditions",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Company",
- "link_to": "Company",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Fiscal Year",
- "link_to": "Fiscal Year",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Sales Taxes",
- "link_to": "Sales Taxes and Charges Template",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "lock-keyhole-open",
- "indent": 1,
- "keep_closed": 0,
- "label": "Opening & Closing",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "COA Importer",
- "link_to": "Chart of Accounts Importer",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Opening Invoice Tool",
- "link_to": "Opening Invoice Creation Tool",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounting Period",
- "link_to": "Accounting Period",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "FX Revaluation",
- "link_to": "Exchange Rate Revaluation",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Period Closing Voucher",
- "link_to": "Period Closing Voucher",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "settings",
- "indent": 1,
- "keep_closed": 0,
- "label": "Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounts Settings",
- "link_to": "Accounts Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Currency Exchange Settings",
- "link_to": "Currency Exchange Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Accounts Setup",
- "type": "Workspace"
-}
diff --git a/erpnext/accounts/workspace/banking/banking.json b/erpnext/accounts/workspace/banking/banking.json
deleted file mode 100644
index d4ff8487759..00000000000
--- a/erpnext/accounts/workspace/banking/banking.json
+++ /dev/null
@@ -1,222 +0,0 @@
-{
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-11 11:51:22.767176",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "circle-dollar-sign",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Banking",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-07-03 13:43:50.924019",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Banking",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 49.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "book-open-check",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Clearance",
- "link_to": "Bank Clearance",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "wrench",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Reconciliation",
- "link_to": "Bank Reconciliation Tool",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "clipboard-check",
- "indent": 0,
- "keep_closed": 0,
- "label": "Reconciliation Statement",
- "link_to": "Bank Reconciliation Statement",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "split",
- "indent": 0,
- "keep_closed": 0,
- "label": "Unreconcile Payment",
- "link_to": "Unreconcile Payment",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "link",
- "indent": 0,
- "keep_closed": 0,
- "label": "Process Payment Reconciliation",
- "link_to": "Process Payment Reconciliation",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 1,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank",
- "link_to": "Bank",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Account",
- "link_to": "Bank Account",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Account Type",
- "link_to": "Bank Account Type",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Account Subtype",
- "link_to": "Bank Account Subtype",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Guarantee",
- "link_to": "Bank Guarantee",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Plaid Settings",
- "link_to": "Plaid Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "scroll-text",
- "indent": 1,
- "keep_closed": 1,
- "label": "Dunning",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Dunning",
- "link_to": "Dunning",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Dunning Type",
- "link_to": "Dunning Type",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Banking",
- "type": "Workspace"
-}
diff --git a/erpnext/accounts/workspace/budgeting/budgeting.json b/erpnext/accounts/workspace/budgeting/budgeting.json
deleted file mode 100644
index c5ea717fe52..00000000000
--- a/erpnext/accounts/workspace/budgeting/budgeting.json
+++ /dev/null
@@ -1,104 +0,0 @@
-{
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-14 14:38:20.315394",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "wallet",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Budgeting",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-07-03 04:24:48.116724",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Budgeting",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 57.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "briefcase-business",
- "indent": 0,
- "keep_closed": 0,
- "label": "Budget",
- "link_to": "Budget",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "badge-cent",
- "indent": 0,
- "keep_closed": 0,
- "label": "Cost Center",
- "link_to": "Cost Center",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "wallet",
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounting Dimension",
- "link_to": "Accounting Dimension",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "notepad-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Cost Center Allocation",
- "link_to": "Cost Center Allocation",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "sheet",
- "indent": 0,
- "keep_closed": 0,
- "label": "Budget Variance",
- "link_to": "Budget Variance Report",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Budgeting",
- "type": "Workspace"
-}
diff --git a/erpnext/accounts/workspace/payments/payments.json b/erpnext/accounts/workspace/payments/payments.json
index fc29978e9e9..0553e0de207 100644
--- a/erpnext/accounts/workspace/payments/payments.json
+++ b/erpnext/accounts/workspace/payments/payments.json
@@ -15,7 +15,7 @@
"label": "Payments",
"link_type": "DocType",
"links": [],
- "modified": "2026-07-03 13:43:50.184761",
+ "modified": "2026-07-14 12:00:00.000000",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
@@ -25,9 +25,23 @@
"public": 1,
"quick_lists": [],
"roles": [],
- "sequence_id": 47.0,
+ "sequence_id": 3.0,
"shortcuts": [],
"sidebar_items": [
+ {
+ "child": 0,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "house",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Home",
+ "link_to": "Payments",
+ "link_type": "Workspace",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
{
"child": 0,
"collapsible": 1,
@@ -161,6 +175,180 @@
"show_arrow": 0,
"type": "Link"
},
+ {
+ "child": 0,
+ "collapsible": 1,
+ "icon": "circle-dollar-sign",
+ "indent": 1,
+ "keep_closed": 0,
+ "label": "Banking",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "book-open-check",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank Clearance",
+ "link_to": "Bank Clearance",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "wrench",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank Reconciliation",
+ "link_to": "Bank Reconciliation Tool",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "clipboard-check",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Reconciliation Statement",
+ "link_to": "Bank Reconciliation Statement",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "icon": "database",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Banking Setup",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank",
+ "link_to": "Bank",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank Account",
+ "link_to": "Bank Account",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank Account Type",
+ "link_to": "Bank Account Type",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank Account Subtype",
+ "link_to": "Bank Account Subtype",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Bank Guarantee",
+ "link_to": "Bank Guarantee",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Plaid Settings",
+ "link_to": "Plaid Settings",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "icon": "receipt-text",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Dunning",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Dunning",
+ "link_to": "Dunning",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Dunning Type",
+ "link_to": "Dunning Type",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
{
"child": 0,
"collapsible": 1,
diff --git a/erpnext/accounts/workspace/share_management/share_management.json b/erpnext/accounts/workspace/share_management/share_management.json
deleted file mode 100644
index c48bec275ce..00000000000
--- a/erpnext/accounts/workspace/share_management/share_management.json
+++ /dev/null
@@ -1,86 +0,0 @@
-{
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-11 11:51:22.831729",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "coins",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Share Management",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-07-03 13:43:51.040978",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Share Management",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 50.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 1,
- "collapsible": 1,
- "icon": "user",
- "indent": 0,
- "keep_closed": 0,
- "label": "Shareholder",
- "link_to": "Shareholder",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "move-horizontal",
- "indent": 0,
- "keep_closed": 0,
- "label": "Share Transfer",
- "link_to": "Share Transfer",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "list",
- "indent": 0,
- "keep_closed": 0,
- "label": "Share Ledger",
- "link_to": "Share Ledger",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "notepad-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Share Balance",
- "link_to": "Share Balance",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Share Management",
- "type": "Workspace"
-}
diff --git a/erpnext/accounts/workspace/subscriptions/subscriptions.json b/erpnext/accounts/workspace/subscriptions/subscriptions.json
deleted file mode 100644
index f97c4a09b95..00000000000
--- a/erpnext/accounts/workspace/subscriptions/subscriptions.json
+++ /dev/null
@@ -1,121 +0,0 @@
-{
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-14 14:08:36.817393",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "wallet",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Subscriptions",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-07-03 14:08:36.999272",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Subscriptions",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 56.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "circle-dollar-sign",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subscription",
- "link_to": "Subscription",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "receipt-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subscription Plan",
- "link_to": "Subscription Plan",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "settings",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subscription Settings",
- "link_to": "Subscription Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 1,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Customer",
- "link_to": "Customer",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Supplier",
- "link_to": "Supplier",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Item",
- "link_to": "Item",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Subscriptions",
- "type": "Workspace"
-}
diff --git a/erpnext/accounts/workspace/taxes/taxes.json b/erpnext/accounts/workspace/taxes/taxes.json
deleted file mode 100644
index e94bacb66d3..00000000000
--- a/erpnext/accounts/workspace/taxes/taxes.json
+++ /dev/null
@@ -1,188 +0,0 @@
-{
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-11 11:51:22.649582",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "coins",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Taxes",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-07-03 13:43:50.894825",
- "modified_by": "Administrator",
- "module": "Accounts",
- "module_onboarding": "Accounting Onboarding",
- "name": "Taxes",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 48.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "panel-bottom-close",
- "indent": 0,
- "keep_closed": 0,
- "label": "Sales Tax Template",
- "link_to": "Sales Taxes and Charges Template",
- "link_type": "DocType",
- "navigate_to_tab": "",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "panel-top-close",
- "indent": 0,
- "keep_closed": 0,
- "label": "Purchase Tax Template",
- "link_to": "Purchase Taxes and Charges Template",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "package",
- "indent": 0,
- "keep_closed": 0,
- "label": "Item Tax Template",
- "link_to": "Item Tax Template",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 1,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "triangle",
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Category",
- "link_to": "Tax Category",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "book-open-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Rule",
- "link_to": "Tax Rule",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "book-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Withholding Category",
- "link_to": "Tax Withholding Category",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Withholding Group",
- "link_to": "Tax Withholding Group",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "notebook-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Deduction Certificate",
- "link_to": "Lower Deduction Certificate",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "sheet",
- "indent": 1,
- "keep_closed": 1,
- "label": "Reports",
- "link_to": "",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "TDS Computation Summary",
- "link_to": "TDS Computation Summary",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Withholding Details",
- "link_to": "Tax Withholding Details",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Taxes",
- "type": "Workspace"
-}
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index be27000db2b..0a28177ba74 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -259,6 +259,7 @@ class PurchaseOrder(BuyingController):
"ref_dn_field": "material_request_item",
"compare_fields": mri_compare_fields,
"is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
},
}
)
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index b0c75c49d9e..b405c0b0be5 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -913,8 +913,10 @@
"fieldname": "job_card",
"fieldtype": "Link",
"label": "Job Card",
+ "no_copy": 1,
"options": "Job Card",
- "search_index": 1
+ "print_hide": 1,
+ "read_only": 1
},
{
"fieldname": "distributed_discount_amount",
@@ -941,7 +943,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2026-06-08 21:00:00.000000",
+ "modified": "2026-07-15 10:30:04.600510",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 4d2d64cfcc1..acdbed969e8 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -3,6 +3,9 @@
frappe.ui.form.on("Supplier", {
setup: function (frm) {
+ frm.set_query("allowed_companies", () => ({
+ query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",
+ }));
frm.set_query("default_price_list", { buying: 1 });
if (frm.doc.__islocal == 1) {
frm.set_value("represents_company", "");
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 12a40cbca7b..caee355c57c 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -54,6 +54,8 @@
"tax_withholding_category",
"tax_withholding_group",
"settings_tab",
+ "company_restrictions_section",
+ "allowed_companies",
"invoice_settings_section",
"is_transporter",
"allow_purchase_invoice_creation_without_purchase_order",
@@ -425,6 +427,20 @@
"fieldtype": "Tab Break",
"label": "Settings"
},
+ {
+ "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)"
+ },
+ {
+ "fieldname": "allowed_companies",
+ "fieldtype": "Table MultiSelect",
+ "label": "Allowed Companies",
+ "options": "Company Restriction",
+ "depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)"
+ },
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
@@ -562,7 +578,7 @@
"link_fieldname": "party"
}
],
- "modified": "2026-06-27 16:12:33.190257",
+ "modified": "2026-07-14 21:00:00.000000",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 1de54ed9313..dfed0be4198 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -17,6 +17,7 @@ from erpnext.accounts.party import (
validate_party_currency_before_merging,
)
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
+from erpnext.stock.doctype.company_restriction.company_restriction import validate_allowed_companies
from erpnext.utilities.transaction_base import TransactionBase
@@ -36,12 +37,14 @@ class Supplier(TransactionBase):
from erpnext.buying.doctype.customer_number_at_supplier.customer_number_at_supplier import (
CustomerNumberAtSupplier,
)
+ from erpnext.stock.doctype.company_restriction.company_restriction import CompanyRestriction
from erpnext.utilities.doctype.portal_user.portal_user import PortalUser
accounts: DF.Table[PartyAccount]
alias: DF.Data | None
allow_purchase_invoice_creation_without_purchase_order: DF.Check
allow_purchase_invoice_creation_without_purchase_receipt: DF.Check
+ allowed_companies: DF.TableMultiSelect[CompanyRestriction]
companies: DF.Table[AllowedToTransactWith]
country: DF.Link | None
customer_numbers: DF.Table[CustomerNumberAtSupplier]
@@ -146,6 +149,7 @@ class Supplier(TransactionBase):
self.validate_internal_supplier()
self.add_role_for_user()
self.validate_currency_for_receivable_payable_and_advance_account()
+ validate_allowed_companies(self)
@frappe.whitelist()
def get_supplier_group_details(self):
diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
index c131439463f..31efaa6690b 100644
--- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
+++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
@@ -307,6 +307,7 @@
"fieldname": "net_rate",
"fieldtype": "Currency",
"label": "Net Rate",
+ "options": "currency",
"print_hide": 1,
"read_only": 1
},
@@ -613,7 +614,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2025-06-17 12:05:52.441645",
+ "modified": "2026-07-15 10:33:24.855979",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation Item",
diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json
index cfd480b3312..4fdfd1fe342 100644
--- a/erpnext/buying/workspace/buying/buying.json
+++ b/erpnext/buying/workspace/buying/buying.json
@@ -501,7 +501,7 @@
"type": "Link"
}
],
- "modified": "2026-07-03 13:43:50.509039",
+ "modified": "2026-07-14 12:00:00.000000",
"modified_by": "Administrator",
"module": "Buying",
"module_onboarding": "Buying Onboarding",
@@ -754,6 +754,83 @@
"show_arrow": 0,
"type": "Link"
},
+ {
+ "child": 0,
+ "collapsible": 1,
+ "icon": "rocket",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Subcontracting",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "folder-tree",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subcontracting BOM",
+ "link_to": "Subcontracting BOM",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subcontracting Inward Order",
+ "link_to": "Subcontracting Inward Order",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subcontracting Delivery",
+ "link_to": "Stock Entry",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subcontracting Order",
+ "link_to": "Subcontracting Order",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subcontracting Receipt",
+ "link_to": "Subcontracting Receipt",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
{
"child": 0,
"collapsible": 1,
@@ -910,6 +987,45 @@
"show_arrow": 0,
"type": "Link"
},
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Subcontract Order Summary",
+ "link_to": "Subcontract Order Summary",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Materials To Be Transferred",
+ "link_to": "Subcontracted Raw Materials To Be Transferred",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "icon": "",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Items To Be Received",
+ "link_to": "Subcontracted Item To Be Received",
+ "link_type": "Report",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
{
"child": 0,
"collapsible": 1,
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 733a7160da8..64d2a0bd62a 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -565,6 +565,7 @@ def show_accounting_ledger_preview(company: str, doctype: str, docname: str):
filters = frappe._dict(company=company, include_dimensions=1)
doc = frappe.get_lazy_doc(doctype, docname)
+ doc.check_permission("read")
doc.run_method("before_gl_preview")
gl_columns, gl_data = get_accounting_ledger_preview(doc, filters)
@@ -580,6 +581,7 @@ def show_stock_ledger_preview(company: str, doctype: str, docname: str):
filters = frappe._dict(company=company)
doc = frappe.get_lazy_doc(doctype, docname)
+ doc.check_permission("read")
doc.run_method("before_sl_preview")
sl_columns, sl_data = get_stock_ledger_preview(doc, filters)
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index e783d9e0fc4..1806aaa9c0a 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -17,6 +17,7 @@ add_to_apps_screen = [
"title": app_title,
"route": app_home,
"has_permission": "erpnext.check_app_permission",
+ "sequence_id": 1,
}
]
@@ -307,6 +308,18 @@ sounds = [
has_upload_permission = {"Employee": "erpnext.setup.doctype.employee.employee.has_upload_permission"}
+permission_query_conditions = {
+ "Item": "erpnext.stock.doctype.company_restriction.company_restriction.get_permission_query_conditions",
+ "Customer": "erpnext.stock.doctype.company_restriction.company_restriction.get_permission_query_conditions",
+ "Supplier": "erpnext.stock.doctype.company_restriction.company_restriction.get_permission_query_conditions",
+}
+
+has_permission = {
+ "Item": "erpnext.stock.doctype.company_restriction.company_restriction.has_permission",
+ "Customer": "erpnext.stock.doctype.company_restriction.company_restriction.has_permission",
+ "Supplier": "erpnext.stock.doctype.company_restriction.company_restriction.has_permission",
+}
+
has_website_permission = {
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
diff --git a/erpnext/manufacturing/doctype/production_plan/services/work_order_planning.py b/erpnext/manufacturing/doctype/production_plan/services/work_order_planning.py
index ae4611e9fe3..3897eddaa1d 100644
--- a/erpnext/manufacturing/doctype/production_plan/services/work_order_planning.py
+++ b/erpnext/manufacturing/doctype/production_plan/services/work_order_planning.py
@@ -38,9 +38,10 @@ class WorkOrderCreationService:
self.doc = doc
def get_production_items(self):
+ bom_warehouse_map = self.get_bom_source_warehouse_map(self.doc.po_items)
item_dict = {}
for d in self.doc.po_items:
- item_details = self._production_item_details(d)
+ item_details = self._production_item_details(d, bom_warehouse_map)
if self.doc.get_items_from == "Material Request":
item_details["qty"] = d.planned_qty
key = (d.item_code, d.material_request_item, d.warehouse, d.planned_start_date)
@@ -52,7 +53,20 @@ class WorkOrderCreationService:
item_dict[key] = item_details
return item_dict
- def _production_item_details(self, d):
+ def get_bom_source_warehouse_map(self, rows):
+ bom_names = {row.bom_no for row in rows if row.bom_no}
+ if not bom_names:
+ return {}
+ return dict(
+ frappe.get_all(
+ "BOM",
+ filters={"name": ["in", list(bom_names)]},
+ fields=["name", "default_source_warehouse"],
+ as_list=True,
+ )
+ )
+
+ def _production_item_details(self, d, bom_warehouse_map):
details = {
"production_item": d.item_code,
"use_multi_level_bom": d.include_exploded_items,
@@ -70,7 +84,7 @@ class WorkOrderCreationService:
"product_bundle_item": d.product_bundle_item,
"planned_start_date": d.planned_start_date,
"project": self.doc.project,
- "source_warehouse": frappe.get_value("BOM", d.bom_no, "default_source_warehouse"),
+ "source_warehouse": bom_warehouse_map.get(d.bom_no),
}
if not details["project"] and d.sales_order:
details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
@@ -112,6 +126,7 @@ class WorkOrderCreationService:
wo_list.append(work_order)
def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
+ bom_warehouse_map = self.get_bom_source_warehouse_map(self.doc.sub_assembly_items)
for row in self.doc.sub_assembly_items:
if row.type_of_manufacturing == "Subcontract":
subcontracted_po.setdefault(row.supplier, []).append(row)
@@ -119,16 +134,16 @@ class WorkOrderCreationService:
if row.type_of_manufacturing == "Material Request":
continue
- work_order = self._sub_assembly_work_order(row, default_warehouses)
+ work_order = self._sub_assembly_work_order(row, default_warehouses, bom_warehouse_map)
if work_order:
wo_list.append(work_order)
- def _sub_assembly_work_order(self, row, default_warehouses):
+ def _sub_assembly_work_order(self, row, default_warehouses, bom_warehouse_map):
if flt(row.qty) <= flt(row.ordered_qty):
return None
work_order_data = {
- "source_warehouse": frappe.get_value("BOM", row.bom_no, "default_source_warehouse"),
+ "source_warehouse": bom_warehouse_map.get(row.bom_no),
"wip_warehouse": default_warehouses.get("wip_warehouse"),
"fg_warehouse": default_warehouses.get("fg_warehouse"),
"scrap_warehouse": default_warehouses.get("scrap_warehouse"),
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 12c13f8aaee..e748cab0008 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -261,6 +261,7 @@ erpnext.patches.v14_0.update_proprietorship_to_individual
erpnext.patches.v15_0.rename_subcontracting_fields
erpnext.patches.v15_0.unset_incorrect_additional_discount_percentage
erpnext.patches.v16_0.convert_commission_rate_to_percent
+erpnext.patches.v16_0.convert_hide_currency_symbol_to_check
[post_model_sync]
erpnext.patches.v15_0.rename_gross_purchase_amount_to_net_purchase_amount
@@ -496,4 +497,5 @@ erpnext.patches.v16_0.backfill_pick_list_transferred_qty
erpnext.patches.v16_0.create_shop_floor_roles
erpnext.patches.v15_0.backfill_sla_link_filters_on_custom_field
erpnext.patches.v15_0.backfill_sla_link_filters_on_docfield
-erpnext.patches.v16_0.crm_settings_handle_allowed_users_for_frappe_crm
\ No newline at end of file
+erpnext.patches.v16_0.crm_settings_handle_allowed_users_for_frappe_crm
+erpnext.patches.v16_0.access_control_for_project_users
diff --git a/erpnext/patches/v16_0/access_control_for_project_users.py b/erpnext/patches/v16_0/access_control_for_project_users.py
new file mode 100644
index 00000000000..7202e6c71ea
--- /dev/null
+++ b/erpnext/patches/v16_0/access_control_for_project_users.py
@@ -0,0 +1,34 @@
+import frappe
+
+
+def execute():
+ Project = frappe.qb.DocType("Project")
+ ProjectUser = frappe.qb.DocType("Project User")
+
+ query = (
+ frappe.qb.from_(Project)
+ .join(ProjectUser)
+ .on(Project.name == ProjectUser.parent)
+ .select(Project.name, ProjectUser.user)
+ .where(Project.status != "Cancelled") # Not considering cancelled Projects.
+ )
+
+ proj_users = query.run(as_dict=1)
+
+ project_mapped_users = get_project_mapped_users(proj_users)
+
+ for d in proj_users:
+ if d.user in project_mapped_users[d.name]:
+ continue
+
+ frappe.share.add_docshare("Project", d.name, user=d.user)
+
+
+def get_project_mapped_users(proj_users):
+ projects = set([d.name for d in proj_users])
+ project_mapped_users = {}
+
+ for d in projects:
+ project_mapped_users[d] = [d.user for d in frappe.share.get_users("Project", d)]
+
+ return project_mapped_users
diff --git a/erpnext/patches/v16_0/convert_hide_currency_symbol_to_check.py b/erpnext/patches/v16_0/convert_hide_currency_symbol_to_check.py
new file mode 100644
index 00000000000..d3ed8ca5a31
--- /dev/null
+++ b/erpnext/patches/v16_0/convert_hide_currency_symbol_to_check.py
@@ -0,0 +1,9 @@
+import frappe
+
+
+def execute():
+ # runs pre_model_sync: field is still a Select, so this returns the raw "Yes"/"No"
+ old_value = frappe.db.get_single_value("Global Defaults", "hide_currency_symbol")
+ new_value = 1 if old_value == "Yes" else 0
+ frappe.db.set_single_value("Global Defaults", "hide_currency_symbol", new_value)
+ frappe.db.set_default("hide_currency_symbol", new_value)
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 8f5a9b03813..b55cec332bd 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -86,7 +86,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "Open\nCompleted\nCancelled",
+ "options": "Open\nOn hold\nCompleted\nCancelled",
"search_index": 1
},
{
@@ -210,13 +210,15 @@
"fieldname": "users",
"fieldtype": "Table",
"label": "Users",
- "options": "Project User"
+ "options": "Project User",
+ "permlevel": 1
},
{
"fieldname": "copied_from",
"fieldtype": "Data",
"hidden": 1,
"label": "Copied From",
+ "permlevel": 1,
"read_only": 1
},
{
@@ -482,13 +484,25 @@
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
- "modified": "2026-05-22 16:45:50.762759",
+ "modified": "2026-07-14 14:32:11.328347",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Projects Manager",
+ "share": 1,
+ "write": 1
+ },
{
"create": 1,
"delete": 1,
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 14c4345ea78..fc85099bf6c 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -59,7 +59,7 @@ class Project(Document):
project_type: DF.Link | None
sales_order: DF.Link | None
second_email: DF.Time | None
- status: DF.Literal["Open", "Completed", "Cancelled"]
+ status: DF.Literal["Open", "On hold", "Completed", "Cancelled"]
subject: DF.Data | None
to_time: DF.Time | None
total_billable_amount: DF.Currency
@@ -90,6 +90,7 @@ class Project(Document):
def validate(self):
if not self.is_new():
self.copy_from_template()
+ self.control_access_for_project_users()
self.send_welcome_email()
self.update_costing()
self.update_percent_complete()
@@ -239,6 +240,7 @@ class Project(Document):
def after_insert(self):
self.copy_from_template("after_insert")
self.link_with_sales_order()
+ self.control_access_for_project_users()
def link_with_sales_order(self) -> None:
"""Back-link the source Sales Order to this project.
@@ -311,8 +313,8 @@ class Project(Document):
pct_complete += row["progress"] * frappe.utils.safe_div(row["task_weight"], weight_sum)
self.percent_complete = flt(flt(pct_complete), 2)
- # don't update status if it is cancelled
- if self.status == "Cancelled":
+ # don't update status if it is manually set to cancelled or on hold
+ if self.status in ("Cancelled", "On hold"):
return
self.status = "Completed" if self.percent_complete == 100 else "Open"
@@ -434,6 +436,34 @@ class Project(Document):
)
user.welcome_email_sent = 1
+ def control_access_for_project_users(self):
+ def revoke_access_for_project_users(removed_users):
+ users = set([d.user for d in frappe.share.get_users(self.doctype, self.name)])
+ for user in removed_users:
+ if user not in users:
+ continue
+
+ frappe.share.remove(self.doctype, self.name, user)
+
+ def grant_access_for_project_users(new_users):
+ for user in new_users:
+ frappe.share.add_docshare(self.doctype, self.name, user=user)
+
+ current_users = set([d.user for d in self.users])
+ old_doc = self.get_doc_before_save()
+
+ if not old_doc:
+ grant_access_for_project_users(current_users)
+ return
+
+ previous_users = set([d.user for d in old_doc.users])
+
+ new_users = current_users - previous_users
+ removed_users = previous_users - current_users
+
+ revoke_access_for_project_users(removed_users)
+ grant_access_for_project_users(new_users)
+
def get_timeline_data(doctype: str, name: str) -> dict[int, int]:
"""Return timeline for attendance"""
diff --git a/erpnext/projects/doctype/project/project_list.js b/erpnext/projects/doctype/project/project_list.js
index 1503b1ee5d3..28a774524d4 100644
--- a/erpnext/projects/doctype/project/project_list.js
+++ b/erpnext/projects/doctype/project/project_list.js
@@ -4,6 +4,8 @@ frappe.listview_settings["Project"] = {
get_indicator: function (doc) {
if (doc.status == "Open" && doc.percent_complete) {
return [__("{0}%", [cint(doc.percent_complete)]), "orange", "percent_complete,>,0|status,=,Open"];
+ } else if (doc.status == "On hold") {
+ return [__("On hold"), "blue", "status,=,On hold"];
} else {
return [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
}
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 90e8d78f60e..d8d11f3ffa0 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -436,6 +436,61 @@ class TestProject(ERPNextTestSuite):
self.assertEqual(project.total_consumed_material_cost, sum(row.amount for row in issue.items))
self.assertGreater(project.total_consumed_material_cost, 0)
+ def _create_portal_user(self, email):
+ """A user with no Project-related role, so read access can only come from
+ control_access_for_project_users() sharing the doc with them."""
+ if not frappe.db.exists("User", email):
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": email,
+ "first_name": "Portal",
+ "send_welcome_email": 0,
+ }
+ ).insert(ignore_permissions=True)
+ return email
+
+ def test_new_project_grants_access_to_its_users(self):
+ member = self._create_portal_user(f"new_proj_member_{frappe.generate_hash(length=6)}@example.com")
+
+ project = frappe.get_doc(
+ doctype="Project",
+ project_name=f"_Test New Project Access {frappe.generate_hash(length=6)}",
+ status="Open",
+ company="_Test Company",
+ )
+ project.append("users", {"user": member, "welcome_email_sent": 1})
+ project.insert() # must not raise
+
+ self.assertTrue(project.has_permission(user=member))
+ shared_with = [d.user for d in frappe.share.get_users("Project", project.name)]
+ self.assertIn(member, shared_with)
+
+ def test_adding_and_removing_project_user_updates_access(self):
+ stays = self._create_portal_user(f"stays_{frappe.generate_hash(length=6)}@example.com")
+ leaves = self._create_portal_user(f"leaves_{frappe.generate_hash(length=6)}@example.com")
+
+ project = frappe.get_doc(
+ doctype="Project",
+ project_name=f"_Test Project User Membership {frappe.generate_hash(length=6)}",
+ status="Open",
+ company="_Test Company",
+ )
+ project.append("users", {"user": stays, "welcome_email_sent": 1})
+ project.insert()
+ self.assertTrue(project.has_permission(user=stays))
+
+ # adding a user on update (not insert) must also grant them access
+ project.append("users", {"user": leaves, "welcome_email_sent": 1})
+ project.save()
+ self.assertTrue(project.has_permission(user=leaves))
+
+ # removing a user must revoke the share that was granted for membership
+ project.users = [d for d in project.users if d.user != leaves]
+ project.save()
+ self.assertFalse(project.has_permission(user=leaves))
+ self.assertTrue(project.has_permission(user=stays))
+
def get_project(name, template):
project = frappe.get_doc(
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index a21cc00b991..5ee6dd871c2 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -3,6 +3,9 @@
frappe.ui.form.on("Customer", {
setup: function (frm) {
+ frm.set_query("allowed_companies", () => ({
+ query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",
+ }));
frm.custom_make_buttons = {
Opportunity: "Opportunity",
Quotation: "Quotation",
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 6dd308d319d..848d99e6a8f 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -4,7 +4,7 @@
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
- "creation": "2013-06-11 14:26:44",
+ "creation": "2026-07-14 12:46:50.256889",
"description": "Buyer of Goods and Services.",
"doctype": "DocType",
"document_type": "Setup",
@@ -65,6 +65,9 @@
"tax_withholding_group",
"tax_withholding_category",
"settings_tab",
+ "company_restrictions_section",
+ "allowed_companies",
+ "section_break_ario",
"so_required",
"dn_required",
"column_break_53",
@@ -512,6 +515,20 @@
"fieldtype": "Tab Break",
"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)"
+ },
+ {
+ "fieldname": "allowed_companies",
+ "fieldtype": "Table MultiSelect",
+ "label": "Allowed Companies",
+ "options": "Company Restriction",
+ "depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)"
+ },
{
"collapsible": 1,
"collapsible_depends_on": "default_sales_partner",
@@ -683,6 +700,10 @@
"label": "Alias",
"no_copy": 1,
"unique": 1
+ },
+ {
+ "fieldname": "section_break_ario",
+ "fieldtype": "Section Break"
}
],
"icon": "fa fa-user",
@@ -696,7 +717,7 @@
"link_fieldname": "party"
}
],
- "modified": "2026-06-27 16:12:10.457900",
+ "modified": "2026-07-14 21:00:00.000000",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index fd16c5d7aed..064e3068716 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -25,6 +25,7 @@ from erpnext.accounts.party import (
validate_party_currency_before_merging,
)
from erpnext.controllers.website_list_for_contact import add_role_for_portal_user
+from erpnext.stock.doctype.company_restriction.company_restriction import validate_allowed_companies
from erpnext.utilities.transaction_base import TransactionBase
from .mapper import (
@@ -51,11 +52,13 @@ class Customer(TransactionBase):
from erpnext.selling.doctype.supplier_number_at_customer.supplier_number_at_customer import (
SupplierNumberAtCustomer,
)
+ from erpnext.stock.doctype.company_restriction.company_restriction import CompanyRestriction
from erpnext.utilities.doctype.portal_user.portal_user import PortalUser
account_manager: DF.Link | None
accounts: DF.Table[PartyAccount]
alias: DF.Data | None
+ allowed_companies: DF.TableMultiSelect[CompanyRestriction]
companies: DF.Table[AllowedToTransactWith]
credit_limits: DF.Table[CustomerCreditLimit]
customer_details: DF.Text | None
@@ -186,6 +189,7 @@ class Customer(TransactionBase):
self.validate_internal_customer()
self.add_role_for_user()
self.validate_currency_for_receivable_payable_and_advance_account()
+ validate_allowed_companies(self)
# set loyalty program tier
if not self.is_new() and (customer := self.get_doc_before_save()):
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index a1b15a1e867..c1315fe518b 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -17,6 +17,7 @@ from erpnext.selling.doctype.customer.mapper import (
make_quotation,
parse_full_name,
)
+from erpnext.setup.utils import get_exchange_rate
from erpnext.tests.utils import ERPNextTestSuite
@@ -29,20 +30,9 @@ class TestCustomer(ERPNextTestSuite):
frappe.defaults.set_user_default("company", company)
self.addCleanup(frappe.defaults.clear_user_default, "company")
- # Seed a deterministic rate so the test does not depend on the live exchange-rate API.
- rate = 83.0
- exchange = frappe.get_doc(
- {
- "doctype": "Currency Exchange",
- "date": nowdate(),
- "from_currency": foreign_currency,
- "to_currency": company_currency,
- "exchange_rate": rate,
- "for_selling": 1,
- "for_buying": 1,
- }
- ).insert(ignore_if_duplicate=True)
- self.addCleanup(frappe.delete_doc, "Currency Exchange", exchange.name, force=1)
+ # Master data seeds a current-dated exchange rate, so make_quotation should
+ # resolve that rate instead of falling back to the default conversion rate of 1.0.
+ expected_rate = get_exchange_rate(foreign_currency, company_currency, nowdate())
customer = frappe.get_doc(
{
@@ -59,7 +49,7 @@ class TestCustomer(ERPNextTestSuite):
self.assertEqual(quotation.currency, foreign_currency)
self.assertNotEqual(flt(quotation.conversion_rate), 1.0)
self.assertNotEqual(flt(quotation.conversion_rate), 0.0)
- self.assertEqual(flt(quotation.conversion_rate), rate)
+ self.assertEqual(flt(quotation.conversion_rate), flt(expected_rate))
def test_get_customer_name_dedupes_with_numeric_suffix(self):
# When a customer name already exists, get_customer_name appends "- ". The
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json
index 55ff08d21fe..305972a5cea 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.json
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.json
@@ -5,16 +5,20 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
+ "defaults_section",
"default_company",
"country",
- "default_distance_unit",
"column_break_8",
"default_currency",
+ "default_distance_unit",
+ "demo_company",
+ "general_settings_section",
"hide_currency_symbol",
"disable_rounded_total",
"disable_in_words",
+ "column_break_hnew",
"use_posting_datetime_for_naming_documents",
- "demo_company"
+ "enable_company_wise_masters"
],
"fields": [
{
@@ -27,7 +31,7 @@
{
"fieldname": "country",
"fieldtype": "Link",
- "label": "Country",
+ "label": "Default Country",
"options": "Country"
},
{
@@ -51,12 +55,12 @@
"reqd": 1
},
{
+ "default": "0",
"description": "Do not show any symbol like $ etc next to currencies.",
"fieldname": "hide_currency_symbol",
- "fieldtype": "Select",
+ "fieldtype": "Check",
"in_list_view": 1,
- "label": "Hide Currency Symbol",
- "options": "\nNo\nYes"
+ "label": "Hide Currency Symbol"
},
{
"default": "0",
@@ -88,6 +92,27 @@
"fieldname": "use_posting_datetime_for_naming_documents",
"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 Allowed Companies 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",
+ "label": "Defaults"
+ },
+ {
+ "fieldname": "general_settings_section",
+ "fieldtype": "Section Break",
+ "label": "General Settings"
+ },
+ {
+ "fieldname": "column_break_hnew",
+ "fieldtype": "Column Break"
}
],
"grid_page_length": 50,
@@ -96,7 +121,7 @@
"in_create": 1,
"issingle": 1,
"links": [],
- "modified": "2026-07-14 13:37:46.177444",
+ "modified": "2026-07-14 18:30:00.000000",
"modified_by": "Administrator",
"module": "Setup",
"name": "Global Defaults",
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py
index a85b04530b0..9684566d3a9 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.py
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.py
@@ -18,6 +18,7 @@ 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 = (
@@ -51,7 +52,8 @@ class GlobalDefaults(Document):
demo_company: DF.Link | None
disable_in_words: DF.Check
disable_rounded_total: DF.Check
- hide_currency_symbol: DF.Literal["", "No", "Yes"]
+ enable_company_wise_masters: DF.Check
+ hide_currency_symbol: DF.Check
use_posting_datetime_for_naming_documents: DF.Check
# end: auto-generated types
diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
index 57e558c0e7d..d930956d516 100644
--- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
+++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
@@ -69,7 +69,7 @@
"type": "Link"
}
],
- "modified": "2026-07-03 13:43:50.429297",
+ "modified": "2026-07-14 12:00:00.000000",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@@ -355,6 +355,116 @@
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
+ },
+ {
+ "child": 0,
+ "collapsible": 1,
+ "icon": "building-2",
+ "indent": 1,
+ "keep_closed": 1,
+ "label": "Organization",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Section Break"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 1,
+ "icon": "building-2",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Company",
+ "link_to": "Company",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "book-text",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Letter Head",
+ "link_to": "Letter Head",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "file-user",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Department",
+ "link_to": "Department",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "book-user",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Branch",
+ "link_to": "Branch",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "users",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "User",
+ "link_to": "User",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "user-round-check",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Role Permissions",
+ "link_to": "permission-manager",
+ "link_type": "Page",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
+ },
+ {
+ "child": 1,
+ "collapsible": 1,
+ "default_workspace": 0,
+ "icon": "mail",
+ "indent": 0,
+ "keep_closed": 0,
+ "label": "Email Account",
+ "link_to": "Email Account",
+ "link_type": "DocType",
+ "open_in_new_tab": 0,
+ "show_arrow": 0,
+ "type": "Link"
}
],
"standard": 1,
diff --git a/erpnext/setup/workspace/organization/organization.json b/erpnext/setup/workspace/organization/organization.json
deleted file mode 100644
index 45ca544db31..00000000000
--- a/erpnext/setup/workspace/organization/organization.json
+++ /dev/null
@@ -1,204 +0,0 @@
-{
- "allowed_users": [
- {
- "user": "Administrator"
- },
- {
- "user": "Guest"
- },
- {
- "user": "accounts@test.com"
- },
- {
- "user": "ankush@erpnext.com"
- },
- {
- "user": "faris@erpnext.com"
- },
- {
- "user": "mention_test_user@example.com"
- },
- {
- "user": "project@frappe.io"
- },
- {
- "user": "rushabh@erpnext.com"
- },
- {
- "user": "saqib@erpnext.com"
- },
- {
- "user": "soham@frappe.io"
- },
- {
- "user": "sohamengineer123@gmail.com"
- },
- {
- "user": "sohamkulkarns9@gmail.com"
- },
- {
- "user": "sydel@frappe.io"
- },
- {
- "user": "test'5@example.com"
- },
- {
- "user": "test1@example.com"
- },
- {
- "user": "test2@example.com"
- },
- {
- "user": "test3@example.com"
- },
- {
- "user": "test4@example.com"
- },
- {
- "user": "test@example.com"
- },
- {
- "user": "test@portal.com"
- },
- {
- "user": "testpassword@example.com"
- },
- {
- "user": "testperm@example.com"
- },
- {
- "user": "web@web.com"
- }
- ],
- "app": "erpnext",
- "charts": [],
- "content": "[]",
- "creation": "2026-06-11 11:51:21.789012",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "building-2",
- "idx": 0,
- "indicator_color": "green",
- "is_hidden": 0,
- "label": "Organization",
- "link_type": "DocType",
- "links": [],
- "modified": "2026-07-03 00:45:57.595188",
- "modified_by": "Administrator",
- "module": "Setup",
- "module_onboarding": "Organization Onboarding",
- "name": "Organization",
- "number_cards": [],
- "owner": "Administrator",
- "public": 1,
- "quick_lists": [],
- "roles": [],
- "sequence_id": 46.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 1,
- "icon": "building-2",
- "indent": 0,
- "keep_closed": 0,
- "label": "Company",
- "link_to": "Company",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "book-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Letter Head",
- "link_to": "Letter Head",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "file-user",
- "indent": 0,
- "keep_closed": 0,
- "label": "Department",
- "link_to": "Department",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "book-user",
- "indent": 0,
- "keep_closed": 0,
- "label": "Branch",
- "link_to": "Branch",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "users",
- "indent": 0,
- "keep_closed": 0,
- "label": "User",
- "link_to": "User",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "user-round-check",
- "indent": 0,
- "keep_closed": 0,
- "label": "Role Permissions",
- "link_to": "permission-manager",
- "link_type": "Page",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "mail",
- "indent": 0,
- "keep_closed": 0,
- "label": "Email Account",
- "link_to": "Email Account",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Organization",
- "type": "Workspace"
-}
diff --git a/erpnext/stock/doctype/company_restriction/__init__.py b/erpnext/stock/doctype/company_restriction/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/stock/doctype/company_restriction/company_restriction.json b/erpnext/stock/doctype/company_restriction/company_restriction.json
new file mode 100644
index 00000000000..2c7c0c804cf
--- /dev/null
+++ b/erpnext/stock/doctype/company_restriction/company_restriction.json
@@ -0,0 +1,39 @@
+{
+ "actions": [],
+ "allow_bulk_edit": 1,
+ "allow_rename": 1,
+ "creation": "2026-07-13 21:39:49.805859",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company"
+ ],
+ "fields": [
+ {
+ "allow_on_submit": 1,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "ignore_user_permissions": 1,
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company",
+ "reqd": 1
+ }
+ ],
+ "grid_page_length": 50,
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2026-07-14 00:15:00.000000",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Company Restriction",
+ "owner": "Administrator",
+ "permissions": [],
+ "row_format": "Dynamic",
+ "rows_threshold_for_grid_search": 20,
+ "sort_field": "creation",
+ "sort_order": "DESC",
+ "states": []
+}
diff --git a/erpnext/stock/doctype/company_restriction/company_restriction.py b/erpnext/stock/doctype/company_restriction/company_restriction.py
new file mode 100644
index 00000000000..6b995e75a33
--- /dev/null
+++ b/erpnext/stock/doctype/company_restriction/company_restriction.py
@@ -0,0 +1,116 @@
+# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from pypika.terms import Bracket, ExistsCriterion
+
+
+class CompanyRestriction(Document):
+ # begin: auto-generated types
+ # This code is auto-generated. Do not modify anything in this block.
+
+ from typing import TYPE_CHECKING
+
+ if TYPE_CHECKING:
+ from frappe.types import DF
+
+ company: DF.Link
+ parent: DF.Data
+ parentfield: DF.Data
+ parenttype: DF.Data
+ # end: auto-generated types
+
+
+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
+ return get_allowed_docs_for_doctype(user_permissions["Company"], doctype) or None
+
+
+def get_permission_query_conditions(user, doctype=None):
+ if not doctype:
+ return None
+
+ allowed_companies = get_allowed_companies(user, doctype)
+ if not allowed_companies:
+ return None
+
+ parent = frappe.qb.DocType(doctype)
+ restriction = frappe.qb.DocType("Company Restriction")
+ restriction_rows = (
+ frappe.qb.from_(restriction)
+ .select(restriction.name)
+ .where(
+ (restriction.parenttype == doctype)
+ & (restriction.parentfield == "allowed_companies")
+ & (restriction.parent == parent.name)
+ )
+ )
+ allowed_rows = restriction_rows.where(restriction.company.isin(allowed_companies))
+ return Bracket(ExistsCriterion(allowed_rows) | ExistsCriterion(restriction_rows).negate())
+
+
+def has_permission(doc, ptype=None, user=None):
+ 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)
+
+
+def validate_allowed_companies(doc):
+ if doc.flags.ignore_permissions:
+ return
+
+ allowed_companies = get_allowed_companies(frappe.session.user, doc.doctype)
+ if not allowed_companies:
+ return
+
+ previous_companies = set()
+ if previous_doc := doc.get_doc_before_save():
+ previous_companies = {row.company for row in previous_doc.get("allowed_companies") or []}
+
+ current_companies = {row.company for row in doc.get("allowed_companies") or []}
+ for company in current_companies.symmetric_difference(previous_companies):
+ if company not in allowed_companies:
+ frappe.throw(
+ _("You are not permitted to add or remove Company {0} in Allowed Companies").format(company),
+ frappe.PermissionError,
+ )
+
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def company_query(
+ doctype: str,
+ txt: str,
+ searchfield: str,
+ start: int,
+ page_len: int,
+ filters: dict | str | None = None,
+):
+ filters = frappe.parse_json(filters) if filters else {}
+ if isinstance(filters, list):
+ filters.append(["Company", "name", "like", f"%{txt}%"])
+ else:
+ filters["name"] = ("like", f"%{txt}%")
+
+ return frappe.get_list(
+ "Company",
+ filters=filters,
+ limit_start=start,
+ limit_page_length=page_len,
+ order_by="name",
+ as_list=True,
+ )
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index b20f53f74e7..eb8034b57c4 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -59,6 +59,9 @@ frappe.ui.form.on("Item", {
},
setup: function (frm) {
+ frm.set_query("allowed_companies", () => ({
+ query: "erpnext.stock.doctype.company_restriction.company_restriction.company_query",
+ }));
frm.add_fetch("attribute", "numeric_values", "numeric_values");
frm.add_fetch("attribute", "from_range", "from_range");
frm.add_fetch("attribute", "to_range", "to_range");
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 6da7ec333b9..81975cd50f1 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:item_code",
- "creation": "2026-02-02 14:41:23.105228",
+ "creation": "2026-07-13 23:00:47.512490",
"description": "A Product or a Service that is bought, sold or kept in stock.",
"doctype": "DocType",
"document_type": "Setup",
@@ -40,6 +40,8 @@
"over_delivery_receipt_allowance",
"column_break_wugd",
"over_billing_allowance",
+ "company_restrictions_section",
+ "allowed_companies",
"section_break_11",
"brand",
"description",
@@ -240,7 +242,6 @@
"description": "ERPNext will make a stock ledger entry for each transaction of this item. Keep unchecked for non-stock or service items.",
"fieldname": "is_stock_item",
"fieldtype": "Check",
- "in_list_view": 0,
"label": "Maintain Stock",
"oldfieldname": "is_stock_item",
"oldfieldtype": "Select",
@@ -281,9 +282,9 @@
"description": "Enable if this item is a company asset like machinery or furniture.",
"fieldname": "is_fixed_asset",
"fieldtype": "Check",
+ "in_list_view": 1,
"label": "Is Fixed Asset",
- "read_only_depends_on": "eval:doc.is_stock_item",
- "in_list_view": 1
+ "read_only_depends_on": "eval:doc.is_stock_item"
},
{
"allow_in_quick_entry": 1,
@@ -596,7 +597,7 @@
"oldfieldtype": "Currency"
},
{
- "description": "Minimum stock level to maintain as a buffer. Used to calculate recommended reorder level: Reorder Level = Safety Stock + (Average Daily Consumption × Lead Time).",
+ "description": "Minimum stock level to maintain as a buffer. Used to calculate recommended reorder level: Reorder Level = Safety Stock + (Average Daily Consumption \u00d7 Lead Time).",
"fieldname": "safety_stock",
"fieldtype": "Float",
"label": "Safety Stock",
@@ -699,9 +700,9 @@
"description": "Allow this item to be used in sales transactions.",
"fieldname": "is_sales_item",
"fieldtype": "Check",
+ "in_list_view": 1,
"label": "Allow Sales",
- "show_description_on_click": 1,
- "in_list_view": 1
+ "show_description_on_click": 1
},
{
"fieldname": "column_break3",
@@ -1084,6 +1085,20 @@
"fieldname": "item_prices_column",
"fieldtype": "Column Break",
"label": "Item Prices"
+ },
+ {
+ "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)"
+ },
+ {
+ "fieldname": "allowed_companies",
+ "fieldtype": "Table MultiSelect",
+ "label": "Allowed Companies",
+ "options": "Company Restriction",
+ "depends_on": "eval:cint(frappe.sys_defaults.enable_company_wise_masters)"
}
],
"icon": "fa fa-tag",
@@ -1091,7 +1106,7 @@
"image_field": "image",
"links": [],
"make_attachments_public": 1,
- "modified": "2026-07-05 23:24:45.734144",
+ "modified": "2026-07-14 21:00:00.000000",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 3fff0cb1c28..1fc62169daa 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -30,6 +30,7 @@ from erpnext.controllers.item_variant import (
make_variant_item_code,
validate_item_variant_attributes,
)
+from erpnext.stock.doctype.company_restriction.company_restriction import validate_allowed_companies
from erpnext.stock.doctype.item_default.item_default import ItemDefault
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
from erpnext.stock.utils import get_valuation_method
@@ -60,6 +61,7 @@ class Item(Document):
if TYPE_CHECKING:
from frappe.types import DF
+ from erpnext.stock.doctype.company_restriction.company_restriction import CompanyRestriction
from erpnext.stock.doctype.item_barcode.item_barcode import ItemBarcode
from erpnext.stock.doctype.item_customer_detail.item_customer_detail import ItemCustomerDetail
from erpnext.stock.doctype.item_default.item_default import ItemDefault
@@ -71,6 +73,7 @@ class Item(Document):
allow_alternative_item: DF.Check
allow_negative_stock: DF.Check
+ allowed_companies: DF.TableMultiSelect[CompanyRestriction]
asset_category: DF.Link | None
asset_naming_series: DF.Literal[None]
attributes: DF.Table[ItemVariantAttribute]
@@ -242,6 +245,7 @@ class Item(Document):
self.validate_serialized_change_with_bundle()
self.validate_standard_cost_change()
self.validate_item_tax_net_rate_range()
+ validate_allowed_companies(self)
if not self.is_new():
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 2d94a892aeb..47c55e37680 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -10,7 +10,7 @@ from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
from frappe.model.document import Document
from frappe.query_builder import DocType, Interval
from frappe.query_builder.functions import CombineDatetime, Max, Now
-from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
+from frappe.utils import cint, get_datetime, get_link_to_form, get_weekday, getdate, now, nowtime
from frappe.utils.user import get_users_with_role
from rq.timeouts import JobTimeoutException
@@ -19,6 +19,7 @@ from erpnext.accounts.services.gl_validator import validate_accounting_period
from erpnext.accounts.utils import get_future_stock_vouchers, repost_gle_for_stock_vouchers
from erpnext.stock.stock_ledger import (
get_affected_transactions,
+ get_item_wh_first_reposted_from_reposting_data,
get_items_to_be_repost,
repost_future_sle,
)
@@ -343,6 +344,21 @@ class RepostItemValuation(Document):
)
).run()
+ def skip_reposts_covered_by_dependents(self):
+ if self.repost_only_accounting_ledgers:
+ return
+
+ coverage = get_item_wh_first_reposted_from_reposting_data(self)
+ if not coverage:
+ return
+
+ source_datetime = get_combine_datetime(self.posting_date, self.posting_time)
+ mark_covered_item_reposts(self.name, coverage, source_datetime)
+
+ affected = get_affected_transactions(self)
+ if affected:
+ mark_covered_transaction_reposts(self, coverage, affected)
+
def _recalculate_valuation_rate(self):
doc = frappe.get_doc(self.voucher_type, self.voucher_no)
if doc.get("is_internal_supplier"):
@@ -376,6 +392,130 @@ def bulk_restart_reposting(names: str | list):
frappe.msgprint(_("Repost Item Valuation restarted for selected failed records."))
+def repost_coverage_cache_key(name):
+ return f"riv_dependent_coverage::{name}"
+
+
+def get_queued_item_reposts(source_name, item_codes):
+ return frappe.get_all(
+ "Repost Item Valuation",
+ filters={
+ "name": ("!=", source_name),
+ "based_on": "Item and Warehouse",
+ "status": "Queued",
+ "docstatus": 1,
+ "recalculate_valuation_rate": 0,
+ "recreate_stock_ledgers": 0,
+ "via_landed_cost_voucher": 0,
+ "item_code": ("in", item_codes),
+ },
+ fields=["name", "item_code", "warehouse", "posting_date", "posting_time"],
+ )
+
+
+def mark_covered_item_reposts(source_name, coverage, source_datetime):
+ item_codes = {item_code for item_code, _ in coverage}
+
+ for row in get_queued_item_reposts(source_name, list(item_codes)):
+ from_datetime = coverage.get((row.item_code, row.warehouse))
+ if not from_datetime:
+ continue
+
+ row_datetime = get_combine_datetime(row.posting_date, row.posting_time)
+ if get_datetime(row_datetime) < get_datetime(source_datetime):
+ continue
+
+ if get_datetime(from_datetime) <= get_datetime(row_datetime):
+ frappe.db.set_value("Repost Item Valuation", row.name, "status", "Skipped")
+
+
+def get_queued_transaction_reposts(source_name, voucher_nos):
+ return frappe.get_all(
+ "Repost Item Valuation",
+ filters={
+ "name": ("!=", source_name),
+ "based_on": "Transaction",
+ "status": "Queued",
+ "docstatus": 1,
+ "repost_only_accounting_ledgers": 0,
+ "recalculate_valuation_rate": 0,
+ "recreate_stock_ledgers": 0,
+ "via_landed_cost_voucher": 0,
+ "voucher_no": ("in", list(voucher_nos)),
+ },
+ fields=["name", "voucher_type", "voucher_no", "posting_date", "posting_time"],
+ )
+
+
+def accumulate_repost_coverage(row_name, coverage, row_datetime):
+ cache_key = repost_coverage_cache_key(row_name)
+ acc = frappe.cache().get_value(cache_key) or {}
+
+ for key, from_datetime in coverage.items():
+ if get_datetime(from_datetime) > get_datetime(row_datetime):
+ continue
+
+ existing = acc.get(key)
+ if not existing or get_datetime(from_datetime) < get_datetime(existing):
+ acc[key] = from_datetime
+
+ frappe.cache().set_value(cache_key, acc, expires_in_sec=86400)
+ return acc
+
+
+def get_repost_items_by_voucher(rows):
+ voucher_nos = {row.voucher_no for row in rows}
+ if not voucher_nos:
+ return {}
+
+ items_by_voucher = {}
+ for sle in frappe.get_all(
+ "Stock Ledger Entry",
+ filters={"voucher_no": ("in", list(voucher_nos))},
+ fields=["voucher_type", "voucher_no", "item_code", "warehouse"],
+ distinct=True,
+ ):
+ items_by_voucher.setdefault((sle.voucher_type, sle.voucher_no), set()).add(
+ (sle.item_code, sle.warehouse)
+ )
+
+ return items_by_voucher
+
+
+def is_transaction_repost_covered(items, acc, row_datetime):
+ if not items:
+ return False
+
+ for key in items:
+ covered = acc.get(key)
+ if not covered or get_datetime(covered) > get_datetime(row_datetime):
+ return False
+
+ return True
+
+
+def mark_covered_transaction_reposts(source, coverage, affected):
+ source_datetime = get_combine_datetime(source.posting_date, source.posting_time)
+ voucher_nos = {voucher_no for _, voucher_no in affected}
+
+ rows = get_queued_transaction_reposts(source.name, voucher_nos)
+ items_by_voucher = get_repost_items_by_voucher(rows)
+
+ for row in rows:
+ if (row.voucher_type, row.voucher_no) not in affected:
+ continue
+
+ row_datetime = get_combine_datetime(row.posting_date, row.posting_time)
+ if get_datetime(row_datetime) < get_datetime(source_datetime):
+ continue
+
+ acc = accumulate_repost_coverage(row.name, coverage, row_datetime)
+ items = items_by_voucher.get((row.voucher_type, row.voucher_no))
+ if is_transaction_repost_covered(items, acc, row_datetime):
+ frappe.db.set_value("Repost Item Valuation", row.name, "status", "Skipped")
+ frappe.cache().delete_value(repost_coverage_cache_key(row.name))
+
+
def on_doctype_update():
frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse")
@@ -407,6 +547,8 @@ def repost(doc):
repost_gl_entries(doc)
+ doc.skip_reposts_covered_by_dependents()
+
doc.set_status("Completed")
doc.db_set("reposting_data_file", None)
remove_attached_file(doc.name)
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index d2c5eaa6096..fe7b4bfd7c1 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -14,10 +14,11 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import (
in_configured_timeslot,
+ mark_covered_transaction_reposts,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.tests.test_utils import StockTestMixin
-from erpnext.stock.utils import PendingRepostingError
+from erpnext.stock.utils import PendingRepostingError, get_combine_datetime
from erpnext.tests.utils import ERPNextTestSuite
@@ -171,6 +172,127 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin):
riv4.set_status("Skipped")
riv3.set_status("Skipped")
+ def _make_queued_transaction_riv(self, voucher):
+ riv = frappe.get_doc(
+ doctype="Repost Item Valuation",
+ based_on="Transaction",
+ voucher_type=voucher.doctype,
+ voucher_no=voucher.name,
+ posting_date=voucher.posting_date,
+ posting_time="00:00:00",
+ )
+ riv.flags.dont_run_in_test = True
+ riv.submit()
+ return riv
+
+ def test_skip_transaction_repost_covered_by_dependent(self):
+ company = "_Test Company with perpetual inventory"
+ warehouse = "Stores - TCP1"
+
+ covered_pr = make_purchase_receipt(
+ company=company, warehouse=warehouse, item_code="_Test Item", qty=5
+ )
+ other_pr = make_purchase_receipt(
+ company=company, warehouse=warehouse, item_code="_Test Item 2", qty=5
+ )
+
+ covered_riv = self._make_queued_transaction_riv(covered_pr)
+ other_riv = self._make_queued_transaction_riv(other_pr)
+
+ earlier_date = add_days(covered_pr.posting_date, -1)
+ source = frappe._dict(name="__test_source_riv__", posting_date=earlier_date, posting_time="00:00:00")
+ coverage = {("_Test Item", warehouse): get_combine_datetime(earlier_date, "00:00:00")}
+ affected = {("Purchase Receipt", covered_pr.name), ("Purchase Receipt", other_pr.name)}
+
+ mark_covered_transaction_reposts(source, coverage, affected)
+
+ covered_riv.reload()
+ other_riv.reload()
+ self.assertEqual(covered_riv.status, "Skipped")
+ self.assertEqual(other_riv.status, "Queued")
+
+ other_riv.db_set("status", "Skipped")
+
+ def _make_dependent_repack(self, company, consumed_items, source_wh, fg_item, fg_wh, qty, posting_date):
+ se = frappe.new_doc("Stock Entry")
+ se.stock_entry_type = "Repack"
+ se.company = company
+ se.set_posting_time = 1
+ se.posting_date = posting_date
+ for item_code in consumed_items:
+ se.append("items", {"item_code": item_code, "s_warehouse": source_wh, "qty": qty})
+ se.append("items", {"item_code": fg_item, "t_warehouse": fg_wh, "qty": qty, "is_finished_item": 1})
+ se.insert()
+ se.submit()
+ return se
+
+ def test_backdated_manufacture_repost_skips_redundant_dependent(self):
+ from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import (
+ execute_reposting_entry,
+ )
+
+ frappe.flags.dont_execute_stock_reposts = True
+ self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts", None)
+
+ original_setting = frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
+ frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 1)
+ self.addCleanup(
+ frappe.db.set_single_value, "Stock Reposting Settings", "item_based_reposting", original_setting
+ )
+
+ company = "_Test Company with perpetual inventory"
+ source_wh = "Stores - TCP1"
+ fg_wh = "Finished Goods - TCP1"
+
+ item_a = make_item(properties={"valuation_method": "FIFO"}).name
+ item_b = make_item(properties={"valuation_method": "FIFO"}).name
+ item_c = make_item(properties={"valuation_method": "FIFO"}).name
+
+ def _day(days):
+ return add_days(nowdate(), days)
+
+ make_stock_entry(
+ item_code=item_a, to_warehouse=source_wh, qty=10, rate=100, posting_date=_day(2), company=company
+ )
+ make_stock_entry(
+ item_code=item_b, to_warehouse=source_wh, qty=10, rate=100, posting_date=_day(3), company=company
+ )
+ self._make_dependent_repack(company, [item_a, item_b], source_wh, item_c, fg_wh, 5, _day(10))
+
+ make_stock_entry(
+ item_code=item_a, to_warehouse=source_wh, qty=10, rate=200, posting_date=_day(1), company=company
+ )
+ make_stock_entry(
+ item_code=item_b, to_warehouse=source_wh, qty=10, rate=200, posting_date=_day(1), company=company
+ )
+ self._make_dependent_repack(company, [item_a, item_b], source_wh, item_c, fg_wh, 5, _day(5))
+
+ rivs = frappe.get_all(
+ "Repost Item Valuation",
+ filters={
+ "docstatus": 1,
+ "based_on": "Item and Warehouse",
+ "status": "Queued",
+ "item_code": ("in", [item_a, item_b, item_c]),
+ },
+ fields=["name", "item_code", "warehouse"],
+ order_by="posting_date asc, posting_time asc, creation asc",
+ )
+ self.assertTrue(
+ any(r.item_code == item_c and r.warehouse == fg_wh for r in rivs),
+ msg="Expected a queued repost for the finished good",
+ )
+
+ for r in rivs:
+ execute_reposting_entry(r.name)
+
+ fg_repost_status = frappe.db.get_value(
+ "Repost Item Valuation",
+ {"based_on": "Item and Warehouse", "item_code": item_c, "warehouse": fg_wh, "docstatus": 1},
+ "status",
+ )
+ self.assertEqual(fg_repost_status, "Skipped")
+
def test_stock_freeze_validation(self):
today = nowdate()
diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
index a8529efcd19..e6969815c27 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
@@ -538,6 +538,65 @@ class TestStockReservationEntry(ERPNextTestSuite):
self.assertEqual(row.delivered_qty, 0, "DN cancel must restore the serial/batch reservation")
self.assertEqual(row.status, "Reserved")
+ @ERPNextTestSuite.change_settings(
+ "Stock Settings",
+ {
+ "allow_negative_stock": 0,
+ "enable_stock_reservation": 1,
+ "auto_reserve_serial_and_batch": 1,
+ "pick_serial_and_batch_based_on": "FIFO",
+ "use_serial_batch_fields": 1,
+ },
+ )
+ def test_batch_shared_across_sales_orders_can_be_delivered(self) -> None:
+ # Regression (#57159): one batch reserved by two Sales Orders. Delivering each order's own
+ # reserved unit must not raise Reserved Batch Conflict — the remainder covers the other order.
+ item_doc = make_batch_item()
+ create_material_receipt(items={item_doc.name: item_doc}, warehouse=self.warehouse, qty=2)
+
+ orders = []
+ for _i in range(2):
+ so = make_sales_order(item_code=item_doc.name, warehouse=self.warehouse, qty=1, rate=100)
+ so.create_stock_reservation_entries()
+ orders.append(so)
+
+ self.assertEqual(
+ len(get_reserved_batch_nos(orders[0].name) | get_reserved_batch_nos(orders[1].name)), 1
+ )
+
+ for so in orders:
+ dn = make_delivery_note(so.name, kwargs={"for_reserved_stock": True})
+ dn.save()
+ dn.submit()
+ self.assertEqual(dn.docstatus, 1)
+
+ @ERPNextTestSuite.change_settings(
+ "Stock Settings",
+ {
+ "allow_negative_stock": 0,
+ "enable_stock_reservation": 1,
+ "auto_reserve_serial_and_batch": 1,
+ "pick_serial_and_batch_based_on": "FIFO",
+ "use_serial_batch_fields": 1,
+ },
+ )
+ def test_delivery_draining_a_batch_reserved_for_another_sales_order_is_blocked(self) -> None:
+ # Guard for #57159 fix: an order without a reservation must still be blocked from draining
+ # a batch below what another order has reserved from it, even if other batches have stock.
+ item_doc = make_batch_item()
+ create_material_receipt(items={item_doc.name: item_doc}, warehouse=self.warehouse, qty=2)
+ create_material_receipt(items={item_doc.name: item_doc}, warehouse=self.warehouse, qty=2)
+
+ so_a = make_sales_order(item_code=item_doc.name, warehouse=self.warehouse, qty=2, rate=100)
+ so_a.create_stock_reservation_entries()
+ (reserved_batch_no,) = get_reserved_batch_nos(so_a.name)
+
+ so_b = make_sales_order(item_code=item_doc.name, warehouse=self.warehouse, qty=2, rate=100)
+ dn = make_delivery_note(so_b.name)
+ dn.items[0].batch_no = reserved_batch_no
+ dn.save()
+ self.assertRaisesRegex(frappe.ValidationError, "is reserved for", dn.submit)
+
@ERPNextTestSuite.change_settings(
"Stock Settings",
{
@@ -893,6 +952,33 @@ def create_items() -> dict:
return items
+def make_batch_item():
+ return make_item(
+ properties={
+ "is_stock_item": 1,
+ "valuation_rate": 100,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "SRBI-.#####.",
+ }
+ )
+
+
+def get_reserved_batch_nos(sales_order: str) -> set:
+ sre = frappe.qb.DocType("Stock Reservation Entry")
+ sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+
+ batch_nos = (
+ frappe.qb.from_(sre)
+ .inner_join(sb_entry)
+ .on(sre.name == sb_entry.parent)
+ .select(sb_entry.batch_no)
+ .where((sre.voucher_no == sales_order) & (sre.docstatus == 1))
+ ).run(pluck=True)
+
+ return set(batch_nos)
+
+
def create_material_receipt(
items: dict, warehouse: str = "_Test Warehouse - _TC", qty: float = 100
) -> StockEntry:
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index fb49b060fb7..01533e9d414 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -30,6 +30,11 @@ def execute(filters=None):
_("Please select either the Item or Warehouse or Warehouse Type filter to generate the report.")
)
+ if not filters.from_date or not filters.to_date:
+ frappe.throw(
+ _("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))
+ )
+
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
index 000aca9f43e..a325a6ca89e 100644
--- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -34,7 +34,18 @@ def update_filters_with_account(filters: Filters) -> None:
def validate_filters(filters: Filters) -> None:
- if filters.from_date > filters.to_date:
+ from_date = filters.from_date
+ to_date = filters.to_date
+
+ if not from_date or not to_date:
+ frappe.throw(
+ _("{0} and {1} are mandatory").format(
+ frappe.bold(_("From Date")),
+ frappe.bold(_("To Date")),
+ )
+ )
+
+ if from_date > to_date:
frappe.throw(_("From Date must be before To Date"))
diff --git a/erpnext/stock/services/serial_batch_bundle_service.py b/erpnext/stock/services/serial_batch_bundle_service.py
index 2e752371ed5..a3e2d2060b2 100644
--- a/erpnext/stock/services/serial_batch_bundle_service.py
+++ b/erpnext/stock/services/serial_batch_bundle_service.py
@@ -9,6 +9,8 @@ delegators for methods reached from other doctypes / ``run_method``; internal
helpers live here only.
"""
+from collections import defaultdict
+
import frappe
from frappe import _, bold
from frappe.utils import cstr, flt, get_link_to_form, getdate
@@ -604,66 +606,63 @@ class SerialBatchBundleService:
if not batches:
return
- field_mapper = {
- "Sales Invoice": [["Sales Order", "sales_order"]],
- "Delivery Note": [["Sales Order", "against_sales_order"]],
- "Stock Entry": [
- ["Work Order", "work_order"],
- ["Subcontracting Inward Order", "subcontracting_inward_order"],
- ],
+ reference_fields = {
+ "Sales Invoice": ["sales_order"],
+ "Delivery Note": ["against_sales_order"],
+ "Stock Entry": ["work_order", "subcontracting_inward_order"],
}.get(self.doc.doctype)
- qty_field = {
- "Sales Invoice": "qty",
- "Delivery Note": "qty",
- "Stock Entry": "fg_completed_qty",
- }.get(self.doc.doctype)
-
- reserved_batches_data = self.get_reserved_batches(batches)
items = self.doc.items
if self.doc.doctype == "Stock Entry":
items = [self.doc]
- for item in items:
- for field in field_mapper:
- if not item.get(field[1]):
- continue
+ own_vouchers = {item.get(field) for item in items for field in reference_fields if item.get(field)}
- value = item.get(field[1])
- for row in reserved_batches_data:
- if self.doc.doctype in ["Sales Invoice", "Delivery Note"] and row.item_code != item.get(
- "item_code"
- ):
- continue
+ outstanding_qty = defaultdict(float)
+ reservations = defaultdict(list)
+ for row in self.get_reserved_batches(batches):
+ if row.voucher_no in own_vouchers:
+ continue
- if row.voucher_no == value:
- continue
+ key = (row.batch_no, row.warehouse)
+ outstanding = flt(row.qty) - flt(row.delivered_qty)
+ outstanding_qty[key] += outstanding
+ if outstanding > 0:
+ reservations[key].append(row)
- batch_qty = get_batch_qty(
- row.batch_no,
- row.warehouse,
- posting_date=self.doc.posting_date,
- posting_time=self.doc.posting_time,
- consider_negative_batches=True,
- )
+ for (batch_no, warehouse), reserved_qty in outstanding_qty.items():
+ if flt(reserved_qty, 6) <= 0:
+ continue
- if item.get(qty_field) < batch_qty:
- continue
+ batch_qty = get_batch_qty(
+ batch_no,
+ warehouse,
+ posting_date=self.doc.posting_date,
+ posting_time=self.doc.posting_time,
+ consider_negative_batches=True,
+ )
- frappe.throw(
- _(
- "The batch {0} is already reserved in {1} {2}. So, cannot proceed with the {3} {4}, which is created against the {5} {6}."
- ).format(
- frappe.bold(row.batch_no),
- frappe.bold(row.voucher_type),
- frappe.bold(row.voucher_no),
- frappe.bold(self.doc.doctype),
- frappe.bold(self.doc.name),
- frappe.bold(field[0]),
- frappe.bold(value),
- ),
- title=_("Reserved Batch Conflict"),
- )
+ if flt(batch_qty, 6) >= flt(reserved_qty, 6):
+ continue
+
+ vouchers = ", ".join(
+ f"{frappe.bold(voucher_type)} {frappe.bold(voucher_no)}"
+ for voucher_type, voucher_no in dict.fromkeys(
+ (row.voucher_type, row.voucher_no) for row in reservations[(batch_no, warehouse)]
+ )
+ )
+ frappe.throw(
+ _(
+ "The batch {0} is reserved for {1} in the warehouse {2} and the remaining quantity is not enough to cover the reservations. So, cannot proceed with the {3} {4}."
+ ).format(
+ frappe.bold(batch_no),
+ vouchers,
+ frappe.bold(warehouse),
+ frappe.bold(self.doc.doctype),
+ frappe.bold(self.doc.name),
+ ),
+ title=_("Reserved Batch Conflict"),
+ )
def get_reserved_batches(self, batches):
doctype = frappe.qb.DocType("Stock Reservation Entry")
@@ -675,9 +674,10 @@ class SerialBatchBundleService:
.on(doctype.name == child_doc.parent)
.select(
child_doc.batch_no,
+ child_doc.qty,
+ child_doc.delivered_qty,
doctype.voucher_type,
doctype.voucher_no,
- doctype.item_code,
doctype.warehouse,
)
.where((doctype.docstatus == 1) & (child_doc.batch_no.isin(batches)))
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 8b28897df60..c1ce66317bc 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -306,6 +306,7 @@ def repost_future_sle(
resume_item_wh_wise_last_posted_sle = (
get_item_wh_wise_last_posted_sle_from_reposting_data(doc, reposting_data) or {}
)
+ item_wh_first_reposted = get_item_wh_first_reposted_from_reposting_data(doc, reposting_data) or {}
if not items_to_be_repost:
return
@@ -328,6 +329,7 @@ def repost_future_sle(
"repost_doc": doc,
"repost_affected_transaction": repost_affected_transaction,
"item_wh_wise_last_posted_sle": resume_item_wh_wise_last_posted_sle,
+ "item_wh_first_reposted": item_wh_first_reposted,
},
allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher,
@@ -337,7 +339,14 @@ def repost_future_sle(
resume_item_wh_wise_last_posted_sle = {}
repost_affected_transaction.update(obj.repost_affected_transaction)
- update_args_in_repost_item_valuation(doc, index, items_to_be_repost, repost_affected_transaction)
+ item_wh_first_reposted = obj.item_wh_first_reposted
+ update_args_in_repost_item_valuation(
+ doc,
+ index,
+ items_to_be_repost,
+ repost_affected_transaction,
+ item_wh_first_reposted=item_wh_first_reposted,
+ )
def update_args_in_repost_item_valuation(
@@ -346,11 +355,15 @@ def update_args_in_repost_item_valuation(
items_to_be_repost,
repost_affected_transaction,
item_wh_wise_last_posted_sle=None,
+ item_wh_first_reposted=None,
):
file_name = ""
if not item_wh_wise_last_posted_sle:
item_wh_wise_last_posted_sle = {}
+ if not item_wh_first_reposted:
+ item_wh_first_reposted = {}
+
if doc.reposting_data_file:
file_name = get_reposting_file_name(doc.doctype, doc.name)
# frappe.delete_doc("File", file_name, ignore_permissions=True, delete_permanently=True)
@@ -360,6 +373,7 @@ def update_args_in_repost_item_valuation(
"repost_affected_transaction": repost_affected_transaction,
"item_wh_wise_last_posted_sle": {str(k): v for k, v in item_wh_wise_last_posted_sle.items()}
or {},
+ "item_wh_first_reposted": {str(k): v for k, v in item_wh_first_reposted.items()},
},
doc,
file_name,
@@ -495,6 +509,16 @@ def get_item_wh_wise_last_posted_sle_from_reposting_data(doc, reposting_data=Non
return frappe._dict()
+def get_item_wh_first_reposted_from_reposting_data(doc, reposting_data=None):
+ if not reposting_data and doc and doc.reposting_data_file:
+ reposting_data = get_reposting_data(doc.reposting_data_file)
+
+ if not reposting_data or not reposting_data.get("item_wh_first_reposted"):
+ return {}
+
+ return {frappe.safe_eval(key): value for key, value in reposting_data.item_wh_first_reposted.items()}
+
+
def get_reposting_data(file_path) -> dict:
file_name = frappe.db.get_value(
"File",
@@ -688,6 +712,7 @@ class update_entries_after:
self.distinct_sles = set()
self.distinct_dependant_item_wh = set()
self.prev_sle_dict = frappe._dict({})
+ self.item_wh_first_reposted = dict(self.args.get("item_wh_first_reposted") or {})
def get_item_wh_wise_last_posted_sle(self):
if self.args and self.args.get("item_wh_wise_last_posted_sle"):
@@ -738,6 +763,10 @@ class update_entries_after:
i += 1
item_wh_key = (sle.item_code, sle.warehouse)
+ sle_datetime = sle.posting_datetime or get_combine_datetime(sle.posting_date, sle.posting_time)
+ existing_datetime = self.item_wh_first_reposted.get(item_wh_key)
+ if not existing_datetime or get_datetime(sle_datetime) < get_datetime(existing_datetime):
+ self.item_wh_first_reposted[item_wh_key] = sle_datetime
if item_wh_key not in self.prev_sle_dict:
self.prev_sle_dict[item_wh_key] = get_previous_sle_of_current_voucher(sle)
@@ -832,6 +861,7 @@ class update_entries_after:
self.items_to_be_repost,
self.repost_affected_transaction,
self.item_wh_wise_last_posted_sle,
+ self.item_wh_first_reposted,
)
if not frappe.in_test:
diff --git a/erpnext/subcontracting/workspace/subcontracting/subcontracting.json b/erpnext/subcontracting/workspace/subcontracting/subcontracting.json
deleted file mode 100644
index 672d6ae28fc..00000000000
--- a/erpnext/subcontracting/workspace/subcontracting/subcontracting.json
+++ /dev/null
@@ -1,415 +0,0 @@
-{
- "app": "erpnext",
- "charts": [
- {
- "chart_name": "Subcontracting Order",
- "label": "Subcontracting Outward Order"
- }
- ],
- "content": "[{\"id\":\"ednT7K5OAg\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Subcontracting Outward Order\",\"col\":12}},{\"id\":\"IlzVs7JD8u\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Subcontracting Outward Order Count\",\"col\":4}},{\"id\":\"wB9idWUvTB\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Subcontracting Inward Order Count\",\"col\":4}},{\"id\":\"4QwMfBRGk8\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Active Subcontracted Items\",\"col\":4}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"Subcontracting Inward and Outward\",\"col\":12}},{\"id\":\"PXXMxfhCfA\",\"type\":\"card\",\"data\":{\"card_name\":\"Subcontracting Inward Order\",\"col\":4}},{\"id\":\"ir3NsTvngO\",\"type\":\"card\",\"data\":{\"card_name\":\"Subcontracting Outward Order\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
- "creation": "2020-03-02 17:11:37.032604",
- "custom_blocks": [],
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "rocket",
- "idx": 2,
- "is_hidden": 0,
- "label": "Subcontracting",
- "links": [
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Reports",
- "link_count": 0,
- "link_type": "DocType",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Subcontract Order Summary",
- "link_count": 0,
- "link_to": "Subcontract Order Summary",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 1,
- "label": "Subcontracted Item To Be Received",
- "link_count": 0,
- "link_to": "Subcontracted Item To Be Received",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 1,
- "label": "Subcontracted Raw Materials To Be Transferred",
- "link_count": 0,
- "link_to": "Subcontracted Raw Materials To Be Transferred",
- "link_type": "Report",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Subcontracting Inward Order",
- "link_count": 3,
- "link_type": "DocType",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Sales Order",
- "link_count": 0,
- "link_to": "Sales Order",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Subcontracting Inward Order",
- "link_count": 0,
- "link_to": "Subcontracting Inward Order",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Subcontracting Delivery",
- "link_count": 0,
- "link_to": "Stock Entry",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Subcontracting Outward Order",
- "link_count": 3,
- "link_type": "DocType",
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Purchase Order",
- "link_count": 0,
- "link_to": "Purchase Order",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Subcontracting Outward Order",
- "link_count": 0,
- "link_to": "Subcontracting Order",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Subcontracting Receipt",
- "link_count": 0,
- "link_to": "Subcontracting Receipt",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 13:43:50.289920",
- "modified_by": "Administrator",
- "module": "Subcontracting",
- "module_onboarding": "Subcontracting Onboarding",
- "name": "Subcontracting",
- "number_cards": [
- {
- "label": "Subcontracting Outward Order Count",
- "number_card_name": "Subcontracting Outward Order Count"
- },
- {
- "label": "Active Subcontracted Items",
- "number_card_name": "Active Subcontracted Items"
- },
- {
- "label": "Subcontracting Inward Order Count",
- "number_card_name": "Subcontracting Inward Order Count"
- }
- ],
- "owner": "Administrator",
- "parent_page": "",
- "public": 1,
- "quick_lists": [],
- "restrict_to_domain": "",
- "roles": [],
- "sequence_id": 8.0,
- "shortcuts": [],
- "sidebar_items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "house",
- "indent": 0,
- "keep_closed": 0,
- "label": "Home",
- "link_to": "Subcontracting",
- "link_type": "Workspace",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "folder-tree",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting BOM",
- "link_to": "Subcontracting BOM",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "move-horizontal",
- "indent": 0,
- "keep_closed": 0,
- "label": "Stock Entry",
- "link_to": "Stock Entry",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "arrow-left-to-line",
- "indent": 1,
- "keep_closed": 0,
- "label": "Inward Order",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Sales Order",
- "link_to": "Sales Order",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Inward Order",
- "link_to": "Subcontracting Inward Order",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Delivery",
- "link_to": "Stock Entry",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "arrow-right-from-line",
- "indent": 1,
- "keep_closed": 0,
- "label": "Outward Order",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Purchase Order",
- "link_to": "Purchase Order",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Order",
- "link_to": "Subcontracting Order",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Receipt",
- "link_to": "Subcontracting Receipt",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 0,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Item",
- "link_to": "Item",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bill of Materials",
- "link_to": "BOM",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "notepad-text",
- "indent": 1,
- "keep_closed": 1,
- "label": "Reports",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontract Order Summary",
- "link_to": "Subcontract Order Summary",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Materials To Be Transferred",
- "link_to": "Subcontracted Raw Materials To Be Transferred",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Items To Be Received",
- "link_to": "Subcontracted Item To Be Received",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "settings",
- "indent": 0,
- "keep_closed": 0,
- "label": "Settings",
- "link_to": "Buying Settings",
- "link_type": "DocType",
- "navigate_to_tab": "subcontract",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "standard": 1,
- "title": "Subcontracting",
- "type": "Workspace"
-}
diff --git a/erpnext/templates/pages/projects.py b/erpnext/templates/pages/projects.py
index 46ad25ed6ed..646e2085ace 100644
--- a/erpnext/templates/pages/projects.py
+++ b/erpnext/templates/pages/projects.py
@@ -6,21 +6,12 @@ import frappe
def get_context(context):
- project_user = frappe.db.get_value(
- "Project User",
- {"parent": frappe.form_dict.project, "user": frappe.session.user},
- ["user", "view_attachments", "hide_timesheets"],
- as_dict=True,
- )
- if frappe.session.user != "Administrator" and (not project_user or frappe.session.user == "Guest"):
- raise frappe.PermissionError
+ project_user = validate_and_get_project_user(project=frappe.form_dict.project)
context.no_cache = 1
context.show_sidebar = True
project = frappe.get_doc("Project", frappe.form_dict.project)
- project.has_permission("read")
-
project.tasks = get_tasks(
project.name, start=0, item_status="open", search=frappe.form_dict.get("search")
)
@@ -66,6 +57,7 @@ def get_tasks(project, start=0, search=None, item_status=None):
@frappe.whitelist()
def get_task_html(project: str, start: int = 0, item_status: str | None = None):
+ validate_and_get_project_user(project=project)
return frappe.render_template(
"erpnext/templates/includes/projects/project_tasks.html",
{
@@ -106,6 +98,7 @@ def get_timesheets(project, start=0, search=None):
@frappe.whitelist()
def get_timesheet_html(project: str, start: int = 0):
+ validate_and_get_project_user(project=project)
return frappe.render_template(
"erpnext/templates/includes/projects/project_timesheets.html",
{"doc": {"timesheets": get_timesheets(project, start)}},
@@ -119,3 +112,12 @@ def get_attachments(project):
filters={"attached_to_name": project, "attached_to_doctype": "Project", "is_private": 0},
fields=["file_name", "file_url", "file_size"],
)
+
+
+def validate_and_get_project_user(project: str):
+ project_doc = frappe.get_doc("Project", project)
+ project_doc.check_permission()
+
+ project_user = next((d for d in project_doc.users if d.user == frappe.session.user), None)
+
+ return project_user
diff --git a/erpnext/templates/pages/test_projects.py b/erpnext/templates/pages/test_projects.py
new file mode 100644
index 00000000000..8d66ce95bc5
--- /dev/null
+++ b/erpnext/templates/pages/test_projects.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+
+from erpnext.projects.doctype.project.test_project import make_project
+from erpnext.templates.pages.projects import validate_and_get_project_user
+from erpnext.tests.utils import ERPNextTestSuite
+
+
+class TestProjectsPage(ERPNextTestSuite):
+ """validate_and_get_project_user() gates the /projects portal page. It must raise
+ frappe.PermissionError for a user who can't read the Project, and otherwise return
+ that user's Project User row (or None if they're permitted but not listed as one --
+ e.g. an internal Projects Manager browsing the portal)."""
+
+ def _create_user(self, email):
+ if not frappe.db.exists("User", email):
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "email": email,
+ "first_name": "Portal",
+ "send_welcome_email": 0,
+ }
+ ).insert(ignore_permissions=True)
+ return email
+
+ def test_raises_permission_error_for_user_without_access(self):
+ project = make_project({"project_name": f"_Test Portal Access {frappe.generate_hash(length=6)}"})
+ outsider = self._create_user(f"outsider_{frappe.generate_hash(length=6)}@example.com")
+
+ with self.set_user(outsider):
+ self.assertRaises(frappe.PermissionError, validate_and_get_project_user, project.name)
+
+ def test_allows_user_listed_as_project_user_and_returns_their_row(self):
+ # Being a Project User shares the Project with that user (see
+ # Project.control_access_for_project_users), which is what lets them past
+ # check_permission() here.
+ member = self._create_user(f"member_{frappe.generate_hash(length=6)}@example.com")
+
+ project = frappe.get_doc(
+ doctype="Project",
+ project_name=f"_Test Portal Access {frappe.generate_hash(length=6)}",
+ status="Open",
+ company="_Test Company",
+ )
+ project.append(
+ "users", {"user": member, "view_attachments": 1, "hide_timesheets": 1, "welcome_email_sent": 1}
+ )
+ project.insert()
+
+ with self.set_user(member):
+ project_user = validate_and_get_project_user(project.name)
+
+ self.assertIsNotNone(project_user)
+ self.assertEqual(project_user.user, member)
+ self.assertEqual(project_user.view_attachments, 1)
+ self.assertEqual(project_user.hide_timesheets, 1)
+
+ def test_allows_internally_permitted_user_not_listed_as_project_user(self):
+ # The permission gate must be the real permission system (check_permission()),
+ # not "is this user in the Project's users child table" -- a Projects Manager
+ # can open any project's portal page without ever being added as its user.
+ project = make_project({"project_name": f"_Test Portal Access {frappe.generate_hash(length=6)}"})
+ manager = self._create_user(f"manager_{frappe.generate_hash(length=6)}@example.com")
+ frappe.get_doc("User", manager).add_roles("Projects Manager")
+
+ with self.set_user(manager):
+ project_user = validate_and_get_project_user(project.name)
+
+ self.assertIsNone(project_user)
diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index deeb8310d9c..aebb7a22650 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -181,6 +181,7 @@ class BootStrapTestData:
self.make_location()
self.make_price_list()
self.make_item_price()
+ self.make_currency_exchange()
self.make_loyalty_program()
self.make_shareholder()
self.make_sales_taxes_template()
@@ -2533,6 +2534,38 @@ class BootStrapTestData:
]
self.make_records(["item_code", "price_list", "price_list_rate"], records)
+ def make_currency_exchange(self):
+ """Seed current-dated USD<->INR rates so foreign-currency documents
+ transacted on ``today()`` resolve an exchange rate deterministically.
+
+ Without this, ``get_exchange_rate`` finds no in-window Currency Exchange
+ record and falls back to an external API that is unreachable in CI,
+ returning ``0`` and breaking tests that create USD documents. The rates
+ mirror the latest values in the Currency Exchange ``test_records`` so
+ cost calculations stay unchanged regardless of which record is picked.
+ """
+ records = [
+ {
+ "doctype": "Currency Exchange",
+ "date": today(),
+ "from_currency": "USD",
+ "to_currency": "INR",
+ "exchange_rate": 62.9,
+ "for_buying": 1,
+ "for_selling": 1,
+ },
+ {
+ "doctype": "Currency Exchange",
+ "date": today(),
+ "from_currency": "INR",
+ "to_currency": "USD",
+ "exchange_rate": 0.0167,
+ "for_buying": 1,
+ "for_selling": 1,
+ },
+ ]
+ self.make_records(["from_currency", "to_currency", "date", "for_buying", "for_selling"], records)
+
def make_operation(self):
records = [
{"doctype": "Operation", "name": "_Test Operation 1", "workstation": "_Test Workstation 1"}
diff --git a/erpnext/workspace_sidebar/accounts_setup.json b/erpnext/workspace_sidebar/accounts_setup.json
deleted file mode 100644
index 93a436ee15b..00000000000
--- a/erpnext/workspace_sidebar/accounts_setup.json
+++ /dev/null
@@ -1,312 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2026-01-23 14:36:51.659571",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "database",
- "idx": 1,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 0,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Chart of Accounts",
- "link_to": "Account",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Chart of Cost Centers",
- "link_to": "Cost Center",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Account Category",
- "link_to": "Account Category",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounting Dimension",
- "link_to": "Accounting Dimension",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Currency",
- "link_to": "Currency",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Currency Exchange",
- "link_to": "Currency Exchange",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Finance Book",
- "link_to": "Finance Book",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Mode of Payment",
- "link_to": "Mode of Payment",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Payment Term",
- "link_to": "Payment Term",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Journal Entry Template",
- "link_to": "Journal Entry Template",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Terms and Conditions",
- "link_to": "Terms and Conditions",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Company",
- "link_to": "Company",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Fiscal Year",
- "link_to": "Fiscal Year",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Sales Taxes",
- "link_to": "Sales Taxes and Charges Template",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "lock-keyhole-open",
- "indent": 1,
- "keep_closed": 0,
- "label": "Opening & Closing",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "COA Importer",
- "link_to": "Chart of Accounts Importer",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Opening Invoice Tool",
- "link_to": "Opening Invoice Creation Tool",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounting Period",
- "link_to": "Accounting Period",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "FX Revaluation",
- "link_to": "Exchange Rate Revaluation",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Period Closing Voucher",
- "link_to": "Period Closing Voucher",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "settings",
- "indent": 1,
- "keep_closed": 0,
- "label": "Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounts Settings",
- "link_to": "Accounts Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Currency Exchange Settings",
- "link_to": "Currency Exchange Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-06-12 14:50:50.262533",
- "modified_by": "Administrator",
- "module": "Accounts",
- "module_onboarding": "Accounting Onboarding",
- "name": "Accounts Setup",
- "owner": "Administrator",
- "standard": 1,
- "title": "Accounts Setup"
-}
diff --git a/erpnext/workspace_sidebar/banking.json b/erpnext/workspace_sidebar/banking.json
deleted file mode 100644
index 90578e81f80..00000000000
--- a/erpnext/workspace_sidebar/banking.json
+++ /dev/null
@@ -1,190 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2025-11-12 14:55:28.092635",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "circle-dollar-sign",
- "idx": 0,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "book-open-check",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Clearance",
- "link_to": "Bank Clearance",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "wrench",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Reconciliation",
- "link_to": "Bank Reconciliation Tool",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "clipboard-check",
- "indent": 0,
- "keep_closed": 0,
- "label": "Reconciliation Statement",
- "link_to": "Bank Reconciliation Statement",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "split",
- "indent": 0,
- "keep_closed": 0,
- "label": "Unreconcile Payment",
- "link_to": "Unreconcile Payment",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "link",
- "indent": 0,
- "keep_closed": 0,
- "label": "Process Payment Reconciliation",
- "link_to": "Process Payment Reconciliation",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 1,
- "label": "Setup",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank",
- "link_to": "Bank",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Account",
- "link_to": "Bank Account",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Account Type",
- "link_to": "Bank Account Type",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Account Subtype",
- "link_to": "Bank Account Subtype",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bank Guarantee",
- "link_to": "Bank Guarantee",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Plaid Settings",
- "link_to": "Plaid Settings",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "scroll-text",
- "indent": 1,
- "keep_closed": 1,
- "label": "Dunning",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Dunning",
- "link_to": "Dunning",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Dunning Type",
- "link_to": "Dunning Type",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 00:06:13.017457",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Banking",
- "owner": "Administrator",
- "standard": 1,
- "title": "Banking"
-}
diff --git a/erpnext/workspace_sidebar/budgeting.json b/erpnext/workspace_sidebar/budgeting.json
deleted file mode 100644
index 3da32884f8d..00000000000
--- a/erpnext/workspace_sidebar/budgeting.json
+++ /dev/null
@@ -1,82 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2025-11-10 16:53:45.409587",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "accounting",
- "idx": 0,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "briefcase-business",
- "indent": 0,
- "keep_closed": 0,
- "label": "Budget",
- "link_to": "Budget",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "badge-cent",
- "indent": 0,
- "keep_closed": 0,
- "label": "Cost Center",
- "link_to": "Cost Center",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "wallet",
- "indent": 0,
- "keep_closed": 0,
- "label": "Accounting Dimension",
- "link_to": "Accounting Dimension",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "notepad-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Cost Center Allocation",
- "link_to": "Cost Center Allocation",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "sheet",
- "indent": 0,
- "keep_closed": 0,
- "label": "Budget Variance",
- "link_to": "Budget Variance Report",
- "link_type": "Report",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 00:06:13.032297",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Budgeting",
- "owner": "Administrator",
- "standard": 1,
- "title": "Budgeting"
-}
diff --git a/erpnext/workspace_sidebar/organization.json b/erpnext/workspace_sidebar/organization.json
deleted file mode 100644
index ab3f8470cb9..00000000000
--- a/erpnext/workspace_sidebar/organization.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2026-02-24 17:39:43.793115",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "organization",
- "idx": 1,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 1,
- "icon": "building-2",
- "indent": 0,
- "keep_closed": 0,
- "label": "Company",
- "link_to": "Company",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "book-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Letter Head",
- "link_to": "Letter Head",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "file-user",
- "indent": 0,
- "keep_closed": 0,
- "label": "Department",
- "link_to": "Department",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "book-user",
- "indent": 0,
- "keep_closed": 0,
- "label": "Branch",
- "link_to": "Branch",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "users",
- "indent": 0,
- "keep_closed": 0,
- "label": "User",
- "link_to": "User",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "user-round-check",
- "indent": 0,
- "keep_closed": 0,
- "label": "Role Permissions",
- "link_to": "permission-manager",
- "link_type": "Page",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "default_workspace": 0,
- "icon": "mail",
- "indent": 0,
- "keep_closed": 0,
- "label": "Email Account",
- "link_to": "Email Account",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 00:37:22.942285",
- "modified_by": "Administrator",
- "module": "Setup",
- "module_onboarding": "Organization Onboarding",
- "name": "Organization",
- "owner": "Administrator",
- "standard": 1,
- "title": "Organization"
-}
diff --git a/erpnext/workspace_sidebar/share_management.json b/erpnext/workspace_sidebar/share_management.json
deleted file mode 100644
index 34ab0ef0db0..00000000000
--- a/erpnext/workspace_sidebar/share_management.json
+++ /dev/null
@@ -1,65 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2025-11-10 16:49:07.269956",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "",
- "idx": 0,
- "items": [
- {
- "child": 1,
- "collapsible": 1,
- "icon": "user",
- "indent": 0,
- "keep_closed": 0,
- "label": "Shareholder",
- "link_to": "Shareholder",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "move-horizontal",
- "indent": 0,
- "keep_closed": 0,
- "label": "Share Transfer",
- "link_to": "Share Transfer",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "list",
- "indent": 0,
- "keep_closed": 0,
- "label": "Share Ledger",
- "link_to": "Share Ledger",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "notepad-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Share Balance",
- "link_to": "Share Balance",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 00:06:13.040767",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Share Management",
- "owner": "Administrator",
- "standard": 1,
- "title": "Share Management"
-}
diff --git a/erpnext/workspace_sidebar/subcontracting.json b/erpnext/workspace_sidebar/subcontracting.json
deleted file mode 100644
index 5587e19f608..00000000000
--- a/erpnext/workspace_sidebar/subcontracting.json
+++ /dev/null
@@ -1,241 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2025-11-17 14:49:59.811213",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "getting-started",
- "idx": 0,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "house",
- "indent": 0,
- "keep_closed": 0,
- "label": "Home",
- "link_to": "Subcontracting",
- "link_type": "Workspace",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "folder-tree",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting BOM",
- "link_to": "Subcontracting BOM",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "move-horizontal",
- "indent": 0,
- "keep_closed": 0,
- "label": "Stock Entry",
- "link_to": "Stock Entry",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "arrow-left-to-line",
- "indent": 1,
- "keep_closed": 0,
- "label": "Inward Order",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Sales Order",
- "link_to": "Sales Order",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Inward Order",
- "link_to": "Subcontracting Inward Order",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Delivery",
- "link_to": "Stock Entry",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "arrow-right-from-line",
- "indent": 1,
- "keep_closed": 0,
- "label": "Outward Order",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Purchase Order",
- "link_to": "Purchase Order",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Order",
- "link_to": "Subcontracting Order",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontracting Receipt",
- "link_to": "Subcontracting Receipt",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 0,
- "label": "Setup",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Item",
- "link_to": "Item",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Bill of Materials",
- "link_to": "BOM",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "notepad-text",
- "indent": 1,
- "keep_closed": 1,
- "label": "Reports",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subcontract Order Summary",
- "link_to": "Subcontract Order Summary",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Materials To Be Transferred",
- "link_to": "Subcontracted Raw Materials To Be Transferred",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "",
- "indent": 0,
- "keep_closed": 0,
- "label": "Items To Be Received",
- "link_to": "Subcontracted Item To Be Received",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "settings",
- "indent": 0,
- "keep_closed": 0,
- "label": "Settings",
- "link_to": "Buying Settings",
- "link_type": "DocType",
- "navigate_to_tab": "subcontract",
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 20:22:17.130321",
- "modified_by": "Administrator",
- "module": "Buying",
- "module_onboarding": "Subcontracting Onboarding",
- "name": "Subcontracting",
- "owner": "Administrator",
- "standard": 1,
- "title": "Subcontracting"
-}
diff --git a/erpnext/workspace_sidebar/subscriptions.json b/erpnext/workspace_sidebar/subscriptions.json
deleted file mode 100644
index ec188edf169..00000000000
--- a/erpnext/workspace_sidebar/subscriptions.json
+++ /dev/null
@@ -1,104 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2025-11-10 16:08:50.904116",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "accounting",
- "idx": 0,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "circle-dollar-sign",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subscription",
- "link_to": "Subscription",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "receipt-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subscription Plan",
- "link_to": "Subscription Plan",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "settings",
- "indent": 0,
- "keep_closed": 0,
- "label": "Subscription Settings",
- "link_to": "Subscription Settings",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 1,
- "label": "Setup",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Customer",
- "link_to": "Customer",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Supplier",
- "link_to": "Supplier",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Item",
- "link_to": "Item",
- "link_type": "DocType",
- "open_in_new_tab": 0,
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-01-10 00:06:13.048591",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Subscriptions",
- "owner": "Administrator",
- "standard": 1,
- "title": "Subscriptions"
-}
diff --git a/erpnext/workspace_sidebar/taxes.json b/erpnext/workspace_sidebar/taxes.json
deleted file mode 100644
index 09061ee1452..00000000000
--- a/erpnext/workspace_sidebar/taxes.json
+++ /dev/null
@@ -1,159 +0,0 @@
-{
- "app": "erpnext",
- "creation": "2025-11-12 15:03:06.180114",
- "docstatus": 0,
- "doctype": "Workspace Sidebar",
- "header_icon": "money-coins-1",
- "idx": 0,
- "items": [
- {
- "child": 0,
- "collapsible": 1,
- "icon": "panel-bottom-close",
- "indent": 0,
- "keep_closed": 0,
- "label": "Sales Tax Template",
- "link_to": "Sales Taxes and Charges Template",
- "link_type": "DocType",
- "navigate_to_tab": "",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "panel-top-close",
- "indent": 0,
- "keep_closed": 0,
- "label": "Purchase Tax Template",
- "link_to": "Purchase Taxes and Charges Template",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "package",
- "indent": 0,
- "keep_closed": 0,
- "label": "Item Tax Template",
- "link_to": "Item Tax Template",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "database",
- "indent": 1,
- "keep_closed": 1,
- "label": "Setup",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "triangle",
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Category",
- "link_to": "Tax Category",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "book-open-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Rule",
- "link_to": "Tax Rule",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "icon": "book-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Withholding Category",
- "link_to": "Tax Withholding Category",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Withholding Group",
- "link_to": "Tax Withholding Group",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "notebook-text",
- "indent": 0,
- "keep_closed": 0,
- "label": "Deduction Certificate",
- "link_to": "Lower Deduction Certificate",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 0,
- "collapsible": 1,
- "icon": "sheet",
- "indent": 1,
- "keep_closed": 1,
- "label": "Reports",
- "link_to": "",
- "link_type": "DocType",
- "show_arrow": 0,
- "type": "Section Break"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "TDS Computation Summary",
- "link_to": "TDS Computation Summary",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- },
- {
- "child": 1,
- "collapsible": 1,
- "indent": 0,
- "keep_closed": 0,
- "label": "Tax Withholding Details",
- "link_to": "Tax Withholding Details",
- "link_type": "Report",
- "show_arrow": 0,
- "type": "Link"
- }
- ],
- "modified": "2026-07-03 18:36:08.105306",
- "modified_by": "Administrator",
- "module": "Accounts",
- "module_onboarding": "Accounting Onboarding",
- "name": "Taxes",
- "owner": "Administrator",
- "standard": 1,
- "title": "Taxes"
-}