Merge branch 'version-16-hotfix' into mergify/bp/version-16-hotfix/pr-53588

This commit is contained in:
Khushi Rawat
2026-04-13 15:41:39 +05:30
committed by GitHub
235 changed files with 7998 additions and 4725 deletions

View File

@@ -173,6 +173,7 @@ class Customer(TransactionBase):
def validate(self):
self.flags.is_new_doc = self.is_new()
self.flags.old_lead = self.lead_name
self.validate_customer_group()
validate_party_accounts(self)
self.validate_credit_limit_on_change()
self.set_loyalty_program()
@@ -356,6 +357,17 @@ class Customer(TransactionBase):
frappe.NameError,
)
def validate_customer_group(self):
if not self.customer_group:
return
is_group = frappe.db.get_value("Customer Group", self.customer_group, "is_group")
if is_group:
frappe.throw(
_("Cannot select a Group type Customer Group. Please select a non-group Customer Group."),
title=_("Invalid Customer Group"),
)
def validate_credit_limit_on_change(self):
if self.get("__islocal") or not self.credit_limits:
return

View File

@@ -1029,11 +1029,11 @@ class TestQuotation(ERPNextTestSuite):
def test_make_quotation_qar_to_inr(self):
quotation = make_quotation(
currency="QAR",
transaction_date="2026-06-04",
transaction_date="2026-01-01",
)
cache = frappe.cache()
key = "currency_exchange_rate_{}:{}:{}".format("2026-06-04", "QAR", "INR")
key = "currency_exchange_rate_{}:{}:{}".format("2026-01-01", "QAR", "INR")
value = cache.get(key)
expected_rate = flt(value) / 3.64

View File

