Merge pull request #52229 from frappe/mergify/bp/version-15-hotfix/pr-52222

This commit is contained in:
Mihir Kandoi
2026-02-01 13:46:46 +05:30
committed by GitHub
9 changed files with 62 additions and 29 deletions

View File

@@ -341,14 +341,17 @@ class StatusUpdater(Document):
):
return
if qty_or_amount == "qty":
action_msg = _(
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
)
if args["target_dt"] != "Quotation Item":
if qty_or_amount == "qty":
action_msg = _(
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
)
else:
action_msg = _(
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
)
else:
action_msg = _(
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
)
action_msg = None
frappe.throw(
_(

View File

@@ -428,3 +428,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "show_party_balance", 1)
execute:frappe.db.set_single_value("Accounts Settings", "show_account_balance", 1)
erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter #2025-12-11
erpnext.patches.v15_0.create_accounting_dimensions_in_advance_taxes_and_charges
erpnext.patches.v16_0.set_ordered_qty_in_quotation_item

View File

@@ -0,0 +1,16 @@
import frappe
def execute():
data = frappe.get_all(
"Sales Order Item",
filters={"quotation_item": ["is", "set"], "docstatus": 1},
fields=["quotation_item", "sum(stock_qty) as ordered_qty"],
group_by="quotation_item",
)
if data:
frappe.db.auto_commit_on_many_writes = 1
frappe.db.bulk_update(
"Quotation Item", {d.quotation_item: {"ordered_qty": d.ordered_qty} for d in data}
)
frappe.db.auto_commit_on_many_writes = 0

View File

@@ -446,7 +446,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
"Quotation",
source_name,
{
"Quotation": {"doctype": "Sales Order", "validation": {"docstatus": ["=", 1]}},
"Quotation": {
"doctype": "Sales Order",
"validation": {"docstatus": ["=", 1]},
},
"Quotation Item": {
"doctype": "Sales Order Item",
"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
@@ -549,6 +552,8 @@ def _make_customer(source_name, ignore_permissions=False):
if quotation.quotation_to == "Customer":
return frappe.get_doc("Customer", quotation.party_name)
elif quotation.quotation_to == "CRM Deal":
return frappe.get_doc("Customer", {"crm_deal": quotation.party_name})
# Check if a Customer already exists for the Lead or Prospect.
existing_customer = None
@@ -610,25 +615,8 @@ def handle_mandatory_error(e, customer, lead_name):
def get_ordered_items(quotation: str):
"""
Returns a dict of ordered items with their total qty based on quotation row name.
In `Sales Order Item`, `quotation_item` is the row name of `Quotation Item`.
Example:
```
{
"refsdjhd2": 10,
"ygdhdshrt": 5,
}
```
"""
return frappe._dict(
frappe.get_all(
"Sales Order Item",
filters={"prevdoc_docname": quotation, "docstatus": 1},
fields=["quotation_item", "sum(qty)"],
group_by="quotation_item",
as_list=1,
"Quotation Item", {"docstatus": 1, "parent": quotation}, ["name", "ordered_qty"], as_list=True
)
)

View File

@@ -828,7 +828,7 @@ class TestQuotation(FrappeTestCase):
# item code same but description different
make_item("_Test Item 2", {"is_stock_item": 1})
quotation = make_quotation(qty=1, rate=100, do_not_submit=1)
quotation = make_quotation(qty=10, rate=100, do_not_submit=1)
# duplicate items
for qty in [1, 1, 2, 3]:
@@ -842,7 +842,7 @@ class TestQuotation(FrappeTestCase):
sales_order.delivery_date = nowdate()
self.assertEqual(len(sales_order.items), 6)
self.assertEqual(sales_order.items[0].qty, 1)
self.assertEqual(sales_order.items[0].qty, 10)
self.assertEqual(sales_order.items[-1].qty, 5)
# Row 1: 10, Row 4: 1, Row 5: 1

View File

@@ -24,6 +24,7 @@
"uom",
"conversion_factor",
"stock_qty",
"ordered_qty",
"available_quantity_section",
"actual_qty",
"column_break_ylrv",
@@ -694,12 +695,23 @@
"print_hide": 1,
"read_only": 1,
"report_hide": 1
},
{
"default": "0",
"fieldname": "ordered_qty",
"fieldtype": "Float",
"hidden": 1,
"label": "Ordered Qty",
"no_copy": 1,
"non_negative": 1,
"read_only": 1,
"reqd": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-08-26 20:31:47.775890",
"modified": "2026-01-30 12:56:08.320190",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",

View File

@@ -48,6 +48,7 @@ class QuotationItem(Document):
margin_type: DF.Literal["", "Percentage", "Amount"]
net_amount: DF.Currency
net_rate: DF.Currency
ordered_qty: DF.Float
page_break: DF.Check
parent: DF.Data
parentfield: DF.Data

View File

@@ -185,6 +185,16 @@ class SalesOrder(SellingController):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.status_updater = [
{
"source_dt": "Sales Order Item",
"target_dt": "Quotation Item",
"join_field": "quotation_item",
"target_field": "ordered_qty",
"target_ref_field": "stock_qty",
"source_field": "stock_qty",
}
]
def onload(self) -> None:
super().onload()
@@ -419,6 +429,7 @@ class SalesOrder(SellingController):
frappe.throw(_("Row #{0}: Set Supplier for item {1}").format(d.idx, d.item_code))
def on_submit(self):
super().update_prevdoc_status()
self.check_credit_limit()
self.update_reserved_qty()

View File

@@ -57,6 +57,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
def tearDown(self):
frappe.set_user("Administrator")
@change_settings("Selling Settings", {"allow_negative_rates_for_items": 1})
def test_sales_order_with_negative_rate(self):
"""
Test if negative rate is allowed in Sales Order via doc submission and update items