Compare commits

..

64 Commits

Author SHA1 Message Date
Nihantra C. Patel
cab1b129c0 fix: validate reverse GL entries on current date under immutable ledger (#56709)
* fix: validate reverse GL entries on current date under immutable ledger

When Immutable Ledger is enabled, the reverse GL entry is posted on the
current date, but the closed-period checks in make_reverse_gl_entries still
validate against the original (backdated) posting date. This blocks cancelling
a backdated voucher, such as a suspense Journal Entry for a migrated NPA loan,
with a books-closed error even though the reverse entry lands in an open period.

Validate both check_freezing_date and validate_against_pcv against the current
date when Immutable Ledger is enabled. When it is disabled, behaviour is
unchanged.

Follow-up to #55268.

* test: reset frozen till date after reverse entry test

The freeze date set on the company was not reset, so it leaked into the next
test which posts entries in that period. Reset it in a finally block.

* fix: prefer explicit posting_date under immutable ledger

Prefer the posting_date argument before frappe.form_dict and getdate, at both
the validation and the GL entry site, so an explicit date passed by the caller
is honoured and validation still matches the posted date.
2026-07-02 10:16:57 +05:30
Mihir Kandoi
171f12c2eb Merge pull request #56697 from mihir-kandoi/pg/advisory-lock
perf: serialize concurrent reposts with an advisory lock
2026-07-02 07:54:03 +05:30
Diptanil Saha
9cea43b006 fix(company): ignore user permissions for link fields having link to Account and Cost Center (#56748) 2026-07-02 07:42:34 +05:30
Mihir Kandoi
15adc92e76 Merge pull request #56744 from mihir-kandoi/pg/repost-recovery-by-exception-type
fix: classify repost recovery by exception type, not traceback string
2026-07-02 07:34:34 +05:30
Raffael Meyer
ba1e8f0005 fix(Quotation): create Customer from Lead (#55923) 2026-07-02 03:27:14 +02:00
Soham Kulkarni
3eaea74a51 Merge pull request #56656 from sokumon/revamp-workspaces
chore: exporting workspaces with sidebars
2026-07-02 05:39:24 +05:30
sokumon
d84eb9a97b fix: remove roles from budgeting workspace 2026-07-02 04:25:22 +05:30
Mihir Kandoi
e5569f681a fix: classify repost recovery by exception type, not traceback string
repost() decided whether a failed Repost Item Valuation was recoverable
(re-queue as "In Progress") or permanently "Failed" by string-matching the
traceback for "timeout" or MariaDB's "Deadlock found". On Postgres a deadlock
surfaces as "deadlock detected" / "could not serialize access" and matches
neither, so a retriable deadlock was marked Failed and never re-queued -- the
scheduler only re-picks Queued/In Progress entries.

Classify by isinstance(e, RecoverableErrors) instead, the same tuple already
used to gate the error email. This covers deadlocks and lock/query timeouts on
both engines (frappe raises QueryDeadlockError / QueryTimeoutError uniformly)
and the advisory-lock repost gate's own QueryTimeoutError, which previously
recovered only because its class name incidentally contains "timeout".
2026-07-02 00:52:20 +05:30
Mihir Kandoi
e99be23b57 Merge pull request #56696 from mihir-kandoi/pg/index-search
perf(postgres): partial/covering indexes + trigram item search
2026-07-02 00:23:18 +05:30
Nabin Hait
5cc866e840 Merge pull request #56737 from frappe/chore/test-bom-operations-time
test: BOM Operations Time report coverage
2026-07-02 00:21:54 +05:30
Mihir Kandoi
9e5b492db1 fix: tighten repost gate timeout, key, and scope (review)
- REPOST_LOCK_TIMEOUT 600 -> 300s: a contended waiter re-queues and frees its
  long-queue worker slot sooner instead of pinning it for up to 10 minutes
  (still well under the 1800s repost job timeout).
- Collision-free lock key: pass a ("stock_repost", item, warehouse) tuple so a
  colon in item_code/warehouse can't map two distinct pairs onto one lock.
- Document that the gate is repost-vs-repost only; the synchronous
  repost_current_voucher submit path is deliberately left ungated (gating a live
  submit behind a background repost would be a worse regression).
2026-07-02 00:20:15 +05:30
Nabin Hait
4cc2902b99 Merge pull request #56735 from frappe/chore/test-process-loss-report
test: Process Loss Report report coverage
2026-07-02 00:20:11 +05:30
Mihir Kandoi
5e1296a0b9 perf(postgres): partial/covering indexes + trigram item search
Postgres-guarded on_doctype_update indexes: partial WHERE is_cancelled=0 + covering INCLUDE on GL Entry/SLE and Serial and Batch Bundle/Entry, and pg_trgm GIN on Item item_code/item_name (~128x faster LIKE search at scale). No-ops on MariaDB. Requires frappe framework support.
2026-07-02 00:00:37 +05:30
Nabin Hait
7ccd729cc5 Merge pull request #56732 from frappe/chore/test-downtime-analysis
test: Downtime Analysis report coverage
2026-07-01 23:57:38 +05:30
Nabin Hait
0aed70153b Merge pull request #56719 from frappe/chore/test-delivered-items-to-be-billed
test: Delivered Items To Be Billed report coverage
2026-07-01 23:54:45 +05:30
Nabin Hait
bdd6a63556 Merge pull request #56718 from frappe/chore/test-received-items-to-be-billed
test: Received Items To Be Billed report coverage
2026-07-01 23:54:37 +05:30
Mihir Kandoi
e51ffb1bf1 Merge pull request #56695 from mihir-kandoi/pg/recursive-cte
perf: use recursive CTEs for BOM and Task tree traversal
2026-07-01 23:53:39 +05:30
Nabin Hait
52d5085360 Merge pull request #56723 from frappe/chore/test-share-balance
fix: Share Balance respects the as-on date + test coverage
2026-07-01 23:52:15 +05:30
Nabin Hait
1969c9ca47 Merge pull request #56721 from frappe/chore/test-payment-period-based-on-invoice-date
fix: Payment Period ages by payment period + test coverage
2026-07-01 23:51:42 +05:30
Mihir Kandoi
83278d6f3b Merge pull request #56741 from mihir-kandoi/fix/multiple-variant-dialog-numeric
fix(item): rework multiple variant dialog for large numeric ranges
2026-07-01 23:43:50 +05:30
Mihir Kandoi
d4da9a3d7d fix(item): error on uncommitted input and escape values in variant dialog
Address review feedback:
- A typed-but-not-selected value passed validation yet was dropped by
  get_selected_attributes (reads committed pills only). Treat any pending
  input as an error so it is never silently omitted from creation.
- Escape pill / pending values before interpolating them into the HTML
  error message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 23:37:32 +05:30
Mihir Kandoi
99152b8300 fix(item): rework multiple variant dialog for large numeric ranges
The 'Create Multiple Variants' dialog rendered one checkbox per attribute
value and read the numeric config from the variant attribute child row. This
broke in several ways:

- A template whose attribute was made numeric after being added kept
  numeric_values=0 on the child row, so the dialog treated it as non-numeric,
  queried the empty Item Attribute Value table, and showed no values.
- Enumerating a large range (e.g. 1-100000) into checkboxes froze the browser.

Rework the dialog:

- Read numeric_values / from_range / to_range / increment from the Item
  Attribute master, and guard increment > 0.
- Replace the checkbox-per-value list with one MultiSelectPills per attribute,
  with a search placeholder.
- Stop enumerating numeric ranges: preview the first few values and validate
  typed input against the range on demand, so huge ranges stay instant.
- Block variant creation with a modal error if any selected value or pending
  input is invalid (out of range, off-increment, or not a number), so garbage
  like '00A' can't reach creation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 23:28:03 +05:30
Mihir Kandoi
4afbd4d3d9 fix(item-attribute): clear attribute values when marking numeric
Marking an attribute numeric hides the Item Attribute Values grid but leaves
its rows in the doc, whose mandatory Attribute Value / Abbreviation block the
save client-side before the server can clear them. Clear the table on the
client too so the save goes through.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 23:27:51 +05:30
Nabin Hait
e3e62a2211 Merge pull request #56716 from frappe/chore/test-voucher-wise-balance
test: Voucher-wise Balance report coverage
2026-07-01 22:55:12 +05:30
Nabin Hait
304a247dc8 test: add coverage for BOM Operations Time report 2026-07-01 22:54:23 +05:30
Nabin Hait
8abef22a49 Merge pull request #56715 from frappe/chore/test-invalid-ledger-entries
fix: Invalid Ledger Entries account filter + test coverage
2026-07-01 22:54:22 +05:30
Nabin Hait
222842b7d1 test: add coverage for Process Loss Report report 2026-07-01 22:54:09 +05:30
Nabin Hait
e179100afd Merge pull request #56714 from frappe/chore/test-purchase-invoice-trends
test: Purchase Invoice Trends report coverage
2026-07-01 22:52:16 +05:30
Nabin Hait
44aa01b115 Merge pull request #56713 from frappe/chore/test-sales-invoice-trends
test: Sales Invoice Trends report coverage
2026-07-01 22:51:47 +05:30
Nabin Hait
1a8ef852f1 test: add coverage for Downtime Analysis report 2026-07-01 22:49:01 +05:30
Mihir Kandoi
bb184f90a7 perf: serialize concurrent reposts with an advisory lock
Wraps per-(item, warehouse) reposting in repost_future_sle with a session-level advisory lock in front of the existing for-update row locks -- an outer gate that turns lock-order deadlocks into an orderly wait. Postgres/MariaDB only; nullcontext elsewhere. Row locks still enforce correctness. Requires frappe advisory_lock.
2026-07-01 22:11:43 +05:30
Mihir Kandoi
b2ad93be81 perf: use recursive CTEs for BOM and Task tree traversal
Reimplements get_ancestor_boms, BOM.traverse_tree, Task.check_recursion and the BOM Explorer report as single recursive-CTE queries (frappe.qb recursive=True), replacing query-per-node walks (N->1). Task cycle detection now catches cycles at any depth (was capped at 15 nodes). Requires the framework recursive-CTE support (frappe#40464).
2026-07-01 22:10:57 +05:30
Mihir Kandoi
65cb89cc40 Merge pull request #56552 from aerele/fix-quotation-conversion-rate-from-customer
fix: set conversion_rate on quotation created from customer
2026-07-01 21:46:28 +05:30
Mihir Kandoi
26a646aae5 Merge pull request #56701 from aerele/fix/pick-list-status-issue
feat(stock): support partial transfer from pick list
2026-07-01 21:45:17 +05:30
Mihir Kandoi
7d5efaf124 Merge pull request #56670 from aerele/fix/pick-list-wo-status-not-started
fix: recompute transferred qty before deciding work order status
2026-07-01 21:44:35 +05:30
Nabin Hait
028cc2cf49 fix: age payments by the payment period (payment date - invoice date) 2026-07-01 21:23:58 +05:30
Nabin Hait
249d519d02 fix: compute Share Balance as-on the selected date 2026-07-01 21:20:57 +05:30
Nabin Hait
f4088d48a1 fix: accept a scalar account filter in Invalid Ledger Entries report 2026-07-01 21:16:20 +05:30
Nabin Hait
02460b4684 test: add coverage for Share Balance report 2026-07-01 21:13:02 +05:30
Nabin Hait
ee8e6e806f test: add coverage for Payment Period Based On Invoice Date report 2026-07-01 21:07:56 +05:30
Nabin Hait
196482348d test: add coverage for Delivered Items To Be Billed report 2026-07-01 21:07:31 +05:30
Nabin Hait
237605889f test: add coverage for Received Items To Be Billed report 2026-07-01 21:07:21 +05:30
Nabin Hait
38ebfd7bd6 test: add coverage for Voucher-wise Balance report 2026-07-01 21:02:29 +05:30
Nabin Hait
9ec2945e6e test: add coverage for Invalid Ledger Entries report 2026-07-01 21:02:21 +05:30
Nabin Hait
cb9ea22b6f test: add coverage for Purchase Invoice Trends report 2026-07-01 21:02:12 +05:30
Nabin Hait
8aec16376e test: add coverage for Sales Invoice Trends report 2026-07-01 21:02:02 +05:30
Nabin Hait
7bc121e308 Merge pull request #56520 from frappe/chore/test-incorrect-serial-and-batch-bundle
test: Incorrect Serial and Batch Bundle report coverage
2026-07-01 20:54:20 +05:30
ervishnucs
e4d6c0854b fix: remove redundant conversion_rate 2026-07-01 19:28:38 +05:30
Nabin Hait
beb2974317 test: flag an unlinked (orphan) serial and batch bundle 2026-07-01 19:25:54 +05:30
Sudharsanan11
fad904d68b fix(stock): backfill transferred qty for existing pick lists
Pick Lists transferred before this feature have transferred_qty = 0 and
their Stock Entry rows carry no pick_list_item link, so the new
is_fully_transferred check would never fire and, with the old
duplicate-entry guard removed, they could be transferred again. Set
transferred_qty = picked_qty for non-Delivery submitted pick lists that
already have a linked Stock Entry so they stay completed and locked.
2026-07-01 16:54:57 +05:30
pandiyan
d072909451 fix: recompute transferred qty before deciding work order status
work order status was decided using a stale transferred-qty value,
computed before the current stock entry's transfer got recomputed.
this left work orders stuck at "not started" for pick-list-driven
transfers, since those entries never set fg_completed_qty and their
transferred qty can only be known from actual item-level transfers.

an earlier attempt fixed this by setting fg_completed_qty from the pick
list's for_qty, but that broke two things tied to fg_completed_qty
being zero: the excess-transfer guard, and the partial-transfer
fraction logic used to avoid marking a work order as fully supplied too
early.

recompute the transferred qty first, then decide status from the fresh
value. revert the fg_completed_qty change since it's no longer needed.
2026-07-01 16:41:15 +05:30
Sudharsanan11
a1daad8d4f test(stock): add test for partial transfer status from pick list 2026-07-01 15:44:35 +05:30
Sudharsanan11
27d5165755 feat(stock): support partial transfer from pick list
Creating a Stock Entry from a Pick List blocked any further entry
(stock_entry_exists) and flipped the pick list to Completed as soon as
one entry existed, so picked stock could not be transferred in parts.

Track transferred_qty per Pick List Item (summed from submitted Stock
Entry rows via a new pick_list_item link, mirroring delivered_qty), add
a Partially Transferred status, and map each new Stock Entry from the
remaining qty so transfers can continue until fully transferred.
2026-07-01 15:44:26 +05:30
sokumon
088b8ff69b fix: remove roles from home workspace 2026-07-01 14:22:43 +05:30
sokumon
3b25878d71 fix: remove text from projects workspace 2026-07-01 13:29:25 +05:30
sokumon
55afd95b20 fix: remove duplicate links from export 2026-06-30 17:24:58 +05:30
sokumon
5a32866b93 chore: export more workspaces 2026-06-30 17:00:54 +05:30
ervishnucs
dead28e50e test: assert quotation from customer uses actual exchange rate 2026-06-28 20:49:56 +05:30
ervishnucs
8446be6518 fix: set currency and price list before computing quotation totals 2026-06-28 20:13:53 +05:30
ervishnucs
e61d299e63 fix: recalculate totals after setting quotation conversion rate 2026-06-28 09:53:38 +05:30
ervishnucs
31ee3f1923 fix: set conversion_rate on quotation created from customer 2026-06-26 13:33:30 +05:30
Nabin Hait
851dfb16be test: reuse BootStrapTestData master data to reduce runtime
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:26:48 +05:30
Nabin Hait
047014f2b5 test: add coverage for Incorrect Serial and Batch Bundle report
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:19:38 +05:30
sokumon
90aba582ec chore: export workspaces with new schema 2026-06-12 13:08:48 +05:30
82 changed files with 9511 additions and 431 deletions

View File

@@ -471,6 +471,25 @@ def on_doctype_update():
frappe.db.add_index("GL Entry", ["posting_date", "company"])
frappe.db.add_index("GL Entry", ["party_type", "party"])
if frappe.db.db_type == "postgres":
# Postgres-only partial/covering indexes for the financial reports (General Ledger, Trial
# Balance, Balance Sheet, P&L), which always filter `is_cancelled = 0` and scope by company.
# `where`/`include` are no-ops on MariaDB and its optimizer ignores these anyway, so they are
# added only on postgres to avoid dead write overhead on this insert-hot table.
frappe.db.add_index(
"GL Entry",
["company", "posting_date", "account"],
index_name="gle_active_detail",
where="is_cancelled = 0",
)
frappe.db.add_index(
"GL Entry",
["company", "account", "posting_date"],
index_name="gle_active_cover",
where="is_cancelled = 0",
include=["debit", "credit"],
)
def rename_gle_sle_docs():
for doctype in ["GL Entry", "Stock Ledger Entry"]:

View File

@@ -360,12 +360,15 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
self.make_period_closing_voucher(posting_date="2021-03-31")
# Passed posting_date is after PCV end date, so cancellation should not fail.
make_reverse_gl_entries(
voucher_type="Journal Entry",
voucher_no=jv.name,
posting_date="2022-01-01",
)
frappe.db.set_value("Company", "Test PCV Company", "accounts_frozen_till_date", "2021-12-31")
try:
make_reverse_gl_entries(
voucher_type="Journal Entry",
voucher_no=jv.name,
)
finally:
frappe.db.set_value("Company", "Test PCV Company", "accounts_frozen_till_date", None)
totals_after_cancel = frappe.get_all(
"GL Entry",

View File

@@ -640,13 +640,15 @@ def make_reverse_gl_entries(
partial_cancel=partial_cancel,
)
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], gl_entries[0]["company"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
# For reverse entries, use the posting_date parameter if provided and valid
# Otherwise fall back to original posting_date
validation_date = posting_date if posting_date else gl_entries[0]["posting_date"]
if immutable_ledger_enabled:
validation_date = posting_date or frappe.form_dict.get("posting_date") or getdate()
else:
validation_date = posting_date if posting_date else gl_entries[0]["posting_date"]
check_freezing_date(validation_date, gl_entries[0]["company"], adv_adj)
validate_against_pcv(is_opening, validation_date, gl_entries[0]["company"])
if partial_cancel:
@@ -715,7 +717,7 @@ def make_reverse_gl_entries(
if immutable_ledger_enabled:
new_gle["is_cancelled"] = 0
new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate()
new_gle["posting_date"] = posting_date or frappe.form_dict.get("posting_date") or getdate()
elif posting_date:
new_gle["posting_date"] = posting_date

View File

@@ -582,12 +582,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
total += flt(row[company])
row["has_value"] = has_value
# when accumulating into the group company, that company's column already consolidates its
# descendants, so summing every company column would double-count; use the group total directly.
if filters.get("accumulated_in_group_company"):
row["total"] = flt(row.get(filters.company, 0.0), 3)
else:
row["total"] = total
row["total"] = total
data.append(row)

View File

@@ -1,118 +0,0 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import flt, today
from erpnext.accounts.report.consolidated_financial_statement.consolidated_financial_statement import (
execute,
)
from erpnext.accounts.utils import get_fiscal_year
from erpnext.tests.utils import ERPNextTestSuite
PARENT_COMPANY = "Parent Group Company India"
CHILD_COMPANY = "Child Company India"
class TestConsolidatedFinancialStatement(ERPNextTestSuite):
"""Consolidation is exercised via the bootstrap group of companies
(`Parent Group Company India` with child `Child Company India`). Income and
expense posted in the child company must surface in the report that is run
for the parent (group) company."""
def setUp(self):
self.fiscal_year = get_fiscal_year(today(), company=PARENT_COMPANY)[0]
def run_report(self, **extra):
filters = frappe._dict(
{
"company": PARENT_COMPANY,
"filter_based_on": "Fiscal Year",
"from_fiscal_year": self.fiscal_year,
"to_fiscal_year": self.fiscal_year,
"periodicity": "Yearly",
"include_default_book_entries": 1,
}
)
filters.update(extra)
return execute(filters)[1]
def post_journal_entry(self, debit_account, credit_account, amount):
je = frappe.new_doc("Journal Entry")
je.posting_date = today()
je.company = CHILD_COMPANY
je.set(
"accounts",
[
{"account": debit_account, "debit_in_account_currency": amount},
{"account": credit_account, "credit_in_account_currency": amount},
],
)
je.save()
je.submit()
return je
def get_row(self, data, account_name_fragment):
for row in data:
if account_name_fragment in str(row.get("account_name") or ""):
return row
return None
def test_profit_and_loss_reflects_child_company_income(self):
amount = 7000
self.post_journal_entry("Cash - CCI", "Sales - CCI", amount)
data = self.run_report(report="Profit and Loss Statement", accumulated_in_group_company=0)
self.assertTrue(data, "Report returned no rows")
# child's Sales account is mapped onto the parent chart (Sales - PGCI)
sales_row = self.get_row(data, "Sales")
self.assertIsNotNone(sales_row, "Sales row missing from consolidated P&L")
self.assertEqual(flt(sales_row.get(CHILD_COMPANY)), amount)
total_income_row = self.get_row(data, "Total Income (Credit)")
self.assertIsNotNone(total_income_row, "Total Income row missing")
self.assertGreaterEqual(flt(total_income_row.get("total")), amount)
def test_profit_and_loss_reflects_child_company_expense(self):
amount = 3000
self.post_journal_entry("Marketing Expenses - CCI", "Cash - CCI", amount)
data = self.run_report(report="Profit and Loss Statement", accumulated_in_group_company=0)
expense_row = self.get_row(data, "Marketing Expenses")
self.assertIsNotNone(expense_row, "Marketing Expenses row missing from consolidated P&L")
self.assertEqual(flt(expense_row.get(CHILD_COMPANY)), amount)
total_expense_row = self.get_row(data, "Total Expense (Debit)")
self.assertIsNotNone(total_expense_row, "Total Expense row missing")
self.assertGreaterEqual(flt(total_expense_row.get("total")), amount)
def test_accumulated_in_group_company_rolls_up_to_parent(self):
"""With `accumulated_in_group_company`, the child's amount is also
accumulated into the parent company column."""
amount = 5000
self.post_journal_entry("Cash - CCI", "Sales - CCI", amount)
data = self.run_report(report="Profit and Loss Statement", accumulated_in_group_company=1)
sales_row = self.get_row(data, "Sales")
self.assertIsNotNone(sales_row)
self.assertEqual(flt(sales_row.get(CHILD_COMPANY)), amount)
# parent column picks up the child value when accumulated
self.assertEqual(flt(sales_row.get(PARENT_COMPANY)), amount)
# the total must equal the consolidated (group) value, not the sum of parent + child columns
self.assertEqual(flt(sales_row.get("total")), amount)
def test_balance_sheet_executes_and_returns_rows(self):
# posting income leaves a balancing entry in the child's Cash (Asset) account
amount = 4000
self.post_journal_entry("Cash - CCI", "Sales - CCI", amount)
data = self.run_report(report="Balance Sheet", accumulated_in_group_company=0)
self.assertTrue(data, "Balance Sheet returned no rows")
cash_row = self.get_row(data, "Cash")
self.assertIsNotNone(cash_row, "Cash asset row missing from consolidated Balance Sheet")
self.assertGreaterEqual(flt(cash_row.get(CHILD_COMPANY)), amount)

View File

@@ -0,0 +1,89 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.report.delivered_items_to_be_billed.delivered_items_to_be_billed import execute
from erpnext.stock.doctype.delivery_note.mapper import make_sales_invoice
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.tests.utils import ERPNextTestSuite
class TestDeliveredItemsToBeBilled(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"posting_date": "2026-06-30",
}
)
filters.update(extra)
return execute(filters)[1]
def stock_up_item(self):
make_stock_entry(
item_code="_Test Item",
target="Stores - _TC",
qty=20,
basic_rate=100,
posting_date="2026-05-25",
)
def test_unbilled_delivery_note_appears(self):
self.stock_up_item()
dn = create_delivery_note(
item_code="_Test Item",
warehouse="Stores - _TC",
qty=5,
rate=300,
customer="_Test Customer",
posting_date="2026-06-01",
)
rows = self.run_report(delivery_note=dn.name)
self.assertEqual(len(rows), 1)
row = rows[0]
self.assertEqual(row.name, dn.name)
self.assertEqual(row.customer, "_Test Customer")
self.assertEqual(row.item_code, "_Test Item")
self.assertEqual(row.amount, 1500)
self.assertEqual(row.billed_amount, 0)
self.assertEqual(row.returned_amount, 0)
self.assertEqual(row.pending_amount, 1500)
def test_fully_billed_delivery_note_drops_out(self):
self.stock_up_item()
dn = create_delivery_note(
item_code="_Test Item",
warehouse="Stores - _TC",
qty=5,
rate=300,
customer="_Test Customer",
posting_date="2026-06-01",
)
self.assertEqual(len(self.run_report(delivery_note=dn.name)), 1)
si = make_sales_invoice(dn.name)
si.posting_date = "2026-06-02"
si.set_posting_time = 1
si.insert()
si.submit()
self.assertEqual(self.run_report(delivery_note=dn.name), [])
def test_date_filter_excludes_later_delivery_notes(self):
self.stock_up_item()
dn = create_delivery_note(
item_code="_Test Item",
warehouse="Stores - _TC",
qty=5,
rate=300,
customer="_Test Customer",
posting_date="2026-07-15",
)
rows = self.run_report(delivery_note=dn.name, posting_date="2026-06-30")
self.assertEqual(rows, [])

View File

@@ -84,7 +84,8 @@ def build_query_filters(filters: dict | None = None) -> list:
qb_filters = []
if filters:
if filters.account:
qb_filters.append(qb.Field("account").isin(filters.account))
accounts = filters.account if isinstance(filters.account, list | tuple) else [filters.account]
qb_filters.append(qb.Field("account").isin(accounts))
if filters.voucher_no:
qb_filters.append(qb.Field("voucher_no").eq(filters.voucher_no))

View File

@@ -0,0 +1,152 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe import qb
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.report.invalid_ledger_entries.invalid_ledger_entries import execute
from erpnext.tests.utils import ERPNextTestSuite
class TestInvalidLedgerEntries(ERPNextTestSuite):
"""Tests for the Invalid Ledger Entries integrity report.
The report flags vouchers that still have *active* ledger entries
(GL Entry with is_cancelled=0 or Payment Ledger Entry with delinked=0)
in the given period, but whose source voucher document is no longer
submitted (docstatus != 1). Such orphaned ledgers indicate corruption.
"""
def setUp(self):
self.company = "_Test Company"
self.debit_account = "_Test Bank - _TC"
self.credit_account = "_Test Cash - _TC"
self.from_date = "2026-01-01"
self.to_date = "2026-12-31"
self.posting_date = "2026-06-01"
def run_report(self, **extra):
filters = frappe._dict(
{
"company": self.company,
"from_date": self.from_date,
"to_date": self.to_date,
}
)
filters.update(extra)
return execute(filters)[1]
def make_submitted_jv(self):
return make_journal_entry(
self.debit_account,
self.credit_account,
amount=500,
posting_date=self.posting_date,
company=self.company,
submit=True,
)
def test_healthy_voucher_not_flagged(self):
"""A normal balanced, submitted Journal Entry must NOT be flagged."""
jv = self.make_submitted_jv()
# It genuinely posted active GL entries, so it is in scope of the scan.
self.assertTrue(
frappe.db.exists(
"GL Entry",
{"voucher_no": jv.name, "is_cancelled": 0, "company": self.company},
)
)
flagged = {row.get("voucher_no") for row in self.run_report()}
self.assertNotIn(jv.name, flagged)
def test_orphaned_gl_entries_flagged(self):
"""A voucher whose document was set non-submitted while its GL entries
remain active (is_cancelled=0) must be flagged as invalid."""
jv = self.make_submitted_jv()
# Corrupt the state: mark the source document as cancelled (docstatus=2)
# without cancelling/removing its GL Entries. This is the exact orphaned
# ledger condition the report detects.
frappe.db.set_value("Journal Entry", jv.name, "docstatus", 2, update_modified=False)
data = self.run_report()
matching = [
row
for row in data
if row.get("voucher_no") == jv.name and row.get("voucher_type") == "Journal Entry"
]
self.assertEqual(len(matching), 1, "Orphaned voucher should be flagged exactly once")
self.assertEqual(matching[0]["voucher_type"], "Journal Entry")
self.assertEqual(matching[0]["voucher_no"], jv.name)
def test_voucher_no_filter_scopes_scan(self):
"""The voucher_no filter must restrict the scan to that voucher only."""
orphan = self.make_submitted_jv()
other = self.make_submitted_jv()
frappe.db.set_value("Journal Entry", orphan.name, "docstatus", 2, update_modified=False)
frappe.db.set_value("Journal Entry", other.name, "docstatus", 2, update_modified=False)
flagged = {row.get("voucher_no") for row in self.run_report(voucher_no=orphan.name)}
self.assertIn(orphan.name, flagged)
self.assertNotIn(other.name, flagged)
def test_account_filter_scopes_scan(self):
"""The account filter (a MultiSelectList, so a list) must restrict the
scan to vouchers touching one of the given accounts."""
orphan = self.make_submitted_jv()
frappe.db.set_value("Journal Entry", orphan.name, "docstatus", 2, update_modified=False)
# Filtering on an account the voucher touches -> flagged.
flagged = {row.get("voucher_no") for row in self.run_report(account=[self.debit_account])}
self.assertIn(orphan.name, flagged)
# Filtering on an unrelated account -> not in scope.
unrelated = "Creditors - _TC"
flagged = {row.get("voucher_no") for row in self.run_report(account=[unrelated])}
self.assertNotIn(orphan.name, flagged)
def test_account_filter_accepts_a_scalar(self):
"""A scalar (non-list) account filter must not crash the query."""
orphan = self.make_submitted_jv()
frappe.db.set_value("Journal Entry", orphan.name, "docstatus", 2, update_modified=False)
flagged = {row.get("voucher_no") for row in self.run_report(account=self.debit_account)}
self.assertIn(orphan.name, flagged)
def test_period_filter_excludes_out_of_range(self):
"""Vouchers posted outside the from/to window must not be scanned."""
orphan = self.make_submitted_jv()
frappe.db.set_value("Journal Entry", orphan.name, "docstatus", 2, update_modified=False)
flagged = {
row.get("voucher_no") for row in self.run_report(from_date="2025-01-01", to_date="2025-12-31")
}
self.assertNotIn(orphan.name, flagged)
def test_cancelled_gl_entries_not_flagged(self):
"""If the ledger entries are properly cancelled (is_cancelled=1), the
voucher is out of scope even when its document is non-submitted."""
jv = self.make_submitted_jv()
gle = qb.DocType("GL Entry")
qb.update(gle).set(gle.is_cancelled, 1).where(gle.voucher_no == jv.name).run()
frappe.db.set_value("Journal Entry", jv.name, "docstatus", 2, update_modified=False)
flagged = {row.get("voucher_no") for row in self.run_report()}
self.assertNotIn(jv.name, flagged)
def test_missing_filters_raises(self):
"""validate_filters must guard mandatory inputs."""
self.assertRaises(frappe.ValidationError, execute, None)
bad = frappe._dict({"from_date": self.from_date, "to_date": self.to_date})
self.assertRaises(frappe.ValidationError, execute, bad)
reversed_dates = frappe._dict(
{"company": self.company, "from_date": self.to_date, "to_date": self.from_date}
)
self.assertRaises(frappe.ValidationError, execute, reversed_dates)

View File

@@ -21,6 +21,8 @@ def execute(filters=None):
entries = get_entries(filters)
invoice_details = get_invoice_posting_date_map(filters)
report = ReceivablePayableReport(filters)
data = []
for d in entries:
invoice = invoice_details.get(d.against_voucher_no) or frappe._dict()
@@ -29,7 +31,9 @@ def execute(filters=None):
d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
if d.against_voucher_no:
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
# age the payment by how long after the invoice it was made (payment date - invoice date)
report.age_as_on = getdate(d.posting_date)
report.get_ageing_data(invoice.posting_date, d)
row = [
d.voucher_type,

View File

@@ -0,0 +1,122 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import getdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.payment_period_based_on_invoice_date.payment_period_based_on_invoice_date import (
execute,
)
from erpnext.tests.utils import ERPNextTestSuite
class TestPaymentPeriodBasedOnInvoiceDate(ERPNextTestSuite):
"""Depth tests for the Payment Period Based On Invoice Date report.
The report lists Payment Ledger Entries against invoices and buckets the paid
amount by the payment period -- how long after the invoice the payment was made
(payment date - invoice date) -- into ranges: range1 (0-30), range2 (30-60),
range3 (60-90), range4 (90 Above).
"""
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"payment_type": "Incoming",
"party_type": "Customer",
"from_date": "2026-01-01",
"to_date": "2026-12-31",
}
)
filters.update(extra)
return execute(filters)
def find_payment_row(self, data, payment_name):
# Row shape (positional): payment_document, payment_entry(voucher_no),
# party_type, party, posting_date, invoice(against_voucher_no),
# invoice_posting_date, due_date, amount, remarks, age,
# range1, range2, range3, range4, [delay_in_payment]
for row in data:
if row[1] == payment_name:
return row
return None
def pay_invoice(self, invoice, payment_date):
pe = get_payment_entry("Sales Invoice", invoice.name)
pe.posting_date = payment_date
pe.reference_no = "1"
pe.reference_date = payment_date
pe.submit()
return pe
def test_paid_amount_lands_in_0_30_bucket(self):
# invoice 2026-06-01, paid 2026-06-20 -> 19 days after -> 0-30 bucket
invoice = create_sales_invoice(customer="_Test Customer", rate=1000, posting_date="2026-06-01")
payment = self.pay_invoice(invoice, "2026-06-20")
columns, data = self.run_report()
row = self.find_payment_row(data, payment.name)
self.assertIsNotNone(row, "Payment row not found in report output")
# Positional assertions on the row shape.
self.assertEqual(row[2], "Customer")
self.assertEqual(row[4], getdate("2026-06-20")) # payment posting date
self.assertEqual(row[5], invoice.name) # against invoice
self.assertEqual(row[6], getdate("2026-06-01")) # invoice posting date
self.assertEqual(row[8], 1000) # amount
self.assertEqual(row[10], 19) # age = payment date - invoice date
# Buckets: 0-30 filled, others empty.
self.assertEqual(row[11], 1000) # range1 (0-30)
self.assertEqual(row[12], 0) # range2 (30-60)
self.assertEqual(row[13], 0) # range3 (60-90)
self.assertEqual(row[14], 0) # range4 (90 Above)
def test_paid_amount_lands_in_30_60_bucket(self):
# invoice 2026-06-01, paid 2026-07-16 -> 45 days after -> 30-60 bucket
invoice = create_sales_invoice(customer="_Test Customer 1", rate=1000, posting_date="2026-06-01")
payment = self.pay_invoice(invoice, "2026-07-16")
columns, data = self.run_report()
row = self.find_payment_row(data, payment.name)
self.assertIsNotNone(row, "Payment row not found in report output")
self.assertEqual(row[8], 1000) # amount
self.assertEqual(row[10], 45) # age = payment date - invoice date
# Buckets: 30-60 filled, others empty.
self.assertEqual(row[11], 0) # range1 (0-30)
self.assertEqual(row[12], 1000) # range2 (30-60)
self.assertEqual(row[13], 0) # range3 (60-90)
self.assertEqual(row[14], 0) # range4 (90 Above)
def test_columns_expose_expected_age_buckets(self):
columns, _data = self.run_report()
labels_by_fieldname = {c["fieldname"]: c["label"] for c in columns}
self.assertEqual(labels_by_fieldname["range1"], "0-30")
self.assertEqual(labels_by_fieldname["range2"], "30-60")
self.assertEqual(labels_by_fieldname["range3"], "60-90")
self.assertEqual(labels_by_fieldname["range4"], "90 Above")
# Sales Invoice link for Incoming payments.
invoice_col = next(c for c in columns if c["fieldname"] == "invoice")
self.assertEqual(invoice_col["options"], "Sales Invoice")
def test_invalid_payment_type_party_type_combo_throws(self):
# Incoming + Supplier is invalid.
self.assertRaises(
frappe.ValidationError,
self.run_report,
payment_type="Incoming",
party_type="Supplier",
)
# Outgoing + Customer is invalid.
self.assertRaises(
frappe.ValidationError,
self.run_report,
payment_type="Outgoing",
party_type="Customer",
)

View File

@@ -0,0 +1,171 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.report.purchase_invoice_trends.purchase_invoice_trends import execute
from erpnext.tests.utils import ERPNextTestSuite
FISCAL_YEAR = "_Test Fiscal Year 2026"
COMPANY = "_Test Company"
SUPPLIER = "_Test Supplier"
ITEM = "_Test Item"
POSTING_DATE = "2026-06-01"
def make_dated_purchase_invoice(qty, rate):
# make_purchase_invoice ignores posting_date unless posting time is explicitly set, so build the
# invoice unsubmitted, pin the posting date, then submit to land it in the intended period bucket.
pi = make_purchase_invoice(
supplier=SUPPLIER, item_code=ITEM, qty=qty, rate=rate, posting_date=POSTING_DATE, do_not_submit=1
)
pi.set_posting_time = 1
pi.posting_date = POSTING_DATE
pi.submit()
return pi
class TestPurchaseInvoiceTrends(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": COMPANY,
"fiscal_year": FISCAL_YEAR,
"period": "Yearly",
"based_on": "Item",
}
)
filters.update(extra)
columns, data = execute(filters)
labels = [c.split(":")[0] if isinstance(c, str) else c.get("label") for c in columns]
return labels, data
@staticmethod
def _cell(labels, row, label):
return row[labels.index(label)]
def _find_row(self, data, key):
for row in data:
if row and row[0] == key:
return row
return None
def test_yearly_item_qty_and_amount(self):
labels_before, data_before = self.run_report()
before = self._find_row(data_before, ITEM)
qty, rate = 4, 250
make_dated_purchase_invoice(qty, rate)
labels, data = self.run_report()
self.assertIn("Item", labels)
self.assertIn("Item Name", labels)
self.assertIn("Currency", labels)
self.assertIn("Total(Qty)", labels)
self.assertIn("Total(Amt)", labels)
# Yearly period bucket uses the fiscal year name as the label prefix
self.assertIn(f"{FISCAL_YEAR} (Qty)", labels)
self.assertIn(f"{FISCAL_YEAR} (Amt)", labels)
row = self._find_row(data, ITEM)
self.assertIsNotNone(row)
before_qty = self._cell(labels_before, before, f"{FISCAL_YEAR} (Qty)") if before else 0
before_amt = self._cell(labels_before, before, f"{FISCAL_YEAR} (Amt)") if before else 0
before_tqty = self._cell(labels_before, before, "Total(Qty)") if before else 0
before_tamt = self._cell(labels_before, before, "Total(Amt)") if before else 0
self.assertEqual(self._cell(labels, row, f"{FISCAL_YEAR} (Qty)") - before_qty, qty)
self.assertEqual(self._cell(labels, row, f"{FISCAL_YEAR} (Amt)") - before_amt, qty * rate)
self.assertEqual(self._cell(labels, row, "Total(Qty)") - before_tqty, qty)
self.assertEqual(self._cell(labels, row, "Total(Amt)") - before_tamt, qty * rate)
def test_monthly_bucket(self):
labels_before, data_before = self.run_report(period="Monthly")
before = self._find_row(data_before, ITEM)
qty, rate = 3, 100
make_dated_purchase_invoice(qty, rate)
labels, data = self.run_report(period="Monthly")
# posting_date 2026-06-01 -> June bucket
self.assertIn("Jun (Qty)", labels)
self.assertIn("Jun (Amt)", labels)
row = self._find_row(data, ITEM)
before_qty = self._cell(labels_before, before, "Jun (Qty)") if before else 0
before_tamt = self._cell(labels_before, before, "Total(Amt)") if before else 0
self.assertEqual(self._cell(labels, row, "Jun (Qty)") - before_qty, qty)
self.assertEqual(self._cell(labels, row, "Total(Amt)") - before_tamt, qty * rate)
def test_quarterly_bucket(self):
labels_before, data_before = self.run_report(period="Quarterly")
before = self._find_row(data_before, ITEM)
qty, rate = 2, 150
make_dated_purchase_invoice(qty, rate)
labels, data = self.run_report(period="Quarterly")
# 2026-06-01 falls in the Apr-Jun quarter
self.assertIn("Apr-Jun (Qty)", labels)
self.assertIn("Apr-Jun (Amt)", labels)
row = self._find_row(data, ITEM)
before_qty = self._cell(labels_before, before, "Apr-Jun (Qty)") if before else 0
before_amt = self._cell(labels_before, before, "Apr-Jun (Amt)") if before else 0
self.assertEqual(self._cell(labels, row, "Apr-Jun (Qty)") - before_qty, qty)
self.assertEqual(self._cell(labels, row, "Apr-Jun (Amt)") - before_amt, qty * rate)
def test_based_on_supplier(self):
labels_before, data_before = self.run_report(based_on="Supplier")
before = self._find_row(data_before, SUPPLIER)
qty, rate = 5, 200
make_dated_purchase_invoice(qty, rate)
labels, data = self.run_report(based_on="Supplier")
self.assertIn("Supplier", labels)
self.assertIn("Supplier Name", labels)
self.assertIn("Supplier Group", labels)
row = self._find_row(data, SUPPLIER)
self.assertIsNotNone(row)
before_tqty = self._cell(labels_before, before, "Total(Qty)") if before else 0
before_tamt = self._cell(labels_before, before, "Total(Amt)") if before else 0
self.assertEqual(self._cell(labels, row, "Total(Qty)") - before_tqty, qty)
self.assertEqual(self._cell(labels, row, "Total(Amt)") - before_tamt, qty * rate)
def test_group_by_item_under_supplier(self):
labels_before, data_before = self.run_report(based_on="Supplier", group_by="Item")
# group_by inserts an "Item" column; the item breakdown row carries the item key there
item_idx = labels_before.index("Item")
before = None
for r in data_before:
if r and r[0] != SUPPLIER and r[item_idx] == ITEM:
before = r
break
qty, rate = 6, 300
make_dated_purchase_invoice(qty, rate)
labels, data = self.run_report(based_on="Supplier", group_by="Item")
self.assertIn("Item", labels)
item_idx = labels.index("Item")
row = None
for r in data:
if r and r[0] != SUPPLIER and r[0] != "'Total'" and r[item_idx] == ITEM:
row = r
break
self.assertIsNotNone(row)
before_tqty = self._cell(labels_before, before, "Total(Qty)") if before else 0
before_tamt = self._cell(labels_before, before, "Total(Amt)") if before else 0
self.assertEqual(self._cell(labels, row, "Total(Qty)") - before_tqty, qty)
self.assertEqual(self._cell(labels, row, "Total(Amt)") - before_tamt, qty * rate)

View File

@@ -0,0 +1,101 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.report.received_items_to_be_billed.received_items_to_be_billed import execute
from erpnext.stock.doctype.purchase_receipt.mapper import make_purchase_invoice as make_pi_from_pr
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.tests.utils import ERPNextTestSuite
class TestReceivedItemsToBeBilled(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"posting_date": "2026-06-30",
}
)
filters.update(extra)
return execute(filters)[1]
def get_row(self, data, purchase_receipt):
matches = [row for row in data if row.get("name") == purchase_receipt]
return matches[0] if matches else None
def test_unbilled_receipt_appears_with_pending_amount(self):
pr = make_purchase_receipt(
item_code="_Test Item",
qty=5,
rate=200,
supplier="_Test Supplier",
posting_date="2026-06-01",
)
row = self.get_row(self.run_report(), pr.name)
self.assertIsNotNone(row, "Unbilled Purchase Receipt should appear in the report")
self.assertEqual(row.get("supplier"), "_Test Supplier")
self.assertEqual(row.get("item_code"), "_Test Item")
self.assertEqual(row.get("amount"), 1000.0)
self.assertEqual(row.get("billed_amount"), 0.0)
self.assertEqual(row.get("returned_amount"), 0.0)
self.assertEqual(row.get("pending_amount"), 1000.0)
def test_billed_receipt_drops_out_of_report(self):
pr = make_purchase_receipt(
item_code="_Test Item",
qty=5,
rate=200,
supplier="_Test Supplier",
posting_date="2026-06-01",
)
self.assertIsNotNone(self.get_row(self.run_report(), pr.name))
pi = make_pi_from_pr(pr.name)
pi.set_posting_time = 1
pi.posting_date = "2026-06-02"
pi.submit()
self.assertIsNone(
self.get_row(self.run_report(), pr.name),
"Fully billed Purchase Receipt should no longer appear in the report",
)
def test_reference_field_filter_limits_to_single_receipt(self):
first_pr = make_purchase_receipt(
item_code="_Test Item",
qty=5,
rate=200,
supplier="_Test Supplier",
posting_date="2026-06-01",
)
second_pr = make_purchase_receipt(
item_code="_Test Item",
qty=3,
rate=100,
supplier="_Test Supplier",
posting_date="2026-06-01",
)
data = self.run_report(purchase_receipt=first_pr.name)
self.assertIsNotNone(self.get_row(data, first_pr.name))
self.assertIsNone(self.get_row(data, second_pr.name))
def test_posting_date_cutoff_excludes_later_receipts(self):
pr = make_purchase_receipt(
item_code="_Test Item",
qty=5,
rate=200,
supplier="_Test Supplier",
posting_date="2026-06-15",
)
self.assertIsNone(
self.get_row(self.run_report(posting_date="2026-06-01"), pr.name),
"Receipt dated after the cutoff should be excluded",
)
self.assertIsNotNone(self.get_row(self.run_report(posting_date="2026-06-30"), pr.name))

View File

@@ -0,0 +1,118 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.sales_invoice_trends.sales_invoice_trends import execute
from erpnext.tests.utils import ERPNextTestSuite
FISCAL_YEAR = "_Test Fiscal Year 2026"
POSTING_DATE = "2026-06-01"
class TestSalesInvoiceTrends(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"fiscal_year": FISCAL_YEAR,
"based_on": "Item",
"period": "Yearly",
}
)
filters.update(extra)
columns, data = execute(filters)
labels = [c.split(":")[0] if isinstance(c, str) else c.get("label") for c in columns]
return labels, data
def _cell(self, data, key_label, key_value, col_label, labels):
"""Return the value at column `col_label` for the row whose first-column
value equals `key_value`, or 0 if that row does not exist yet."""
key_idx = labels.index(key_label)
col_idx = labels.index(col_label)
for row in data:
if row[key_idx] == key_value:
return row[col_idx] or 0
return 0
def test_yearly_item_amount_and_total(self):
# Yearly period => a single "<FY> (Qty)"/"(Amt)" bucket, plus Total(Qty)/Total(Amt).
labels, before = self.run_report()
qty_col = f"{FISCAL_YEAR} (Qty)"
amt_col = f"{FISCAL_YEAR} (Amt)"
before_qty = self._cell(before, "Item", "_Test Item", qty_col, labels)
before_amt = self._cell(before, "Item", "_Test Item", amt_col, labels)
before_tot_qty = self._cell(before, "Item", "_Test Item", "Total(Qty)", labels)
before_tot_amt = self._cell(before, "Item", "_Test Item", "Total(Amt)", labels)
create_sales_invoice(item="_Test Item", qty=4, rate=200, posting_date=POSTING_DATE)
labels, after = self.run_report()
self.assertEqual(self._cell(after, "Item", "_Test Item", qty_col, labels) - before_qty, 4)
self.assertEqual(self._cell(after, "Item", "_Test Item", amt_col, labels) - before_amt, 800)
self.assertEqual(self._cell(after, "Item", "_Test Item", "Total(Qty)", labels) - before_tot_qty, 4)
self.assertEqual(self._cell(after, "Item", "_Test Item", "Total(Amt)", labels) - before_tot_amt, 800)
def test_monthly_lands_in_june_bucket(self):
# Monthly period => one bucket per month; a 2026-06-01 invoice hits "Jun (Qty)"/"(Amt)".
labels, before = self.run_report(period="Monthly")
before_qty = self._cell(before, "Item", "_Test Item", "Jun (Qty)", labels)
before_amt = self._cell(before, "Item", "_Test Item", "Jun (Amt)", labels)
before_tot = self._cell(before, "Item", "_Test Item", "Total(Amt)", labels)
create_sales_invoice(item="_Test Item", qty=3, rate=100, posting_date=POSTING_DATE)
labels, after = self.run_report(period="Monthly")
self.assertEqual(self._cell(after, "Item", "_Test Item", "Jun (Qty)", labels) - before_qty, 3)
self.assertEqual(self._cell(after, "Item", "_Test Item", "Jun (Amt)", labels) - before_amt, 300)
self.assertEqual(self._cell(after, "Item", "_Test Item", "Total(Amt)", labels) - before_tot, 300)
# Nothing should leak into an unrelated month.
self.assertEqual(self._cell(after, "Item", "_Test Item", "Jan (Amt)", labels), 0)
def test_quarterly_lands_in_apr_jun_bucket(self):
# Quarterly period over a Jan-Dec fiscal year => Apr-Jun is the 2nd quarter; June lands there.
labels, before = self.run_report(period="Quarterly")
before_qty = self._cell(before, "Item", "_Test Item", "Apr-Jun (Qty)", labels)
before_amt = self._cell(before, "Item", "_Test Item", "Apr-Jun (Amt)", labels)
create_sales_invoice(item="_Test Item", qty=5, rate=50, posting_date=POSTING_DATE)
labels, after = self.run_report(period="Quarterly")
self.assertEqual(self._cell(after, "Item", "_Test Item", "Apr-Jun (Qty)", labels) - before_qty, 5)
self.assertEqual(self._cell(after, "Item", "_Test Item", "Apr-Jun (Amt)", labels) - before_amt, 250)
# Jan-Mar quarter must stay untouched.
self.assertEqual(self._cell(after, "Item", "_Test Item", "Jan-Mar (Amt)", labels), 0)
def test_based_on_customer_total(self):
# based_on=Customer => first column is "Customer"; the customer's Total(Amt) reflects the sale.
labels, before = self.run_report(based_on="Customer")
before_tot_qty = self._cell(before, "Customer", "_Test Customer", "Total(Qty)", labels)
before_tot_amt = self._cell(before, "Customer", "_Test Customer", "Total(Amt)", labels)
create_sales_invoice(
customer="_Test Customer", item="_Test Item", qty=2, rate=300, posting_date=POSTING_DATE
)
labels, after = self.run_report(based_on="Customer")
self.assertEqual(
self._cell(after, "Customer", "_Test Customer", "Total(Qty)", labels) - before_tot_qty, 2
)
self.assertEqual(
self._cell(after, "Customer", "_Test Customer", "Total(Amt)", labels) - before_tot_amt, 600
)
def test_group_by_item_under_customer(self):
# based_on=Customer + group_by=Item inserts an "Item" breakdown column before the period
# buckets; the per-item detail row carries the item key and the amount for that customer/item.
labels, before = self.run_report(based_on="Customer", group_by="Item")
# In group_by mode the detail rows key off the group_by column ("Item"), so snapshot by item.
before_amt = self._cell(before, "Item", "_Test Item", "Total(Amt)", labels)
create_sales_invoice(
customer="_Test Customer", item="_Test Item", qty=6, rate=100, posting_date=POSTING_DATE
)
labels, after = self.run_report(based_on="Customer", group_by="Item")
self.assertIn("Item", labels)
self.assertEqual(self._cell(after, "Item", "_Test Item", "Total(Amt)", labels) - before_amt, 600)

View File

@@ -15,8 +15,6 @@ def execute(filters=None):
columns = get_columns(filters)
filters.get("date")
data = []
if not filters.get("shareholder"):
@@ -24,7 +22,7 @@ def execute(filters=None):
else:
share_type, no_of_shares, rate, amount = 1, 2, 3, 4
all_shares = get_all_shares(filters.get("shareholder"))
all_shares = get_all_shares(filters.get("shareholder"), filters.get("date"))
for share_entry in all_shares:
row = False
for datum in data:
@@ -63,5 +61,28 @@ def get_columns(filters):
return columns
def get_all_shares(shareholder):
return frappe.get_doc("Shareholder", shareholder).share_balance
def get_all_shares(shareholder, date):
"""Share movements for the shareholder up to (and including) `date`, signed by direction:
shares received are positive, shares transferred/sold out are negative."""
transfers = frappe.get_all(
"Share Transfer",
filters={"docstatus": 1, "date": ("<=", date)},
fields=["share_type", "no_of_shares", "rate", "amount", "from_shareholder", "to_shareholder"],
order_by="date",
)
shares = []
for transfer in transfers:
if transfer.to_shareholder == shareholder:
shares.append(transfer)
elif transfer.from_shareholder == shareholder:
shares.append(
frappe._dict(
share_type=transfer.share_type,
no_of_shares=-transfer.no_of_shares,
rate=transfer.rate,
amount=-transfer.amount,
)
)
return shares

View File

@@ -0,0 +1,177 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.report.share_balance.share_balance import execute
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
class TestShareBalanceReport(ERPNextTestSuite):
def setUp(self):
self.share_type = create_share_type("_Test Share Balance Equity")
self.shareholder = create_shareholder("_Test Share Balance Holder", COMPANY)
def test_date_filter_is_mandatory(self):
self.assertRaises(frappe.ValidationError, execute, frappe._dict({"shareholder": self.shareholder}))
def test_no_shareholder_returns_empty_data(self):
# `shareholder` is optional; without it the report yields no rows.
columns, data = execute(frappe._dict({"date": "2026-06-01", "company": COMPANY}))
self.assertEqual(data, [])
self.assertEqual(len(columns), 5)
def test_balance_after_issue(self):
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=1,
to_no=100,
no_of_shares=100,
rate=10,
date="2026-06-01",
)
row = self.get_row(date="2026-06-05")
self.assertEqual(row[0], self.shareholder)
self.assertEqual(row[1], self.share_type)
self.assertEqual(row[2], 100) # no_of_shares
self.assertEqual(row[3], 10) # average rate
self.assertEqual(row[4], 1000) # amount = 100 * 10
def test_balance_increases_on_second_issue(self):
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=1,
to_no=100,
no_of_shares=100,
rate=10,
date="2026-06-01",
)
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=101,
to_no=200,
no_of_shares=100,
rate=20,
date="2026-06-10",
)
# The report groups by share type, summing shares and amount and
# recomputing the average rate: (1000 + 2000) / 200 = 15.
row = self.get_row(date="2026-06-15")
self.assertEqual(row[2], 200)
self.assertEqual(row[3], 15)
self.assertEqual(row[4], 3000)
def test_balance_reduces_after_transfer_out(self):
other_holder = create_shareholder("_Test Share Balance Holder 2", COMPANY)
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=1,
to_no=100,
no_of_shares=100,
rate=10,
date="2026-06-01",
)
create_share_transfer(
transfer_type="Transfer",
from_shareholder=self.shareholder,
to_shareholder=other_holder,
share_type=self.share_type,
from_no=1,
to_no=40,
no_of_shares=40,
rate=10,
date="2026-06-10",
)
row = self.get_row(date="2026-06-15")
self.assertEqual(row[2], 60) # 100 issued - 40 transferred out
self.assertEqual(row[4], 600)
other_row = self.get_row(date="2026-06-15", shareholder=other_holder)
self.assertEqual(other_row[2], 40)
self.assertEqual(other_row[4], 400)
def test_as_on_date_before_issue_shows_no_holding(self):
# the report is as-on `date`: before any share transfer, the shareholder holds nothing
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=1,
to_no=100,
no_of_shares=100,
rate=10,
date="2026-06-01",
)
data = execute(
frappe._dict({"date": "2026-05-01", "company": COMPANY, "shareholder": self.shareholder})
)[1]
self.assertEqual(data, [])
def test_as_on_date_reflects_holding_up_to_that_date(self):
# two issues on different dates; an as-on date between them sees only the first
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=1,
to_no=100,
no_of_shares=100,
rate=10,
date="2026-06-01",
)
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
share_type=self.share_type,
from_no=101,
to_no=200,
no_of_shares=100,
rate=20,
date="2026-06-10",
)
self.assertEqual(self.get_row(date="2026-06-05")[2], 100) # only the first issue
self.assertEqual(self.get_row(date="2026-06-15")[2], 200) # both issues
def get_row(self, date, shareholder=None):
filters = frappe._dict(
{"date": date, "company": COMPANY, "shareholder": shareholder or self.shareholder}
)
data = execute(filters)[1]
holdings = [r for r in data if r[1] == self.share_type]
self.assertEqual(len(holdings), 1, f"Expected one row for share type, got: {data}")
return holdings[0]
def create_share_type(title):
if not frappe.db.exists("Share Type", title):
frappe.get_doc({"doctype": "Share Type", "title": title}).insert()
return title
def create_shareholder(title, company):
shareholder = frappe.get_doc({"doctype": "Shareholder", "title": title, "company": company}).insert()
return shareholder.name
def create_share_transfer(**kwargs):
kwargs.setdefault("company", COMPANY)
kwargs.setdefault("asset_account", "Cash - _TC")
kwargs.setdefault("equity_or_liability_account", "Creditors - _TC")
transfer = frappe.get_doc({"doctype": "Share Transfer", **kwargs})
transfer.submit()
return transfer