@@ -63,6 +63,13 @@ frappe.ui.form.on("Sales Order", {
});
}
},
transaction_date(frm) {
prevent_past_delivery_dates(frm);
frm.set_value("delivery_date", "");
frm.doc.items.forEach((d) => {
frappe.model.set_value(d.doctype, d.name, "delivery_date", "");
});
},
refresh: function (frm) {
frm.fields_dict["items"].grid.update_docfield_property(

View File

@@ -10,7 +10,6 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user
from frappe.tests import change_settings
from frappe.utils import add_days, flt, nowdate, today
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.controllers.accounts_controller import InvalidQtyError, get_due_date, update_child_qty_rate
from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
make_maintenance_schedule,
@@ -35,10 +34,7 @@ from erpnext.stock.get_item_details import get_bin_details
from erpnext.tests.utils import ERPNextTestSuite
class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
def setUp(self):
self.create_customer("_Test Customer Credit")
class TestSalesOrder(ERPNextTestSuite):
@ERPNextTestSuite.change_settings(
"Stock Settings",
{
@@ -2439,7 +2435,7 @@ class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
def test_credit_limit_on_so_reopning(self):
# set credit limit
company = "_Test Company"
customer = frappe.get_doc("Customer", self.customer)
customer = frappe.get_doc("Customer", "_Test Customer")
customer.credit_limits = []
customer.append(
"credit_limits", {"company": company, "credit_limit": 1000, "bypass_credit_limit_check": False}
@@ -2447,35 +2443,33 @@ class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
customer.save()
so1 = make_sales_order(qty=9, rate=100, do_not_submit=True)
so1.customer = self.customer
so1.customer = customer.name
so1.save().submit()
so1.update_status("Closed")
so2 = make_sales_order(qty=9, rate=100, do_not_submit=True)
so2.customer = self.customer
so2.customer = customer.name
so2.save().submit()
self.assertRaises(frappe.ValidationError, so1.update_status, "Draft")
@ERPNextTestSuite.change_settings("Stock Settings", {"enable_stock_reservation": True})
def test_warehouse_mapping_based_on_stock_reservation(self):
self.create_company(company_name="Glass Ceiling", abbr="GC")
self.create_item("Lamy Safari 2", True, self.warehouse_stores, self.company, 2000)
self.create_customer()
self.clear_old_entries()
warehouse = "Stores - _TC"
warehouse_finished = "Finished Goods - _TC"
so = frappe.new_doc("Sales Order")
so.company = self.company
so.customer = self.customer
so.company = "_Test Company"
so.customer = "_Test Customer"
so.transaction_date = today()
so.append(
"items",
{
"item_code": self.item,
"item_code": "_Test Item",
"qty": 10,
"rate": 2000,
"warehouse": self.warehouse_stores,
"warehouse": "Stores - _TC",
"delivery_date": today(),
},
)
@@ -2485,12 +2479,12 @@ class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
se = frappe.get_doc(
{
"doctype": "Stock Entry",
"company": self.company,
"company": "_Test Company",
"stock_entry_type": "Material Receipt",
"posting_date": today(),
"items": [
{"item_code": self.item, "t_warehouse": self.warehouse_stores, "qty": 5},
{"item_code": self.item, "t_warehouse": self.warehouse_finished_goods, "qty": 5},
{"item_code": "_Test Item", "t_warehouse": warehouse, "qty": 5},
{"item_code": "_Test Item", "t_warehouse": warehouse_finished, "qty": 5},
],
}
)
@@ -2503,7 +2497,7 @@ class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
{
"sales_order_item": itm.name,
"item_code": itm.item_code,
"warehouse": self.warehouse_stores,
"warehouse": warehouse,
"qty_to_reserve": 2,
}
]
@@ -2513,7 +2507,7 @@ class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
{
"sales_order_item": itm.name,
"item_code": itm.item_code,
"warehouse": self.warehouse_finished_goods,
"warehouse": warehouse_finished,
"qty_to_reserve": 3,
}
]
@@ -2523,31 +2517,31 @@ class TestSalesOrder(AccountsTestMixin, ERPNextTestSuite):
dn = make_delivery_note(so.name, kwargs={"for_reserved_stock": True})
self.assertEqual(2, len(dn.items))
self.assertEqual(dn.items[0].qty, 2)
self.assertEqual(dn.items[0].warehouse, self.warehouse_stores)
self.assertEqual(dn.items[0].warehouse, warehouse)
self.assertEqual(dn.items[1].qty, 3)
self.assertEqual(dn.items[1].warehouse, self.warehouse_finished_goods)
self.assertEqual(dn.items[1].warehouse, warehouse_finished)
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
warehouse = create_warehouse("Test Warehouse 1", company=self.company)
warehouse = create_warehouse("Test Warehouse 1", company="_Test Company")
make_stock_entry(
item_code=self.item,
item_code="_Test Item",
target=warehouse,
qty=5,
company=self.company,
company="_Test Company",
)
so = frappe.new_doc("Sales Order")
so.reserve_stock = 1
so.company = self.company
so.customer = self.customer
so.company = "_Test Company"
so.customer = "_Test Customer"
so.transaction_date = today()
so.currency = "INR"
so.append(
"items",
{
"item_code": self.item,
"item_code": "_Test Item",
"qty": 5,
"rate": 2000,
"warehouse": warehouse,

View File

@@ -343,7 +343,8 @@
"fieldname": "discount_amount",
"fieldtype": "Currency",
"label": "Discount Amount",
"options": "currency"
"options": "currency",
"print_hide": 1
},
{
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
@@ -503,12 +504,14 @@
{
"fieldname": "weight_per_unit",
"fieldtype": "Float",
"label": "Weight Per Unit"
"label": "Weight Per Unit",
"print_hide": 1
},
{
"fieldname": "total_weight",
"fieldtype": "Float",
"label": "Total Weight",
"print_hide": 1,
"read_only": 1
},
{
@@ -822,6 +825,7 @@
"label": "Rate of Stock UOM",
"no_copy": 1,
"options": "currency",
"print_hide": 1,
"read_only": 1
},
{
@@ -830,6 +834,7 @@
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"print_hide": 1,
"read_only": 1
},
{
@@ -837,6 +842,7 @@
"fieldtype": "Float",
"label": "Picked Qty (in Stock UOM)",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
@@ -910,6 +916,7 @@
"fieldtype": "Float",
"label": "Production Plan Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
@@ -926,7 +933,8 @@
"fieldname": "distributed_discount_amount",
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
"options": "currency",
"print_hide": 1
},
{
"allow_on_submit": 1,
@@ -995,6 +1003,7 @@
"label": "Subcontracted Quantity",
"no_copy": 1,
"non_negative": 1,
"print_hide": 1,
"read_only": 1
},
{
@@ -1010,7 +1019,8 @@
"fieldname": "fg_item_qty",
"fieldtype": "Float",
"label": "Finished Good Qty",
"mandatory_depends_on": "eval:parent.is_subcontracted"
"mandatory_depends_on": "eval:parent.is_subcontracted",
"print_hide": 1
},
{
"fieldname": "requested_qty",
@@ -1025,7 +1035,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2026-02-21 16:39:00.200328",
"modified": "2026-02-22 16:40:00.200328",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",

View File

@@ -49,7 +49,7 @@
"section_break_zwh6",
"allow_delivery_of_overproduced_qty",
"column_break_mla9",
"deliver_scrap_items"
"deliver_secondary_items"
],
"fields": [
{
@@ -260,13 +260,6 @@
"fieldname": "column_break_mla9",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "If enabled, the Scrap Item generated against a Finished Good will also be added in the Stock Entry when delivering that Finished Good.",
"fieldname": "deliver_scrap_items",
"fieldtype": "Check",
"label": "Deliver Scrap Items"
},
{
"fieldname": "item_price_tab",
"fieldtype": "Tab Break",
@@ -320,6 +313,13 @@
"fieldname": "enable_utm",
"fieldtype": "Check",
"label": "Enable UTM"
},
{
"default": "0",
"description": "If enabled, the Secondary Items generated against a Finished Good will also be added in the Stock Entry when delivering that Finished Good.",
"fieldname": "deliver_secondary_items",
"fieldtype": "Check",
"label": "Deliver Secondary Items"
}
],
"grid_page_length": 50,

View File

@@ -41,7 +41,7 @@ class SellingSettings(Document):
blanket_order_allowance: DF.Float
cust_master_name: DF.Literal["Customer Name", "Naming Series", "Auto Name"]
customer_group: DF.Link | None
deliver_scrap_items: DF.Check
deliver_secondary_items: DF.Check
dn_required: DF.Literal["No", "Yes"]
dont_reserve_sales_order_qty_on_sales_return: DF.Check
editable_bundle_item_rates: DF.Check
@@ -93,10 +93,10 @@ class SellingSettings(Document):
self.validate_fallback_to_default_price_list()
if old_doc.enable_tracking_sales_commissions != self.enable_tracking_sales_commissions:
if old_doc and old_doc.enable_tracking_sales_commissions != self.enable_tracking_sales_commissions:
toggle_tracking_sales_commissions_section(not self.enable_tracking_sales_commissions)
if old_doc.enable_utm != self.enable_utm:
if old_doc and old_doc.enable_utm != self.enable_utm:
toggle_utm_analytics_section(not self.enable_utm)
def validate_fallback_to_default_price_list(self):