fix: validate over ordering of quotation

(cherry picked from commit 4cc306d2d8)

# Conflicts:
#	erpnext/controllers/status_updater.py
#	erpnext/patches.txt
#	erpnext/selling/doctype/quotation/quotation.py
#	erpnext/selling/doctype/quotation_item/quotation_item.json
This commit is contained in:
Mihir Kandoi
2026-01-30 16:37:08 +05:30
committed by Mergify
parent 4dfc5671b0
commit 0e60750bd8
7 changed files with 74 additions and 15 deletions

View File

@@ -341,10 +341,22 @@ class StatusUpdater(Document):
):
return
<<<<<<< HEAD
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["source_dt"] != "Pick List Item" and 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.'
)
>>>>>>> 4cc306d2d8 (fix: validate over ordering of quotation)
else:
action_msg = _(
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'

View File

@@ -428,3 +428,9 @@ 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
<<<<<<< HEAD
=======
execute:frappe.delete_doc_if_exists("Workspace Sidebar", "Opening & Closing")
erpnext.patches.v16_0.migrate_transaction_deletion_task_flags_to_status # 2
erpnext.patches.v16_0.set_ordered_qty_in_quotation_item
>>>>>>> 4cc306d2d8 (fix: validate over ordering of quotation)

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,16 @@ 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(
<<<<<<< HEAD
"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
>>>>>>> 4cc306d2d8 (fix: validate over ordering of quotation)
)
)

View File

@@ -24,6 +24,7 @@
"uom",
"conversion_factor",
"stock_qty",
"ordered_qty",
"available_quantity_section",
"actual_qty",
"column_break_ylrv",
@@ -694,18 +695,34 @@
"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",
"owner": "Administrator",
"permissions": [],
<<<<<<< HEAD
"sort_field": "modified",
=======
"row_format": "Dynamic",
"sort_field": "creation",
>>>>>>> 4cc306d2d8 (fix: validate over ordering of quotation)
"sort_order": "DESC",
"states": [],
"track_changes": 1

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