feat: allowing rate modification in update item in quotation (backport #53147) (#53150)

Co-authored-by: Nishka Gosalia <nishkagosalia@Nishkas-MacBook-Air.local>
This commit is contained in:
mergify[bot]
2026-03-04 07:29:46 +00:00
committed by GitHub
parent f9e5ac6b60
commit 072ab8d5f3
3 changed files with 82 additions and 14 deletions

View File

@@ -37,7 +37,7 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(sq.get("items")[0].qty, 5) self.assertEqual(sq.get("items")[0].qty, 5)
self.assertEqual(sq.get("items")[1].rate, 300) self.assertEqual(sq.get("items")[1].rate, 300)
def test_update_supplier_quotation_child_rate_disallow(self): def test_update_supplier_quotation_child_rate(self):
sq = frappe.copy_doc(test_records[0]) sq = frappe.copy_doc(test_records[0])
sq.submit() sq.submit()
trans_item = json.dumps( trans_item = json.dumps(
@@ -50,6 +50,22 @@ class TestPurchaseOrder(FrappeTestCase):
}, },
] ]
) )
update_child_qty_rate("Supplier Quotation", trans_item, sq.name)
sq.reload()
self.assertEqual(sq.get("items")[0].rate, 300)
po = make_purchase_order(sq.name)
po.schedule_date = add_days(today(), 1)
po.submit()
trans_item = json.dumps(
[
{
"item_code": sq.items[0].item_code,
"rate": 20,
"qty": sq.items[0].qty,
"docname": sq.items[0].name,
},
]
)
self.assertRaises( self.assertRaises(
frappe.ValidationError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name frappe.ValidationError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name
) )

View File

@@ -3838,20 +3838,28 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
return False return False
def validate_quantity(child_item, new_data): def validate_quantity_and_rate(child_item, new_data):
if not flt(new_data.get("qty")) and not is_allowed_zero_qty(): if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
frappe.throw( frappe.throw(
_("Row #{0}: Quantity for Item {1} cannot be zero.").format( _("Row #{0}:Quantity for Item {1} cannot be zero.").format(
new_data.get("idx"), frappe.bold(new_data.get("item_code")) new_data.get("idx"), frappe.bold(new_data.get("item_code"))
), ),
title=_("Invalid Qty"), title=_("Invalid Qty"),
) )
if parent_doctype == "Sales Order" and flt(new_data.get("qty")) < flt(child_item.delivered_qty): qty_limits = {
frappe.throw(_("Cannot set quantity less than delivered quantity")) "Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity")),
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity")),
}
if parent_doctype == "Purchase Order" and flt(new_data.get("qty")) < flt(child_item.received_qty): if parent_doctype in qty_limits:
frappe.throw(_("Cannot set quantity less than received quantity")) qty_field, error_message = qty_limits[parent_doctype]
if flt(new_data.get("qty")) < flt(child_item.get(qty_field)):
frappe.throw(
_("Row #{0}:").format(new_data.get("idx"))
+ error_message.format(frappe.bold(new_data.get("item_code"))),
title=_("Invalid Qty"),
)
if parent_doctype in ["Quotation", "Supplier Quotation"]: if parent_doctype in ["Quotation", "Supplier Quotation"]:
if (parent_doctype == "Quotation" and not ordered_items) or ( if (parent_doctype == "Quotation" and not ordered_items) or (
@@ -3864,7 +3872,15 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
if parent_doctype == "Quotation" if parent_doctype == "Quotation"
else purchased_items.get(child_item.name) else purchased_items.get(child_item.name)
) )
if qty_to_check: if qty_to_check:
if not rate_unchanged:
frappe.throw(
_(
"Cannot update rate as item {0} is already ordered or purchased against this quotation"
).format(frappe.bold(new_data.get("item_code")))
)
if flt(new_data.get("qty")) < qty_to_check: if flt(new_data.get("qty")) < qty_to_check:
frappe.throw(_("Cannot reduce quantity than ordered or purchased quantity")) frappe.throw(_("Cannot reduce quantity than ordered or purchased quantity"))
@@ -3980,10 +3996,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
): ):
continue continue
validate_quantity(child_item, d) validate_quantity_and_rate(child_item, d)
if parent_doctype in ["Quotation", "Supplier Quotation"]:
if not rate_unchanged:
frappe.throw(_("Rates cannot be modified for quoted items"))
if flt(child_item.get("qty")) != flt(d.get("qty")): if flt(child_item.get("qty")) != flt(d.get("qty")):
any_qty_changed = True any_qty_changed = True

View File

@@ -52,8 +52,22 @@ class TestQuotation(FrappeTestCase):
self.assertEqual(qo.get("items")[0].qty, 11) self.assertEqual(qo.get("items")[0].qty, 11)
self.assertEqual(qo.get("items")[-1].rate, 100) self.assertEqual(qo.get("items")[-1].rate, 100)
def test_update_child_disallow_rate_change(self): def test_update_child_rate_change(self):
qo = make_quotation(qty=4) from erpnext.stock.doctype.item.test_item import make_item
item_1 = make_item("_Test Item")
item_2 = make_item("_Test Item 1")
item_list = [
{"item_code": item_1.item_code, "warehouse": "_Test Warehouse - _TC", "qty": 10, "rate": 300},
{"item_code": item_2.item_code, "warehouse": "_Test Warehouse - _TC", "qty": 5, "rate": 400},
]
qo = make_quotation(item_list=item_list)
so = make_sales_order(qo.name, args={"filtered_children": [qo.items[0].name]})
so.delivery_date = nowdate()
so.submit()
qo.reload()
trans_item = json.dumps( trans_item = json.dumps(
[ [
{ {
@@ -61,10 +75,35 @@ class TestQuotation(FrappeTestCase):
"rate": 5000, "rate": 5000,
"qty": qo.items[0].qty, "qty": qo.items[0].qty,
"docname": qo.items[0].name, "docname": qo.items[0].name,
} },
{
"item_code": qo.items[1].item_code,
"rate": qo.items[1].rate,
"qty": qo.items[1].qty,
"docname": qo.items[1].name,
},
] ]
) )
self.assertRaises(frappe.ValidationError, update_child_qty_rate, "Quotation", trans_item, qo.name) self.assertRaises(frappe.ValidationError, update_child_qty_rate, "Quotation", trans_item, qo.name)
trans_item = json.dumps(
[
{
"item_code": qo.items[0].item_code,
"rate": qo.items[0].rate,
"qty": qo.items[0].qty,
"docname": qo.items[0].name,
},
{
"item_code": qo.items[1].item_code,
"rate": 50,
"qty": qo.items[1].qty,
"docname": qo.items[1].name,
},
]
)
update_child_qty_rate("Quotation", trans_item, qo.name)
qo.reload()
self.assertEqual(qo.items[1].rate, 50)
def test_update_child_removing_item(self): def test_update_child_removing_item(self):
qo = make_quotation(qty=10) qo = make_quotation(qty=10)