fix: Handle duplicate Items qty in Quotation

fix: Handle duplicate Items qty in Quotation
(cherry picked from commit 39f6d8ffb6)
This commit is contained in:
Abdeali Chharchhodawala
2025-06-02 14:49:15 +05:30
committed by Mergify
parent d7124779bf
commit 4c1b415b9d
2 changed files with 85 additions and 32 deletions

View File

@@ -174,29 +174,22 @@ class Quotation(SellingController):
)
def get_ordered_status(self):
status = "Open"
ordered_items = frappe._dict(
frappe.db.get_all(
"Sales Order Item",
{"prevdoc_docname": self.name, "docstatus": 1},
["item_code", "sum(qty)"],
group_by="item_code",
as_list=1,
)
)
ordered_items = get_ordered_items(self.name)
if not ordered_items:
return status
return "Open"
has_alternatives = any(row.is_alternative for row in self.get("items"))
self._items = self.get_valid_items() if has_alternatives else self.get("items")
self._items = (
self.get_valid_items()
if any(row.is_alternative for row in self.get("items"))
else self.get("items")
)
if any(row.qty > ordered_items.get(row.item_code, 0.0) for row in self._items):
status = "Partially Ordered"
else:
status = "Ordered"
for row in self._items:
if row.name not in ordered_items or row.qty > ordered_items[row.name]:
return "Partially Ordered"
return status
return "Ordered"
def get_valid_items(self):
"""
@@ -371,15 +364,7 @@ def make_sales_order(source_name: str, target_doc=None):
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
customer = _make_customer(source_name, ignore_permissions)
ordered_items = frappe._dict(
frappe.db.get_all(
"Sales Order Item",
{"prevdoc_docname": source_name, "docstatus": 1},
["item_code", "sum(qty)"],
group_by="item_code",
as_list=1,
)
)
ordered_items = get_ordered_items(source_name)
selected_rows = [x.get("name") for x in frappe.flags.get("args", {}).get("selected_items", [])]
@@ -417,7 +402,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
target.run_method("calculate_taxes_and_totals")
def update_item(obj, target, source_parent):
balance_qty = obj.qty if is_unit_price_row(obj) else obj.qty - ordered_items.get(obj.item_code, 0.0)
balance_qty = obj.qty if is_unit_price_row(obj) else obj.qty - ordered_items.get(obj.name, 0.0)
target.qty = balance_qty if balance_qty > 0 else 0
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
@@ -433,10 +418,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
3. If no selections: Simple row: Map if adequate qty
"""
balance_qty = item.qty - ordered_items.get(item.item_code, 0.0)
has_valid_qty: bool = (balance_qty > 0) or is_unit_price_row(item)
if not has_valid_qty:
if not ((item.qty > ordered_items.get(item.name, 0.0)) or is_unit_price_row(item)):
return False
if not selected_rows:
@@ -603,3 +585,28 @@ def handle_mandatory_error(e, customer, lead_name):
message += _("Please create Customer from Lead {0}.").format(get_link_to_form("Lead", lead_name))
frappe.throw(message, title=_("Mandatory Missing"))
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,
)
)

View File

@@ -815,6 +815,52 @@ class TestQuotation(FrappeTestCase):
quotation.reload()
self.assertEqual(quotation.status, "Ordered")
def test_duplicate_items_in_quotation(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
# 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)
# duplicate items
for qty in [1, 1, 2, 3]:
quotation.append("items", {"item_code": "_Test Item", "qty": qty, "rate": 100})
quotation.append("items", {"item_code": "_Test Item 2", "qty": 5, "rate": 100})
quotation.submit()
sales_order = make_sales_order(quotation.name)
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[-1].qty, 5)
# Row 1: 10, Row 4: 1, Row 5: 1
sales_order.items[0].qty = 10
sales_order.items[3].qty = 1
sales_order.items[4].qty = 1
sales_order.submit()
quotation.reload()
self.assertEqual(quotation.status, "Partially Ordered")
sales_order_2 = make_sales_order(quotation.name)
sales_order_2.delivery_date = nowdate()
self.assertEqual(len(sales_order_2.items), 2)
self.assertEqual(sales_order_2.items[0].qty, 1)
self.assertEqual(sales_order_2.items[1].qty, 2)
self.assertEqual(sales_order_2.items[0].quotation_item, quotation.items[3].name)
self.assertEqual(sales_order_2.items[1].quotation_item, quotation.items[4].name)
sales_order_2.submit()
quotation.reload()
self.assertEqual(quotation.status, "Ordered")
test_records = frappe.get_test_records("Quotation")