View File

@@ -0,0 +1,63 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.report.voucher_wise_balance.voucher_wise_balance import execute
from erpnext.tests.utils import ERPNextTestSuite
class TestVoucherWiseBalance(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"from_date": "2026-01-01",
"to_date": "2026-12-31",
}
)
filters.update(extra)
return execute(filters)[1]
def find_row(self, data, voucher_no):
for row in data:
if row.get("voucher_no") == voucher_no:
return row
return None
def test_balanced_voucher_not_flagged(self):
jv = make_journal_entry(
"Sales - _TC", "_Test Bank - _TC", 1000, submit=True, posting_date="2026-06-01"
)
data = self.run_report()
self.assertIsNone(
self.find_row(data, jv.name),
msg="A balanced voucher (debit == credit) must not be flagged.",
)
def test_imbalanced_voucher_flagged(self):
jv = make_journal_entry(
"Sales - _TC", "_Test Bank - _TC", 1000, submit=True, posting_date="2026-06-01"
)
# Tamper one GL Entry: drop the debit side so debit != credit for this voucher.
gle_name = frappe.db.get_value(
"GL Entry",
{"voucher_no": jv.name, "is_cancelled": 0, "debit": [">", 0]},
"name",
)
self.assertIsNotNone(gle_name, msg="Expected a debit GL Entry for the journal entry.")
frappe.db.set_value("GL Entry", gle_name, {"debit": 400, "debit_in_account_currency": 400})
data = self.run_report()
row = self.find_row(data, jv.name)
self.assertIsNotNone(row, msg="An imbalanced voucher must be flagged by the report.")
self.assertEqual(row.get("voucher_type"), "Journal Entry")
self.assertEqual(row.get("credit"), 1000)
self.assertEqual(row.get("debit"), 400)
self.assertNotEqual(
row.get("debit"), row.get("credit"), msg="Flagged rows must have debit != credit."
)

