Compare commits

...

21 Commits

Author SHA1 Message Date
Crowdin Bot
5c7c0161fb fix: sync translations from crowdin 2026-08-31 04:08:31 +00:00
Diptanil Saha
d5df40986d fix(timesheet): scoping whitelisted methods output to projects and timesheets that are acccessible to users (#58267) 2026-08-30 23:16:32 +05:30
MochaMind
1cedd1421d chore: update POT file (#58566) 2026-08-30 13:00:36 +02:00
Mihir Kandoi
db56080285 fix(manufacturing): use packed row delivery date (#58568) 2026-08-30 10:44:23 +00:00
Pandiyan P
8b43394533 fix(stock): load available serial no report (#58558) 2026-08-29 20:24:58 +05:30
Diptanil Saha
caf8a36bdb fix(accounts): added permission checks on multiple payment entry whitelisted methods (#58555) 2026-08-29 18:06:25 +05:30
Diptanil Saha
a59792a605 refactor(stock): remove dead warehouse_condition branch on get_stock_ledger_entries (#58552) 2026-08-29 11:39:11 +00:00
Shllokkk
6d13e4a633 Merge pull request #58324 from Shllokkk/crm-note-edit-note-permission-check
fix(crm): check write permission in edit_note
2026-08-28 20:34:38 +05:30
Mihir Kandoi
90ac7db704 test: restore ERPNext test coverage (#58542) 2026-08-28 20:09:03 +05:30
Shllokkk
eb49f51d29 fix(crm): check write permission in edit_note 2026-08-28 17:17:04 +05:30
rohitwaghchaure
0223223385 feat: batch split operation to produce child batches per piece (#58530)
* feat: batch split operation to produce child batches per piece

* fix: single input validation, weight conserving lineage, cancel cleanup and naming race for batch split

* fix: delete cancelled batch split bundle along with unused child batches

* fix: retain all child batches when any sibling of a split bundle is in use

* fix: run batch split cancel cleanup only for batch split entries

* fix: make batch split flag read only on stock entry type

* fix: restrict cancel cleanup to child batches minted by the cancelled entry

* refactor: name child batches from item batch series and retain them on cancel

* feat: batch split tree report for parent to child batch traceability

* refactor: source each piece wholly from a single parent batch

* fix: weight per piece sizes the child batches instead of scaling raw material consumption

* fix: apportion child batch lineage proportionally to parent batch quantities

* fix: cap child batch lineage at the whole piece capacity of each parent batch

* fix: exclude batches of cancelled split entries from the batch split tree
2026-08-28 17:11:02 +05:30
Diptanil Saha
ca49de633f fix(stock): auto-select batch no before saving transaction records (#58536) 2026-08-28 17:01:23 +05:30
Soham Kulkarni
64186f229f Merge pull request #58476 from sokumon/module-sidebars
feat: erpnext sidebars and workspaces
2026-08-28 16:13:20 +05:30
Afsal Syed
074c84e880 fix: persist redistributed additional costs during stock entry repost (#58433)
* fix: persist redistributed additional costs during stock entry repost

* test: cover additional cost persistence on stock entry recalculation
2026-08-28 15:51:51 +05:30
rohitwaghchaure
1f7f8cd9d3 feat: alternative finished goods conversion against work order (#58479)
* feat: alternative finished goods conversion against work order

* fix: tighten validations for finished goods conversion

* fix: postgres compatible lock and qty checks post transfer qty for fg conversion

* fix: default single alternative item and hide Change Finished Item button without alternatives
2026-08-28 12:56:06 +05:30
Mihir Kandoi
9160182727 test: improve ERPNext test isolation and runtime (#58507) 2026-08-28 10:41:22 +05:30
sokumon
0985451276 fix: remove the conf file 2026-08-28 02:27:14 +05:30
sokumon
987408f203 chore: some more fixtures 2026-08-28 02:27:14 +05:30
sokumon
2a14b5d953 feat: ship new erpnext workspaces 2026-08-28 02:27:14 +05:30
Diptanil Saha
9018573179 fix(accounts): set pos profile on invoices respecting user permissions (#58508) 2026-08-27 22:06:36 +05:30
Raffael Meyer
e95ca2444c fix: translate doctype in map msg (#58515) 2026-08-27 16:14:49 +00:00
205 changed files with 105457 additions and 79941 deletions

View File

@@ -4,29 +4,18 @@
import frappe
from frappe.utils import add_months, getdate
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed, if_lending_app_not_installed
class TestBankClearance(ERPNextTestSuite):
def setUp(self):
frappe.clear_cache()
create_warehouse(
warehouse_name="_Test Warehouse",
properties={"parent_warehouse": "All Warehouses - _TC"},
company="_Test Company",
)
create_item("_Test Item")
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company")
make_bank_account()
add_transactions()
@@ -139,11 +128,8 @@ def add_transactions():
def make_payment_entry():
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
supplier = create_supplier(supplier_name="_Test Supplier")
pi = make_purchase_invoice(
supplier=supplier.name,
supplier="_Test Supplier",
supplier_warehouse="_Test Warehouse - _TC",
expense_account="Cost of Goods Sold - _TC",
uom="Nos",
@@ -158,10 +144,6 @@ def make_payment_entry():
def make_pos_sales_invoice():
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
@@ -170,13 +152,13 @@ def make_pos_sales_invoice():
)
mode_of_payment.save()
customer = make_customer(customer="_Test Customer")
mode_of_payment = frappe.get_doc("Mode of Payment", "Wire Transfer")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank Clearance - _TC")
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
si = create_sales_invoice(
customer="_Test Customer", item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1
)
si.set("payments", [])
si.append("payments", {"mode_of_payment": "Wire Transfer", "amount": 1000})
si.insert()

View File

@@ -23,8 +23,6 @@ from erpnext.tests.utils import ERPNextTestSuite, if_lending_app_installed
class TestBankTransaction(ERPNextTestSuite):
def setUp(self):
make_pos_profile()
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
uniq_identifier = frappe.generate_hash(length=10)
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
@@ -32,6 +30,7 @@ class TestBankTransaction(ERPNextTestSuite):
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
)
make_pos_profile()
add_transactions(bank_account=bank_account)
add_vouchers(gl_account=gl_account)
@@ -47,7 +46,7 @@ class TestBankTransaction(ERPNextTestSuite):
from_date=bank_transaction.date,
to_date=utils.today(),
)
self.assertEqual(linked_payments[0]["party"], "Conrad Electronic")
self.assertIn("Conrad Electronic", [payment["party"] for payment in linked_payments])
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self):

View File

@@ -234,8 +234,10 @@ frappe.ui.form.on("Dunning", {
dn: frm.doc.name,
},
callback: function (r) {
var doc = frappe.model.sync(r.message);
frappe.set_route("Form", doc[0].doctype, doc[0].name);
if (!r.exc) {
var doc = frappe.model.sync(r.message);
frappe.set_route("Form", doc[0].doctype, doc[0].name);
}
},
});
},

View File

@@ -31,11 +31,4 @@ class TestFinanceBook(ERPNextTestSuite):
def create_finance_book():
if not frappe.db.exists("Finance Book", "_Test Finance Book"):
finance_book = frappe.get_doc(
{"doctype": "Finance Book", "finance_book_name": "_Test Finance Book"}
).insert()
else:
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
return finance_book
return frappe.get_doc("Finance Book", "Test Finance Book 1")

View File

@@ -27,6 +27,7 @@ def get_payment_entry_against_order(
) -> dict | Document:
"""Build an advance-payment Journal Entry against an unbilled Sales/Purchase Order."""
ref_doc = frappe.get_doc(dt, dn)
ref_doc.check_permission()
if flt(ref_doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
@@ -78,6 +79,8 @@ def get_payment_entry_against_invoice(
) -> dict | Document:
"""Build a payment Journal Entry against a Sales/Purchase Invoice's outstanding amount."""
ref_doc = frappe.get_doc(dt, dn)
ref_doc.check_permission()
if dt == "Sales Invoice":
party_type = "Customer"
party_account = get_party_account_based_on_invoice_discounting(dn) or ref_doc.debit_to
@@ -118,6 +121,8 @@ def get_payment_entry(ref_doc, args: dict) -> dict | Document:
Returns the Journal Entry document when `args["journal_entry"]` is truthy, otherwise its
dict (for client calls).
"""
frappe.has_permission("Journal Entry", ptype="create", throw=True)
je = frappe.new_doc("Journal Entry")
je.update({"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")})

View File

@@ -318,9 +318,8 @@ class TestJournalEntry(ERPNextTestSuite):
)
# the guard must not disclose the reversal to a user who cannot read the entry
frappe.set_user("Guest")
self.addCleanup(frappe.set_user, "Administrator")
self.assertRaises(frappe.PermissionError, make_reverse_journal_entry, rjv.name)
with self.set_user("Guest"):
self.assertRaises(frappe.PermissionError, make_reverse_journal_entry, rjv.name)
def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD

View File

@@ -2626,7 +2626,11 @@ def get_payment_entry(
reference_date: str | date | None = None,
created_from_payment_request: bool | None = None,
):
frappe.has_permission("Payment Entry", ptype="create", throw=True)
doc = frappe.get_doc(dt, dn)
doc.check_permission()
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (100.0 + over_billing_allowance):
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))

View File

@@ -789,7 +789,6 @@ class TestPaymentEntry(ERPNextTestSuite):
company="_Test Company",
)
frappe.db.set_value("Company", "_Test Company", "bank_charges_account", bank_charges_account)
self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "bank_charges_account", "")
pe = frappe.new_doc("Payment Entry")
pe.payment_type = "Internal Transfer"
@@ -834,7 +833,6 @@ class TestPaymentEntry(ERPNextTestSuite):
company="_Test Company",
)
frappe.db.set_value("Company", "_Test Company", "bank_charges_account", bank_charges_account)
self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "bank_charges_account", "")
pe = frappe.new_doc("Payment Entry")
pe.payment_type = "Internal Transfer"
@@ -1109,8 +1107,6 @@ class TestPaymentEntry(ERPNextTestSuite):
)
frappe.db.set_value("Company", "_Test Company", "exchange_gain_account", gain_account)
frappe.db.set_value("Company", "_Test Company", "exchange_loss_account", loss_account)
self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "exchange_gain_account", "")
self.addCleanup(frappe.db.set_value, "Company", "_Test Company", "exchange_loss_account", "")
si_gain = create_sales_invoice(
customer="_Test Customer USD",

View File

@@ -201,8 +201,6 @@ class TestPaymentReconciliation(ERPNextTestSuite):
)
frappe.db.set_value("Company", self.company, "exchange_gain_account", gain_account)
frappe.db.set_value("Company", self.company, "exchange_loss_account", loss_account)
self.addCleanup(frappe.db.set_value, "Company", self.company, "exchange_gain_account", "")
self.addCleanup(frappe.db.set_value, "Company", self.company, "exchange_loss_account", "")
return gain_account, loss_account
def create_foreign_currency_sales_invoice(self, conversion_rate):
@@ -1331,15 +1329,6 @@ class TestPaymentReconciliation(ERPNextTestSuite):
test_user = "test@example.com"
permitted_ccs = ["_Test Cost Center - _TC", "_Test Cost Center 2 - _TC"]
restricted_cc = "_Test Write Off Cost Center - _TC"
existing_apply_strict_user_permissions = cint(
frappe.db.get_single_value("System Settings", "apply_strict_user_permissions")
)
self.addCleanup(
frappe.db.set_single_value,
"System Settings",
"apply_strict_user_permissions",
existing_apply_strict_user_permissions,
)
transaction_date = nowdate()
rate = 100

View File

@@ -29,6 +29,9 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestSuite
PAYMENT_URL = "https://example.com/payment"
SEND_EMAIL_MOCK = MagicMock(return_value=None)
GET_PAYMENT_URL_MOCK = MagicMock(return_value=PAYMENT_URL)
GET_PAYMENT_GATEWAY_CONTROLLER_MOCK = MagicMock()
payment_gateways = [
{"doctype": "Payment Gateway", "gateway": "_Test Gateway"},
@@ -71,6 +74,18 @@ payment_method = [
]
@patch(
"erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.send_email",
new=SEND_EMAIL_MOCK,
)
@patch(
"erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.get_payment_url",
new=GET_PAYMENT_URL_MOCK,
)
@patch(
"erpnext.accounts.doctype.payment_request.payment_request._get_payment_gateway_controller",
new=GET_PAYMENT_GATEWAY_CONTROLLER_MOCK,
)
class TestPaymentRequest(ERPNextTestSuite):
def setUp(self):
for payment_gateway in payment_gateways:
@@ -89,24 +104,11 @@ class TestPaymentRequest(ERPNextTestSuite):
):
frappe.get_doc(method).insert(ignore_permissions=True)
send_email = patch(
"erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.send_email",
return_value=None,
)
self.send_email = send_email.start()
self.addCleanup(send_email.stop)
get_payment_url = patch(
# this also shadows one (1) call to _get_payment_gateway_controller
"erpnext.accounts.doctype.payment_request.payment_request.PaymentRequest.get_payment_url",
return_value=PAYMENT_URL,
)
self.get_payment_url = get_payment_url.start()
self.addCleanup(get_payment_url.stop)
_get_payment_gateway_controller = patch(
"erpnext.accounts.doctype.payment_request.payment_request._get_payment_gateway_controller",
)
self._get_payment_gateway_controller = _get_payment_gateway_controller.start()
self.addCleanup(_get_payment_gateway_controller.stop)
for mock in (SEND_EMAIL_MOCK, GET_PAYMENT_URL_MOCK, GET_PAYMENT_GATEWAY_CONTROLLER_MOCK):
mock.reset_mock()
self.send_email = SEND_EMAIL_MOCK
self.get_payment_url = GET_PAYMENT_URL_MOCK
self._get_payment_gateway_controller = GET_PAYMENT_GATEWAY_CONTROLLER_MOCK
def test_payment_request_linkings(self):
so_inr = make_sales_order(currency="INR", do_not_save=True)

View File

@@ -21,13 +21,12 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestPOSClosingEntry(ERPNextTestSuite):
def setUp(self):
init_user_and_profile()
self.test_user, self.pos_profile = init_user_and_profile()
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
frappe.db.set_single_value("POS Settings", "invoice_type", "POS Invoice")
def test_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(self.pos_profile, self.test_user.name)
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
@@ -59,8 +58,7 @@ class TestPOSClosingEntry(ERPNextTestSuite):
"""
Test if POS Closing Entry is created without item code
"""
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(self.pos_profile, self.test_user.name)
pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
@@ -79,10 +77,9 @@ class TestPOSClosingEntry(ERPNextTestSuite):
"""
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(self.pos_profile, self.test_user.name)
test_item_qty = get_test_item_qty(pos_profile)
test_item_qty = get_test_item_qty(self.pos_profile)
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
@@ -104,13 +101,11 @@ class TestPOSClosingEntry(ERPNextTestSuite):
pcv_doc.flags.in_test = True
pcv_doc.submit()
opening_entry = create_opening_entry(pos_profile, test_user.name)
test_item_qty_after_sales = get_test_item_qty(pos_profile)
test_item_qty_after_sales = get_test_item_qty(self.pos_profile)
self.assertEqual(test_item_qty_after_sales, test_item_qty - 1)
def test_cancelling_of_pos_closing_entry(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(self.pos_profile, self.test_user.name)
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
@@ -169,9 +164,7 @@ class TestPOSClosingEntry(ERPNextTestSuite):
pos_profile.insert()
self.assertTrue(frappe.db.exists("POS Profile", pos_profile.name))
test_user = init_user_and_profile(do_not_create_pos_profile=1)
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(pos_profile, self.test_user.name)
pos_inv1 = create_pos_invoice(rate=350, do_not_submit=1, pos_profile=pos_profile.name)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
pos_inv1.save()
@@ -195,9 +188,6 @@ class TestPOSClosingEntry(ERPNextTestSuite):
def test_merging_into_sales_invoice_for_batched_item(self):
frappe.flags.print_message = False
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
init_user_and_profile,
)
from erpnext.stock.doctype.batch.batch import get_batch_qty
item_doc = make_item(
@@ -220,8 +210,7 @@ class TestPOSClosingEntry(ERPNextTestSuite):
)
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(self.pos_profile, self.test_user.name)
pos_inv = create_pos_invoice(
item_code=item_code,
@@ -291,18 +280,17 @@ class TestPOSClosingEntry(ERPNextTestSuite):
@ERPNextTestSuite.change_settings("POS Settings", {"invoice_type": "Sales Invoice"})
def test_closing_entries_with_sales_invoice(self):
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
opening_entry = create_opening_entry(self.pos_profile, self.test_user.name)
pos_si = create_sales_invoice(
qty=10, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
qty=10, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=1
)
pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si.save()
pos_si.submit()
pos_si2 = create_sales_invoice(
qty=5, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=11
qty=5, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=11
)
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si2.save()
@@ -332,12 +320,10 @@ class TestPOSClosingEntry(ERPNextTestSuite):
"""
from erpnext.accounts.doctype.sales_invoice.mapper import make_sales_return
test_user, pos_profile = init_user_and_profile()
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
opening_entry1 = create_opening_entry(self.pos_profile, self.test_user.name)
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
pos_si1, pos_si2 = create_multiple_sales_invoices(self.pos_profile)
pos_inv = create_pos_invoice(rate=100, do_not_save=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
@@ -357,13 +343,13 @@ class TestPOSClosingEntry(ERPNextTestSuite):
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc1.name)
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
opening_entry2 = create_opening_entry(self.pos_profile, self.test_user.name)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(self.pos_profile)
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
pos_si3 = create_sales_invoice(
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
qty=1, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=1
)
pos_si3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_si3.save)
@@ -394,16 +380,14 @@ class TestPOSClosingEntry(ERPNextTestSuite):
"""
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile()
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
opening_entry1 = create_opening_entry(self.pos_profile, self.test_user.name)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(self.pos_profile)
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
pos_sinv = create_sales_invoice(
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
qty=1, is_created_using_pos=1, pos_profile=self.pos_profile.name, do_not_save=1
)
pos_sinv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_sinv.save)
@@ -421,9 +405,9 @@ class TestPOSClosingEntry(ERPNextTestSuite):
self.assertEqual(pcv_doc1.grand_total, 300)
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
opening_entry2 = create_opening_entry(self.pos_profile, self.test_user.name)
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
pos_si1, pos_si2 = create_multiple_sales_invoices(self.pos_profile)
pos_inv3 = create_pos_invoice(rate=100, do_not_save=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})

View File

@@ -4,6 +4,7 @@ import copy
import frappe
from frappe import _
from frappe.utils import add_to_date
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
@@ -53,14 +54,14 @@ class TestPOSInvoice(POSInvoiceTestMixin):
w2 = frappe.get_doc(w.doctype, w.name)
import time
time.sleep(1)
w.save()
import time
time.sleep(1)
frappe.db.set_value(
w.doctype,
w.name,
"modified",
add_to_date(w.modified, seconds=1),
update_modified=False,
)
self.assertRaises(frappe.TimestampMismatchError, w2.save)
def test_change_naming_series(self):
@@ -902,9 +903,6 @@ class TestPOSInvoice(POSInvoiceTestMixin):
self.assertEqual(pos_inv.items[0].rate, 300)
def test_delivered_serial_no_case(self):
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
init_user_and_profile,
)
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
@@ -916,8 +914,6 @@ class TestPOSInvoice(POSInvoiceTestMixin):
self.assertEqual(serial_no, delivered_serial_no)
init_user_and_profile()
pos_inv = create_pos_invoice(
item_code="_Test Serialized Item With Series",
serial_no=[serial_no],
@@ -931,13 +927,9 @@ class TestPOSInvoice(POSInvoiceTestMixin):
def test_bundle_stock_availability_validation(self):
from erpnext.accounts.doctype.pos_invoice.pos_invoice import ProductBundleStockValidationError
from erpnext.accounts.doctype.pos_invoice_merge_log.test_pos_invoice_merge_log import (
init_user_and_profile,
)
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import create_item
init_user_and_profile()
from erpnext.stock.utils import get_stock_balance
frappe.set_user("Administrator")
@@ -959,9 +951,18 @@ class TestPOSInvoice(POSInvoiceTestMixin):
is_stock_item=1,
)
# Add initial stock: SubA=5, SubB=2
make_stock_entry(item_code=sub_item_a, target=warehouse, qty=5, company=company)
make_stock_entry(item_code=sub_item_b, target=warehouse, qty=2, company=company)
# Set initial stock to SubA=5 and SubB=2, even when this test is rerun on the same site.
for item_code, target_qty in ((sub_item_a, 5), (sub_item_b, 2)):
balance = get_stock_balance(item_code, warehouse)
difference = target_qty - balance
if difference:
make_stock_entry(
item_code=item_code,
to_warehouse=warehouse if difference > 0 else None,
from_warehouse=warehouse if difference < 0 else None,
qty=abs(difference),
company=company,
)
# Create Product Bundle: Test Bundle (SubA x2 + SubB x1)
bundle_item = "_Test Bundle"
@@ -1010,16 +1011,19 @@ class TestPOSInvoice(POSInvoiceTestMixin):
def create_pos_invoice(**args):
args = frappe._dict(args)
pos_profile = None
if not args.pos_profile:
pos_profile = make_pos_profile()
pos_profile.save()
pos_profile_name = args.pos_profile
if not pos_profile_name:
pos_profile_name = frappe.db.exists("POS Profile", "_Test POS Profile")
if not pos_profile_name:
pos_profile = make_pos_profile()
pos_profile.save()
pos_profile_name = pos_profile.name
pos_inv = frappe.new_doc("POS Invoice")
pos_inv.update(args)
pos_inv.update_stock = 1
pos_inv.is_pos = 1
pos_inv.pos_profile = args.pos_profile or pos_profile.name
pos_inv.pos_profile = pos_profile_name
if args.posting_date:
pos_inv.set_posting_time = 1

View File

@@ -26,14 +26,10 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
init_user_and_profile,
)
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
)
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 270})
pos_inv.save()
@@ -55,14 +51,10 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
init_user_and_profile,
)
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
)
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
pos_inv.append(
@@ -107,9 +99,6 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
init_user_and_profile,
)
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
)
@@ -121,7 +110,6 @@ class TestPOSInvoiceMerging(POSInvoiceTestMixin):
make_item(item, {"is_stock_item": 1})
make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300)
test_user, pos_profile = init_user_and_profile()
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "amount": 300})
pos_inv.append(

View File

@@ -289,6 +289,11 @@ def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page
user = frappe.session["user"]
company = filters.get("company") or frappe.defaults.get_user_default("company")
allowed_pos_profiles = frappe.get_list("POS Profile", pluck="name")
if not allowed_pos_profiles:
return {}
pf = frappe.qb.DocType("POS Profile")
pfu = frappe.qb.DocType("POS Profile User")
@@ -298,6 +303,7 @@ def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page
.on(pfu.parent == pf.name)
.select(pf.name)
.where((pfu.user == user) & (pf.company == company) & pf.name.like(f"%{txt}%") & (pf.disabled == 0))
.where(pf.name.isin(allowed_pos_profiles))
.limit(page_len)
.offset(start)
.run()
@@ -314,6 +320,7 @@ def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page
& (pf.company == company)
& pf.name.like(f"%{txt}%")
& (pf.disabled == 0)
& (pf.name.isin(allowed_pos_profiles))
)
.run()
)

