mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-15 04:45:09 +00:00
fix: Handle duplicate Items qty in Quotation
fix: Handle duplicate Items qty in Quotation
(cherry picked from commit 39f6d8ffb6)
This commit is contained in:
committed by
Mergify
parent
d7124779bf
commit
4c1b415b9d
@@ -174,29 +174,22 @@ class Quotation(SellingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_ordered_status(self):
|
def get_ordered_status(self):
|
||||||
status = "Open"
|
ordered_items = get_ordered_items(self.name)
|
||||||
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,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not ordered_items:
|
if not ordered_items:
|
||||||
return status
|
return "Open"
|
||||||
|
|
||||||
has_alternatives = any(row.is_alternative for row in self.get("items"))
|
self._items = (
|
||||||
self._items = self.get_valid_items() if has_alternatives else self.get("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):
|
for row in self._items:
|
||||||
status = "Partially Ordered"
|
if row.name not in ordered_items or row.qty > ordered_items[row.name]:
|
||||||
else:
|
return "Partially Ordered"
|
||||||
status = "Ordered"
|
|
||||||
|
|
||||||
return status
|
return "Ordered"
|
||||||
|
|
||||||
def get_valid_items(self):
|
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):
|
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||||
customer = _make_customer(source_name, ignore_permissions)
|
customer = _make_customer(source_name, ignore_permissions)
|
||||||
ordered_items = frappe._dict(
|
ordered_items = get_ordered_items(source_name)
|
||||||
frappe.db.get_all(
|
|
||||||
"Sales Order Item",
|
|
||||||
{"prevdoc_docname": source_name, "docstatus": 1},
|
|
||||||
["item_code", "sum(qty)"],
|
|
||||||
group_by="item_code",
|
|
||||||
as_list=1,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
selected_rows = [x.get("name") for x in frappe.flags.get("args", {}).get("selected_items", [])]
|
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")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
|
||||||
def update_item(obj, target, source_parent):
|
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.qty = balance_qty if balance_qty > 0 else 0
|
||||||
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
|
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
|
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
|
3. If no selections: Simple row: Map if adequate qty
|
||||||
"""
|
"""
|
||||||
balance_qty = item.qty - ordered_items.get(item.item_code, 0.0)
|
if not ((item.qty > ordered_items.get(item.name, 0.0)) or is_unit_price_row(item)):
|
||||||
has_valid_qty: bool = (balance_qty > 0) or is_unit_price_row(item)
|
|
||||||
|
|
||||||
if not has_valid_qty:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not selected_rows:
|
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))
|
message += _("Please create Customer from Lead {0}.").format(get_link_to_form("Lead", lead_name))
|
||||||
|
|
||||||
frappe.throw(message, title=_("Mandatory Missing"))
|
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,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -815,6 +815,52 @@ class TestQuotation(FrappeTestCase):
|
|||||||
quotation.reload()
|
quotation.reload()
|
||||||
self.assertEqual(quotation.status, "Ordered")
|
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")
|
test_records = frappe.get_test_records("Quotation")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user