View File

@@ -0,0 +1,329 @@
{
"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

@@ -0,0 +1,222 @@
{
"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-06-14 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": "tool",
"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

@@ -0,0 +1,104 @@
{
"app": "erpnext",
"charts": [],
"content": "[]",
"creation": "2026-06-14 14:38:20.315394",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "accounting",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "Budgeting",
"link_type": "DocType",
"links": [],
"modified": "2026-07-02 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": "accounting",
"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

@@ -13,7 +13,7 @@
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "table",
"icon": "sheet",
"idx": 1,
"indicator_color": "",
"is_hidden": 0,
@@ -266,9 +266,10 @@
"type": "Link"
}
],
"modified": "2026-05-18 09:49:45.138296",
"modified": "2026-06-14 13:44:08.095321",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Financial Reports",
"number_cards": [],
"owner": "Administrator",
@@ -279,6 +280,417 @@
"roles": [],
"sequence_id": 5.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "accounting",
"indent": 1,
"keep_closed": 0,
"label": "Financial Reports",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Balance Sheet",
"link_to": "Balance Sheet",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Profit and Loss",
"link_to": "Profit and Loss Statement",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Cash Flow",
"link_to": "Cash Flow",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Trial Balance",
"link_to": "Trial Balance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Consolidated Report",
"link_to": "Consolidated Financial Statement",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Custom Financial Statement",
"link_to": "Custom Financial Statement",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Financial Report Template",
"link_to": "Financial Report Template",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "book-text",
"indent": 1,
"keep_closed": 0,
"label": "Ledgers",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "General Ledger",
"link_to": "General Ledger",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customer Ledger",
"link_to": "Customer Ledger Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Ledger",
"link_to": "Supplier Ledger Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"indent": 1,
"keep_closed": 1,
"label": "Registers",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Accounts Receivable",
"link_to": "Accounts Receivable",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Accounts Payable",
"link_to": "Accounts Payable",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "AR Summary",
"link_to": "Accounts Receivable Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "AP Summary",
"link_to": "Accounts Payable Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Register",
"link_to": "Sales Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Register",
"link_to": "Purchase Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item-wise sales Register",
"link_to": "Item-wise Sales Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item-wise Purchase Register",
"link_to": "Item-wise Purchase Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "dollar-sign",
"indent": 1,
"keep_closed": 1,
"label": "Profitability",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Gross Profit",
"link_to": "Gross Profit",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Profitability Analysis",
"link_to": "Profitability Analysis",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Invoice Trends",
"link_to": "Sales Invoice Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Invoice Trends",
"link_to": "Purchase Invoice Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "scroll-text",
"indent": 1,
"keep_closed": 1,
"label": "Other Reports",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Trial Balance for Party",
"link_to": "Trial Balance for Party",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Period Based On Invoice Date",
"link_to": "Payment Period Based On Invoice Date",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Partners Commission",
"link_to": "Sales Partners Commission",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customer Credit Balance",
"link_to": "Customer Credit Balance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Payment Summary",
"link_to": "Sales Payment Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Address And Contacts",
"link_to": "Address And Contacts",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "UAE VAT 201",
"link_to": "UAE VAT 201",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Financial Reports",
"type": "Workspace"
}

View File