View File

@@ -578,17 +578,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
make_purchase_invoice as create_purchase_invoice,
)
original_value = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
self.addCleanup(
frappe.db.set_single_value,
"Buying Settings",
"set_landed_cost_based_on_purchase_invoice_rate",
original_value,
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
@@ -616,16 +606,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
make_purchase_invoice as create_purchase_invoice,
)
original_value = frappe.db.get_single_value(
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
)
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
self.addCleanup(
frappe.db.set_single_value,
"Buying Settings",
"set_landed_cost_based_on_purchase_invoice_rate",
original_value,
)
pr = frappe.new_doc("Purchase Receipt")
pr.currency = "USD"
@@ -3545,7 +3526,6 @@ def make_purchase_invoice_against_cost_center(**args):
def setup_provisional_accounting(**args):
args = frappe._dict(args)
create_item("_Test Non Stock Item", is_stock_item=0)
company = args.company or "_Test Company"
provisional_account = create_account(
account_name=args.account_name or "Provision Account",

View File

@@ -7,7 +7,7 @@ import json
import frappe
from frappe import qb
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.utils import add_days, cint, flt, format_date, getdate, nowdate, today
from frappe.utils import add_days, add_to_date, cint, flt, format_date, getdate, nowdate, today
import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
@@ -129,14 +129,14 @@ class TestSalesInvoice(ERPNextTestSuite):
w2 = frappe.get_doc(w.doctype, w.name)
import time
time.sleep(1)
w.save()
import time
time.sleep(1)
frappe.db.set_value(
w.doctype,
w.name,
"modified",
add_to_date(w.modified, seconds=1),
update_modified=False,
)
self.assertRaises(frappe.TimestampMismatchError, w2.save)
def test_sales_invoice_change_naming_series(self):
@@ -3817,25 +3817,12 @@ class TestSalesInvoice(ERPNextTestSuite):
# enable common party accounting
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1)
# create a dimension and make it mandatory
if not frappe.get_all("Accounting Dimension", filters={"document_type": "Department"}):
dim = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
"dimension_defaults": [{"company": "_Test Company", "mandatory_for_bs": True}],
}
)
dim.save()
else:
dim = frappe.get_doc(
"Accounting Dimension",
frappe.get_all("Accounting Dimension", filters={"document_type": "Department"})[0],
)
dim.disabled = False
dim.dimension_defaults = []
dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True})
dim.save()
# make the shared department dimension mandatory
dim = frappe.get_doc("Accounting Dimension", {"document_type": "Department"})
dim.disabled = False
dim.dimension_defaults = []
dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True})
dim.save()
# create a sales invoice
si = create_sales_invoice(
@@ -5789,12 +5776,6 @@ def create_internal_parties():
allowed_to_interact_with="Wind Power LLC",
)
create_internal_customer(
customer_name="_Test Internal Customer 2",
represents_company="_Test Company with perpetual inventory",
allowed_to_interact_with="_Test Company with perpetual inventory",
)
create_internal_customer(
customer_name="_Test Internal Customer 3",
represents_company="_Test Company",
@@ -5815,12 +5796,6 @@ def create_internal_parties():
allowed_to_interact_with="_Test Company 1",
)
create_internal_supplier(
supplier_name="_Test Internal Supplier 2",
represents_company="_Test Company with perpetual inventory",
allowed_to_interact_with="_Test Company with perpetual inventory",
)
create_internal_supplier(
supplier_name="_Test Internal Customer 3",
represents_company="_Test Company",

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from unittest.mock import patch
import frappe
from frappe.utils.data import (
@@ -658,23 +659,15 @@ class TestSubscription(ERPNextTestSuite):
sub2 = create_subscription(start_date="2018-01-02")
processed = []
original_process = Subscription.process
original_rollback = frappe.db.rollback
def patched(self, posting_date=None):
processed.append(self.name)
if self.name == sub1.name:
raise frappe.ValidationError("forced failure")
Subscription.process = patched
# process_all calls frappe.db.rollback() on error which would otherwise wipe
# the test transaction; stub it so we can observe the iteration in isolation.
frappe.db.rollback = lambda *a, **kw: None
try:
# Stub transaction recovery so the test can observe the complete iteration in isolation.
with patch.object(Subscription, "process", patched), patch.object(frappe.db, "rollback"):
process_all([sub1.name, sub2.name])
finally:
Subscription.process = original_process
frappe.db.rollback = original_rollback
self.assertEqual(processed, [sub1.name, sub2.name])
@@ -1073,12 +1066,6 @@ def create_plan(**kwargs):
def create_parties():
if not frappe.db.exists("Supplier", "_Test Supplier"):
supplier = frappe.new_doc("Supplier")
supplier.supplier_name = "_Test Supplier"
supplier.supplier_group = "All Supplier Groups"
supplier.insert()
if not frappe.db.exists("Customer", "_Test Subscription Customer"):
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test Subscription Customer"

View File

@@ -63,25 +63,6 @@ class TestTaxRule(ERPNextTestSuite):
def test_for_parent_supplier_group(self):
purchase_template = "_Test Purchase Taxes and Charges Template - _TC"
if not frappe.db.exists("Purchase Taxes and Charges Template", purchase_template):
frappe.get_doc(
{
"doctype": "Purchase Taxes and Charges Template",
"title": "_Test Purchase Taxes and Charges Template",
"company": "_Test Company",
"taxes": [
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"description": "VAT",
"doctype": "Purchase Taxes and Charges",
"cost_center": "Main - _TC",
"rate": 6,
}
],
}
).insert()
make_tax_rule(
supplier_group="All Supplier Groups",
tax_type="Purchase",

View File

@@ -869,9 +869,7 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
self.assertEqual(rows_b[0].future_amount, 50.0)
def test_sales_person(self):
sales_person = frappe.get_doc(
{"doctype": "Sales Person", "sales_person_name": "John Clark", "enabled": True}
).insert()
sales_person = frappe.get_doc("Sales Person", "_Test Sales Person")
si = self.create_sales_invoice(do_not_submit=True)
si.append("sales_team", {"sales_person": sales_person.name, "allocated_percentage": 100})
si.save().submit()
@@ -1494,17 +1492,8 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
def test_receivable_filtered_by_sales_partner(self):
frappe.set_user("Administrator")
partner_a, partner_b = "_Test AR Sales Partner A", "_Test AR Sales Partner B"
for partner in (partner_a, partner_b):
if not frappe.db.exists("Sales Partner", partner):
frappe.get_doc(
{
"doctype": "Sales Partner",
"partner_name": partner,
"commission_rate": 0,
"territory": "All Territories",
}
).insert()
partner_a = "_Test Sales Partner India - 1"
partner_b = "_Test Sales Partner India - 2"
def _si(sales_partner):
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True, qty=2)

View File

@@ -193,16 +193,7 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
self.assertEqual(len(rpt_output), 0)
def test_03_summary_sales_partner_column(self):
partner = "_Test AR Summary Sales Partner"
if not frappe.db.exists("Sales Partner", partner):
frappe.get_doc(
{
"doctype": "Sales Partner",
"partner_name": partner,
"commission_rate": 0,
"territory": "All Territories",
}
).insert()
partner = "_Test Sales Partner India - 1"
si = create_sales_invoice(
item=self.item,

View File

@@ -21,10 +21,8 @@ class TestGeneralLedger(ERPNextTestSuite):
from frappe.utils import today
frappe.db.set_single_value("Accounts Settings", "general_ledger_remarks_length", 50)
self.addCleanup(frappe.db.set_single_value, "Accounts Settings", "general_ledger_remarks_length", 0)
si = create_sales_invoice(company=self.company)
self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name)
create_sales_invoice(company=self.company)
columns, data = execute(
frappe._dict(
@@ -42,15 +40,6 @@ class TestGeneralLedger(ERPNextTestSuite):
self.assertTrue(data)
self.assertTrue(any("remarks" in row for row in data))
@staticmethod
def _cancel_and_delete(doctype, name):
if not frappe.db.exists(doctype, name):
return
doc = frappe.get_doc(doctype, name)
if doc.docstatus == 1:
doc.cancel()
frappe.delete_doc(doctype, name, force=1)
def clear_old_entries(self):
doctype_list = [
"GL Entry",

View File

@@ -642,7 +642,7 @@ class TestGrossProfit(ERPNextTestSuite):
self.assertEqual(total.get("gross_profit_%"), -50.0)
def test_sales_person_wise_gross_profit(self):
sales_person = make_sales_person("_Test Sales Person")
sales_person = frappe.get_doc("Sales Person", "_Test Sales Person")
posting_date = get_first_day(nowdate())
qty = 10
@@ -1194,19 +1194,3 @@ class TestGrossProfit(ERPNextTestSuite):
self.assertEqual(base_rate, 220.0) # avg selling rate = 220/1
self.assertEqual(gross_profit, 120.0) # 220 - 100
self.assertAlmostEqual(gp_percent, 54.545, places=2) # 120/220 * 100
def make_sales_person(sales_person_name="_Test Sales Person"):
if not frappe.db.exists("Sales Person", {"sales_person_name": sales_person_name}):
sales_person_doc = frappe.get_doc(
{
"doctype": "Sales Person",
"is_group": 0,
"parent_sales_person": "Sales Team",
"sales_person_name": sales_person_name,
}
).insert(ignore_permissions=True)
else:
sales_person_doc = frappe.get_doc("Sales Person", {"sales_person_name": sales_person_name})
return sales_person_doc

View File

@@ -12,7 +12,7 @@ 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)
self.shareholder = get_shareholder("Iron Man", COMPANY)
def test_date_filter_is_mandatory(self):
self.assertRaises(frappe.ValidationError, execute, frappe._dict({"shareholder": self.shareholder}))
@@ -96,7 +96,7 @@ class TestShareBalanceReport(ERPNextTestSuite):
self.assertEqual(row[4], 3000)
def test_balance_reduces_after_transfer_out(self):
other_holder = create_shareholder("_Test Share Balance Holder 2", COMPANY)
other_holder = get_shareholder("Thor", COMPANY)
create_share_transfer(
transfer_type="Issue",
to_shareholder=self.shareholder,
@@ -187,9 +187,8 @@ def create_share_type(title):
return title
def create_shareholder(title, company):
shareholder = frappe.get_doc({"doctype": "Shareholder", "title": title, "company": company}).insert()
return shareholder.name
def get_shareholder(title, company):
return frappe.db.get_value("Shareholder", {"title": title, "company": company}, "name")
def create_share_transfer(**kwargs):

View File

@@ -23,7 +23,7 @@ COL_SHARE_TRANSFER = 8
class TestShareLedger(ERPNextTestSuite):
def setUp(self):
self.shareholder = self.create_shareholder("_Test Share Ledger Holder")
self.shareholder = self.get_shareholder("Iron Man")
# Issue 100 shares on 2026-06-01, then another 50 on 2026-06-10.
self.first = self.issue_shares(date="2026-06-01", from_no=1, to_no=100, rate=10)
self.second = self.issue_shares(date="2026-06-10", from_no=101, to_no=150, rate=12)
@@ -72,7 +72,7 @@ class TestShareLedger(ERPNextTestSuite):
self.assertEqual(data[0][COL_NO_OF_SHARES], 100)
def test_transfer_type_label_when_shareholder_is_seller(self):
buyer = self.create_shareholder("_Test Share Ledger Buyer")
buyer = self.get_shareholder("Thor")
transfer = self.make_transfer(
from_shareholder=self.shareholder,
to_shareholder=buyer,
@@ -87,7 +87,7 @@ class TestShareLedger(ERPNextTestSuite):
self.assertEqual(row[COL_TRANSFER_TYPE], f"Transfer to {buyer}")
def test_transfer_type_label_when_shareholder_is_buyer(self):
seller = self.create_shareholder("_Test Share Ledger Seller")
seller = self.get_shareholder("Hulk")
# the seller must own shares before it can transfer them
self.issue_shares(date="2026-06-12", from_no=201, to_no=300, rate=10, shareholder=seller)
transfer = self.make_transfer(
@@ -119,15 +119,8 @@ class TestShareLedger(ERPNextTestSuite):
self.assertIsNotNone(row, f"Share Transfer {transfer_name} missing from ledger")
return row
def create_shareholder(self, title):
doc = frappe.get_doc(
{
"doctype": "Shareholder",
"title": title,
"company": COMPANY,
}
).insert()
return doc.name
def get_shareholder(self, title):
return frappe.db.get_value("Shareholder", {"title": title, "company": COMPANY}, "name")
def issue_shares(self, date, from_no, to_no, rate, shareholder=None):
doc = frappe.get_doc(

File diff suppressed because it is too large Load Diff

View File

@@ -4,25 +4,9 @@
{
"chart_name": "Profit and Loss",
"label": "Profit and Loss"
},
{
"chart_name": "Accounts Receivable Ageing",
"label": "Accounts Receivable Ageing"
},
{
"chart_name": "Accounts Payable Ageing",
"label": "Accounts Payable Ageing"
},
{
"chart_name": "Bank Balance",
"label": "Bank Balance"
},
{
"chart_name": "Budget Variance",
"label": "Budget Variance"
}
],
"content": "[{\"id\":\"acc_ov_hdr1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Accounting Overview</b></span>\",\"col\":12}},{\"id\":\"acc_ov_nc01\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Bills\",\"col\":3}},{\"id\":\"acc_ov_nc02\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Bills\",\"col\":3}},{\"id\":\"acc_ov_nc03\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Payment\",\"col\":3}},{\"id\":\"acc_ov_nc04\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Payment\",\"col\":3}},{\"id\":\"acc_ov_ch01\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"acc_ov_ch02\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Accounts Receivable Ageing\",\"col\":6}},{\"id\":\"acc_ov_ch03\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Accounts Payable Ageing\",\"col\":6}},{\"id\":\"acc_ov_ch04\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Bank Balance\",\"col\":6}},{\"id\":\"acc_ov_ch05\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Budget Variance\",\"col\":6}}]",
"content": "[{\"id\": \"a17de17773\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}, {\"id\": \"9a0e234f25\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Bills\", \"col\": 4}}, {\"id\": \"58b384d2dd\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Incoming Bills\", \"col\": 4}}, {\"id\": \"575d11919a\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Incoming Payment\", \"col\": 4}}]",
"creation": "2026-07-14 12:00:00",
"custom_blocks": [],
"docstatus": 0,
@@ -36,27 +20,23 @@
"label": "Accounting",
"link_type": "DocType",
"links": [],
"modified": "2026-07-14 14:28:55.763394",
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Accounting",
"number_cards": [
{
"label": "Outgoing Bills",
"label": "Total Outgoing Bills",
"number_card_name": "Total Outgoing Bills"
},
{
"label": "Incoming Bills",
"label": "Total Incoming Bills",
"number_card_name": "Total Incoming Bills"
},
{
"label": "Incoming Payment",
"label": "Total Incoming Payment",
"number_card_name": "Total Incoming Payment"
},
{
"label": "Outgoing Payment",
"number_card_name": "Total Outgoing Payment"
}
],
"owner": "Administrator",

View File

@@ -2,11 +2,11 @@
"app": "erpnext",
"charts": [
{
"chart_name": "Profit and Loss",
"label": "Profit and Loss"
"chart_name": "Budget Variance",
"label": "Budget Variance"
}
],
"content": "[{\"id\":\"tS7ZWzC24I\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"8Ej2KxPxOt\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Ledgers\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"content": "[{\"id\": \"12e194822e\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Budget Variance\", \"col\": 12}}, {\"id\": \"1e3181e0cb\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Bills\", \"col\": 4}}, {\"id\": \"94f620327b\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Incoming Bills\", \"col\": 4}}, {\"id\": \"e3e5de6c45\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Payment\", \"col\": 4}}]",
"creation": "2024-01-05 16:09:16.766939",
"custom_blocks": [],
"docstatus": 0,
@@ -18,260 +18,26 @@
"indicator_color": "",
"is_hidden": 0,
"label": "Financial Reports",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Profitability",
"link_count": 0,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Gross Profit",
"link_count": 0,
"link_to": "Gross Profit",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Profitability Analysis",
"link_count": 0,
"link_to": "Profitability Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Invoice Trends",
"link_count": 0,
"link_to": "Sales Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Purchase Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Invoice Trends",
"link_count": 0,
"link_to": "Purchase Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Financial Statements",
"link_count": 5,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Trial Balance",
"link_count": 0,
"link_to": "Trial Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Profit and Loss Statement",
"link_count": 0,
"link_to": "Profit and Loss Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Balance Sheet",
"link_count": 0,
"link_to": "Balance Sheet",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Cash Flow",
"link_count": 0,
"link_to": "Cash Flow",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Consolidated Financial Statement",
"link_count": 0,
"link_to": "Consolidated Financial Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Ledgers",
"link_count": 3,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "General Ledger",
"link_count": 0,
"link_to": "General Ledger",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Ledger Summary",
"link_count": 0,
"link_to": "Customer Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Supplier Ledger Summary",
"link_count": 0,
"link_to": "Supplier Ledger Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Other Reports",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Trial Balance for Party",
"link_count": 0,
"link_to": "Trial Balance for Party",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Journal Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Payment Period Based On Invoice Date",
"link_count": 0,
"link_to": "Payment Period Based On Invoice Date",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Partners Commission",
"link_count": 0,
"link_to": "Sales Partners Commission",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Customer",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Credit Balance",
"link_count": 0,
"link_to": "Customer Credit Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Payment Summary",
"link_count": 0,
"link_to": "Sales Payment Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Address",
"hidden": 0,
"is_query_report": 1,
"label": "Address And Contacts",
"link_count": 0,
"link_to": "Address And Contacts",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "UAE VAT 201",
"link_count": 0,
"link_to": "UAE VAT 201",
"link_type": "Report",
"onboard": 0,
"only_for": "United Arab Emirates",
"type": "Link"
}
],
"modified": "2026-07-03 13:44:08.095321",
"links": [],
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Financial Reports",
"number_cards": [],
"number_cards": [
{
"label": "Total Outgoing Bills",
"number_card_name": "Total Outgoing Bills"
},
{
"label": "Total Incoming Bills",
"number_card_name": "Total Incoming Bills"
},
{
"label": "Total Outgoing Payment",
"number_card_name": "Total Outgoing Payment"
}
],
"owner": "Administrator",
"parent_page": "",
"public": 1,

View File

@@ -2,11 +2,11 @@
"app": "erpnext",
"charts": [
{
"chart_name": "Profit and Loss",
"label": "Profit and Loss"
"chart_name": "Outgoing Bills (Sales Invoice)",
"label": "Outgoing Bills (Sales Invoice)"
}
],
"content": "[{\"id\":\"nDhfcJYbKH\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Outgoing Payment\",\"col\":3}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Tax Masters\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Banking\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}}]",
"content": "[{\"id\": \"89faa04783\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Bills (Sales Invoice)\", \"col\": 12}}, {\"id\": \"aa2a9ef0d3\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Bills\", \"col\": 4}}, {\"id\": \"f24f8645d6\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Incoming Bills\", \"col\": 4}}, {\"id\": \"a2cd2dc7f7\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Payment\", \"col\": 4}}]",
"creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [],
"docstatus": 0,
@@ -18,595 +18,23 @@
"indicator_color": "",
"is_hidden": 0,
"label": "Invoicing",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Multi Currency",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Currency",
"link_count": 0,
"link_to": "Currency",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Currency Exchange",
"link_count": 0,
"link_to": "Currency Exchange",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Exchange Rate Revaluation",
"link_count": 0,
"link_to": "Exchange Rate Revaluation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Subscription Management",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Subscription Plan",
"link_count": 0,
"link_to": "Subscription Plan",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Subscription",
"link_count": 0,
"link_to": "Subscription",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Subscription Settings",
"link_count": 0,
"link_to": "Subscription Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Share Management",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Shareholder",
"link_count": 0,
"link_to": "Shareholder",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Share Transfer",
"link_count": 0,
"link_to": "Share Transfer",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Share Transfer",
"hidden": 0,
"is_query_report": 1,
"label": "Share Ledger",
"link_count": 0,
"link_to": "Share Ledger",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Share Transfer",
"hidden": 0,
"is_query_report": 1,
"label": "Share Balance",
"link_count": 0,
"link_to": "Share Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Cost Center and Budgeting",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Chart of Cost Centers",
"link_count": 0,
"link_to": "Cost Center",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Budget",
"link_count": 0,
"link_to": "Budget",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Dimension",
"link_count": 0,
"link_to": "Accounting Dimension",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Cost Center",
"hidden": 0,
"is_query_report": 0,
"label": "Cost Center Allocation",
"link_count": 0,
"link_to": "Cost Center Allocation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Cost Center",
"hidden": 0,
"is_query_report": 1,
"label": "Budget Variance Report",
"link_count": 0,
"link_to": "Budget Variance Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Monthly Distribution",
"link_count": 0,
"link_to": "Monthly Distribution",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Opening and Closing",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"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": 0,
"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": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Period Closing Voucher",
"link_count": 0,
"link_to": "Period Closing Voucher",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Banking",
"link_count": 6,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank",
"link_count": 0,
"link_to": "Bank",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Account",
"link_count": 0,
"link_to": "Bank Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Clearance",
"link_count": 0,
"link_to": "Bank Clearance",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bank Reconciliation Tool",
"link_count": 0,
"link_to": "Bank Reconciliation Tool",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "GL Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Bank Reconciliation Statement",
"link_count": 0,
"link_to": "Bank Reconciliation Statement",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Plaid Settings",
"link_count": 0,
"link_to": "Plaid Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Tax Masters",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Sales Taxes and Charges Template",
"link_count": 0,
"link_to": "Sales Taxes and Charges Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Taxes and Charges Template",
"link_count": 0,
"link_to": "Purchase Taxes and Charges Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Item Tax Template",
"link_count": 0,
"link_to": "Item Tax Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Tax Category",
"link_count": 0,
"link_to": "Tax Category",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Tax Rule",
"link_count": 0,
"link_to": "Tax Rule",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Tax Withholding Category",
"link_count": 0,
"link_to": "Tax Withholding Category",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Lower Deduction Certificate",
"link_count": 0,
"link_to": "Lower Deduction Certificate",
"link_type": "DocType",
"onboard": 0,
"only_for": "India",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Masters",
"link_count": 8,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"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,
"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": "Accounts Settings",
"link_count": 0,
"link_to": "Accounts Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Fiscal Year",
"link_count": 0,
"link_to": "Fiscal Year",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Dimension",
"link_count": 0,
"link_to": "Accounting Dimension",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Finance Book",
"link_count": 0,
"link_to": "Finance Book",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Accounting Period",
"link_count": 0,
"link_to": "Accounting Period",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Payment Term",
"link_count": 0,
"link_to": "Payment Term",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 5,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payment Entry",
"link_count": 0,
"link_to": "Payment Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry",
"link_count": 0,
"link_to": "Journal Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Journal Entry Template",
"link_count": 0,
"link_to": "Journal Entry Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Terms and Conditions",
"link_count": 0,
"link_to": "Terms and Conditions",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Mode of Payment",
"link_count": 0,
"link_to": "Mode of Payment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2026-07-03 13:44:08.471142",
"links": [],
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Invoicing",
"number_cards": [
{
"label": "Outgoing Bills",
"label": "Total Outgoing Bills",
"number_card_name": "Total Outgoing Bills"
},
{
"label": "Incoming Bills",
"label": "Total Incoming Bills",
"number_card_name": "Total Incoming Bills"
},
{
"label": "Incoming Payment",
"number_card_name": "Total Incoming Payment"
},
{
"label": "Outgoing Payment",
"label": "Total Outgoing Payment",
"number_card_name": "Total Outgoing Payment"
}
],

View File

@@ -1,7 +1,12 @@
{
"app": "erpnext",
"charts": [],
"content": "[]",
"charts": [
{
"chart_name": "Bank Balance",
"label": "Bank Balance"
}
],
"content": "[{\"id\": \"8ad97059fb\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Bank Balance\", \"col\": 12}}, {\"id\": \"0290c3d3fb\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Incoming Payment\", \"col\": 4}}, {\"id\": \"4751e5b274\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Payment\", \"col\": 4}}, {\"id\": \"4852db07c3\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Outgoing Bills\", \"col\": 4}}]",
"creation": "2026-06-11 11:51:21.886461",
"custom_blocks": [],
"docstatus": 0,
@@ -15,12 +20,25 @@
"label": "Payments",
"link_type": "DocType",
"links": [],
"modified": "2026-07-14 12:00:00.000000",
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "Accounts",
"module_onboarding": "Accounting Onboarding",
"name": "Payments",
"number_cards": [],
"number_cards": [
{
"label": "Total Incoming Payment",
"number_card_name": "Total Incoming Payment"
},
{
"label": "Total Outgoing Payment",
"number_card_name": "Total Outgoing Payment"
},
{
"label": "Total Outgoing Bills",
"number_card_name": "Total Outgoing Bills"
}
],
"owner": "Administrator",
"public": 1,
"quick_lists": [],

View File

@@ -2109,13 +2109,17 @@ def create_asset_category(enable_cwip=1):
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0, asset_category=None):
item_code = item_code or "Macbook Pro"
if frappe.db.exists("Item", item_code):
return frappe.get_doc("Item", item_code)
meta = frappe.get_meta("Asset")
naming_series = meta.get_field("naming_series").options.splitlines()[0] or "ACC-ASS-.YYYY.-"
try:
item = frappe.get_doc(
{
"doctype": "Item",
"item_code": item_code or "Macbook Pro",
"item_code": item_code,
"item_name": "Macbook Pro",
"description": "Macbook Pro Retina Display",
"asset_category": asset_category or "Computers",

View File

@@ -54,14 +54,16 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer):
for item_row in doc.stock_items:
sle_list = self.sle_map.get(item_row.name)
if sle_list:
_inv_dict = doc.get_inventory_account_dict(item_row, self.inventory_account_map)
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, self.precision)
if erpnext.is_perpetual_inventory_enabled(doc.company):
_inv_dict = doc.get_inventory_account_dict(item_row, self.inventory_account_map)
account = _inv_dict["account"]
account_currency = _inv_dict["account_currency"]
else:
account = doc.get_company_default("default_expense_account")
account_currency = None
target_against.add(account)
gl_entries.append(
@@ -74,7 +76,7 @@ class AssetCapitalizationGLComposer(BaseStockGLComposer):
"remarks": doc.get("remarks") or "Accounting Entry for Stock",
"credit": -1 * stock_value_difference,
},
_inv_dict["account_currency"],
account_currency,
item=item_row,
)
)

View File

@@ -440,7 +440,11 @@ def create_asset_capitalization(**args):
target_asset = frappe.get_doc("Asset", args.target_asset) if args.target_asset else frappe._dict()
target_item_code = target_asset.item_code or args.target_item_code
company = target_asset.company or args.company or "_Test Company"
warehouse = args.warehouse or create_warehouse("_Test Warehouse", company=company)
warehouse = args.warehouse or (
"_Test Warehouse - _TC"
if company == "_Test Company"
else create_warehouse("_Test Warehouse", company=company)
)
source_warehouse = args.source_warehouse or warehouse
asset_capitalization = frappe.new_doc("Asset Capitalization")

View File

@@ -20,7 +20,6 @@ from erpnext.assets.doctype.asset.test_asset import (
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
get_serial_nos_from_bundle,
make_serial_batch_bundle,
@@ -32,7 +31,6 @@ class TestAssetRepair(ERPNextTestSuite):
def setUp(self):
self.load_test_records("Stock Entry")
set_depreciation_settings_in_company()
create_item("_Test Stock Item")
def test_asset_status(self):
date = nowdate()

View File

@@ -0,0 +1,371 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "archive",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "house",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Assets",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "chart-column",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Asset",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "laptop",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset",
"link_to": "Asset",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "trending-down",
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "sprout",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Capitalization",
"link_to": "Asset Capitalization",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "move-horizontal",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Movement",
"link_to": "Asset Movement",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "rocket",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Maintenance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Maintenance",
"link_to": "Asset Maintenance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Repair",
"link_to": "Asset Repair",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "sheet",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Reports",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Maintenance",
"link_to": "Asset Maintenance",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Activity",
"link_to": "Asset Activity",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "database",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Setup",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Item",
"link_to": "Item",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Asset Category",
"link_to": "Asset Category",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Location",
"link_to": "Location",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "settings",
"indent": 0,
"is_default_module": 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"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "Assets"
}

View File

@@ -6,7 +6,7 @@
"label": "Asset Value Analytics"
}
],
"content": "[{\"id\":\"Q-Cl7bMXDm\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"id\":\"gsSQjvl0Tx\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"xRYRq1sW1O\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"Kx2j5N9BKZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"id\":\"jeNsxtLaH3\",\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"id\":\"EX5e3NvL51\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"content": "[{\"id\": \"91d49bd2aa\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Asset Value Analytics\", \"col\": 12}}, {\"id\": \"6e2f5c71fd\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Total Assets\", \"col\": 4}}, {\"id\": \"812fb9cc2a\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Asset Value\", \"col\": 4}}, {\"id\": \"f8f0df5c57\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"New Assets (This Year)\", \"col\": 4}}]",
"creation": "2020-03-02 15:43:27.634865",
"custom_blocks": [],
"docstatus": 0,
@@ -17,194 +17,26 @@
"idx": 0,
"is_hidden": 0,
"label": "Assets",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Assets",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Asset",
"link_count": 0,
"link_to": "Asset",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Location",
"link_count": 0,
"link_to": "Location",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Category",
"link_count": 0,
"link_to": "Asset Category",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Movement",
"link_count": 0,
"link_to": "Asset Movement",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Maintenance",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Maintenance Team",
"link_count": 0,
"link_to": "Asset Maintenance Team",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Asset Maintenance Team",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Maintenance",
"link_count": 0,
"link_to": "Asset Maintenance",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Asset Maintenance",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Maintenance Log",
"link_count": 0,
"link_to": "Asset Maintenance Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Asset",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Value Adjustment",
"link_count": 0,
"link_to": "Asset Value Adjustment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Asset",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Repair",
"link_count": 0,
"link_to": "Asset Repair",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Asset",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Capitalization",
"link_count": 0,
"link_to": "Asset Capitalization",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Asset",
"hidden": 0,
"is_query_report": 1,
"label": "Asset Depreciation Ledger",
"link_count": 0,
"link_to": "Asset Depreciation Ledger",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Asset",
"hidden": 0,
"is_query_report": 1,
"label": "Asset Depreciations and Balances",
"link_count": 0,
"link_to": "Asset Depreciations and Balances",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Asset Maintenance",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Maintenance",
"link_count": 0,
"link_to": "Asset Maintenance",
"link_type": "Report",
"onboard": 0,
"report_ref_doctype": "Asset Maintenance",
"type": "Link"
},
{
"dependencies": "Asset Activity",
"hidden": 0,
"is_query_report": 0,
"label": "Asset Activity",
"link_count": 0,
"link_to": "Asset Activity",
"link_type": "Report",
"onboard": 0,
"report_ref_doctype": "Asset Activity",
"type": "Link"
}
],
"modified": "2026-07-03 13:44:08.417956",
"links": [],
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "Assets",
"module_onboarding": "Asset Onboarding",
"name": "Assets",
"number_cards": [],
"number_cards": [
{
"label": "Total Assets",
"number_card_name": "Total Assets"
},
{
"label": "Asset Value",
"number_card_name": "Asset Value"
},
{
"label": "New Assets (This Year)",
"number_card_name": "New Assets (This Year)"
}
],
"owner": "Administrator",
"parent_page": "",
"public": 1,

