From 494bbf01245c72750421813834430b16a3b82739 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 13:51:33 -0400 Subject: [PATCH] feat: add checkbox to reserve qty on sales return --- .../selling_settings/selling_settings.json | 9 ++- .../delivery_note/test_delivery_note.py | 18 +++++- erpnext/stock/stock_balance.py | 59 ++++++++++--------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 2abb169b8a0..7e2d1c74367 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -20,6 +20,7 @@ "editable_price_list_rate", "validate_selling_price", "editable_bundle_item_rates", + "dont_reserve_sales_order_qty_on_sales_return", "sales_transactions_settings_section", "so_required", "dn_required", @@ -172,6 +173,12 @@ "fieldname": "enable_discount_accounting", "fieldtype": "Check", "label": "Enable Discount Accounting for Selling" + }, + { + "default": "0", + "fieldname": "dont_reserve_sales_order_qty_on_sales_return", + "fieldtype": "Check", + "label": "Don't Reserve Sales Order Qty on Sales Return" } ], "icon": "fa fa-cog", @@ -179,7 +186,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-05-31 19:39:48.398738", + "modified": "2022-06-17 12:30:57.221570", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index ce90eec016b..8dd2d117577 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1064,11 +1064,23 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn.items[0].rate, rate) - def test_reserved_qty(self): + def test_reserve_qty_on_sales_return(self): + frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) + self.reserved_qty_check() + + def test_dont_reserve_qty_on_sales_return(self): + frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 1) + self.reserved_qty_check() + + def reserved_qty_check(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note from erpnext.stock.stock_balance import get_reserved_qty + dont_reserve_qty = frappe.db.get_single_value( + "Selling Settings", "dont_reserve_sales_order_qty_on_sales_return" + ) + item = make_item().name warehouse = "_Test Warehouse - _TC" qty_to_reserve = 5 @@ -1093,8 +1105,8 @@ class TestDeliveryNote(FrappeTestCase): returned = frappe.get_doc("Delivery Note", dn_return.name) returned.update_prevdoc_status() - # Test that item qty is not reserved on sales return. - self.assertEqual(get_reserved_qty(item, warehouse), 0) + # Test that item qty is not reserved on sales return, if selling setting don't reserve qty is checked. + self.assertEqual(get_reserved_qty(item, warehouse), 0 if dont_reserve_qty else qty_to_reserve) def create_delivery_note(**args): diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 2f5bac91d37..fbb5bf8e160 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -98,10 +98,6 @@ def get_balance_qty_from_sle(item_code, warehouse): def get_reserved_qty(item_code, warehouse): - SalesOrder = DocType("Sales Order") - SalesOrderItem = DocType("Sales Order Item") - PackedItem = DocType("Packed Item") - def append_open_so_query(q: QueryBuilder, child_table: Table) -> QueryBuilder: return ( q.inner_join(SalesOrder) @@ -110,19 +106,29 @@ def get_reserved_qty(item_code, warehouse): .where(SalesOrder.status != "Closed") ) + SalesOrder = DocType("Sales Order") + SalesOrderItem = DocType("Sales Order Item") + PackedItem = DocType("Packed Item") + + dont_reserve_qty_on_sales_return = frappe.db.get_single_value( + "Selling Settings", "dont_reserve_sales_order_qty_on_sales_return" + ) + tab = ( frappe.qb.from_(SalesOrderItem) - .select( - SalesOrderItem.stock_qty.as_("dnpi_qty"), - SalesOrderItem.qty.as_("so_item_qty"), - SalesOrderItem.delivered_qty.as_("so_item_delivered_qty"), - SalesOrderItem.returned_qty.as_("so_item_returned_qty"), - SalesOrderItem.parent, - SalesOrderItem.name, - ) .where(SalesOrderItem.item_code == item_code) .where(SalesOrderItem.warehouse == warehouse) ) + for field, cond in [ + (SalesOrderItem.stock_qty.as_("dnpi_qty"), 1), + (SalesOrderItem.qty.as_("so_item_qty"), 1), + (SalesOrderItem.delivered_qty.as_("so_item_delivered_qty"), 1), + (SalesOrderItem.returned_qty.as_("so_item_returned_qty"), dont_reserve_qty_on_sales_return), + (SalesOrderItem.parent, 1), + (SalesOrderItem.name, 1), + ]: + if cond: + tab = tab.select(field) tab = append_open_so_query(tab, SalesOrderItem) dnpi = ( @@ -131,28 +137,23 @@ def get_reserved_qty(item_code, warehouse): .where(PackedItem.item_code == item_code) .where(PackedItem.warehouse == warehouse) ) - dnpi = append_open_so_query(dnpi, PackedItem) + append_open_so_query(dnpi, PackedItem) - qty_queries = {} - for key, so_item_field in [ - ("so_item_qty", "qty"), - ("so_item_delivered_qty", "delivered_qty"), - ("so_item_returned_qty", "returned_qty"), + dnpi_parent = frappe.qb.from_(dnpi).select(dnpi.qty.as_("dnpi_qty")) + for key, so_item_field, cond in [ + ("so_item_qty", "qty", 1), + ("so_item_delivered_qty", "delivered_qty", 1), + ("so_item_returned_qty", "returned_qty", dont_reserve_qty_on_sales_return), ]: - qty_queries.update( - { - key: ( + if cond: + dnpi_parent = dnpi_parent.select( + ( frappe.qb.from_(SalesOrderItem) .select(SalesOrderItem[so_item_field]) .where(SalesOrderItem.name == dnpi.parent_detail_docname) .where(SalesOrderItem.delivered_by_supplier == 0) - ) - } - ) - - dnpi_parent = frappe.qb.from_(dnpi).select(dnpi.qty.as_("dnpi_qty")) - for key, query in qty_queries.items(): - dnpi_parent = dnpi_parent.select(query.as_(key)) + ).as_(key) + ) dnpi_parent = dnpi_parent.select(dnpi.parent, dnpi.name) dnpi_parent = dnpi_parent + tab @@ -166,7 +167,7 @@ def get_reserved_qty(item_code, warehouse): ( dnpi_parent.so_item_qty - dnpi_parent.so_item_delivered_qty - - dnpi_parent.so_item_returned_qty + - (dnpi_parent.so_item_returned_qty if dont_reserve_qty_on_sales_return else 0) ) / dnpi_parent.so_item_qty )