@@ -587,9 +587,10 @@
"type": "Link"
}
],
"modified": "2026-01-23 11:05:47.246213",
"modified": "2026-06-14 13:44:08.471142",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Invoicing",
"number_cards": [
{
@@ -617,6 +618,354 @@
"roles": [],
"sequence_id": 2.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Invoicing",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Accounts",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "list-tree",
"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": 0,
"collapsible": 1,
"icon": "arrow-left-to-line",
"indent": 1,
"keep_closed": 0,
"label": "Receivables",
"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": "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": "Sales Invoice",
"link_to": "Sales Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Credit Note",
"link_to": "Sales Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"route_options": "{\"is_return\": 1}",
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Accounts Receivable",
"link_to": "Accounts Receivable",
"link_type": "Report",
"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": "Payables",
"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": "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": "Purchase Invoice",
"link_to": "Purchase Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Debit Note",
"link_to": "Purchase Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"route_options": "{\"is_return\": 1}",
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Accounts Payable",
"link_to": "Accounts Payable",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "money-coins-1",
"indent": 1,
"keep_closed": 0,
"label": "Payments",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Entry",
"link_to": "Payment Entry",
"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",
"link_to": "Journal Entry",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Request",
"link_to": "Payment Request",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Order",
"link_to": "Payment Order",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Reconciliation",
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"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,
"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": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Repost Accounting Ledger",
"link_to": "Repost Accounting Ledger",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Repost Payment Ledger",
"link_to": "Repost Payment Ledger",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "sheet",
"indent": 1,
"keep_closed": 0,
"label": "Reports",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "General Ledger",
"link_to": "General Ledger",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Trial Balance",
"link_to": "Trial Balance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Financial Reports",
"link_to": "Financial Reports",
"link_type": "Workspace",
"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": "Accounts Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Invoicing",
"type": "Workspace"
}

View File

@@ -0,0 +1,240 @@
{
"app": "erpnext",
"charts": [],
"content": "[]",
"creation": "2026-06-11 11:51:21.886461",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "receipt-text",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "Payments",
"link_type": "DocType",
"links": [],
"modified": "2026-06-14 13:43:50.184761",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Payments",
"number_cards": [],
"owner": "Administrator",
"public": 1,
"quick_lists": [],
"roles": [],
"sequence_id": 47.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Payments",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "money-coins-1",
"indent": 1,
"keep_closed": 0,
"label": "Payments",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Entry",
"link_to": "Payment Entry",
"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",
"link_to": "Journal Entry",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Request",
"link_to": "Payment Request",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Order",
"link_to": "Payment Order",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Payment Reconciliation",
"link_to": "Payment Reconciliation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"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,
"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": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Repost Accounting Ledger",
"link_to": "Repost Accounting Ledger",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Repost Payment Ledger",
"link_to": "Repost Payment Ledger",
"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_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Accounts Receivable",
"link_to": "Accounts Receivable",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Accounts Payable",
"link_to": "Accounts Payable",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "General Ledger",
"link_to": "General Ledger",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Trial Balance",
"link_to": "Trial Balance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Financial Reports",
"link_to": "Financial Reports",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Payments",
"type": "Workspace"
}

View File

@@ -0,0 +1,86 @@
{
"app": "erpnext",
"charts": [],
"content": "[]",
"creation": "2026-06-11 11:51:22.831729",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "money-coins-1",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "Share Management",
"link_type": "DocType",
"links": [],
"modified": "2026-06-14 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": "customer",
"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

@@ -0,0 +1,121 @@
{
"app": "erpnext",
"charts": [],
"content": "[]",
"creation": "2026-06-14 14:08:36.817393",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "accounting",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "Subscriptions",
"link_type": "DocType",
"links": [],
"modified": "2026-06-14 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

@@ -0,0 +1,188 @@
{
"app": "erpnext",
"charts": [],
"content": "[]",
"creation": "2026-06-11 11:51:22.649582",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "money-coins-1",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "Taxes",
"link_type": "DocType",
"links": [],
"modified": "2026-06-14 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": "stock",
"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

@@ -199,9 +199,10 @@
"type": "Link"
}
],
"modified": "2025-12-31 16:22:38.132729",
"modified": "2026-06-14 13:44:08.417956",
"modified_by": "Administrator",
"module": "Assets",
"module_onboarding": "Asset Onboarding",
"name": "Assets",
"number_cards": [],
"owner": "Administrator",
@@ -212,6 +213,294 @@
"roles": [],
"sequence_id": 7.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Assets",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Asset",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "laptop",
"indent": 0,
"keep_closed": 0,
"label": "Asset",
"link_to": "Asset",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "trending-down",
"indent": 0,
"keep_closed": 0,
"label": "Depreciation Schedule",
"link_to": "Asset Depreciation Schedule",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "sprout",
"indent": 0,
"keep_closed": 0,
"label": "Asset Capitalization",
"link_to": "Asset Capitalization",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "move-horizontal",
"indent": 0,
"keep_closed": 0,
"label": "Asset Movement",
"link_to": "Asset Movement",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "getting-started",
"indent": 1,
"keep_closed": 1,
"label": "Maintenance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Maintenance Team",
"link_to": "Asset Maintenance Team",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Maintenance",
"link_to": "Asset Maintenance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Maintenance Log",
"link_to": "Asset Maintenance Log",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Value Adjustment",
"link_to": "Asset Value Adjustment",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Repair",
"link_to": "Asset Repair",
"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_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Fixed Asset Register",
"link_to": "Fixed Asset Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Depreciation Ledger",
"link_to": "Asset Depreciation Ledger",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Depreciations and Balances",
"link_to": "Asset Depreciations and Balances",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Maintenance",
"link_to": "Asset Maintenance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Asset Activity",
"link_to": "Asset Activity",
"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": "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": "Asset Category",
"link_to": "Asset Category",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Location",
"link_to": "Location",
"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": "Settings",
"link_to": "Accounts Settings",
"link_type": "DocType",
"navigate_to_tab": "assets_tab",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link",
"url": ""
}
],
"standard": 1,
"title": "Assets",
"type": "Workspace"
}

View File

@@ -341,17 +341,6 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Item Wise Consumption",
"link_count": 0,
"link_to": "Item Wise Consumption",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -512,9 +501,10 @@
"type": "Link"
}
],
"modified": "2026-01-02 14:55:59.078773",
"modified": "2026-06-14 13:43:50.509039",
"modified_by": "Administrator",
"module": "Buying",
"module_onboarding": "Buying Onboarding",
"name": "Buying",
"number_cards": [
{
@@ -538,6 +528,403 @@
"roles": [],
"sequence_id": 5.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Buying",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Buying",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "notepad-text",
"indent": 0,
"keep_closed": 0,
"label": "Material Request",
"link_to": "Material Request",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "git-pull-request-arrow",
"indent": 0,
"keep_closed": 0,
"label": "Request for Quotation",
"link_to": "Request for Quotation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "book-open-text",
"indent": 0,
"keep_closed": 0,
"label": "Supplier Quotation",
"link_to": "Supplier Quotation",
"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": "Purchase Order",
"link_to": "Purchase Order",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "liabilities",
"indent": 0,
"keep_closed": 0,
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"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": "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": "Supplier Group",
"link_to": "Supplier Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"icon": "",
"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": "Price List",
"link_to": "Price List",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Address",
"link_to": "Address",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Contacts",
"link_to": "Contact",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Scorecard",
"link_to": "Supplier Scorecard",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Scorecard Criteria",
"link_to": "Supplier Scorecard Criteria",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Scorecard Variable",
"link_to": "Supplier Scorecard Variable",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Scorecard Standing",
"link_to": "Supplier Scorecard Standing",
"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_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Analytics",
"link_to": "Purchase Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Order Analysis",
"link_to": "Purchase Order Analysis",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Requested Items to Order and Receive",
"link_to": "Requested Items to Order and Receive",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Items To Be Requested",
"link_to": "Items To Be Requested",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item-wise Purchase History",
"link_to": "Item-wise Purchase History",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Receipt Trends ",
"link_to": "Purchase Receipt Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Invoice Trends",
"link_to": "Purchase Invoice Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Purchase Order Trends",
"link_to": "Purchase Order Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Procurement Tracker",
"link_to": "Procurement Tracker",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item Wise Consumption",
"link_to": "Item Wise Consumption",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Quotation Comparison",
"link_to": "Supplier Quotation Comparison",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Supplier Addresses And Contacts",
"link_to": "Address And Contacts",
"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",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Buying",
"type": "Workspace"
}

View File

@@ -167,7 +167,8 @@ status_map = {
"Pick List": [
["Draft", None],
["Open", "eval:self.docstatus == 1"],
["Completed", "stock_entry_exists"],
["Completed", "is_fully_transferred"],
["Partially Transferred", "is_partially_transferred"],
[
"Partly Delivered",
"eval:self.purpose == 'Delivery' and self.delivery_status == 'Partly Delivered'",

View File

@@ -2,11 +2,11 @@
"app": "erpnext",
"charts": [
{
"chart_name": "Won Opportunities",
"label": "Won Opportunities"
"chart_name": "Territory Wise Sales",
"label": "Territory Wise Sales"
}
],
"content": "[{\"id\":\"4jhDsfZ7EP\",\"type\":\"header\",\"data\":{\"text\":\"This module is scheduled for deprecation and will be completely removed in version 17, please use <a href=\\\"https://frappe.io/crm\\\">Frappe CRM</a> instead.\",\"col\":12}},{\"id\":\"-bzBQ_IbL9\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Won Opportunities\",\"col\":12}},{\"id\":\"LdM1QgUnqU\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"New Lead (Last 1 Month)\",\"col\":4}},{\"id\":\"X23-SXBcYG\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"New Opportunity (Last 1 Month)\",\"col\":4}},{\"id\":\"3rm7fH52M-\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Won Opportunity (Last 1 Month)\",\"col\":4}},{\"id\":\"K6a2Kh5Zav\",\"type\":\"spacer\",\"data\":{\"col\":12}}]",
"content": "[{\"id\":\"4jhDsfZ7EP\",\"type\":\"header\",\"data\":{\"text\":\"This module is scheduled for deprecation and will be completely removed in version 17, please use <a href=\\\"http://frappe.io/crm\\\">Frappe CRM</a> instead.\",\"col\":12}},{\"id\":\"Cj2TyhgiWy\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Territory Wise Sales\",\"col\":12}},{\"id\":\"LAKRmpYMRA\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"69RN0XsiJK\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead\",\"col\":3}},{\"id\":\"t6PQ0vY-Iw\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Opportunity\",\"col\":3}},{\"id\":\"VOFE0hqXRD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"id\":\"0ik53fuemG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"id\":\"wdROEmB_XG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"-I9HhcgUKE\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"ttpROKW9vk\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"-76QPdbBHy\",\"type\":\"card\",\"data\":{\"card_name\":\"Sales Pipeline\",\"col\":4}},{\"id\":\"_YmGwzVWRr\",\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"id\":\"Bma1PxoXk3\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"80viA0R83a\",\"type\":\"card\",\"data\":{\"card_name\":\"Campaign\",\"col\":4}},{\"id\":\"Buo5HtKRFN\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"sLS_x4FMK2\",\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}}]",
"creation": "2020-01-23 14:48:30.183272",
"custom_blocks": [],
"docstatus": 0,
@@ -18,6 +18,14 @@
"is_hidden": 0,
"label": "CRM",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Lead",
"hidden": 0,
@@ -115,6 +123,14 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Maintenance",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
@@ -148,6 +164,183 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Pipeline",
"link_count": 7,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Lead",
"link_count": 0,
"link_to": "Lead",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Opportunity",
"link_count": 0,
"link_to": "Opportunity",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contract",
"link_count": 0,
"link_to": "Contract",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Appointment",
"link_count": 0,
"link_to": "Appointment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 0,
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Communication",
"link_count": 0,
"link_to": "Communication",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 2,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "CRM Settings",
"link_count": 0,
"link_to": "CRM Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "SMS Settings",
"link_count": 0,
"link_to": "SMS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Campaign",
"link_count": 5,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Campaign",
"link_count": 0,
"link_to": "Campaign",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Campaign",
"link_count": 0,
"link_to": "Email Campaign",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "SMS Center",
"link_count": 0,
"link_to": "SMS Center",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "SMS Log",
"link_count": 0,
"link_to": "SMS Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Group",
"link_count": 0,
"link_to": "Email Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -228,24 +421,11 @@
"type": "Link"
}
],
"modified": "2026-01-03 15:05:23.983099",
"modified": "2026-06-14 13:44:08.297053",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
"number_cards": [
{
"label": "New Lead (Last 1 Month)",
"number_card_name": "New Lead (Last 1 Month)"
},
{
"label": "New Opportunity (Last 1 Month)",
"number_card_name": "New Opportunity (Last 1 Month)"
},
{
"label": "Won Opportunity (Last 1 Month)",
"number_card_name": "Won Opportunity (Last 1 Month)"
}
],
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
@@ -253,7 +433,552 @@
"restrict_to_domain": "",
"roles": [],
"sequence_id": 17.0,
"shortcuts": [],
"shortcuts": [
{
"color": "Blue",
"format": "{} Open",
"label": "Lead",
"link_to": "Lead",
"stats_filter": "{\"status\":\"Open\"}",
"type": "DocType"
},
{
"color": "Blue",
"format": "{} Assigned",
"label": "Opportunity",
"link_to": "Opportunity",
"stats_filter": "{\"_assign\": [\"like\", '%' + frappe.session.user + '%']}",
"type": "DocType"
},
{
"label": "Customer",
"link_to": "Customer",
"type": "DocType"
},
{
"label": "Sales Analytics",
"link_to": "Sales Analytics",
"report_ref_doctype": "Sales Order",
"type": "Report"
},
{
"label": "Dashboard",
"link_to": "CRM",
"type": "Dashboard"
}
],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "CRM",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "users-round",
"indent": 0,
"keep_closed": 0,
"label": "Lead",
"link_to": "Lead",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "lightbulb",
"indent": 0,
"keep_closed": 0,
"label": "Opportunity",
"link_to": "Opportunity",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "customer",
"indent": 0,
"keep_closed": 0,
"label": "Customer",
"link_to": "Customer",
"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_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 Analytics",
"link_to": "Sales Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Lead Details",
"link_to": "Lead Details",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Pipeline Analytics",
"link_to": "Sales Pipeline Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Opportunity Summary by Sales Stage",
"link_to": "Opportunity Summary by Sales Stage",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Funnel",
"link_to": "sales-funnel",
"link_type": "Page",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Prospects Engaged But Not Converted",
"link_to": "Prospects Engaged But Not Converted",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "First Response Time for Opportunity",
"link_to": "First Response Time for Opportunity",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Campaign Efficiency",
"link_to": "Campaign Efficiency",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Lead Owner Efficiency",
"link_to": "Lead Owner Efficiency",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "getting-started",
"indent": 1,
"keep_closed": 1,
"label": "Maintenance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Maintenance Schedule",
"link_to": "Maintenance Schedule",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Maintenance Visit",
"link_to": "Maintenance Visit",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Warranty Claim",
"link_to": "Warranty Claim",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "funnel",
"indent": 1,
"keep_closed": 1,
"label": "Sales Pipeline",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Lead",
"link_to": "Lead",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Opportunity",
"link_to": "Opportunity",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"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": "Contract",
"link_to": "Contract",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Appointment",
"link_to": "Appointment",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Communication",
"link_to": "Communication",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "sell",
"indent": 1,
"keep_closed": 1,
"label": "Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Campaign",
"link_to": "Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Email Campaign",
"link_to": "Email Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "SMS Center",
"link_to": "SMS Center",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "SMS Log",
"link_to": "SMS Log",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Email Group",
"link_to": "Email Group",
"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": "Territory",
"link_to": "Territory",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customer Group",
"link_to": "Customer Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Contact",
"link_to": "Contact",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Prospect",
"link_to": "Prospect",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Person",
"link_to": "Sales Person",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Stage",
"link_to": "Sales Stage",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Lead Source",
"link_to": "UTM Source",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "settings",
"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,
"indent": 0,
"keep_closed": 0,
"label": "CRM Settings",
"link_to": "CRM Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "SMS Settings",
"link_to": "SMS Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "CRM",
"type": "Workspace"
}

View File

@@ -8,7 +8,7 @@ app_email = "hello@frappe.io"
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
app_home = "/desk"
app_home = "/desk/home"
add_to_apps_screen = [
{

View File

@@ -739,7 +739,7 @@ class BOM(WebsiteGenerator):
)
)
def check_recursion(self, bom_list=None):
def check_recursion(self):
"""Check whether recursion occurs in any bom"""
bom_list = self.traverse_tree()
child_items = frappe.get_all(
@@ -861,21 +861,30 @@ class BOM(WebsiteGenerator):
self.append("items", row)
def traverse_tree(self, bom_list=None):
count = 0
if not bom_list:
bom_list = []
def traverse_tree(self):
"""Return this BOM and every descendant BOM. The whole sub-tree is fetched in one recursive
CTE (frappe.qb) instead of a query-per-node walk; the only caller (check_recursion) uses the
result purely as a membership set. Portable across postgres and mariadb 10.2+."""
bom_item = frappe.qb.DocType("BOM Item")
tree = frappe.qb.Table("bom_tree")
if self.name not in bom_list:
bom_list.append(self.name)
seed = (
frappe.qb.from_(bom_item)
.select(bom_item.bom_no.as_("bom"))
.where((bom_item.parent == self.name) & (bom_item.bom_no != "") & (bom_item.parenttype == "BOM"))
)
recursion = (
frappe.qb.from_(bom_item)
.join(tree)
.on(bom_item.parent == tree.bom)
.select(bom_item.bom_no)
.where((bom_item.bom_no != "") & (bom_item.parenttype == "BOM"))
)
descendants = (
frappe.qb.with_(seed + recursion, "bom_tree", recursive=True).from_(tree).select(tree.bom)
).run(pluck=True)
while count < len(bom_list):
for child_bom in _get_bom_children(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
count += 1
bom_list.reverse()
return bom_list
return [self.name, *descendants]
def company_currency(self):
return erpnext.get_company_currency(self.company)

View File

@@ -67,29 +67,33 @@ def update_cost_in_level(doc: "BOMUpdateLog", bom_list: list[str], batch_name: i
frappe.db.commit() # nosemgrep
def get_ancestor_boms(new_bom: str, bom_list: list | None = None) -> list:
"Recursively get all ancestors of BOM."
bom_list = bom_list or []
def get_ancestor_boms(new_bom: str) -> list:
"""Return every ancestor BOM of `new_bom` (BOMs that consume it, transitively) in one recursive
CTE built with frappe.qb -- portable across postgres and mariadb 10.2+. `UNION` makes it
cycle-safe (it stops once no new BOM is reached); a BOM that is its own ancestor is rejected."""
bom_item = frappe.qb.DocType("BOM Item")
tree = frappe.qb.Table("ancestor_boms")
parents = (
seed = (
frappe.qb.from_(bom_item)
.select(bom_item.parent)
.select(bom_item.parent.as_("bom"))
.where((bom_item.bom_no == new_bom) & (bom_item.docstatus < 2) & (bom_item.parenttype == "BOM"))
.run(as_dict=True)
)
recursion = (
frappe.qb.from_(bom_item)
.join(tree)
.on(bom_item.bom_no == tree.bom)
.select(bom_item.parent)
.where((bom_item.docstatus < 2) & (bom_item.parenttype == "BOM"))
)
ancestors = (
frappe.qb.with_(seed + recursion, "ancestor_boms", recursive=True).from_(tree).select(tree.bom)
).run(pluck=True)
for d in parents:
if new_bom == d.parent:
frappe.throw(_("BOM recursion: {0} cannot be child of {1}").format(new_bom, d.parent))
if new_bom in ancestors:
frappe.throw(_("BOM recursion: {0} cannot be an ancestor of itself").format(new_bom))
if d.parent not in tuple(bom_list):
bom_list.append(d.parent)
get_ancestor_boms(d.parent, bom_list)
return bom_list
return ancestors
def update_new_bom_in_bom_items(unit_cost: float, current_bom: str, new_bom: str) -> None:

View File

@@ -149,6 +149,16 @@ class RequiredItemsService:
self.recompute_material_transferred_for_manufacturing(transferred_items)
def refresh_material_transferred_for_manufacturing(self):
"""Recompute material_transferred_for_manufacturing only, without touching per-row
transferred_qty or stock reservations. Used to get a status decision (Not Started vs
In Process) based on fresh data, ahead of the fuller update_required_items() pass.
"""
if self.doc.skip_transfer:
return
transferred_items = self._material_transfer_qty_by_item(is_return=0)
self.recompute_material_transferred_for_manufacturing(transferred_items)
def recompute_material_transferred_for_manufacturing(self, transferred_items):
"""Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty."""
# When fg_completed_qty > 0 (direct stock entries, excess transfer), preserve the

View File

@@ -87,6 +87,12 @@ class StatusService:
def update_status(self, status=None):
"""Update status of work order if unknown"""
if self.doc.docstatus == 1:
# Refresh material_transferred_for_manufacturing before deciding status so pick-list-
# driven transfers (where this qty is derived from item transfers, not fg_completed_qty)
# are reflected immediately, instead of only after the next status update call.
self.doc.refresh_material_transferred_for_manufacturing()
if self.doc.status != "Closed":
if status not in ["Stopped", "Closed"]:
status = self.get_status(status)

View File

@@ -1003,6 +1003,9 @@ class WorkOrder(Document):
def update_transferred_qty_for_required_items(self):
return RequiredItemsService(self).update_transferred_qty_for_required_items()
def refresh_material_transferred_for_manufacturing(self):
return RequiredItemsService(self).refresh_material_transferred_for_manufacturing()
def update_returned_qty(self):
return RequiredItemsService(self).update_returned_qty()

View File

@@ -2,6 +2,8 @@
# For license information, please see license.txt
from collections import defaultdict
import frappe
from frappe import _
@@ -14,29 +16,47 @@ def execute(filters=None):
def get_data(filters, data):
get_exploded_items(filters.bom, data)
children_map = fetch_exploded_bom_items(filters.bom)
build_exploded_rows(filters.bom, children_map, data)
def get_exploded_items(bom, data, indent=0, qty=1):
exploded_items = frappe.get_all(
"BOM Item",
filters={"parent": bom},
fields=[
"qty",
"bom_no",
"qty",
"item_code",
"item_name",
"description",
"uom",
"idx",
"is_phantom_item",
],
order_by="idx ASC",
def fetch_exploded_bom_items(root_bom):
"""Every BOM Item in the exploded tree of `root_bom`, grouped by its parent BOM, in one
recursive CTE -- replaces a query-per-node walk with a single query. UNION keeps it cycle-safe
and fetches each sub-BOM's items only once even when it is reused across the tree."""
bom_item = frappe.qb.DocType("BOM Item")
tree = frappe.qb.Table("exploded_bom")
fields = [
bom_item.parent,
bom_item.qty,
bom_item.bom_no,
bom_item.item_code,
bom_item.item_name,
bom_item.description,
bom_item.uom,
bom_item.idx,
bom_item.is_phantom_item,
]
seed = frappe.qb.from_(bom_item).select(*fields).where(bom_item.parent == root_bom)
recursion = (
frappe.qb.from_(bom_item)
.join(tree)
.on(bom_item.parent == tree.bom_no)
.select(*fields)
.where(tree.bom_no != "")
)
rows = (
frappe.qb.with_(seed + recursion, "exploded_bom", recursive=True).from_(tree).select(tree.star)
).run(as_dict=True)
for item in exploded_items:
item["indent"] = indent
children_map = defaultdict(list)
for row in rows:
children_map[row.parent].append(row)
return children_map
def build_exploded_rows(bom, children_map, data, indent=0, qty=1):
for item in sorted(children_map.get(bom, []), key=lambda row: row.idx):
data.append(
{
"item_code": item.item_code,
@@ -51,7 +71,7 @@ def get_exploded_items(bom, data, indent=0, qty=1):
}
)
if item.bom_no:
get_exploded_items(item.bom_no, data, indent=indent + 1, qty=item.qty)
build_exploded_rows(item.bom_no, children_map, data, indent + 1, item.qty)
def get_columns():

View File

@@ -0,0 +1,101 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.report.bom_operations_time.bom_operations_time import execute
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestSuite
OPERATION = "_Test BOM Ops Time Operation"
WORKSTATION = "_Test BOM Ops Time Workstation"
TIME_IN_MINS = 45
class TestBOMOperationsTime(ERPNextTestSuite):
def setUp(self):
ensure_workstation_and_operation()
self.rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name
self.fg_item = make_item(properties={"is_stock_item": 1}).name
self.bom = build_bom_with_operation(self.fg_item, self.rm_item)
def run_report(self, **extra):
filters = frappe._dict({"bom_id": [self.bom.name]})
filters.update(extra)
return execute(filters)[1]
def test_operation_row_appears_with_expected_values(self):
rows = self.run_report()
bom_rows = [row for row in rows if row.name == self.bom.name]
self.assertEqual(len(bom_rows), 1)
row = bom_rows[0]
self.assertEqual(row.item, self.fg_item)
self.assertEqual(row.operation, OPERATION)
self.assertEqual(row.workstation, WORKSTATION)
self.assertEqual(row.time_in_mins, TIME_IN_MINS)
def test_item_code_filter_scopes_to_bom(self):
rows = self.run_report(item_code=self.fg_item)
self.assertTrue(rows)
self.assertTrue(all(row.item == self.fg_item for row in rows))
self.assertIn(self.bom.name, {row.name for row in rows})
def test_workstation_filter(self):
matching = self.run_report(workstation=WORKSTATION)
self.assertIn(self.bom.name, {row.name for row in matching})
other_workstation = ensure_other_workstation()
non_matching = self.run_report(workstation=other_workstation)
self.assertNotIn(self.bom.name, {row.name for row in non_matching})
def test_draft_bom_excluded(self):
draft_bom = build_bom_with_operation(
make_item(properties={"is_stock_item": 1}).name, self.rm_item, do_not_submit=True
)
rows = execute(frappe._dict({"bom_id": [draft_bom.name]}))[1]
self.assertEqual(rows, [])
def ensure_workstation_and_operation():
if not frappe.db.exists("Workstation", WORKSTATION):
frappe.get_doc({"doctype": "Workstation", "workstation_name": WORKSTATION}).insert(
ignore_permissions=True
)
if not frappe.db.exists("Operation", OPERATION):
frappe.get_doc({"doctype": "Operation", "name": OPERATION, "workstation": WORKSTATION}).insert(
ignore_permissions=True
)
def ensure_other_workstation():
name = "_Test BOM Ops Time Workstation 2"
if not frappe.db.exists("Workstation", name):
frappe.get_doc({"doctype": "Workstation", "workstation_name": name}).insert(ignore_permissions=True)
return name
def build_bom_with_operation(fg_item, rm_item, do_not_submit=False):
bom = make_bom(
item=fg_item,
raw_materials=[rm_item],
with_operations=1,
do_not_save=True,
)
bom.append(
"operations",
{
"operation": OPERATION,
"workstation": WORKSTATION,
"time_in_mins": TIME_IN_MINS,
"hour_rate": 100,
},
)
bom.insert(ignore_permissions=True)
if not do_not_submit:
bom.submit()
return bom

View File

@@ -0,0 +1,92 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import add_days, get_datetime, today
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.manufacturing.report.downtime_analysis.downtime_analysis import execute
from erpnext.setup.doctype.employee.test_employee import make_employee
from erpnext.tests.utils import ERPNextTestSuite
class TestDowntimeAnalysis(ERPNextTestSuite):
def setUp(self):
self.workstation = make_workstation(workstation="_Test Downtime Workstation").name
self.other_workstation = make_workstation(workstation="_Test Downtime Workstation 2").name
self.operator = make_employee("test_downtime_operator@example.com", company="_Test Company")
# from_time / to_time are two hours apart -> downtime of 120 minutes (2 hours).
self.from_time = get_datetime(f"{today()} 09:00:00")
self.to_time = get_datetime(f"{today()} 11:00:00")
self.entry = self.make_downtime_entry(self.workstation)
def make_downtime_entry(self, workstation, **extra):
values = {
"doctype": "Downtime Entry",
"workstation": workstation,
"operator": self.operator,
"from_time": self.from_time,
"to_time": self.to_time,
"stop_reason": "Machine malfunction",
}
values.update(extra)
return frappe.get_doc(values).insert()
def run_report(self, **extra):
filters = frappe._dict(
{
"from_date": add_days(today(), -1),
"to_date": add_days(today(), 1),
}
)
filters.update(extra)
return execute(filters)[1]
def row_for_entry(self, rows, name):
return next((row for row in rows if row.get("name") == name), None)
def test_downtime_is_computed_in_hours(self):
# validate() stores downtime in minutes; the report converts it to hours.
self.assertEqual(self.entry.downtime, 120)
row = self.row_for_entry(self.run_report(), self.entry.name)
self.assertIsNotNone(row, "Downtime Entry not present in report output")
self.assertEqual(row.get("workstation"), self.workstation)
self.assertEqual(row.get("operator"), self.operator)
self.assertEqual(row.get("stop_reason"), "Machine malfunction")
self.assertEqual(row.get("downtime"), 2.0)
def test_workstation_filter_scopes_rows(self):
other = self.make_downtime_entry(self.other_workstation)
rows = self.run_report(workstation=self.workstation)
names = {row.get("name") for row in rows}
self.assertIn(self.entry.name, names)
self.assertNotIn(other.name, names)
self.assertTrue(all(row.get("workstation") == self.workstation for row in rows))
def test_date_range_excludes_out_of_window_entries(self):
# The report filters from_time >= from_date and to_time <= to_date; a window
# ending before the entry's from_time must exclude it.
rows = self.run_report(from_date=add_days(today(), -10), to_date=add_days(today(), -5))
self.assertIsNone(self.row_for_entry(rows, self.entry.name))
def test_chart_aggregates_downtime_per_workstation(self):
self.make_downtime_entry(self.workstation)
chart = execute(
frappe._dict(
{
"from_date": add_days(today(), -1),
"to_date": add_days(today(), 1),
"workstation": self.workstation,
}
)
)[3]
self.assertIn(self.workstation, chart["data"]["labels"])
index = chart["data"]["labels"].index(self.workstation)
# Two entries of 2 hours each for this workstation -> 4 hours aggregated.
self.assertEqual(chart["data"]["datasets"][0]["values"][index], 4.0)

View File

@@ -0,0 +1,118 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import nowdate
from erpnext.manufacturing.doctype.work_order.mapper import make_stock_entry
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.report.process_loss_report.process_loss_report import execute
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.tests.utils import ERPNextTestSuite
class TestProcessLossReport(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict(
{
"company": "_Test Company",
"from_date": nowdate(),
"to_date": nowdate(),
}
)
filters.update(extra)
return execute(filters)[1]
def find_row(self, data, work_order):
for row in data:
if row.get("name") == work_order:
return row
return None
def make_manufactured_work_order(self, planned_qty, produced_qty):
"""Create a submitted WO and manufacture `produced_qty` of `planned_qty`.
The difference is booked as process loss on the Manufacture stock entry,
which propagates to the work order's `process_loss_qty`.
"""
wo_order = make_wo_order_test_record(production_item="_Test FG Item", qty=planned_qty)
test_stock_entry.make_stock_entry(
item_code="_Test Item", target="Stores - _TC", qty=100, basic_rate=100
)
test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=100, basic_rate=100
)
transfer = frappe.get_doc(
make_stock_entry(wo_order.name, "Material Transfer for Manufacture", planned_qty)
)
for d in transfer.get("items"):
d.s_warehouse = "Stores - _TC"
transfer.insert()
transfer.submit()
manufacture = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", planned_qty))
# Reduce the finished good qty below fg_completed_qty so the difference is
# recorded as process loss.
process_loss_qty = planned_qty - produced_qty
if process_loss_qty:
for d in manufacture.get("items"):
if d.is_finished_item:
d.qty = produced_qty
d.transfer_qty = produced_qty * (d.conversion_factor or 1)
manufacture.insert()
manufacture.submit()
wo_order.reload()
return wo_order
def test_work_order_with_process_loss_is_listed(self):
wo_order = self.make_manufactured_work_order(planned_qty=5, produced_qty=4)
self.assertEqual(wo_order.process_loss_qty, 1)
self.assertEqual(wo_order.produced_qty, 4)
data = self.run_report(work_order=wo_order.name)
row = self.find_row(data, wo_order.name)
self.assertIsNotNone(row, "Work order with process loss should appear in the report")
self.assertEqual(row.production_item, "_Test FG Item")
self.assertEqual(row.qty_to_manufacture, 5)
self.assertEqual(row.produced_qty, 4)
self.assertEqual(row.process_loss_qty, 1)
# total_pl_value = process_loss_qty * (total_fg_value / qty_to_manufacture)
expected_pl_value = row.process_loss_qty * (row.total_fg_value / row.qty_to_manufacture)
self.assertAlmostEqual(row.total_pl_value, expected_pl_value)
self.assertGreater(row.total_pl_value, 0)
def test_work_order_without_process_loss_is_not_listed(self):
wo_order = self.make_manufactured_work_order(planned_qty=5, produced_qty=5)
self.assertEqual(wo_order.process_loss_qty, 0)
self.assertEqual(wo_order.produced_qty, 5)
data = self.run_report(work_order=wo_order.name)
self.assertIsNone(
self.find_row(data, wo_order.name),
"Work order that produced the full planned qty should not appear (no loss)",
)
def test_item_and_work_order_filters_are_ineffective(self):
"""BUG: the `item` and `work_order` filters in process_loss_report.get_data
call `query.where(...)` without reassigning the result. frappe's query
builder is immutable, so `.where()` returns a new query and these extra
conditions are silently dropped. A non-matching item filter therefore fails
to exclude the row. This test documents the current (buggy) behaviour; if the
report is fixed to reassign the query, update the assertion below to
`assertIsNone`.
"""
wo_order = self.make_manufactured_work_order(planned_qty=5, produced_qty=4)
# A non-matching item filter should exclude the row, but currently does not.
data = self.run_report(item="_Test FG Item 2")
self.assertIsNotNone(
self.find_row(data, wo_order.name),
"Filter bug regressed/fixed: `item` filter now takes effect - update this test",
)

View File

@@ -432,9 +432,10 @@
"type": "Link"
}
],
"modified": "2026-05-05 11:00:26.131777",
"modified": "2026-06-14 13:44:07.420267",
"modified_by": "Administrator",
"module": "Manufacturing",
"module_onboarding": "Manufacturing Onboarding",
"name": "Manufacturing",
"number_cards": [
{
@@ -458,6 +459,465 @@
"roles": [],
"sequence_id": 8.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Manufacturing",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Manufacturing",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "list-tree",
"indent": 0,
"keep_closed": 0,
"label": "BOM",
"link_to": "BOM",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "factory",
"indent": 0,
"keep_closed": 0,
"label": "Work Order",
"link_to": "Work Order",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "person-standing",
"indent": 0,
"keep_closed": 0,
"label": "Job Card",
"link_to": "Job Card",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "stock",
"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": "getting-started",
"indent": 1,
"keep_closed": 1,
"label": "Material Planning",
"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": "Item Lead Time",
"link_to": "Item Lead Time",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Production Plan",
"link_to": "Production Plan",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Forecasting",
"link_to": "Exponential Smoothing Forecasting",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Master Production Schedule",
"link_to": "Master Production Schedule",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Forecast",
"link_to": "Sales Forecast",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Production Planning Report",
"link_to": "Production Planning Report",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "tool",
"indent": 1,
"keep_closed": 1,
"label": "Tools",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "BOM Creator",
"link_to": "BOM Creator",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "BOM Update Tool",
"link_to": "BOM Update Tool",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "BOM Comparison Tool",
"link_to": "bom-comparison-tool",
"link_type": "Page",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Downtime Entry",
"link_to": "Downtime Entry",
"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,
"indent": 0,
"keep_closed": 0,
"label": "Production Planning Report",
"link_to": "Production Planning Report",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Work Order Summary",
"link_to": "Work Order Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Quality Inspection Summary",
"link_to": "Quality Inspection Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Downtime Analysis",
"link_to": "Downtime Analysis",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Job Card Summary",
"link_to": "Job Card Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "BOM Search",
"link_to": "BOM Search",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Production Analytics",
"link_to": "Production Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "BOM Operations Time",
"link_to": "BOM Operations Time",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Work Order Consumed Materials",
"link_to": "Work Order Consumed Materials",
"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": "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": "Item",
"link_to": "Item",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Warehouse",
"link_to": "Warehouse",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Operation",
"link_to": "Operation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Workstation",
"link_to": "Workstation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Workstation Type",
"link_to": "Workstation Type",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Plant Floor",
"link_to": "Plant Floor",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Routing",
"link_to": "Routing",
"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": "Settings",
"link_to": "Manufacturing Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Manufacturing",
"type": "Workspace"
}

View File

@@ -492,3 +492,4 @@ erpnext.patches.v16_0.rename_subscription_billing_period_fields
erpnext.patches.v16_0.drop_redundant_serial_no_index_from_sabb
erpnext.patches.v16_0.set_default_close_opportunity_after_days
execute:frappe.db.set_single_value("Accounts Settings", "pcv_job_timeout", 3600)
erpnext.patches.v16_0.backfill_pick_list_transferred_qty

View File

@@ -0,0 +1,58 @@
import frappe
from frappe.query_builder.functions import Sum
from frappe.utils import flt
def execute():
StockEntry = frappe.qb.DocType("Stock Entry")
StockEntryDetail = frappe.qb.DocType("Stock Entry Detail")
pick_lists = (
frappe.qb.from_(StockEntry)
.select(StockEntry.pick_list)
.distinct()
.where((StockEntry.pick_list.isnotnull()) & (StockEntry.docstatus == 1))
).run(pluck=True)
if not pick_lists:
return
rows = (
frappe.qb.from_(StockEntryDetail)
.join(StockEntry)
.on(StockEntryDetail.parent == StockEntry.name)
.select(
StockEntry.pick_list,
StockEntryDetail.item_code,
StockEntryDetail.s_warehouse,
Sum(StockEntryDetail.transfer_qty).as_("qty"),
)
.where((StockEntry.pick_list.isin(pick_lists)) & (StockEntry.docstatus == 1))
.groupby(StockEntry.pick_list, StockEntryDetail.item_code, StockEntryDetail.s_warehouse)
).run(as_dict=True)
transferred = {(r.pick_list, r.item_code, r.s_warehouse): flt(r.qty) for r in rows}
items = frappe.get_all(
"Pick List Item",
filters={"parent": ("in", pick_lists), "picked_qty": (">", 0)},
fields=["name", "parent", "item_code", "warehouse", "picked_qty"],
order_by="idx",
)
updates = {}
for row in items:
key = (row.parent, row.item_code, row.warehouse)
available = transferred.get(key, 0)
if available <= 0:
continue
qty = min(flt(row.picked_qty), available)
transferred[key] = available - qty
updates[row.name] = {"transferred_qty": qty}
if not updates:
return
frappe.db.auto_commit_on_many_writes = True
frappe.db.bulk_update("Pick List Item", updates)
frappe.db.auto_commit_on_many_writes = False

View File

@@ -9,7 +9,7 @@ from frappe import _, throw
from frappe.desk.form.assign_to import clear, close_all_assignments
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Max, Min, Sum
from frappe.utils import add_days, add_to_date, cstr, date_diff, flt, get_link_to_form, getdate, today
from frappe.utils import add_days, add_to_date, date_diff, flt, get_link_to_form, getdate, today
from frappe.utils.data import format_date
from frappe.utils.nestedset import NestedSet
@@ -247,25 +247,32 @@ class Task(NestedSet):
def check_recursion(self):
if self.flags.ignore_recursion_check:
return
check_list = [["task", "parent"], ["parent", "task"]]
for d in check_list:
task_list, count = [self.name], 0
while len(task_list) > count:
tasks = frappe.get_all(
"Task Depends On",
filters={d[1]: cstr(task_list[count])},
fields=[d[0]],
as_list=True,
)
count = count + 1
for b in tasks:
if b[0] == self.name:
frappe.throw(_("Circular Reference Error"), CircularReferenceError)
if b[0]:
task_list.append(b[0])
# "Task Depends On" is a directed edge (parent depends on `task`); a cycle exists if this
# task is reachable from itself along either direction. One recursive CTE per direction
# fetches the whole reachable set in a single query -- UNION makes it cycle-safe at any
# depth, so unlike the old per-node BFS it needs no arbitrary depth cap.
for select_field, filter_field in (("task", "parent"), ("parent", "task")):
if self._reaches_self(select_field, filter_field):
frappe.throw(_("Circular Reference Error"), CircularReferenceError)
if count == 15:
break
def _reaches_self(self, select_field: str, filter_field: str) -> bool:
depends_on = frappe.qb.DocType("Task Depends On")
tree = frappe.qb.Table("dependency_tree")
seed = (
frappe.qb.from_(depends_on)
.select(depends_on[select_field].as_("node"))
.where(depends_on[filter_field] == self.name)
)
recursion = (
frappe.qb.from_(depends_on)
.join(tree)
.on(depends_on[filter_field] == tree.node)
.select(depends_on[select_field])
)
reachable = (
frappe.qb.with_(seed + recursion, "dependency_tree", recursive=True).from_(tree).select(tree.node)
).run(pluck=True)
return self.name in reachable
def reschedule_dependent_tasks(self):
end_date = self.exp_end_date or self.act_end_date

View File

@@ -18,6 +18,14 @@
"is_hidden": 0,
"label": "Projects",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Projects",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -37,6 +45,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Project",
"link_count": 0,
"link_to": "Project",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Task",
"link_count": 0,
"link_to": "Task",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -59,6 +89,17 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Project Template",
"link_count": 0,
"link_to": "Project Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -70,6 +111,28 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Project Type",
"link_count": 0,
"link_to": "Project Type",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Project",
"hidden": 0,
"is_query_report": 0,
"label": "Project Update",
"link_count": 0,
"link_to": "Project Update",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Project",
"hidden": 0,
@@ -89,6 +152,14 @@
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Time Tracking",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
@@ -100,6 +171,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Timesheet",
"link_count": 0,
"link_to": "Timesheet",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Activity Type",
"link_count": 0,
"link_to": "Activity Type",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -122,6 +215,17 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Activity Type",
"hidden": 0,
"is_query_report": 0,
"label": "Activity Cost",
"link_count": 0,
"link_to": "Activity Cost",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -130,6 +234,25 @@
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Timesheet",
"hidden": 0,
"is_query_report": 1,
"label": "Daily Timesheet Summary",
"link_count": 0,
"link_to": "Daily Timesheet Summary",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Timesheet",
"hidden": 0,
@@ -152,6 +275,17 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Project",
"hidden": 0,
"is_query_report": 1,
"label": "Project wise Stock Tracking",
"link_count": 0,
"link_to": "Project wise Stock Tracking",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Project",
"hidden": 0,
@@ -163,6 +297,28 @@
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Project",
"hidden": 0,
"is_query_report": 1,
"label": "Timesheet Billing Summary",
"link_count": 0,
"link_to": "Timesheet Billing Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Task",
"hidden": 0,
"is_query_report": 1,
"label": "Delayed Tasks Summary",
"link_count": 0,
"link_to": "Delayed Tasks Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Task",
"hidden": 0,
@@ -182,6 +338,24 @@
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 1,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Projects Settings",
"link_count": 0,
"link_to": "Projects Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -193,9 +367,10 @@
"type": "Link"
}
],
"modified": "2026-01-02 17:26:44.644507",
"modified": "2026-07-01 13:20:50.651608",
"modified_by": "Administrator",
"module": "Projects",
"module_onboarding": "Projects Onboarding",
"name": "Projects",
"number_cards": [
{
@@ -219,6 +394,250 @@
"roles": [],
"sequence_id": 11.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Projects",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Project",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "projects",
"indent": 0,
"keep_closed": 0,
"label": "Project",
"link_to": "Project",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "list-todo",
"indent": 0,
"keep_closed": 0,
"label": "Task",
"link_to": "Task",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "calendar-clock",
"indent": 0,
"keep_closed": 0,
"label": "Timesheet",
"link_to": "Timesheet",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"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,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Activity Type",
"link_to": "Activity Type",
"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": "Activity Cost",
"link_to": "Activity Cost",
"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": "Project Template",
"link_to": "Project 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": "Project Type",
"link_to": "Project Type",
"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": "Project Update",
"link_to": "Project Update",
"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,
"icon": "",
"indent": 0,
"keep_closed": 0,
"label": "Project Summary",
"link_to": "Project 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": "Daily Timesheet Summary",
"link_to": "Daily Timesheet 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": "Timesheet Billing Summary",
"link_to": "Timesheet Billing 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": "Project wise Stock Tracking",
"link_to": "Project wise Stock Tracking",
"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": "Delayed Tasks Summary",
"link_to": "Delayed Tasks Summary",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "settings",
"indent": 0,
"keep_closed": 0,
"label": "Settings",
"link_to": "Projects Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Projects",
"type": "Workspace"
}

View File

@@ -161,7 +161,7 @@
"type": "Link"
}
],
"modified": "2026-01-02 17:32:47.522875",
"modified": "2026-06-14 13:44:07.920643",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality",
@@ -174,6 +174,161 @@
"roles": [],
"sequence_id": 9.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Quality",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "inspection-panel",
"indent": 0,
"keep_closed": 0,
"label": "Quality Inspection",
"link_to": "Quality Inspection",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "goal",
"indent": 0,
"keep_closed": 0,
"label": "Quality Goal",
"link_to": "Quality Goal",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "review",
"indent": 0,
"keep_closed": 0,
"label": "Quality Review",
"link_to": "Quality Review",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "square-activity",
"indent": 0,
"keep_closed": 0,
"label": "Quality Action",
"link_to": "Quality Action",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "grid-2x2-check",
"indent": 0,
"keep_closed": 0,
"label": "Non Conformance",
"link_to": "Non Conformance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "thumbs-up",
"indent": 0,
"keep_closed": 0,
"label": "Quality Feedback",
"link_to": "Quality Feedback",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "users",
"indent": 0,
"keep_closed": 0,
"label": "Quality Meeting",
"link_to": "Quality Meeting",
"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": "Quality Procedure",
"link_to": "Quality Procedure",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Feedback Template",
"link_to": "Quality Feedback Template",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Quality Inspection Template",
"link_to": "Quality Inspection Template",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Quality",
"type": "Workspace"
}

View File

@@ -21,9 +21,6 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None):
)
target_doc.quotation_to = "Customer"
target_doc.run_method("set_missing_values")
target_doc.run_method("set_other_charges")
target_doc.run_method("calculate_taxes_and_totals")
price_list, currency = frappe.db.get_value(
"Customer", {"name": source_name}, ["default_price_list", "default_currency"]
@@ -33,6 +30,10 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None):
if currency:
target_doc.currency = currency
target_doc.run_method("set_missing_values")
target_doc.run_method("set_other_charges")
target_doc.run_method("calculate_taxes_and_totals")
return target_doc