View File

@@ -0,0 +1,48 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "layers",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Bulk Transaction Log Detail",
"link_to": "Bulk Transaction Log Detail",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Bulk Transaction Log",
"link_to": "Bulk Transaction Log",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "Bulk Transaction",
"name": "Bulk Transaction",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "Bulk Transaction"
}

View File

@@ -378,9 +378,7 @@ class TestPurchaseOrder(ERPNextTestSuite):
po.submit()
first_item_of_po = po.get("items")[0]
company_default = frappe.db.get_value("Company", po.company, "default_warehouse")
frappe.db.set_value("Company", po.company, "default_warehouse", None)
self.addCleanup(frappe.db.set_value, "Company", po.company, "default_warehouse", company_default)
def get_trans_items(item_code):
return json.dumps(
@@ -794,14 +792,9 @@ class TestPurchaseOrder(ERPNextTestSuite):
self.assertRaises(frappe.ValidationError, below_minimum.insert)
def test_marginal_min_order_qty_overage_toast(self):
original_precision = frappe.db.get_default("float_precision")
frappe.db.set_default("float_precision", "3")
self.addCleanup(frappe.db.set_default, "float_precision", original_precision)
if not frappe.db.exists("UOM", "Gram"):
frappe.get_doc({"doctype": "UOM", "uom_name": "Gram"}).insert()
item_doc = make_item(properties={"min_order_qty": 50000, "stock_uom": "Gram"})
item_doc = make_item(properties={"min_order_qty": 50000, "stock_uom": "_Test UOM 1"})
item_doc.append("uoms", {"uom": "Pound", "conversion_factor": 453.592292197})
item_doc.save()
item = item_doc.name
@@ -1799,25 +1792,11 @@ def create_po_for_sc_testing():
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
company = "_Test Company with perpetual inventory"
create_internal_customer(
"_Test Internal Customer 2",
company,
company,
)
create_internal_supplier(
"_Test Internal Supplier 2",
company,
company,
)
warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company)
create_warehouse("_Test Internal Warehouse GIT", company=company)

