Merge branch 'develop' into strip-account-number-coa-importer

This commit is contained in:
Shllokkk
2026-07-16 00:18:46 +05:30
committed by GitHub
63 changed files with 2229 additions and 3068 deletions

View File

@@ -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

View File

@@ -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"
}
}
}
}

View File

@@ -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:

View File

@@ -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()

View File

@@ -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"))

View File

@@ -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\":\"<span class=\\\"h4\\\"><b>Accounting Overview</b></span>\",\"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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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,

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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,
},
}
)

View File

@@ -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",

View File

@@ -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", "");

View File

@@ -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",

View File

@@ -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):

View File

@@ -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",

View File

@@ -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,

View File

@@ -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)

View File

@@ -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",

View File

@@ -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"),

View File

@@ -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
erpnext.patches.v16_0.crm_settings_handle_allowed_users_for_frappe_crm
erpnext.patches.v16_0.access_control_for_project_users

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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"""

View File

@@ -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];
}

View File

@@ -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(

View File

@@ -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",

View File

@@ -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",

View File

@@ -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()):

View File

@@ -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 "- <max suffix + 1>". The

View File

@@ -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 <b>Allowed Companies</b> table. Transactions will only show masters configured for the selected company.",
"fieldname": "enable_company_wise_masters",
"fieldtype": "Check",
"label": "Enable Company-wise Master Filtering"
},
{
"fieldname": "defaults_section",
"fieldtype": "Section Break",
"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",

View File

@@ -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

View File

@@ -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,

View File

@@ -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"
}

View File

@@ -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": []
}

View File

@@ -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,
)

View File

@@ -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");

View File

@@ -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",

View File

@@ -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")

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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)))

View File

@@ -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:

View File

@@ -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\":\"<span class=\\\"h4\\\"><b>Subcontracting Inward and Outward</b></span>\",\"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"
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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"}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}