View File

@@ -5,7 +5,7 @@
import json
import frappe
from frappe.utils import flt
from frappe.utils import flt, nowdate
from erpnext.accounts.party import get_due_date
from erpnext.exceptions import PartyDisabled, PartyFrozen
@@ -14,12 +14,53 @@ from erpnext.selling.doctype.customer.customer import (
get_customer_outstanding,
)
from erpnext.selling.doctype.customer.mapper import (
make_quotation,
parse_full_name,
)
from erpnext.tests.utils import ERPNextTestSuite
class TestCustomer(ERPNextTestSuite):
def test_quotation_from_customer_uses_actual_exchange_rate(self):
company = "_Test Company"
company_currency = frappe.get_cached_value("Company", company, "default_currency")
foreign_currency = "USD" if company_currency != "USD" else "EUR"
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)
customer = frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "_Test Customer FX Quotation",
"customer_type": "Company",
"default_currency": foreign_currency,
}
).insert()
self.addCleanup(frappe.delete_doc, "Customer", customer.name, force=1)
quotation = make_quotation(customer.name)
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)
def test_get_customer_name_dedupes_with_numeric_suffix(self):
# When a customer name already exists, get_customer_name appends "- <max suffix + 1>". The
# Postgres branch extracts the suffix with regexp_replace/NULLIF/CAST (pypika's Substring cannot