View File

@@ -15,6 +15,7 @@ from erpnext.buying.doctype.request_for_quotation.test_request_for_quotation imp
from erpnext.buying.doctype.supplier_quotation.mapper import make_purchase_order
from erpnext.buying.doctype.supplier_quotation.supplier_quotation import set_expired_status
from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate
from erpnext.tests.assertions import assert_raises_with_savepoint
from erpnext.tests.utils import ERPNextTestSuite
@@ -161,12 +162,9 @@ class TestPurchaseOrder(ERPNextTestSuite):
]
)
frappe.db.savepoint("before_cancel")
# check if item having purchase order can be removed
self.assertRaises(
frappe.LinkExistsError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name
)
frappe.db.rollback(save_point="before_cancel")
with assert_raises_with_savepoint(self, frappe.LinkExistsError):
update_child_qty_rate("Supplier Quotation", trans_item, sq.name)
trans_item = json.dumps(
[

View File

@@ -0,0 +1,642 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "shopping-cart",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "house",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "Buying",
"link_type": "Workspace",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "chart-column",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Dashboard",
"link_to": "Buying",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "notepad-text",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Material Request",
"link_to": "Material Request",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "git-pull-request-arrow",
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "book-open-text",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Supplier Quotation",
"link_to": "Supplier Quotation",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "receipt-text",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Purchase Order",
"link_to": "Purchase Order",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "scale",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Purchase Invoice",
"link_to": "Purchase Invoice",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "database",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Setup",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Supplier",
"link_to": "Supplier",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Supplier Group",
"link_to": "Supplier Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Item",
"link_to": "Item",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Price List",
"link_to": "Price List",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Address",
"link_to": "Address",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Contacts",
"link_to": "Contact",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Supplier Scorecard",
"link_to": "Supplier Scorecard",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "rocket",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Subcontracting",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"icon": "folder-tree",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Subcontracting BOM",
"link_to": "Subcontracting BOM",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Subcontracting Delivery",
"link_to": "Stock Entry",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Subcontracting Order",
"link_to": "Subcontracting Order",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Subcontracting Receipt",
"link_to": "Subcontracting Receipt",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "sheet",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Reports",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Purchase Analytics",
"link_to": "Purchase Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Procurement Tracker",
"link_to": "Procurement Tracker",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "settings",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Settings",
"link_to": "Buying Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "Buying"
}

View File

@@ -2,11 +2,11 @@
"app": "erpnext",
"charts": [
{
"chart_name": "Purchase Order Trends",
"label": "Purchase Order Trends"
"chart_name": "Purchase Order Analysis",
"label": "Purchase Order Analysis"
}
],
"content": "[{\"id\":\"j3dJGo8Ok6\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"id\":\"k75jSq2D6Z\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Purchase Orders Count\",\"col\":4}},{\"id\":\"UPXys0lQLj\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Purchase Amount\",\"col\":4}},{\"id\":\"yQGK3eb2hg\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Average Order Values\",\"col\":4}},{\"id\":\"oN7lXSwQji\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"Xe2GVLOq8J\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"QwqyG6XuUt\",\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"id\":\"bTPjOxC_N_\",\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"id\":\"87ht0HIneb\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"EDOsBOmwgw\",\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"id\":\"oWNNIiNb2i\",\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"id\":\"7F_13-ihHB\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"pfwiLvionl\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"id\":\"8ySDy6s4qn\",\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
"content": "[{\"id\": \"983cd8ea63\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Purchase Order Analysis\", \"col\": 12}}, {\"id\": \"d88a483751\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Purchase Orders to Receive\", \"col\": 4}}, {\"id\": \"22a7c36674\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Purchase Orders to Bill\", \"col\": 4}}, {\"id\": \"5fe5b5c369\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Annual Purchase\", \"col\": 4}}]",
"creation": "2020-01-28 11:50:26.195467",
"custom_blocks": [],
"docstatus": 0,
@@ -17,507 +17,24 @@
"idx": 0,
"is_hidden": 0,
"label": "Buying",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Buying",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Material Request",
"link_count": 0,
"link_to": "Material Request",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item, Supplier",
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Order",
"link_count": 0,
"link_to": "Purchase Order",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item, Supplier",
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Invoice",
"link_count": 0,
"link_to": "Purchase Invoice",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item, Supplier",
"hidden": 0,
"is_query_report": 0,
"label": "Request for Quotation",
"link_count": 0,
"link_to": "Request for Quotation",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item, Supplier",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Quotation",
"link_count": 0,
"link_to": "Supplier Quotation",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Items & Pricing",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"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": "Item Price",
"link_count": 0,
"link_to": "Item Price",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Price List",
"link_count": 0,
"link_to": "Price List",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Product Bundle",
"link_count": 0,
"link_to": "Product Bundle",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Item Group",
"link_count": 0,
"link_to": "Item Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Promotional Scheme",
"link_count": 0,
"link_to": "Promotional Scheme",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Pricing Rule",
"link_count": 0,
"link_to": "Pricing Rule",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Buying Settings",
"link_count": 0,
"link_to": "Buying Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Purchase Taxes and Charges Template",
"link_count": 0,
"link_to": "Purchase Taxes and Charges Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Terms and Conditions Template",
"link_count": 0,
"link_to": "Terms and Conditions",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Supplier",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"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,
"is_query_report": 0,
"label": "Supplier Group",
"link_count": 0,
"link_to": "Supplier Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contact",
"link_count": 0,
"link_to": "Contact",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Address",
"link_count": 0,
"link_to": "Address",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Scorecard",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Scorecard",
"link_count": 0,
"link_to": "Supplier Scorecard",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Scorecard Variable",
"link_count": 0,
"link_to": "Supplier Scorecard Variable",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Scorecard Criteria",
"link_count": 0,
"link_to": "Supplier Scorecard Criteria",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Supplier Scorecard Standing",
"link_count": 0,
"link_to": "Supplier Scorecard Standing",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Key Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Analytics",
"link_count": 0,
"link_to": "Purchase Analytics",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Order Analysis",
"link_count": 0,
"link_to": "Purchase Order Analysis",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Items to Order and Receive",
"link_count": 0,
"link_to": "Requested Items to Order and Receive",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Order Trends",
"link_count": 0,
"link_to": "Purchase Order Trends",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Procurement Tracker",
"link_count": 0,
"link_to": "Procurement Tracker",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Other Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Items To Be Requested",
"link_count": 0,
"link_to": "Items To Be Requested",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Item-wise Purchase History",
"link_count": 0,
"link_to": "Item-wise Purchase History",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Receipt Trends",
"link_count": 0,
"link_to": "Purchase Receipt Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Purchase Invoice Trends",
"link_count": 0,
"link_to": "Purchase Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Subcontracted Raw Materials To Be Transferred",
"link_count": 0,
"link_to": "Subcontracted Raw Materials To Be Transferred",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Subcontracted Item To Be Received",
"link_count": 0,
"link_to": "Subcontracted Item To Be Received",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Supplier Quotation Comparison",
"link_count": 0,
"link_to": "Supplier Quotation Comparison",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Material Requests for which Supplier Quotations are not created",
"link_count": 0,
"link_to": "Material Requests for which Supplier Quotations are not created",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Supplier Addresses And Contacts",
"link_count": 0,
"link_to": "Address And Contacts",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Regional",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Import Supplier Invoice",
"link_count": 0,
"link_to": "Import Supplier Invoice",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2026-07-14 12:00:00.000000",
"links": [],
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "Buying",
"module_onboarding": "Buying Onboarding",
"name": "Buying",
"number_cards": [
{
"label": "Purchase Orders Count",
"number_card_name": "Purchase Orders Count"
"label": "Purchase Orders to Receive",
"number_card_name": "Purchase Orders to Receive"
},
{
"label": "Total Purchase Amount",
"number_card_name": "Total Purchase Amount"
"label": "Purchase Orders to Bill",
"number_card_name": "Purchase Orders to Bill"
},
{
"label": "Average Order Values",
"number_card_name": "Average Order Values"
"label": "Annual Purchase",
"number_card_name": "Annual Purchase"
}
],
"owner": "Administrator",

View File

@@ -0,0 +1,33 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "messages-square",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Communication Medium",
"link_to": "Communication Medium",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "Communication",
"name": "Communication",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "Communication"
}

View File

@@ -53,7 +53,6 @@ class TestAccountsController(ERPNextTestSuite):
self.item = "_Test Item"
self.customer = "_Test Customer USD"
self.supplier = "_Test Supplier USD"
self.create_account()
frappe.flags.is_reverse_depr_entry = False
def create_account(self):
@@ -99,6 +98,7 @@ class TestAccountsController(ERPNextTestSuite):
setattr(self, x.attribute_name, acc.name)
def setup_advance_accounts_in_party_master(self):
self.create_account()
company = frappe.get_doc("Company", self.company)
company.book_advance_payments_in_separate_party_account = 1
company.save()

View File

@@ -7,15 +7,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestSalesAndPurchaseReturn(ERPNextTestSuite):
@staticmethod
def _cancel_and_delete(doctype, name):
if not frappe.db.exists(doctype, name):
return
doc = frappe.get_doc(doctype, name)
if doc.docstatus == 1:
doc.cancel()
frappe.delete_doc(doctype, name, force=1)
def test_sales_return_validates_against_original(self):
# Submitting a return Delivery Note runs validate_returned_items (Item / Packed Item lookups
# via frappe.get_all) and get_already_returned_items (qb GROUP BY of the returned qty) -- both
@@ -24,16 +15,13 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
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
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
dn = create_delivery_note(qty=5)
self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name)
return_dn = make_sales_return(dn.name)
return_dn.insert()
return_dn.submit()
self.addCleanup(self._cancel_and_delete, "Delivery Note", return_dn.name)
self.assertEqual(return_dn.is_return, 1)
self.assertEqual(return_dn.items[0].qty, -5)
@@ -44,7 +32,6 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
pi = make_purchase_invoice(qty=10)
self.addCleanup(self._cancel_and_delete, "Purchase Invoice", pi.name)
return_pi = make_purchase_invoice(
is_return=1,
@@ -66,7 +53,6 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
pi.items[0].item_code = ""
pi.save()
pi.submit()
self.addCleanup(self._cancel_and_delete, "Purchase Invoice", pi.name)
return_pi = make_purchase_invoice(
item_name="_Test Item",
@@ -86,11 +72,9 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
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
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
dn = create_delivery_note(qty=5)
self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name)
return_dn = make_sales_return(dn.name)
return_dn.items[0].qty = 0
@@ -104,7 +88,6 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
si = create_sales_invoice(qty=10)
self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name)
return_si = make_return_doc(si.doctype, si.name)
return_si.items[0].qty = 0
@@ -131,14 +114,11 @@ class TestSalesAndPurchaseReturn(ERPNextTestSuite):
si.items[0].stock_uom = "Kg"
si.items[0].conversion_factor = 0.013888889
si.save().submit()
self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name)
first_return = make_return_doc(si.doctype, si.name)
first_return.items[0].qty = -24
first_return.save().submit()
self.addCleanup(self._cancel_and_delete, "Sales Invoice", first_return.name)
second_return = make_return_doc(si.doctype, si.name)
self.assertEqual(second_return.items[0].qty, -24)
second_return.save().submit()
self.addCleanup(self._cancel_and_delete, "Sales Invoice", second_return.name)