View File

@@ -228,7 +228,7 @@ def _make_customer(source_name, ignore_permissions=False):
def create_customer_from_lead(lead_name, ignore_permissions=False):
from erpnext.crm.doctype.lead.lead import _make_customer
from erpnext.crm.doctype.lead.mapper import _make_customer
customer = _make_customer(lead_name, ignore_permissions=ignore_permissions)
customer.flags.ignore_permissions = ignore_permissions

View File

@@ -622,9 +622,10 @@
"type": "Link"
}
],
"modified": "2026-02-19 13:01:26.893303",
"modified": "2026-06-14 13:44:07.820564",
"modified_by": "Administrator",
"module": "Selling",
"module_onboarding": "Selling Onboarding",
"name": "Selling",
"number_cards": [
{
@@ -648,6 +649,762 @@
"roles": [],
"sequence_id": 6.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Selling",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Selling",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "receipt-text",
"indent": 0,
"keep_closed": 0,
"label": "Quotation",
"link_to": "Quotation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "sell",
"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": 0,
"collapsible": 1,
"icon": "receipt",
"indent": 0,
"keep_closed": 0,
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "computer",
"indent": 1,
"keep_closed": 1,
"label": "POS",
"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": "POS",
"link_to": "point-of-sale",
"link_type": "Page",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "POS Profile",
"link_to": "POS Profile",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "POS Invoice",
"link_to": "POS Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "POS Opening Entry",
"link_to": "POS Opening Entry",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "POS Closing Entry",
"link_to": "POS Closing Entry",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "POS Invoice Merge Log",
"link_to": "POS Invoice Merge Log",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "POS Settings",
"link_to": "POS Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Loyalty Program",
"link_to": "Loyalty Program",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Loyalty Point Entry",
"link_to": "Loyalty Point Entry",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "stock",
"indent": 1,
"keep_closed": 1,
"label": "Items & Pricing",
"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": "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": "Item Group",
"link_to": "Item Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Price List",
"link_to": "Price List",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item Price",
"link_to": "Item Price",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Pricing Rule",
"link_to": "Pricing Rule",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Promotional Scheme",
"link_to": "Promotional Scheme",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Coupon Code",
"link_to": "Coupon Code",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Blanket Order",
"link_to": "Blanket Order",
"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": "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": "Customer Group",
"link_to": "Customer Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Address",
"link_to": "Address",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Contact",
"link_to": "Contact",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Territory",
"link_to": "Territory",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Campaign",
"link_to": "Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Person",
"link_to": "Sales Person",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Partner",
"link_to": "Sales Partner",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Monthly Distribution",
"link_to": "Monthly Distribution",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Terms Template",
"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": "Tax Template",
"link_to": "Sales Taxes and Charges Template",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Product Bundle",
"link_to": "Product Bundle",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "UTM Source",
"link_to": "UTM Source",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Shipping Rule",
"link_to": "Shipping Rule",
"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_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Register",
"link_to": "Sales Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item-wise Sales Register",
"link_to": "Item-wise Sales Register",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Analytics",
"link_to": "Sales Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customer Addresses And Contacts",
"link_to": "Address And Contacts",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Inactive Customers",
"link_to": "Inactive Customers",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Invoice Trends",
"link_to": "Sales Invoice Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customer Credit Balance",
"link_to": "Customer Credit Balance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customers Without Any Sales Transactions",
"link_to": "Customers Without Any Sales Transactions",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Partners Commission",
"link_to": "Sales Partners Commission",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Available Stock for Packing Items",
"link_to": "Available Stock for Packing Items",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Territory Target Variance Based On Item Group",
"link_to": "Territory Target Variance Based On Item Group",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Person Target Variance Based On Item Group",
"link_to": "Sales Person Target Variance Based On Item Group",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Partner Target Variance Based On Item Group",
"link_to": "Sales Partner Target Variance based on Item Group",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Pending SO Items For Purchase Request",
"link_to": "Pending SO Items For Purchase Request",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Funnel",
"link_to": "sales-funnel",
"link_type": "Page",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Order Analysis",
"link_to": "Sales Order Analysis",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Customer Acquisition and Loyalty",
"link_to": "Customer Acquisition and Loyalty",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Quotation Trends",
"link_to": "Quotation Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Order Trends",
"link_to": "Sales Order Trends",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item-wise Sales History",
"link_to": "Item-wise Sales History",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Sales Person-wise Transaction Summary",
"link_to": "Sales Person-wise Transaction Summary",
"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": "Selling Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Selling",
"type": "Workspace"
}

View File

@@ -353,33 +353,48 @@
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "round_off_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Round Off Account",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "round_off_cost_center",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Round Off Cost Center",
"no_copy": 1,
"options": "Cost Center"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "write_off_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Write Off Account",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "exchange_gain_loss_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Exchange Gain / Loss Account",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "unrealized_exchange_gain_loss_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Unrealized Exchange Gain/Loss Account",
"no_copy": 1,
"options": "Account"
},
{
@@ -526,15 +541,19 @@
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "accumulated_depreciation_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Accumulated Depreciation Account",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "depreciation_expense_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Depreciation Expense Account",
"no_copy": 1,
"options": "Account"
@@ -549,29 +568,39 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "disposal_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Gain/Loss Account on Asset Disposal",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "depreciation_cost_center",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Asset Depreciation Cost Center",
"no_copy": 1,
"options": "Cost Center"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "capital_work_in_progress_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Capital Work In Progress Account",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "asset_received_but_not_billed",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Asset Received But Not Billed",
"no_copy": 1,
"options": "Account"
},
{
@@ -703,15 +732,21 @@
"options": "Warehouse"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Unrealized Profit / Loss Account",
"no_copy": 1,
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "default_discount_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Default Payment Discount Account",
"no_copy": 1,
"options": "Account"
},
{
@@ -753,8 +788,10 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account",
"fieldname": "default_advance_received_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Default Advance Received Account",
"mandatory_depends_on": "book_advance_payments_as_liability",
"no_copy": 1,
"options": "Account"
},
{
@@ -763,8 +800,10 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/advance-in-separate-party-account",
"fieldname": "default_advance_paid_account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Default Advance Paid Account",
"mandatory_depends_on": "book_advance_payments_as_liability",
"no_copy": 1,
"options": "Account"
},
{
@@ -844,9 +883,12 @@
"options": "Account"
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "round_off_for_opening",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Round Off for Opening",
"no_copy": 1,
"options": "Account"
},
{
@@ -1026,7 +1068,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2026-07-01 11:48:07.853494",
"modified": "2026-07-02 07:21:21.794533",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -84,6 +84,7 @@ class Company(NestedSet):
default_operating_cost_account: DF.Link | None
default_payable_account: DF.Link | None
default_provisional_account: DF.Link | None
default_purchase_price_variance_account: DF.Link | None
default_receivable_account: DF.Link | None
default_sales_contact: DF.Link | None
default_scrap_warehouse: DF.Link | None

View File

@@ -1,5 +0,0 @@
frappe.listview_settings["Company"] = {
onload() {
frappe.breadcrumbs.add("Accounts");
},
};

View File

@@ -69,7 +69,7 @@
"type": "Link"
}
],
"modified": "2026-01-09 13:05:08.007297",
"modified": "2026-06-14 13:43:50.429297",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@@ -128,6 +128,236 @@
"type": "DocType"
}
],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "earth",
"indent": 0,
"keep_closed": 0,
"label": "Global Defaults",
"link_to": "Global Defaults",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "washing-machine",
"indent": 0,
"keep_closed": 0,
"label": "System Settings",
"link_to": "System Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "accounting",
"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": 0,
"collapsible": 1,
"icon": "computer",
"indent": 0,
"keep_closed": 0,
"label": "POS Settings",
"link_to": "POS Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "sell",
"indent": 0,
"keep_closed": 0,
"label": "Selling Settings",
"link_to": "Selling Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "buying",
"indent": 0,
"keep_closed": 0,
"label": "Buying Settings",
"link_to": "Buying Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "stock",
"indent": 0,
"keep_closed": 0,
"label": "Stock Settings",
"link_to": "Stock Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "building-2",
"indent": 0,
"keep_closed": 0,
"label": "Manufacturing Settings",
"link_to": "Manufacturing Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "projects",
"indent": 0,
"keep_closed": 0,
"label": "Projects Settings",
"link_to": "Projects Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "crm",
"indent": 0,
"keep_closed": 0,
"label": "CRM Settings",
"link_to": "CRM Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "support",
"indent": 0,
"keep_closed": 0,
"label": "Support Settings",
"link_to": "Support Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "getting-started",
"indent": 1,
"keep_closed": 1,
"label": "Other Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"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": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Item Variant Settings",
"link_to": "Item Variant Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Delivery Settings",
"link_to": "Delivery 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"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Appointment Booking Settings",
"link_to": "Appointment Booking Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Stock Reposting Settings",
"link_to": "Stock Reposting Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "ERPNext Settings",
"type": "Workspace"
}

View File

@@ -1,7 +1,7 @@
{
"app": "erpnext",
"charts": [],
"content": "[{\"id\":\"aCk49ShVRs\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"id\":\"kb3XPLg8lb\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"nWd2KJPW8l\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"snrzfbFr5Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"id\":\"SHJKakmLLf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":3}},{\"id\":\"CPxEyhaf3G\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"WU4F-HUcIQ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":3}},{\"id\":\"d_KVM1gsf9\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"JVu8-FJZCu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"JiuSi0ubOg\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"id\":\"ji2Jlm3Q8i\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"id\":\"N61oiXpuwK\",\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"id\":\"6J0CVl1mPo\",\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
"content": "[{\"id\":\"kb3XPLg8lb\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"nWd2KJPW8l\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"snrzfbFr5Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"id\":\"SHJKakmLLf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":3}},{\"id\":\"CPxEyhaf3G\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"d_KVM1gsf9\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"JVu8-FJZCu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"JiuSi0ubOg\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"id\":\"ji2Jlm3Q8i\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"id\":\"N61oiXpuwK\",\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"id\":\"6J0CVl1mPo\",\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
"creation": "2020-01-23 13:46:38.833076",
"custom_blocks": [],
"docstatus": 0,
@@ -13,6 +13,14 @@
"is_hidden": 0,
"label": "Home",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounting",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -32,6 +40,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chart of Accounts",
"link_count": 0,
"link_to": "Account",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Company",
"link_count": 0,
"link_to": "Company",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -54,6 +84,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier",
"link_count": 0,
"link_to": "Supplier",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -73,6 +125,14 @@
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
@@ -84,6 +144,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Item",
"link_count": 0,
"link_to": "Item",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Warehouse",
"link_count": 0,
"link_to": "Warehouse",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -106,6 +188,17 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Brand",
"link_count": 0,
"link_to": "Brand",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -117,6 +210,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Unit of Measure (UOM)",
"link_count": 0,
"link_to": "UOM",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reconciliation",
"link_count": 0,
"link_to": "Stock Reconciliation",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -136,6 +251,25 @@
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "CRM",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Lead",
"link_count": 0,
"link_to": "Lead",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -158,6 +292,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer Group",
"link_count": 0,
"link_to": "Customer Group",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Territory",
"link_count": 0,
"link_to": "Territory",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -177,6 +333,14 @@
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Data Import and Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
@@ -188,6 +352,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Import Data",
"link_count": 0,
"link_to": "Data Import",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Opening Invoice Creation Tool",
"link_count": 0,
"link_to": "Opening Invoice Creation Tool",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -210,6 +396,28 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chart of Accounts Importer",
"link_count": 0,
"link_to": "Chart of Accounts Importer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Letter Head",
"link_count": 0,
"link_to": "Letter Head",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -221,6 +429,17 @@
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Account",
"link_count": 0,
"link_to": "Email Account",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
@@ -233,7 +452,7 @@
"type": "Link"
}
],
"modified": "2025-07-02 14:12:28.407612",
"modified": "2026-07-01 14:22:16.927245",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
@@ -267,6 +486,74 @@
"type": "DocType"
}
],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Item",
"link_to": "Item",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Home",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Customer",
"link_to": "Customer",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Supplier",
"link_to": "Supplier",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Sales Invoice",
"link_to": "Sales Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Home",
"type": "Workspace"
}

View File