View File

@@ -7,15 +7,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestSellingControllerConversions(ERPNextTestSuite):
@staticmethod
def _cancel_and_delete(doctype, name):
if not frappe.db.exists(doctype, name):
return
doc = frappe.get_doc(doctype, name)
if doc.docstatus == 1:
doc.cancel()
frappe.delete_doc(doctype, name, force=1)
def test_partial_delivery_updates_sales_order_status(self):
# Submitting a Delivery Note against a Sales Order calls
# SellingController.get_already_delivered_qty / get_so_qty_and_warehouse and StatusUpdater
@@ -24,8 +15,7 @@ class TestSellingControllerConversions(ERPNextTestSuite):
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name)
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=20, basic_rate=100)
so = make_sales_order(qty=10)
@@ -33,7 +23,6 @@ class TestSellingControllerConversions(ERPNextTestSuite):
dn.items[0].qty = 4
dn.insert()
dn.submit()
self.addCleanup(self._cancel_and_delete, "Delivery Note", dn.name)
so.reload()
self.assertEqual(so.per_delivered, 40.0)

View File

@@ -8,15 +8,6 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestStockControllerConversions(ERPNextTestSuite):
@staticmethod
def _cancel_and_delete(doctype, name):
if not frappe.db.exists(doctype, name):
return
doc = frappe.get_doc(doctype, name)
if doc.docstatus == 1:
doc.cancel()
frappe.delete_doc(doctype, name, force=1)
def test_future_sle_exists_detects_later_entries(self):
# future_sle_exists / get_conditions_to_validate_future_sle were converted to query builder
# (Count + Criterion.any). A later SLE for the same item+warehouse must be detected, which
@@ -26,8 +17,7 @@ class TestStockControllerConversions(ERPNextTestSuite):
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
item = make_item("_Test Future SLE Item", {"is_stock_item": 1}).name
se = make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=10, basic_rate=100)
self.addCleanup(self._cancel_and_delete, "Stock Entry", se.name)
make_stock_entry(item_code=item, target="_Test Warehouse - _TC", qty=10, basic_rate=100)
# Pretend a different voucher posts a day earlier for the same item/warehouse: the existing
# (later) SLE must be reported as a future entry.
@@ -52,7 +42,6 @@ class TestStockControllerConversions(ERPNextTestSuite):
posting_date=add_days(today(), -5),
posting_time="01:00:00",
)
self.addCleanup(self._cancel_and_delete, "Stock Entry", opening.name)
return opening
@@ -106,7 +95,6 @@ class TestStockControllerConversions(ERPNextTestSuite):
finally:
stock_ledger.make_entry = original_make_entry
self.addCleanup(self._cancel_and_delete, "Stock Entry", entry.name)
if inject is not None:
self.assertTrue(injected, "the later SL Entry was not written during the submit")
@@ -126,9 +114,6 @@ class TestStockControllerConversions(ERPNextTestSuite):
pluck="name",
)
)
for name in names:
self.addCleanup(frappe.delete_doc, "Repost Item Valuation", name, force=1)
return names
def test_repost_queued_for_entry_backdated_while_its_sl_entries_were_written(self):

View File

@@ -0,0 +1,607 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "handshake",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "chart-column",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Home",
"link_to": "CRM",
"link_type": "Dashboard",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "users-round",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Lead",
"link_to": "Lead",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "lightbulb",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Opportunity",
"link_to": "Opportunity",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "user",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Customer",
"link_to": "Customer",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "sheet",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Reports",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Sales Analytics",
"link_to": "Sales Analytics",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Lead Details",
"link_to": "Lead Details",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Sales Funnel",
"link_to": "sales-funnel",
"link_type": "Page",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Campaign Efficiency",
"link_to": "Campaign Efficiency",
"link_type": "Report",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 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"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "rocket",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Maintenance",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Maintenance Schedule",
"link_to": "Maintenance Schedule",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Maintenance Visit",
"link_to": "Maintenance Visit",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Warranty Claim",
"link_to": "Warranty Claim",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "funnel",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Sales Pipeline",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Contract",
"link_to": "Contract",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Appointment",
"link_to": "Appointment",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Communication",
"link_to": "Communication",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "store",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Campaign",
"link_to": "Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Email Campaign",
"link_to": "Email Campaign",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "SMS Center",
"link_to": "SMS Center",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "SMS Log",
"link_to": "SMS Log",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Email Group",
"link_to": "Email Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "database",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Setup",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Territory",
"link_to": "Territory",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Customer Group",
"link_to": "Customer Group",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Contact",
"link_to": "Contact",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Prospect",
"link_to": "Prospect",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Sales Person",
"link_to": "Sales Person",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Sales Stage",
"link_to": "Sales Stage",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Lead Source",
"link_to": "UTM Source",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "settings",
"indent": 1,
"is_default_module": 0,
"keep_closed": 1,
"label": "Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "CRM Settings",
"link_to": "CRM Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "SMS Settings",
"link_to": "SMS Settings",
"link_type": "DocType",
"open_in_new_tab": 0,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "CRM"
}

View File

@@ -256,6 +256,9 @@ class CRMNote(Document):
@frappe.whitelist()
def edit_note(self, note: str, row_id: str):
# db_update() skips the write check that save() does in add_note/delete_note
self.check_permission("write")
for d in self.notes:
if cstr(d.name) == row_id:
d.note = note

View File

@@ -2,11 +2,11 @@
"app": "erpnext",
"charts": [
{
"chart_name": "Territory Wise Sales",
"label": "Territory Wise Sales"
"chart_name": "Incoming Leads",
"label": "Incoming Leads"
}
],
"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}}]",
"content": "[{\"id\": \"5df9733229\", \"type\": \"chart\", \"data\": {\"chart_name\": \"Incoming Leads\", \"col\": 12}}, {\"id\": \"1f47c95d4e\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Open Opportunity\", \"col\": 4}}, {\"id\": \"9a0668d938\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"New Lead (Last 1 Month)\", \"col\": 4}}, {\"id\": \"096cbb7b12\", \"type\": \"number_card\", \"data\": {\"number_card_name\": \"Won Opportunity (Last 1 Month)\", \"col\": 4}}]",
"creation": "2020-01-23 14:48:30.183272",
"custom_blocks": [],
"docstatus": 0,
@@ -17,415 +17,25 @@
"idx": 0,
"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,
"is_query_report": 1,
"label": "Lead Details",
"link_count": 0,
"link_to": "Lead Details",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Sales Pipeline Analytics",
"link_count": 0,
"link_to": "Sales Pipeline Analytics",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Opportunity Summary by Sales Stage",
"link_count": 0,
"link_to": "Opportunity Summary by Sales Stage",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Sales Funnel",
"link_count": 0,
"link_to": "sales-funnel",
"link_type": "Page",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Lead",
"hidden": 0,
"is_query_report": 1,
"label": "Prospects Engaged But Not Converted",
"link_count": 0,
"link_to": "Prospects Engaged But Not Converted",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Opportunity",
"hidden": 0,
"is_query_report": 1,
"label": "First Response Time for Opportunity",
"link_count": 0,
"link_to": "First Response Time for Opportunity",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Order",
"hidden": 0,
"is_query_report": 1,
"label": "Inactive Customers",
"link_count": 0,
"link_to": "Inactive Customers",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Lead",
"hidden": 0,
"is_query_report": 1,
"label": "Campaign Efficiency",
"link_count": 0,
"link_to": "Campaign Efficiency",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Lead",
"hidden": 0,
"is_query_report": 1,
"label": "Lead Owner Efficiency",
"link_count": 0,
"link_to": "Lead Owner Efficiency",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Maintenance",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Maintenance Schedule",
"link_count": 0,
"link_to": "Maintenance Schedule",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Maintenance Visit",
"link_count": 0,
"link_to": "Maintenance Visit",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Warranty Claim",
"link_count": 0,
"link_to": "Warranty Claim",
"link_type": "DocType",
"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,
"label": "Masters",
"link_count": 7,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Territory",
"link_count": 0,
"link_to": "Territory",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Customer Group",
"link_count": 0,
"link_to": "Customer Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Contact",
"link_count": 0,
"link_to": "Contact",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Prospect",
"link_count": 0,
"link_to": "Prospect",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Person",
"link_count": 0,
"link_to": "Sales Person",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Stage",
"link_count": 0,
"link_to": "Sales Stage",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Lead Source",
"link_count": 0,
"link_to": "UTM Source",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2026-07-03 13:44:08.297053",
"links": [],
"modified": "2026-08-26 18:34:03.000000",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
"number_cards": [],
"number_cards": [
{
"label": "Open Opportunity",
"number_card_name": "Open Opportunity"
},
{
"label": "New Lead (Last 1 Month)",
"number_card_name": "New Lead (Last 1 Month)"
},
{
"label": "Won Opportunity (Last 1 Month)",
"number_card_name": "Won Opportunity (Last 1 Month)"
}
],
"owner": "Administrator",
"parent_page": "",
"public": 1,
@@ -433,40 +43,7 @@
"restrict_to_domain": "",
"roles": [],
"sequence_id": 17.0,
"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"
}
],
"shortcuts": [],
"sidebar_items": [
{
"child": 0,

View File

@@ -0,0 +1,183 @@
{
"app": "erpnext",
"creation": "2026-08-26 23:30:00",
"docstatus": 0,
"doctype": "Dock",
"idx": 0,
"items": [
{
"added": 0,
"hidden": 0,
"icon": "landmark",
"link_to": "Accounts",
"link_type": "Sidebar",
"title": "Accounts"
},
{
"added": 0,
"hidden": 0,
"icon": "handshake",
"link_to": "CRM",
"link_type": "Sidebar",
"title": "CRM"
},
{
"added": 0,
"hidden": 0,
"icon": "shopping-cart",
"link_to": "Buying",
"link_type": "Sidebar",
"title": "Buying"
},
{
"added": 0,
"hidden": 0,
"icon": "folder-kanban",
"link_to": "Projects",
"link_type": "Sidebar",
"title": "Projects"
},
{
"added": 0,
"hidden": 0,
"icon": "store",
"link_to": "Selling",
"link_type": "Sidebar",
"title": "Selling"
},
{
"added": 0,
"hidden": 0,
"icon": "sliders-horizontal",
"link_to": "Setup",
"link_type": "Sidebar",
"title": "Setup"
},
{
"added": 0,
"hidden": 0,
"icon": "building-2",
"link_to": "Manufacturing",
"link_type": "Sidebar",
"title": "Manufacturing"
},
{
"added": 0,
"hidden": 0,
"icon": "package",
"link_to": "Stock",
"link_type": "Sidebar",
"title": "Stock"
},
{
"added": 0,
"hidden": 0,
"icon": "headset",
"link_to": "Support",
"link_type": "Sidebar",
"title": "Support"
},
{
"added": 0,
"hidden": 0,
"icon": "pocket-knife",
"link_to": "Utilities",
"link_type": "Sidebar",
"title": "Utilities"
},
{
"added": 0,
"hidden": 0,
"icon": "archive",
"link_to": "Assets",
"link_type": "Sidebar",
"title": "Assets"
},
{
"added": 0,
"hidden": 0,
"icon": "panels-top-left",
"link_to": "Portal",
"link_type": "Sidebar",
"title": "Portal"
},
{
"added": 0,
"hidden": 0,
"icon": "wrench",
"link_to": "Maintenance",
"link_type": "Sidebar",
"title": "Maintenance"
},
{
"added": 0,
"hidden": 0,
"icon": "globe",
"link_to": "Regional",
"link_type": "Sidebar",
"title": "Regional"
},
{
"added": 0,
"hidden": 0,
"icon": "plug",
"link_to": "ERPNext Integrations",
"link_type": "Sidebar",
"title": "Integrations"
},
{
"added": 0,
"hidden": 0,
"icon": "shield-check",
"link_to": "Quality",
"link_type": "Sidebar",
"title": "Quality"
},
{
"added": 0,
"hidden": 0,
"icon": "messages-square",
"link_to": "Communication",
"link_type": "Sidebar",
"title": "Communication"
},
{
"added": 0,
"hidden": 0,
"icon": "phone",
"link_to": "Telephony",
"link_type": "Sidebar",
"title": "Telephony"
},
{
"added": 0,
"hidden": 0,
"icon": "layers",
"link_to": "Bulk Transaction",
"link_type": "Sidebar",
"title": "Bulk Transaction"
},
{
"added": 0,
"hidden": 0,
"icon": "factory",
"link_to": "Subcontracting",
"link_type": "Sidebar",
"title": "Subcontracting"
},
{
"added": 0,
"hidden": 0,
"icon": "file-code",
"link_to": "EDI",
"link_type": "Sidebar",
"title": "EDI"
}
],
"modified": "2026-08-28 12:00:00.000000",
"modified_by": "Administrator",
"name": "erpnext",
"owner": "Administrator",
"standard": 1,
"user": ""
}

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from contextlib import contextmanager
from unittest.mock import Mock, patch
import frappe
@@ -51,12 +52,9 @@ SAMPLE_GENERICODE = b"""<?xml version="1.0" encoding="UTF-8"?>
class TestCodeListImport(ERPNextTestSuite):
def test_import_genericode_rejects_remote_file_url(self):
self.set_upload_context(
file_name="trusted.xml",
file_url="https://example.com/codelists/trusted.xml",
)
with patch("erpnext.edi.doctype.code_list.code_list_import.requests.get") as mock_get:
with self.upload_context(
file_name="trusted.xml", file_url="https://example.com/codelists/trusted.xml"
), patch("erpnext.edi.doctype.code_list.code_list_import.requests.get") as mock_get:
with self.assertRaisesRegex(
frappe.ValidationError, "Importing Code Lists from remote URLs is not allowed."
):
@@ -65,12 +63,9 @@ class TestCodeListImport(ERPNextTestSuite):
mock_get.assert_not_called()
def test_import_genericode_rejects_file_scheme_url(self):
self.set_upload_context(
file_name="trusted.xml",
file_url="file:///tmp/trusted.xml",
)
with patch("erpnext.edi.doctype.code_list.code_list_import.requests.get") as mock_get:
with self.upload_context(file_name="trusted.xml", file_url="file:///tmp/trusted.xml"), patch(
"erpnext.edi.doctype.code_list.code_list_import.requests.get"
) as mock_get:
with self.assertRaisesRegex(
frappe.ValidationError, "Importing Code Lists from remote URLs is not allowed."
):
@@ -110,36 +105,34 @@ class TestCodeListImport(ERPNextTestSuite):
code_list_import.import_genericode_from_url("https://example.com/codelists/trusted.xml")
def test_import_genericode_from_uploaded_file_returns_metadata(self):
self.set_upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml")
with self.upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml"):
import_result = code_list_import.import_genericode()
import_result = code_list_import.import_genericode()
self.assert_import_response(import_result)
self.assert_import_response(import_result)
file_doc = frappe.get_doc("File", import_result["file"])
self.assertEqual(file_doc.get_content(encodings=()), SAMPLE_GENERICODE)
file_doc = frappe.get_doc("File", import_result["file"])
self.assertEqual(file_doc.get_content(encodings=()), SAMPLE_GENERICODE)
def test_process_genericode_import_reads_file_doc_content(self):
self.set_upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml")
with self.upload_context(content=SAMPLE_GENERICODE, file_name="uploaded_genericode.xml"):
import_result = code_list_import.import_genericode()
count = code_list_import.process_genericode_import(
code_list_name=import_result["code_list"],
file_name=import_result["file"],
code_column="code",
title_column="name",
)
import_result = code_list_import.import_genericode()
count = code_list_import.process_genericode_import(
code_list_name=import_result["code_list"],
file_name=import_result["file"],
code_column="code",
title_column="name",
)
self.assertEqual(count, 3)
self.assertEqual(frappe.db.count("Common Code", {"code_list": import_result["code_list"]}), 3)
self.assertEqual(
frappe.db.get_value(
"Common Code",
{"code_list": import_result["code_list"], "common_code": "A"},
"title",
),
"Alpha",
)
self.assertEqual(count, 3)
self.assertEqual(frappe.db.count("Common Code", {"code_list": import_result["code_list"]}), 3)
self.assertEqual(
frappe.db.get_value(
"Common Code",
{"code_list": import_result["code_list"], "common_code": "A"},
"title",
),
"Alpha",
)
def test_import_genericode_from_local_file_url(self):
source_file = frappe.get_doc(
@@ -150,32 +143,38 @@ class TestCodeListImport(ERPNextTestSuite):
"is_private": 1,
}
).insert()
self.set_upload_context(file_name=source_file.file_name, file_url=source_file.file_url)
with self.upload_context(file_name=source_file.file_name, file_url=source_file.file_url):
import_result = code_list_import.import_genericode()
import_result = code_list_import.import_genericode()
self.assert_import_response(import_result)
self.assert_import_response(import_result)
def set_upload_context(
self,
@staticmethod
@contextmanager
def upload_context(
content: bytes | None = None,
file_name: str = "genericode.xml",
file_url: str | None = None,
docname: str | None = None,
):
attrs = ("form_dict", "uploaded_file", "uploaded_file_url", "uploaded_filename")
originals = {attr: getattr(frappe.local, attr, None) for attr in attrs}
missing = object()
attrs = {
"form_dict": frappe._dict(doctype="Code List", docname=docname),
"uploaded_file": content,
"uploaded_file_url": file_url,
"uploaded_filename": file_name,
}
originals = {key: getattr(frappe.local, key, missing) for key in attrs}
for key, value in attrs.items():
setattr(frappe.local, key, value)
frappe.local.form_dict = frappe._dict(doctype="Code List", docname=docname)
frappe.local.uploaded_file = content
frappe.local.uploaded_file_url = file_url
frappe.local.uploaded_filename = file_name
def restore():
for attr, value in originals.items():
setattr(frappe.local, attr, value)
self.addCleanup(restore)
try:
yield
finally:
for key, value in originals.items():
if value is missing:
delattr(frappe.local, key)
else:
setattr(frappe.local, key, value)
def assert_import_response(self, import_result):
self.assertEqual(

View File

@@ -0,0 +1,48 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "file-code",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Code List",
"link_to": "Code List",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Common Code",
"link_to": "Common Code",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "EDI",
"name": "EDI",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "EDI"
}

View File

@@ -0,0 +1,34 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "plug",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"icon": "settings",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Plaid Settings",
"link_to": "Plaid Settings",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "ERPNext Integrations"
}

View File

@@ -70,6 +70,8 @@ after_install = "erpnext.setup.install.after_install"
after_app_install = "erpnext.setup.install.after_app_install"
after_app_uninstall = "erpnext.setup.install.after_app_uninstall"
before_tests = "erpnext.tests.utils.bootstrap_test_data"
boot_session = "erpnext.startup.boot.boot_session"
notification_config = "erpnext.startup.notifications.get_notification_config"
get_help_messages = "erpnext.utilities.activation.get_help_messages"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -18,25 +18,6 @@ class TestMaintenanceSchedule(ERPNextTestSuite):
def setUp(self):
self.load_test_records("Stock Entry")
@classmethod
def make_sales_person(cls):
records = [
{
"doctype": "Sales Person",
"is_group": 0,
"parent_sales_person": "Sales Team",
"sales_person_name": "_Test Sales Person",
},
]
cls.sales_person = []
for x in records:
if not frappe.db.exists("Sales Person", {"sales_person_name": x.get("sales_person_name")}):
cls.sales_person.append(frappe.get_doc(x).insert())
else:
cls.sales_person.append(
frappe.get_doc("Sales Person", {"sales_person_name": x.get("sales_person_name")})
)
def test_events_should_be_created_and_deleted(self):
ms = make_maintenance_schedule()
ms.generate_schedule()

View File

@@ -9,7 +9,7 @@ from erpnext.tests.utils import ERPNextTestSuite
class TestMaintenanceVisit(ERPNextTestSuite):
def setUp(self):
self.sales_person = make_sales_person("_Test Maintenance Service Person")
self.sales_person = frappe.get_doc("Sales Person", "_Test Sales Person")
def make_warranty_claim(self):
# Warranty Claim is not submittable; it provides a real target for the
@@ -129,14 +129,6 @@ class TestMaintenanceVisit(ERPNextTestSuite):
self.assertIsNone(claim.resolution_date)
def make_sales_person(name):
sales_person = frappe.get_doc({"doctype": "Sales Person", "sales_person_name": name})
sales_person.insert(ignore_if_duplicate=True)
if not sales_person.name:
sales_person = frappe.get_doc("Sales Person", {"sales_person_name": name})
return sales_person
def make_maintenance_visit():
mv = frappe.new_doc("Maintenance Visit")
mv.company = "_Test Company"
@@ -144,8 +136,6 @@ def make_maintenance_visit():
mv.mntc_date = today()
mv.completion_status = "Partially Completed"
sales_person = make_sales_person("Dwight Schrute")
mv.append(
"purposes",
{
@@ -153,7 +143,7 @@ def make_maintenance_visit():
"sales_person": "Sales Team",
"description": "Test Item",
"work_done": "Test Work Done",
"service_person": sales_person.name,
"service_person": "_Test Sales Person",
},
)
mv.insert(ignore_permissions=True)

View File

@@ -0,0 +1,78 @@
{
"app": "erpnext",
"creation": "2026-08-16 00:00:00.000000",
"docstatus": 0,
"doctype": "Sidebar",
"header_icon": "wrench",
"idx": 0,
"items": [
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Maintenance Schedule",
"link_to": "Maintenance Schedule",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Maintenance Visit",
"link_to": "Maintenance Visit",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
},
{
"added": 0,
"child": 0,
"collapsible": 1,
"hidden": 0,
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Reports",
"link_type": "DocType",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Section Break"
},
{
"added": 0,
"child": 1,
"collapsible": 1,
"hidden": 0,
"icon": "table",
"indent": 0,
"is_default_module": 0,
"keep_closed": 0,
"label": "Maintenance Schedules",
"link_to": "Maintenance Schedules",
"link_type": "Report",
"open_in_new_tab": 1,
"show_arrow": 0,
"type": "Link"
}
],
"modified": "2026-08-16 00:00:00.000000",
"modified_by": "Administrator",
"module": "Maintenance",
"name": "Maintenance",
"owner": "Administrator",
"sequence_id": 0.0,
"standard": 1,
"title": "Maintenance"
}

View File

@@ -1116,13 +1116,6 @@ frappe.ui.form.on("BOM", {
doc.qty = 1.0;
this.grid.set_value("qty", 1.0, doc);
},
get_query() {
return {
filters: {
name: ["!=", row.finished_good],
},
};
},
},
{
label: __("Qty"),

View File

@@ -339,6 +339,7 @@ class BOM(WebsiteGenerator):
self.validate_uoms()
self.set_default_uom()
self.validate_semi_finished_goods()
self.validate_batch_split_operations()
self.validate_secondary_items()
self.set_fg_cost_allocation()
self.validate_total_cost_allocation()
@@ -395,6 +396,36 @@ class BOM(WebsiteGenerator):
),
)
def validate_batch_split_operations(self):
for row in self.operations:
if not row.get("batch_split"):
continue
if not self.track_semi_finished_goods:
frappe.throw(
_(
"Row #{0}: Batch Split is only supported when 'Track Semi Finished Goods' is enabled."
).format(row.idx)
)
if flt(row.weight_per_piece) <= 0:
frappe.throw(
_("Row #{0}: Weight Per Piece is required for the Batch Split operation {1}.").format(
row.idx, bold(row.operation)
)
)
if row.finished_good:
item_details = frappe.get_cached_value(
"Item", row.finished_good, ["has_batch_no", "create_new_batch"], as_dict=1
)
if not item_details.has_batch_no or not item_details.create_new_batch:
frappe.throw(
_(
"Row #{0}: The item {1} must have 'Has Batch No' and 'Automatically Create New Batch' enabled as the operation {2} is marked as Batch Split."
).format(row.idx, bold(row.finished_good), bold(row.operation))
)
def validate_secondary_items(self):
for item in self.secondary_items:
if not item.is_legacy and item.item_code == self.item:

View File

@@ -22,6 +22,8 @@
"is_final_finished_good",
"set_cost_based_on_bom_qty",
"quality_inspection_required",
"batch_split",
"weight_per_piece",
"warehouse_section",
"skip_material_transfer",
"backflush_from_wip_warehouse",
@@ -302,13 +304,30 @@
"fieldname": "quality_inspection_required",
"fieldtype": "Check",
"label": "Quality Inspection Required"
},
{
"default": "0",
"depends_on": "eval:parent.track_semi_finished_goods === 1",
"description": "On completion of the Job Card, split the consumed batch into one child batch per finished piece",
"fieldname": "batch_split",
"fieldtype": "Check",
"label": "Batch Split"
},
{
"depends_on": "eval:doc.batch_split",
"description": "Produced quantity is split into one batch per this many units of the finished good",
"fieldname": "weight_per_piece",
"fieldtype": "Float",
"label": "Weight Per Piece",
"mandatory_depends_on": "eval:doc.batch_split",
"non_negative": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-08-08 12:00:00.000000",
"modified": "2026-08-28 18:00:00.000000",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",

View File

@@ -19,6 +19,7 @@ class BOMOperation(Document):
base_hour_rate: DF.Currency
base_operating_cost: DF.Currency
batch_size: DF.Float
batch_split: DF.Check
bom_no: DF.Link | None
cost_per_unit: DF.Float
description: DF.TextEditor | None
@@ -41,6 +42,7 @@ class BOMOperation(Document):
skip_material_transfer: DF.Check
source_warehouse: DF.Link | None
time_in_mins: DF.Float
weight_per_piece: DF.Float
wip_warehouse: DF.Link | None
workstation: DF.Link | None
workstation_type: DF.Link | None

View File

@@ -27,6 +27,8 @@
"finished_good",
"column_break_mcnb",
"semi_fg_bom",
"batch_split",
"weight_per_piece",
"section_break_folk",
"pending_qty",
"column_break_cyjw",
@@ -551,6 +553,20 @@
"options": "BOM",
"read_only": 1
},
{
"default": "0",
"fieldname": "batch_split",
"fieldtype": "Check",
"label": "Batch Split",
"read_only": 1
},
{
"depends_on": "eval:doc.batch_split",
"fieldname": "weight_per_piece",
"fieldtype": "Float",
"label": "Weight Per Piece",
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:!doc.is_corrective_job_card",
@@ -700,7 +716,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2026-08-12 15:28:19.126628",
"modified": "2026-08-28 12:00:00.000000",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",

View File

@@ -85,6 +85,7 @@ class JobCard(Document):
amended_from: DF.Link | None
backflush_from_wip_warehouse: DF.Check
barcode: DF.Barcode | None
batch_split: DF.Check
batch_no: DF.Link | None
bom_no: DF.Link | None
company: DF.Link
@@ -141,6 +142,7 @@ class JobCard(Document):
time_required: DF.Float
total_completed_qty: DF.Float
total_time_in_mins: DF.Float
weight_per_piece: DF.Float
track_semi_finished_goods: DF.Check
transferred_qty: DF.Float
wip_warehouse: DF.Link | None

View File

@@ -1796,6 +1796,133 @@ class TestJobCard(ERPNextTestSuite):
8,
)
def test_batch_split_operation_creates_child_batches(self):
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
get_batch_from_bundle,
)
original_value = frappe.db.get_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
)
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
self.addCleanup(
frappe.db.set_single_value,
"Stock Settings",
"auto_create_serial_and_batch_bundle_for_outward",
original_value,
)
warehouse = "Stores - _TC"
rm = make_item(
"Batch Split Rod KG",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BS-ROD-KG-.####",
},
).name
fg = make_item(
"Batch Split Rod PC",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BS-ROD-PC-.####",
},
).name
fg_bom = frappe.new_doc(
"BOM",
company="_Test Company",
item=fg,
quantity=1,
with_operations=1,
track_semi_finished_goods=1,
)
fg_bom.append("items", {"item_code": rm, "qty": 1, "operation_row_id": 1})
operation = {
"operation": "Batch Split Op A",
"workstation": "_Test Workstation A",
"finished_good": fg,
"finished_good_qty": 1,
"is_final_finished_good": 1,
"sequence_id": 1,
"time_in_mins": 60,
"source_warehouse": warehouse,
"fg_warehouse": warehouse,
"skip_material_transfer": 1,
"batch_split": 1,
"weight_per_piece": 10,
}
make_workstation(operation)
make_operation(operation)
fg_bom.append("operations", operation)
fg_bom.insert()
fg_bom.submit()
work_order = make_wo_order_test_record(
item=fg,
qty=50,
source_warehouse=warehouse,
fg_warehouse=warehouse,
bom_no=fg_bom.name,
skip_transfer=1,
do_not_save=True,
)
work_order.save()
work_order.submit()
self.assertEqual(work_order.operations[0].batch_split, 1)
self.assertEqual(flt(work_order.operations[0].weight_per_piece), 10.0)
source_entry = make_stock_entry(item_code=rm, target=warehouse, qty=100, basic_rate=100)
parent_batch = get_batch_from_bundle(source_entry.items[0].serial_and_batch_bundle)
job_card = frappe.get_doc(
"Job Card", frappe.db.get_value("Job Card", {"work_order": work_order.name}, "name")
)
self.assertEqual(job_card.batch_split, 1)
job_card.append(
"time_logs",
{"from_time": "2024-03-01 08:00:00", "to_time": "2024-03-01 09:00:00", "completed_qty": 50},
)
job_card.save()
job_card.submit()
manufacture_entry = frappe.get_doc(job_card.make_stock_entry_for_semi_fg_item())
manufacture_entry.submit()
rm_row = next(row for row in manufacture_entry.items if row.item_code == rm)
self.assertEqual(flt(rm_row.transfer_qty), 50.0)
fg_row = next(row for row in manufacture_entry.items if row.item_code == fg)
self.assertEqual(flt(fg_row.transfer_qty), 50.0)
entries = frappe.get_all(
"Serial and Batch Entry",
filters={"parent": fg_row.serial_and_batch_bundle},
fields=["batch_no", "qty"],
)
self.assertEqual(len(entries), 5)
for entry in entries:
self.assertEqual(flt(entry.qty), 10.0)
self.assertTrue(entry.batch_no.startswith("BS-ROD-PC-"))
self.assertEqual(frappe.db.get_value("Batch", entry.batch_no, "parent_batch"), parent_batch)
manufacture_entry.reload()
manufacture_entry.cancel()
for entry in entries:
self.assertTrue(frappe.db.exists("Batch", entry.batch_no))
self.assertTrue(frappe.db.exists("Batch", parent_batch))
def test_semi_fg_pending_qty_is_left_to_another_job_card(self):
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
from erpnext.stock.doctype.item.test_item import make_item

Some files were not shown because too many files have changed in this diff Show More