@@ -0,0 +1,204 @@
{
"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": "organization",
"idx": 0,
"indicator_color": "green",
"is_hidden": 0,
"label": "Organization",
"link_type": "DocType",
"links": [],
"modified": "2026-06-16 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": "organization",
"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

@@ -1074,62 +1074,141 @@ $.extend(erpnext.item, {
function make_fields_from_attribute_values(attr_dict) {
let fields = [];
let att_key = frm.doc.attributes.map((idx) => idx.attribute);
att_key.forEach((name, i) => {
let attributes = frm.doc.attributes.filter((row) => !row.disabled);
attributes.forEach((row, i) => {
let name = row.attribute;
if (i % 3 === 0) {
fields.push({ fieldtype: "Section Break" });
}
fields.push({ fieldtype: "Column Break", label: name });
fields.push({ fieldtype: "Column Break" });
fields.push({
fieldtype: "Data",
placeholder: "Search",
fieldname: `search_${frappe.scrub(name)}`,
onchange: function (e) {
let value = e.target.value;
let result = attr_dict[name].filter((attr_value) =>
attr_value.toString().toLowerCase().includes(value.toLowerCase())
);
attr_dict[name].forEach((attr_value) => {
if (result.includes(attr_value)) {
me.multiple_variant_dialog.set_df_property(attr_value, "hidden", 0);
} else {
me.multiple_variant_dialog.set_df_property(attr_value, "hidden", 1);
}
});
},
});
attr_dict[name].forEach((value) => {
fields.push({
fieldtype: "Check",
label: value,
fieldname: value,
default: 0,
onchange: function () {
let selected_attributes = get_selected_attributes();
let lengths = Object.keys(selected_attributes).map((key) => {
return selected_attributes[key].length;
});
if (!lengths.length) {
me.multiple_variant_dialog.get_primary_btn().html(__("Create Variants"));
me.multiple_variant_dialog.disable_primary_action();
} else {
let no_of_combinations = lengths.reduce((a, b) => a * b, 1);
let msg;
if (no_of_combinations === 1) {
msg = __("Make {0} Variant", [no_of_combinations]);
} else {
msg = __("Make {0} Variants", [no_of_combinations]);
}
me.multiple_variant_dialog.get_primary_btn().html(msg);
me.multiple_variant_dialog.enable_primary_action();
}
},
});
fieldtype: "MultiSelectPills",
label: name,
fieldname: frappe.scrub(name),
placeholder: __("Search values..."),
get_data: (txt) => get_attribute_suggestions(attr_dict[name], txt),
onchange: update_primary_action,
});
});
return fields;
}
function get_attribute_suggestions(spec, txt) {
if (!spec) return [];
return Array.isArray(spec) ? filter_list(spec, txt) : numeric_suggestions(spec, txt);
}
// Cap matches so a long value list never hands everything to Awesomplete,
// which would freeze the browser.
function filter_list(values, txt) {
txt = (txt || "").toLowerCase();
let matches = [];
for (let value of values) {
if (!txt || value.toLowerCase().includes(txt)) {
matches.push(value);
if (matches.length >= 50) break;
}
}
return matches;
}
// Numeric ranges aren't enumerated. With no input, preview the first few
// values; once the user types, accept it only if it lies on the increment
// within [from, to]. Both paths are cheap even for huge ranges.
function numeric_suggestions(range, txt) {
let { from_range: from, to_range: to, increment } = range;
if (!(increment > 0) || from > to) return [];
txt = (txt || "").trim();
if (!txt) {
let preview = [];
for (
let value = from;
value <= to && preview.length < 50;
value = flt(value + increment, 6)
) {
preview.push(String(value));
}
return preview;
}
return is_valid_attribute_value(range, txt) ? [String(flt(txt, 6))] : [];
}
function is_valid_attribute_value(spec, value) {
if (!spec || !value) return false;
if (Array.isArray(spec)) return spec.includes(value);
let { from_range: from, to_range: to, increment } = spec;
if (!(increment > 0)) return false;
// Reject anything that isn't cleanly a number ("abc", "5000xyz", "");
// flt would coerce these to 0 and wrongly accept them.
let text = String(value).trim();
let num = Number(text);
if (text === "" || !Number.isFinite(num)) return false;
if (num < from || num > to) return false;
let steps = (num - from) / increment;
return Math.abs(Math.round(steps) - steps) <= 1e-6;
}
// Block variant creation if anything is wrong: an invalid committed pill, or
// text typed but not added as a pill (which get_selected_attributes would
// otherwise drop silently). The user must fix each before creation proceeds.
function validate_selected_attributes() {
let errors = [];
frm.doc.attributes.forEach((row) => {
if (row.disabled) return;
let field = me.multiple_variant_dialog.get_field(frappe.scrub(row.attribute));
if (!field) return;
let attribute = frappe.utils.escape_html(row.attribute);
let spec = attr_val_fields[row.attribute];
let invalid = [
...new Set((field.get_value() || []).filter((v) => !is_valid_attribute_value(spec, v))),
];
if (invalid.length) {
let values = invalid.map(frappe.utils.escape_html).join(", ");
errors.push(__("{0}: remove invalid value(s) {1}", [attribute, values]));
}
let pending = (field.$input?.val() || "").trim();
if (pending) {
let value = frappe.utils.escape_html(pending);
errors.push(
__("{0}: select the typed value {1} from the list or clear it", [attribute, value])
);
}
});
if (errors.length) {
frappe.throw({
title: __("Invalid Attribute Values"),
message: errors.join("<br>"),
indicator: "red",
});
}
}
function update_primary_action() {
let selected_attributes = get_selected_attributes();
let counts = Object.keys(selected_attributes).map((key) => selected_attributes[key].length);
if (!counts.length) {
me.multiple_variant_dialog.get_primary_btn().html(__("Create Variants"));
me.multiple_variant_dialog.disable_primary_action();
} else {
let no_of_combinations = counts.reduce((a, b) => a * b, 1);
let msg =
no_of_combinations === 1
? __("Make {0} Variant", [no_of_combinations])
: __("Make {0} Variants", [no_of_combinations]);
me.multiple_variant_dialog.get_primary_btn().html(msg);
me.multiple_variant_dialog.enable_primary_action();
}
}
function make_and_show_dialog(fields) {
me.multiple_variant_dialog = new frappe.ui.Dialog({
title: __("Select Attribute Values"),
@@ -1155,6 +1234,8 @@ $.extend(erpnext.item, {
});
me.multiple_variant_dialog.set_primary_action(__("Create Variants"), () => {
validate_selected_attributes();
let selected_attributes = get_selected_attributes();
let use_template_image = me.multiple_variant_dialog.get_value("use_template_image");
@@ -1182,72 +1263,70 @@ $.extend(erpnext.item, {
});
});
$($(me.multiple_variant_dialog.$wrapper.find(".form-column")).find(".frappe-control")).css(
"margin-bottom",
"0px"
);
me.multiple_variant_dialog.disable_primary_action();
me.multiple_variant_dialog.clear();
me.multiple_variant_dialog.show();
me.multiple_variant_dialog.$wrapper
.find("div[data-fieldname^='search_']")
.find(".clearfix")
.hide();
}
function get_selected_attributes() {
let selected_attributes = {};
me.multiple_variant_dialog.$wrapper.find(".form-column").each((i, col) => {
if (i === 0) return;
let attribute_name = $(col).find(".column-label").html().trim();
selected_attributes[attribute_name] = [];
let checked_opts = $(col).find(".checkbox input");
checked_opts.each((i, opt) => {
if ($(opt).is(":checked")) {
selected_attributes[attribute_name].push($(opt).attr("data-fieldname"));
}
});
if (!selected_attributes[attribute_name].length) {
delete selected_attributes[attribute_name];
frm.doc.attributes.forEach((row) => {
if (row.disabled) return;
let values = me.multiple_variant_dialog.get_value(frappe.scrub(row.attribute));
if (values && values.length) {
selected_attributes[row.attribute] = values;
}
});
return selected_attributes;
}
frm.doc.attributes.forEach(function (d) {
if (!d.disabled) {
let p = new Promise((resolve) => {
if (!d.numeric_values) {
frappe
.call({
method: "frappe.client.get_list",
args: {
doctype: "Item Attribute Value",
filters: [["parent", "=", d.attribute]],
fields: ["attribute_value"],
limit_page_length: 0,
parent: "Item Attribute",
order_by: "idx",
},
})
.then((r) => {
if (r.message) {
attr_val_fields[d.attribute] = r.message.map(function (d) {
return d.attribute_value;
// Read the numeric configuration from the Item Attribute master
// instead of the variant attribute row, which may be stale or
// blank if the attribute was made numeric after it was added here.
frappe.db
.get_value("Item Attribute", d.attribute, [
"numeric_values",
"from_range",
"to_range",
"increment",
])
.then((res) => {
let attr = res.message || {};
if (!attr.numeric_values) {
frappe
.call({
method: "frappe.client.get_list",
args: {
doctype: "Item Attribute Value",
filters: [["parent", "=", d.attribute]],
fields: ["attribute_value"],
limit_page_length: 0,
parent: "Item Attribute",
order_by: "idx",
},
})
.then((r) => {
attr_val_fields[d.attribute] = (r.message || []).map(
(row) => row.attribute_value
);
resolve();
});
resolve();
}
});
} else {
let values = [];
for (var i = d.from_range; i <= d.to_range; i = flt(i + d.increment, 6)) {
values.push(i);
}
attr_val_fields[d.attribute] = values;
resolve();
}
} else {
// Store the range instead of enumerating it; a large range
// (e.g. 1-100000) is slow to build and to search. Values are
// validated against the range on demand while typing.
attr_val_fields[d.attribute] = {
from_range: flt(attr.from_range),
to_range: flt(attr.to_range),
increment: flt(attr.increment),
};
resolve();
}
});
});
promises.push(p);

View File

@@ -1372,7 +1372,8 @@ def get_purchase_voucher_details(doctype, item_code, document_name=None):
query = query.select(parent_doc.transaction_date)
query = query.orderby(parent_doc.transaction_date, parent_doc.name, order=Order.desc)
return query.run(as_dict=1)
# only the latest ([0]) row is ever used, so fetch just that instead of every purchase of the item
return query.limit(1).run(as_dict=1)
def check_stock_uom_with_bin(item, stock_uom):
@@ -1762,3 +1763,13 @@ def get_default_warehouse_for_opening_stock(item, company: str, warehouse: str |
"No warehouse found for company {0}. Please set a Default Warehouse in Item Defaults or Stock Settings."
).format(frappe.bold(company))
)
def on_doctype_update():
if frappe.db.db_type == "postgres":
# The Item link-search (erpnext.controllers.queries.item_query) filters
# `item_code/item_name LIKE '%txt%'` -- a leading-wildcard LIKE no btree can serve. pg_trgm
# GIN indexes accelerate it. Item is read-heavy/write-light master data, so GIN maintenance
# cost is negligible. Postgres-only (`using` is a no-op on MariaDB, which has its own FULLTEXT).
frappe.db.add_index("Item", ["item_code"], using="gin_trgm")
frappe.db.add_index("Item", ["item_name"], using="gin_trgm")

View File

@@ -1,4 +1,13 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Item Attribute", {});
frappe.ui.form.on("Item Attribute", {
numeric_values(frm) {
// Numeric attributes have no discrete values; drop the rows so their
// mandatory Attribute Value / Abbreviation don't block the save.
if (frm.doc.numeric_values) {
frm.clear_table("item_attribute_values");
frm.refresh_field("item_attribute_values");
}
},
});

View File

@@ -285,9 +285,6 @@ def create_stock_entry(pick_list: str | dict):
pick_list = frappe.get_doc(frappe.parse_json(pick_list))
validate_item_locations(pick_list)
if stock_entry_exists(pick_list.get("name")):
return frappe.msgprint(_("Stock Entry has already been created against this Pick List"))
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.pick_list = pick_list.get("name")
stock_entry.purpose = pick_list.get("purpose")
@@ -301,6 +298,9 @@ def create_stock_entry(pick_list: str | dict):
else:
stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
if not stock_entry.get("items"):
return frappe.msgprint(_("All picked items have already been transferred against this Pick List"))
stock_entry.set_missing_values()
return stock_entry.as_dict()
@@ -366,6 +366,8 @@ def update_stock_entry_based_on_work_order(pick_list, stock_entry):
stock_entry.project = work_order.project
for location in pick_list.locations:
if get_pending_transfer_stock_qty(location) <= 0:
continue
item = frappe._dict()
update_common_item_properties(item, location)
item.t_warehouse = wip_warehouse
@@ -377,6 +379,8 @@ def update_stock_entry_based_on_work_order(pick_list, stock_entry):
def update_stock_entry_based_on_material_request(pick_list, stock_entry):
for location in pick_list.locations:
if get_pending_transfer_stock_qty(location) <= 0:
continue
target_warehouse = None
if location.material_request_item:
target_warehouse = frappe.get_value(
@@ -392,6 +396,8 @@ def update_stock_entry_based_on_material_request(pick_list, stock_entry):
def update_stock_entry_items_with_no_reference(pick_list, stock_entry):
for location in pick_list.locations:
if get_pending_transfer_stock_qty(location) <= 0:
continue
item = frappe._dict()
update_common_item_properties(item, location)
@@ -400,11 +406,18 @@ def update_stock_entry_items_with_no_reference(pick_list, stock_entry):
return stock_entry
def get_pending_transfer_stock_qty(location):
"""Stock qty of this pick list row still to be moved into a Stock Entry."""
return flt(location.picked_qty) - flt(location.transferred_qty)
def update_common_item_properties(item, location):
pending_stock_qty = get_pending_transfer_stock_qty(location)
item.item_code = location.item_code
item.item_name = location.item_name
item.s_warehouse = location.warehouse
item.transfer_qty = location.picked_qty
item.qty = flt(location.picked_qty / (location.conversion_factor or 1), location.precision("qty"))
item.transfer_qty = pending_stock_qty
item.qty = flt(pending_stock_qty / (location.conversion_factor or 1), location.precision("qty"))
item.uom = location.uom
item.conversion_factor = location.conversion_factor
item.stock_uom = location.stock_uom
@@ -412,3 +425,4 @@ def update_common_item_properties(item, location):
item.serial_no = location.serial_no
item.batch_no = location.batch_no
item.material_request_item = location.material_request_item
item.pick_list_item = location.name

View File

@@ -190,7 +190,7 @@
"in_standard_filter": 1,
"label": "Status",
"no_copy": 1,
"options": "Draft\nOpen\nPartly Delivered\nCompleted\nCancelled",
"options": "Draft\nOpen\nPartly Delivered\nPartially Transferred\nCompleted\nCancelled",
"print_hide": 1,
"read_only": 1,
"report_hide": 1,
@@ -278,7 +278,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2026-02-06 18:14:18.361039",
"modified": "2026-07-01 14:27:50.617011",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",

View File

@@ -71,7 +71,9 @@ class PickList(TransactionBase):
purpose: DF.Literal["Material Transfer for Manufacture", "Material Transfer", "Delivery"]
scan_barcode: DF.Data | None
scan_mode: DF.Check
status: DF.Literal["Draft", "Open", "Partly Delivered", "Completed", "Cancelled"]
status: DF.Literal[
"Draft", "Open", "Partly Delivered", "Partially Transferred", "Completed", "Cancelled"
]
work_order: DF.Link | None
# end: auto-generated types
@@ -417,6 +419,34 @@ class PickList(TransactionBase):
return stock_entry_exists(self.name)
def get_transfer_status(self):
"""Return the pick list's transfer progress based on how much of the picked qty has been
moved into submitted Stock Entries (tracked on Pick List Item.transferred_qty).
Only applies to purposes that move stock via Stock Entry; the Delivery purpose is tracked
via delivery_status instead. Returns "Completed", "Partially Transferred" or None."""
if self.purpose == "Delivery":
return None
total_picked = sum(flt(row.picked_qty) for row in self.locations)
if not total_picked:
return None
total_transferred = sum(flt(row.transferred_qty) for row in self.locations)
if total_transferred <= 0:
return None
if total_transferred >= total_picked:
return "Completed"
return "Partially Transferred"
def is_fully_transferred(self):
return self.get_transfer_status() == "Completed"
def is_partially_transferred(self):
return self.get_transfer_status() == "Partially Transferred"
def update_reference_qty(self):
packed_items = []
so_items = []

View File

@@ -7,6 +7,7 @@ frappe.listview_settings["Pick List"] = {
Draft: "red",
Open: "orange",
"Partly Delivered": "orange",
"Partially Transferred": "yellow",
Completed: "green",
Cancelled: "red",
};

View File

@@ -13,6 +13,7 @@ from erpnext.stock.doctype.pick_list.mapper import (
create_delivery,
create_delivery_note,
create_dn_for_pick_lists,
create_stock_entry,
)
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
@@ -1221,6 +1222,64 @@ class TestPickList(ERPNextTestSuite):
pl.reload()
self.assertEqual(pl.status, "Cancelled")
def test_pick_list_partial_transfer_status(self):
"""Partial Stock Entries from a Pick List should track transferred_qty and drive the
Partially Transferred / Completed status, and allow further transfers for the remainder."""
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
item = make_item(properties={"is_stock_item": 1}).name
source_warehouse = "_Test Warehouse - _TC"
target_warehouse = create_warehouse("_Test Transfer Target Warehouse")
make_stock_entry(item=item, to_warehouse=source_warehouse, qty=10)
pick_list = frappe.get_doc(
{
"doctype": "Pick List",
"company": "_Test Company",
"purpose": "Material Transfer",
"pick_manually": 1,
"locations": [
{
"item_code": item,
"qty": 10,
"stock_qty": 10,
"conversion_factor": 1,
"warehouse": source_warehouse,
"picked_qty": 10,
}
],
}
)
pick_list.submit()
self.assertEqual(pick_list.status, "Open")
# Transfer 4 of the 10 picked units.
se1 = frappe.get_doc(create_stock_entry(pick_list.as_dict()))
self.assertEqual(se1.items[0].qty, 10)
se1.items[0].qty = 4
se1.items[0].t_warehouse = target_warehouse
se1.submit()
pick_list.reload()
self.assertEqual(pick_list.locations[0].transferred_qty, 4)
self.assertEqual(pick_list.status, "Partially Transferred")
# The next Stock Entry should only offer the remaining 6 units.
se2 = frappe.get_doc(create_stock_entry(pick_list.as_dict()))
self.assertEqual(se2.items[0].qty, 6)
se2.items[0].t_warehouse = target_warehouse
se2.submit()
pick_list.reload()
self.assertEqual(pick_list.locations[0].transferred_qty, 10)
self.assertEqual(pick_list.status, "Completed")
# Cancelling the last entry rolls transferred_qty and status back.
se2.cancel()
pick_list.reload()
self.assertEqual(pick_list.locations[0].transferred_qty, 4)
self.assertEqual(pick_list.status, "Partially Transferred")
def test_pick_list_validation(self):
warehouse = "_Test Warehouse - _TC"
item = make_item("Test Non Serialized Pick List Item", properties={"is_stock_item": 1}).name

View File

@@ -22,6 +22,7 @@
"conversion_factor",
"stock_uom",
"delivered_qty",
"transferred_qty",
"available_quantity_section",
"actual_qty",
"column_break_kyek",
@@ -255,6 +256,16 @@
"read_only": 1,
"report_hide": 1
},
{
"default": "0",
"fieldname": "transferred_qty",
"fieldtype": "Float",
"label": "Transferred Qty (in Stock UOM)",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"report_hide": 1
},
{
"fieldname": "available_quantity_section",
"fieldtype": "Section Break",
@@ -285,7 +296,7 @@
],
"istable": 1,
"links": [],
"modified": "2026-03-17 16:25:10.358013",
"modified": "2026-07-01 14:27:50.617011",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",

View File

@@ -39,6 +39,7 @@ class PickListItem(Document):
stock_qty: DF.Float
stock_reserved_qty: DF.Float
stock_uom: DF.Link | None
transferred_qty: DF.Float
uom: DF.Link | None
use_serial_batch_fields: DF.Check
warehouse: DF.Link | None

View File

@@ -425,10 +425,10 @@ def repost(doc):
if isinstance(message, dict):
message = message.get("message")
status = "Failed"
# If failed because of timeout, set status to In Progress
if traceback and ("timeout" in traceback.lower() or "Deadlock found" in traceback):
status = "In Progress"
# Recoverable errors (deadlock, lock/query timeout, job timeout) re-queue as In Progress.
# Classify by type: the old traceback string-match only knew MariaDB's "Deadlock found" and
# missed Postgres deadlocks ("deadlock detected"), failing them permanently.
status = "In Progress" if isinstance(e, RecoverableErrors) else "Failed"
if traceback:
message += "<br><br>" + "<b>Traceback:</b> <br>" + traceback
@@ -447,7 +447,8 @@ def repost(doc):
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
)
if outgoing_email_account and not isinstance(e, RecoverableErrors):
# status == "Failed" already implies e is not recoverable, so no need to re-check here.
if outgoing_email_account:
notify_error_to_stock_managers(doc, message)
doc.set_status("Failed")
finally:

View File

@@ -220,6 +220,39 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin):
sorted(frappe.parse_json(frappe.as_json(set([("a", "b"), ("c", "d")])))),
)
def test_recoverable_error_requeues_instead_of_failing(self):
# A recoverable DB error (e.g. Postgres deadlock -> QueryDeadlockError) must re-queue the
# repost as "In Progress"; a non-recoverable error still fails. Regression: the old check
# string-matched MariaDB's "Deadlock found" and missed Postgres deadlocks ("deadlock detected").
from unittest.mock import patch
from frappe.exceptions import QueryDeadlockError
from erpnext.stock.doctype.repost_item_valuation import repost_item_valuation as riv
orig_max_writes = frappe.db.MAX_WRITES_PER_TRANSACTION
self.addCleanup(setattr, frappe.db, "MAX_WRITES_PER_TRANSACTION", orig_max_writes)
def status_after(error):
doc = frappe.new_doc("Repost Item Valuation")
doc.name = "test-recoverable-riv"
doc.set_status = doc.log_error = doc.db_set = MagicMock()
captured = {}
with (
patch.object(frappe, "in_test", False),
patch.object(frappe.db, "exists", return_value=True),
patch.object(frappe.db, "commit"),
patch.object(frappe.db, "rollback"),
patch.object(frappe.db, "set_value", side_effect=lambda *a, **k: captured.update(a[2])),
patch.object(riv, "repost_sl_entries", side_effect=error),
patch.object(frappe, "get_cached_value", return_value=None),
):
riv.repost(doc)
return captured.get("status")
self.assertEqual(status_after(QueryDeadlockError("deadlock detected")), "In Progress")
self.assertEqual(status_after(ValueError("boom")), "Failed")
def test_gl_repost_progress(self):
from erpnext.accounts import utils

View File

@@ -1814,6 +1814,27 @@ class SerialandBatchBundle(Document):
self.set("entries", [])
def on_doctype_update():
if frappe.db.db_type == "postgres":
# Bundle-direct lookups (get_ledgers_from_serial_batch_bundle, get_picked_*) always filter
# `is_cancelled = 0` and scope by voucher_no or item_code+warehouse -- none of which the parent
# bundle is otherwise indexed on (only voucher_type/voucher_detail_no are). Partial indexes keep
# only the active bundles. Postgres-only (`where` is a no-op on MariaDB, and MariaDB's optimizer
# ignores partial predicates anyway).
frappe.db.add_index(
"Serial and Batch Bundle",
["voucher_no"],
index_name="sabb_active_voucher",
where="is_cancelled = 0",
)
frappe.db.add_index(
"Serial and Batch Bundle",
["item_code", "warehouse"],
index_name="sabb_active_item_wh",
where="is_cancelled = 0",
)
@frappe.whitelist()
def download_blank_csv_template(content: str | list):
csv_data = []

View File

@@ -164,6 +164,15 @@ class StockEntry(StockController, SubcontractingInwardController):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._configure_purpose_class()
self.status_updater = [
{
"source_dt": "Stock Entry Detail",
"target_dt": "Pick List Item",
"join_field": "pick_list_item",
"target_field": "transferred_qty",
"source_field": "transfer_qty",
}
]
if self.subcontracting_inward_order:
self.subcontract_data = frappe._dict(
@@ -349,6 +358,7 @@ class StockEntry(StockController, SubcontractingInwardController):
self.delink_asset_repair_sabb()
self.validate_closed_subcontracting_order()
self.update_subcontracting_order_status()
self.update_pick_list_status()
self.cancel_stock_reserve_for_wip_and_fg()
if self.work_order and self.purpose == "Material Consumption for Manufacture":
@@ -1484,6 +1494,9 @@ class StockEntry(StockController, SubcontractingInwardController):
def update_pick_list_status(self):
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
if self.pick_list:
self.update_qty()
update_pick_list_status(self.pick_list)
def set_missing_values(self):

View File

@@ -72,6 +72,7 @@
"col_break6",
"material_request",
"material_request_item",
"pick_list_item",
"original_item",
"reference_section",
"against_stock_entry",
@@ -424,6 +425,16 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "pick_list_item",
"fieldtype": "Link",
"hidden": 1,
"label": "Pick List Item",
"no_copy": 1,
"options": "Pick List Item",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "original_item",
"fieldtype": "Link",
@@ -679,7 +690,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-06-30 12:18:34.132425",
"modified": "2026-07-01 14:27:50.617011",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",

View File

@@ -58,6 +58,7 @@ class StockEntryDetail(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
pick_list_item: DF.Link | None
po_detail: DF.Data | None
project: DF.Link | None
putaway_rule: DF.Link | None

View File

@@ -364,3 +364,15 @@ class StockLedgerEntry(Document):
def on_doctype_update():
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["item_code", "warehouse", "posting_datetime", "creation"])
if frappe.db.db_type == "postgres":
# Postgres-only partial index for date-range stock reports (Stock Ledger / Stock Balance)
# that scan across all items: they filter `is_cancelled = 0` and sort by posting_datetime.
# The existing item_code-leading composite can't serve an all-items date scan. `where` is a
# no-op on MariaDB, so this is added only on postgres.
frappe.db.add_index(
"Stock Ledger Entry",
["company", "posting_datetime", "creation"],
index_name="sle_active_posting",
where="is_cancelled = 0",
)

View File

@@ -0,0 +1,83 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle import (
execute,
)
from erpnext.tests.utils import ERPNextTestSuite
class TestIncorrectSerialAndBatchBundle(ERPNextTestSuite):
def run_report(self, **extra):
filters = frappe._dict({"company": "_Test Company"})
filters.update(extra)
return execute(filters)[1]
def test_healthy_bundles_not_flagged(self):
batch_item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "ISBB-.#####",
}
).name
serial_item = "_Test Serialized Item With Series"
make_stock_entry(
item_code=batch_item,
qty=10,
rate=100,
to_warehouse="Stores - _TC",
posting_date="2026-06-01",
)
make_stock_entry(
item_code=serial_item,
qty=3,
rate=100,
to_warehouse="Stores - _TC",
posting_date="2026-06-01",
)
data = self.run_report()
bundles = frappe.get_all(
"Serial and Batch Bundle",
filters={"item_code": ["in", [batch_item, serial_item]]},
pluck="name",
)
flagged_names = {row.get("name") for row in data}
self.assertFalse(
flagged_names.intersection(bundles),
msg="Healthy serial/batch bundles should not be flagged as incorrect.",
)
def test_unlinked_bundle_is_flagged(self):
# an actual incorrect state: a submitted Serial and Batch Bundle left without any linking
# Stock Ledger Entry (e.g. the SLE was purged but the bundle survived)
batch_item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "ISBB-ORPHAN-.#####",
}
).name
entry = make_stock_entry(
item_code=batch_item, qty=5, rate=100, to_warehouse="Stores - _TC", posting_date="2026-06-01"
)
bundle = frappe.db.get_value("Serial and Batch Bundle", {"voucher_no": entry.name}, "name")
self.assertTrue(bundle)
# orphan the bundle: drop the Stock Ledger Entry that referenced it
frappe.db.delete("Stock Ledger Entry", {"serial_and_batch_bundle": bundle})
flagged = {row.get("name"): row for row in self.run_report()}
self.assertIn(bundle, flagged)
self.assertEqual(flagged[bundle]["is_cancelled"], 0)

View File

@@ -5,6 +5,7 @@ import copy
import gzip
import json
from collections import deque
from contextlib import nullcontext
import frappe
from frappe import _, bold, scrub
@@ -261,6 +262,28 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
return sle
# A repost waits this long for another repost's per-(item, warehouse) gate before giving up. Kept
# well under the 1800s repost job timeout so a wait can't burn the whole budget, and short enough
# that a contended worker re-queues (recoverable QueryTimeoutError) and frees the slot for other
# items instead of pinning it.
REPOST_LOCK_TIMEOUT = 300
def repost_gate(item_code, warehouse):
"""Serialize concurrent background reposts of the same (item, warehouse) with a session-level
advisory lock taken before the inner `... for update` row locks, so they take turns instead of
racing into a lock-order deadlock. Row locks still enforce correctness; this only cuts the
deadlock/retry churn. Scope is repost-vs-repost only -- the synchronous repost_current_voucher
submit path is deliberately not gated (blocking a submit behind a background repost would be a
worse regression) and keeps relying on the existing deadlock retry. No advisory locks, no gate."""
# hasattr keeps this a graceful opt-in: on an ERPNext predating frappe.db.advisory_lock, fall
# back to no gate rather than raising and marking the Repost Item Valuation permanently Failed.
if frappe.db.db_type in ("postgres", "mariadb") and hasattr(frappe.db, "advisory_lock"):
# Tuple key: a colon in item_code/warehouse can't collide two distinct pairs onto one lock.
return frappe.db.advisory_lock(("stock_repost", item_code, warehouse), timeout=REPOST_LOCK_TIMEOUT)
return nullcontext()
def repost_future_sle(
items_to_be_repost=None,
voucher_type=None,
@@ -289,22 +312,25 @@ def repost_future_sle(
while index < len(items_to_be_repost):
validate_item_warehouse(items_to_be_repost[index])
obj = update_entries_after(
{
"item_code": items_to_be_repost[index].get("item_code"),
"warehouse": items_to_be_repost[index].get("warehouse"),
"posting_date": items_to_be_repost[index].get("posting_date"),
"posting_time": items_to_be_repost[index].get("posting_time"),
"creation": items_to_be_repost[index].get("creation"),
"current_idx": index,
"items_to_be_repost": items_to_be_repost,
"repost_doc": doc,
"repost_affected_transaction": repost_affected_transaction,
"item_wh_wise_last_posted_sle": resume_item_wh_wise_last_posted_sle,
},
allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher,
)
item_code = items_to_be_repost[index].get("item_code")
warehouse = items_to_be_repost[index].get("warehouse")
with repost_gate(item_code, warehouse):
obj = update_entries_after(
{
"item_code": item_code,
"warehouse": warehouse,
"posting_date": items_to_be_repost[index].get("posting_date"),
"posting_time": items_to_be_repost[index].get("posting_time"),
"creation": items_to_be_repost[index].get("creation"),
"current_idx": index,
"items_to_be_repost": items_to_be_repost,
"repost_doc": doc,
"repost_affected_transaction": repost_affected_transaction,
"item_wh_wise_last_posted_sle": resume_item_wh_wise_last_posted_sle,
},
allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher,
)
index += 1

View File

@@ -1,4 +1,78 @@
{
"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": "stock@xyz.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": [
{
@@ -789,9 +863,10 @@
"type": "Link"
}
],
"modified": "2026-01-02 12:38:50.043198",
"modified": "2026-06-17 12:11:34.739020",
"modified_by": "Administrator",
"module": "Stock",
"module_onboarding": "Stock Onboarding",
"name": "Stock",
"number_cards": [
{
@@ -815,6 +890,737 @@
"roles": [],
"sequence_id": 7.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Stock",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "chart",
"indent": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Stock",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "stock",
"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,
"default_workspace": 0,
"icon": "receipt-text",
"indent": 0,
"keep_closed": 0,
"label": "Purchase Receipt",
"link_to": "Purchase Receipt",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "truck",
"indent": 0,
"keep_closed": 0,
"label": "Delivery Note",
"link_to": "Delivery Note",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "arrow-left-to-line",
"indent": 0,
"keep_closed": 0,
"label": "Material Request",
"link_to": "Material Request",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "caravan",
"indent": 0,
"keep_closed": 0,
"label": "Pick List",
"link_to": "Pick List",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "tool",
"indent": 1,
"keep_closed": 1,
"label": "Tools",
"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": "Stock Reconciliation",
"link_to": "Stock Reconciliation",
"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": "Landed Cost Voucher",
"link_to": "Landed Cost Voucher",
"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": "Repost Item Valuation",
"link_to": "Repost Item Valuation",
"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": "Packing Slip",
"link_to": "Packing Slip",
"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": "Quality Inspection",
"link_to": "Quality Inspection",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "database",
"indent": 1,
"keep_closed": 1,
"label": "Setup",
"link_to": "",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"child": 1,
"collapsible": 1,
"default_workspace": 1,
"icon": "",
"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,
"default_workspace": 0,
"indent": 0,
"keep_closed": 0,
"label": "Item Group",
"link_to": "Item Group",
"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": "Item Attribute",
"link_to": "Item Attribute",
"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": "Brand",
"link_to": "Brand",
"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": "Warehouse",
"link_to": "Warehouse",
"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": "Unit of Measure (UOM)",
"link_to": "UOM",
"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": "UOM Conversion Factor",
"link_to": "UOM Conversion Factor",
"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": "Serial No",
"link_to": "Serial No",
"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": "Batch No",
"link_to": "Batch",
"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": "Serial and Batch Bundle",
"link_to": "Serial and Batch Bundle",
"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": "Inventory Dimension",
"link_to": "Inventory 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": "Shipping Rule",
"link_to": "Shipping Rule",
"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": "Item Alternative",
"link_to": "Item Alternative",
"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": "Quality Inspection Template",
"link_to": "Quality Inspection 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": "Delivery Trip",
"link_to": "Delivery Trip",
"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": "Stock Ledger",
"link_to": "Stock Ledger",
"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": "Stock Balance",
"link_to": "Stock Balance",
"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": "Quick Stock Balance",
"link_to": "Quick Stock Balance",
"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": "Stock Projected Qty",
"link_to": "Stock Projected Qty",
"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": "Stock Analytics",
"link_to": "Stock Analytics",
"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": "Stock Ageing",
"link_to": "Stock Ageing",
"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": "Purchase Receipt Trends",
"link_to": "Purchase Receipt Trends",
"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": "Delivery Note Trends",
"link_to": "Delivery Note Trends",
"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": "Item Price Stock",
"link_to": "Item Price Stock",
"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": "Warehouse Wise Stock Balance",
"link_to": "Warehouse Wise Stock Balance",
"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": "Item Shortage Report",
"link_to": "Item Shortage Report",
"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": "Serial No and Batch Traceability",
"link_to": "Serial No and Batch Traceability",
"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": "Serial No Status",
"link_to": "Serial No Status",
"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": "Serial No Ledger",
"link_to": "Serial No Ledger",
"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": "Serial No Warranty Expiry",
"link_to": "Serial No Warranty Expiry",
"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": "Batch-Wise Balance History",
"link_to": "Batch-Wise Balance History",
"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": "Batch Item Expiry Status",
"link_to": "Batch Item Expiry Status",
"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": "Requested Items To Be Transferred",
"link_to": "Requested Items To Be Transferred",
"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": "Itemwise Recommended Reorder Level",
"link_to": "Itemwise Recommended Reorder Level",
"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": "Item Variant Details",
"link_to": "Item Variant Details",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"default_workspace": 0,
"icon": "settings",
"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": "Stock Settings",
"link_to": "Stock 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": "Item Variant Settings",
"link_to": "Item Variant 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": "Stock Reposting Settings",
"link_to": "Stock Reposting 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": "Delivery Settings",
"link_to": "Delivery Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Stock",
"type": "Workspace"
}

View File

@@ -13,7 +13,7 @@
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "organization",
"icon": "getting-started",
"idx": 2,
"is_hidden": 0,
"label": "Subcontracting",
@@ -138,9 +138,10 @@
"type": "Link"
}
],
"modified": "2025-12-19 16:50:25.976741",
"modified": "2026-06-14 13:43:50.289920",
"modified_by": "Administrator",
"module": "Subcontracting",
"module_onboarding": "Subcontracting Onboarding",
"name": "Subcontracting",
"number_cards": [
{
@@ -164,6 +165,251 @@
"roles": [],
"sequence_id": 8.0,
"shortcuts": [],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"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

@@ -1,7 +1,7 @@
{
"app": "erpnext",
"charts": [],
"content": "[{\"id\":\"HOEnlt9aR9\",\"type\":\"header\",\"data\":{\"text\":\"This module is scheduled for deprecation and will be completely removed in version 17, please use <a href=\\\"https://frappe.io/helpdesk\\\">Frappe Helpdesk</a> instead.\",\"col\":12}},{\"id\":\"oxhWhXp9b2\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"Ff8Ab3nLLN\",\"type\":\"card\",\"data\":{\"card_name\":\"Issues\",\"col\":4}},{\"id\":\"_lndiuJTVP\",\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"id\":\"R_aNO5ESzJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Service Level Agreement\",\"col\":4}},{\"id\":\"N8aA2afWfi\",\"type\":\"card\",\"data\":{\"card_name\":\"Warranty\",\"col\":4}},{\"id\":\"M5fxGuFwUR\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"xKH0kO9q4P\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"content": "[{\"id\":\"HOEnlt9aR9\",\"type\":\"header\",\"data\":{\"text\":\"This module is scheduled for deprecation and will be completely removed in version 17, please use <a href=\\\"https://frappe.io/helpdesk\\\">Frappe Helpdesk</a> instead.\",\"col\":12}},{\"id\":\"qzP2mZrGOu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"Fkdjo6bJ7A\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Issue\",\"col\":3}},{\"id\":\"OTS8kx2f3x\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Maintenance Visit\",\"col\":3}},{\"id\":\"smDTSjBR3Z\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Service Level Agreement\",\"col\":3}},{\"id\":\"WCqL_gBYGU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"oxhWhXp9b2\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"Ff8Ab3nLLN\",\"type\":\"card\",\"data\":{\"card_name\":\"Issues\",\"col\":4}},{\"id\":\"_lndiuJTVP\",\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"id\":\"R_aNO5ESzJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Service Level Agreement\",\"col\":4}},{\"id\":\"N8aA2afWfi\",\"type\":\"card\",\"data\":{\"card_name\":\"Warranty\",\"col\":4}},{\"id\":\"M5fxGuFwUR\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"xKH0kO9q4P\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:48:23.224699",
"custom_blocks": [],
"docstatus": 0,
@@ -172,7 +172,7 @@
"type": "Link"
}
],
"modified": "2026-01-02 17:45:04.203273",
"modified": "2026-06-14 13:44:07.764547",
"modified_by": "Administrator",
"module": "Support",
"name": "Support",
@@ -184,7 +184,179 @@
"restrict_to_domain": "",
"roles": [],
"sequence_id": 12.0,
"shortcuts": [],
"shortcuts": [
{
"color": "Yellow",
"format": "{} Assigned",
"label": "Issue",
"link_to": "Issue",
"stats_filter": "{\n \"_assign\": [\"like\", '%' + frappe.session.user + '%'],\n \"status\": \"Open\"\n}",
"type": "DocType"
},
{
"label": "Maintenance Visit",
"link_to": "Maintenance Visit",
"type": "DocType"
},
{
"label": "Service Level Agreement",
"link_to": "Service Level Agreement",
"type": "DocType"
}
],
"sidebar_items": [
{
"child": 0,
"collapsible": 1,
"icon": "home",
"indent": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Support",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "file-question-mark",
"indent": 0,
"keep_closed": 0,
"label": "Issue",
"link_to": "Issue",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "calendar-days",
"indent": 0,
"keep_closed": 0,
"label": "Maintenance Schedule",
"link_to": "Maintenance Schedule",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "calendar-check-2",
"indent": 0,
"keep_closed": 0,
"label": "Maintenance Visit",
"link_to": "Maintenance Visit",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 0,
"collapsible": 1,
"icon": "grid-2x2-check",
"indent": 0,
"keep_closed": 0,
"label": "Warranty Claim",
"link_to": "Warranty Claim",
"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": "Issue Type",
"link_to": "Issue Type",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Issue Priority",
"link_to": "Issue Priority",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Service Level Agreement",
"link_to": "Service Level Agreement",
"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,
"indent": 0,
"keep_closed": 0,
"label": "First Response Time for Issues",
"link_to": "First Response Time for Issues",
"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": "Support Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"standard": 1,
"title": "Support",
"type": "Workspace"
}

View File

@@ -14,6 +14,7 @@
"keep_closed": 0,
"label": "Setup",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
@@ -25,6 +26,7 @@
"label": "Chart of Accounts",
"link_to": "Account",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -36,6 +38,7 @@
"label": "Chart of Cost Centers",
"link_to": "Cost Center",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -47,6 +50,7 @@
"label": "Account Category",
"link_to": "Account Category",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -58,6 +62,7 @@
"label": "Accounting Dimension",
"link_to": "Accounting Dimension",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -69,6 +74,7 @@
"label": "Currency",
"link_to": "Currency",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -80,6 +86,7 @@
"label": "Currency Exchange",
"link_to": "Currency Exchange",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -91,6 +98,7 @@
"label": "Finance Book",
"link_to": "Finance Book",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -102,6 +110,7 @@
"label": "Mode of Payment",
"link_to": "Mode of Payment",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -113,6 +122,7 @@
"label": "Payment Term",
"link_to": "Payment Term",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -124,6 +134,7 @@
"label": "Journal Entry Template",
"link_to": "Journal Entry Template",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -135,6 +146,7 @@
"label": "Terms and Conditions",
"link_to": "Terms and Conditions",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -146,6 +158,7 @@
"label": "Company",
"link_to": "Company",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -157,6 +170,7 @@
"label": "Fiscal Year",
"link_to": "Fiscal Year",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -168,6 +182,7 @@
"label": "Sales Taxes",
"link_to": "Sales Taxes and Charges Template",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -179,6 +194,7 @@
"keep_closed": 0,
"label": "Opening & Closing",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
@@ -191,6 +207,7 @@
"label": "COA Importer",
"link_to": "Chart of Accounts Importer",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -203,6 +220,7 @@
"label": "Opening Invoice Tool",
"link_to": "Opening Invoice Creation Tool",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -215,6 +233,7 @@
"label": "Accounting Period",
"link_to": "Accounting Period",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -227,6 +246,7 @@
"label": "FX Revaluation",
"link_to": "Exchange Rate Revaluation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -239,6 +259,7 @@
"label": "Period Closing Voucher",
"link_to": "Period Closing Voucher",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -250,6 +271,7 @@
"keep_closed": 0,
"label": "Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
@@ -262,6 +284,7 @@
"label": "Accounts Settings",
"link_to": "Accounts Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -273,22 +296,12 @@
"label": "Currency Exchange Settings",
"link_to": "Currency Exchange Settings",
"link_type": "DocType",
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Repost Accounting Ledger Settings",
"link_to": "Repost Accounting Ledger Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-02-23 22:20:51.043478",
"modified": "2026-06-12 14:50:50.262533",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",

View File

@@ -15,6 +15,7 @@
"label": "Budget",
"link_to": "Budget",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -27,6 +28,7 @@
"label": "Cost Center",
"link_to": "Cost Center",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -39,6 +41,7 @@
"label": "Accounting Dimension",
"link_to": "Accounting Dimension",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -51,6 +54,7 @@
"label": "Cost Center Allocation",
"link_to": "Cost Center Allocation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -63,6 +67,7 @@
"label": "Budget Variance",
"link_to": "Budget Variance Report",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
@@ -70,8 +75,8 @@
"modified": "2026-01-10 00:06:13.032297",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",
"name": "Budgeting",
"owner": "Administrator",
"standard": 1,
"title": "Budget"
"title": "Budgeting"
}

View File

@@ -15,6 +15,7 @@
"label": "Global Defaults",
"link_to": "Global Defaults",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -27,6 +28,7 @@
"label": "System Settings",
"link_to": "System Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -39,6 +41,7 @@
"label": "Accounts Settings",
"link_to": "Accounts Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -51,6 +54,7 @@
"label": "POS Settings",
"link_to": "POS Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -63,6 +67,7 @@
"label": "Selling Settings",
"link_to": "Selling Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -75,6 +80,7 @@
"label": "Buying Settings",
"link_to": "Buying Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -87,6 +93,7 @@
"label": "Stock Settings",
"link_to": "Stock Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -99,6 +106,7 @@
"label": "Manufacturing Settings",
"link_to": "Manufacturing Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -111,6 +119,7 @@
"label": "Projects Settings",
"link_to": "Projects Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -123,6 +132,7 @@
"label": "CRM Settings",
"link_to": "CRM Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -135,6 +145,7 @@
"label": "Support Settings",
"link_to": "Support Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -146,6 +157,7 @@
"keep_closed": 1,
"label": "Other Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
@@ -157,6 +169,7 @@
"label": "Subscription Settings",
"link_to": "Subscription Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -168,6 +181,7 @@
"label": "Item Variant Settings",
"link_to": "Item Variant Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -179,6 +193,7 @@
"label": "Delivery Settings",
"link_to": "Delivery Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -190,6 +205,7 @@
"label": "Currency Exchange Settings",
"link_to": "Currency Exchange Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -201,6 +217,7 @@
"label": "Appointment Booking Settings",
"link_to": "Appointment Booking Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -212,22 +229,12 @@
"label": "Stock Reposting Settings",
"link_to": "Stock Reposting Settings",
"link_type": "DocType",
"show_arrow": 0,
"type": "Link"
},
{
"child": 1,
"collapsible": 1,
"indent": 0,
"keep_closed": 0,
"label": "Repost Accounting Ledger Settings",
"link_to": "Repost Accounting Ledger Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-01-10 00:06:12.956275",
"modified": "2026-06-12 14:51:11.333051",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",

View File

@@ -9,89 +9,103 @@
{
"child": 0,
"collapsible": 1,
"default_workspace": 1,
"icon": "organization",
"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-02-24 18:08:00.796746",
"modified": "2026-06-16 00:37:22.942285",
"modified_by": "Administrator",
"module": "Setup",
"module_onboarding": "Organization Onboarding",

View File

@@ -15,6 +15,7 @@
"label": "Subscription",
"link_to": "Subscription",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -27,6 +28,7 @@
"label": "Subscription Plan",
"link_to": "Subscription Plan",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -39,6 +41,7 @@
"label": "Subscription Settings",
"link_to": "Subscription Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -50,6 +53,7 @@
"keep_closed": 1,
"label": "Setup",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
@@ -61,6 +65,7 @@
"label": "Customer",
"link_to": "Customer",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -72,6 +77,7 @@
"label": "Supplier",
"link_to": "Supplier",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
@@ -83,6 +89,7 @@
"label": "Item",
"link_to": "Item",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
@@ -90,8 +97,8 @@
"modified": "2026-01-10 00:06:13.048591",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
"name": "Subscriptions",
"owner": "Administrator",
"standard": 1,
"title": "Subscription"
"title": "Subscriptions"
}