From 695e2bcfbcb27d0502bfd0620d5a4d3268175e14 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 14 May 2022 11:06:24 -0400 Subject: [PATCH 01/15] fix: don't reserve qty on sales return. --- erpnext/stock/stock_balance.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index e05d1c3a29..cf5acbdd55 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -97,7 +97,7 @@ def get_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql( """ select - sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty) / so_item_qty)) + sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty - so_item_returned_qty) / so_item_qty)) from ( (select @@ -112,6 +112,11 @@ def get_reserved_qty(item_code, warehouse): where name = dnpi.parent_detail_docname and delivered_by_supplier = 0 ) as so_item_delivered_qty, + ( + select returned_qty from `tabSales Order Item` + where name = dnpi.parent_detail_docname + and delivered_by_supplier = 0 + ) as so_item_returned_qty, parent, name from ( @@ -125,7 +130,8 @@ def get_reserved_qty(item_code, warehouse): ) dnpi) union (select stock_qty as dnpi_qty, qty as so_item_qty, - delivered_qty as so_item_delivered_qty, parent, name + delivered_qty as so_item_delivered_qty, + returned_qty as so_item_returned_qty, parent, name from `tabSales Order Item` so_item where item_code = %s and warehouse = %s and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0) From 47f867b77a5a3f74fd0fe482de815f870aed6e7d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Mon, 16 May 2022 14:27:45 -0400 Subject: [PATCH 02/15] test: don't reserve qty on sales return. --- .../delivery_note/test_delivery_note.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index fffcdca380..ba47fa3a46 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1064,6 +1064,32 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn.items[0].rate, rate) + def test_reserved_qty(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 + + item = make_item() + + so = make_sales_order(items=[{"item_code": item.name}]) + + soi = so.items[0] + + # Test that item qty has been reserved on submit of sales order. + self.assertEqual(get_reserved_qty(soi.item_code, soi.warehouse), soi.qty) + + dn = make_delivery_note(so.name) + dn.submit() + + # Test that item qty is no longer reserved since qty has been delivered. + self.assertEqual(get_reserved_qty(soi.item_code, soi.warehouse), 0) + + sr = make_return_doc("Delivery Note", dn.name) + sr.submit() + + # Test that item qty is not reserved on sales return. + self.assertEqual(get_reserved_qty(soi.item_code, soi.warehouse), 0) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From 4d0fa85a755eb86d5011b0f31dcb6d01c0a74f4d Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Mon, 16 May 2022 19:45:59 -0400 Subject: [PATCH 03/15] fix(test): update_prevdoc_status --- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index ba47fa3a46..e55bdef7c9 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1087,6 +1087,9 @@ class TestDeliveryNote(FrappeTestCase): sr = make_return_doc("Delivery Note", dn.name) sr.submit() + returned = frappe.get_doc("Delivery Note", sr.name) + returned.update_prevdoc_status() + # Test that item qty is not reserved on sales return. self.assertEqual(get_reserved_qty(soi.item_code, soi.warehouse), 0) From 5a402a570915d5c6e6100e1b6444b9536db2bf56 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Tue, 17 May 2022 13:32:26 -0400 Subject: [PATCH 04/15] fix(test): use unique item in sales order. --- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index e55bdef7c9..c74b570fdc 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1071,10 +1071,13 @@ class TestDeliveryNote(FrappeTestCase): item = make_item() - so = make_sales_order(items=[{"item_code": item.name}]) + so = make_sales_order(item_code=item.name) soi = so.items[0] + # Make qty avl for test. + make_stock_entry(item_code=item.name, target=soi.warehouse, qty=10, basic_rate=100) + # Test that item qty has been reserved on submit of sales order. self.assertEqual(get_reserved_qty(soi.item_code, soi.warehouse), soi.qty) From d259c2e36ab52d4674187002f5ccd7828f774d9b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 11:50:54 -0400 Subject: [PATCH 05/15] fix: merge conflict --- .../delivery_note/test_delivery_note.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index fffcdca380..ce90eec016 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1064,6 +1064,38 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn.items[0].rate, rate) + def test_reserved_qty(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 + + item = make_item().name + warehouse = "_Test Warehouse - _TC" + qty_to_reserve = 5 + + so = make_sales_order(item_code=item, qty=qty_to_reserve) + + # Make qty avl for test. + make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, basic_rate=100) + + # Test that item qty has been reserved on submit of sales order. + self.assertEqual(get_reserved_qty(item, warehouse), qty_to_reserve) + + dn = make_delivery_note(so.name) + dn.save().submit() + + # Test that item qty is no longer reserved since qty has been delivered. + self.assertEqual(get_reserved_qty(item, warehouse), 0) + + dn_return = make_return_doc("Delivery Note", dn.name) + dn_return.save().submit() + + 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) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From f3f26d2c786670a9127010ba07da1665caf0737c Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 11:51:21 -0400 Subject: [PATCH 06/15] refactor: get_reserved_qty query builder --- erpnext/stock/stock_balance.py | 127 ++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index cf5acbdd55..2f5bac91d3 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -3,7 +3,11 @@ import frappe +from frappe.query_builder import DocType +from frappe.query_builder.functions import Sum +from frappe.query_builder.utils import Table from frappe.utils import cstr, flt, now, nowdate, nowtime +from pypika.queries import QueryBuilder from erpnext.controllers.stock_controller import create_repost_item_valuation_entry @@ -94,57 +98,84 @@ def get_balance_qty_from_sle(item_code, warehouse): def get_reserved_qty(item_code, warehouse): - reserved_qty = frappe.db.sql( - """ - select - sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty - so_item_returned_qty) / so_item_qty)) - from - ( - (select - qty as dnpi_qty, + 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) + .on(SalesOrder.name == child_table.parent) + .where(SalesOrder.docstatus == 1) + .where(SalesOrder.status != "Closed") + ) + + 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) + ) + tab = append_open_so_query(tab, SalesOrderItem) + + dnpi = ( + frappe.qb.from_(PackedItem) + .select(PackedItem.qty, PackedItem.parent_detail_docname, PackedItem.parent, PackedItem.name) + .where(PackedItem.item_code == item_code) + .where(PackedItem.warehouse == warehouse) + ) + dnpi = 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"), + ]: + qty_queries.update( + { + key: ( + 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)) + dnpi_parent = dnpi_parent.select(dnpi.parent, dnpi.name) + + dnpi_parent = dnpi_parent + tab + + q = ( + frappe.qb.from_(dnpi_parent) + .select( + Sum( + dnpi_parent.dnpi_qty + * ( ( - select qty from `tabSales Order Item` - where name = dnpi.parent_detail_docname - and (delivered_by_supplier is null or delivered_by_supplier = 0) - ) as so_item_qty, - ( - select delivered_qty from `tabSales Order Item` - where name = dnpi.parent_detail_docname - and delivered_by_supplier = 0 - ) as so_item_delivered_qty, - ( - select returned_qty from `tabSales Order Item` - where name = dnpi.parent_detail_docname - and delivered_by_supplier = 0 - ) as so_item_returned_qty, - parent, name - from - ( - select qty, parent_detail_docname, parent, name - from `tabPacked Item` dnpi_in - where item_code = %s and warehouse = %s - and parenttype="Sales Order" - and item_code != parent_item - and exists (select * from `tabSales Order` so - where name = dnpi_in.parent and docstatus = 1 and status != 'Closed') - ) dnpi) - union - (select stock_qty as dnpi_qty, qty as so_item_qty, - delivered_qty as so_item_delivered_qty, - returned_qty as so_item_returned_qty, parent, name - from `tabSales Order Item` so_item - where item_code = %s and warehouse = %s - and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0) - and exists(select * from `tabSales Order` so - where so.name = so_item.parent and so.docstatus = 1 - and so.status != 'Closed')) - ) tab - where - so_item_qty >= so_item_delivered_qty - """, - (item_code, warehouse, item_code, warehouse), + dnpi_parent.so_item_qty + - dnpi_parent.so_item_delivered_qty + - dnpi_parent.so_item_returned_qty + ) + / dnpi_parent.so_item_qty + ) + ) + ) + .where(dnpi_parent.so_item_qty >= dnpi_parent.so_item_delivered_qty) ) + reserved_qty = q.run() return flt(reserved_qty[0][0]) if reserved_qty else 0 From 494bbf01245c72750421813834430b16a3b82739 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 13:51:33 -0400 Subject: [PATCH 07/15] 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 2abb169b8a..7e2d1c7436 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 ce90eec016..8dd2d11757 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 2f5bac91d3..fbb5bf8e16 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 ) From 0328874018003cbd49e5653be0d97240bd464499 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 14:45:33 -0400 Subject: [PATCH 08/15] fix: syntax missing ) --- erpnext/stock/stock_balance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 682ac5d507..0ab530a2cb 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -174,6 +174,7 @@ def get_reserved_qty(item_code, warehouse): ) ) .where(dnpi_parent.so_item_qty >= dnpi_parent.so_item_delivered_qty) + ) reserved_qty = q.run() return flt(reserved_qty[0][0]) if reserved_qty else 0 From 591b5917a937d68ff90f35a7e562095f549b5960 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 15:27:17 -0400 Subject: [PATCH 09/15] fix: re-assign after append query --- erpnext/stock/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 0ab530a2cb..a8e056c992 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -137,7 +137,7 @@ def get_reserved_qty(item_code, warehouse): .where(PackedItem.item_code == item_code) .where(PackedItem.warehouse == warehouse) ) - append_open_so_query(dnpi, PackedItem) + dnpi = append_open_so_query(dnpi, PackedItem) dnpi_parent = frappe.qb.from_(dnpi).select(dnpi.qty.as_("dnpi_qty")) for key, so_item_field, cond in [ From 179e2d2c74e6888396dec202c0246f43046874d6 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 15:29:32 -0400 Subject: [PATCH 10/15] fix: reset selling setting --- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 8dd2d11757..442ebab726 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1108,6 +1108,9 @@ class TestDeliveryNote(FrappeTestCase): # 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 tearDown(self): + frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From 6a6c5603755a97b54af5d50b18e447dcb8d8ac7e Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 17 Jun 2022 15:51:11 -0400 Subject: [PATCH 11/15] fix: move to transcation settings --- .../selling/doctype/selling_settings/selling_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 7e2d1c7436..73bfbd32cc 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -20,7 +20,6 @@ "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", @@ -28,6 +27,7 @@ "column_break_5", "allow_multiple_items", "allow_against_multiple_purchase_orders", + "dont_reserve_sales_order_qty_on_sales_return", "hide_tax_id", "enable_discount_accounting" ], @@ -186,7 +186,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-06-17 12:30:57.221570", + "modified": "2022-06-17 15:50:43.968334", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From ef6480803bb4437c4b0aa508d86af2e3f2142449 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Wed, 22 Jun 2022 14:10:40 -0400 Subject: [PATCH 12/15] chore: linter --- .../delivery_note/test_delivery_note.py | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 3611d0b415..f2bcdb3948 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1064,32 +1064,32 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(dn.items[0].rate, rate) - def test_internal_transfer_precision_gle(self): - from erpnext.selling.doctype.customer.test_customer import create_internal_customer - - item = make_item(properties={"valuation_method": "Moving Average"}).name - company = "_Test Company with perpetual inventory" - warehouse = "Stores - TCP1" - target = "Finished Goods - TCP1" - customer = create_internal_customer(represents_company=company) - - # average rate = 128.015 - rates = [101.45, 150.46, 138.25, 121.9] - - for rate in rates: - make_stock_entry(item_code=item, target=warehouse, qty=1, rate=rate) - - dn = create_delivery_note( - item_code=item, - company=company, - customer=customer, - qty=4, - warehouse=warehouse, - target_warehouse=target, - ) - self.assertFalse( - frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype}) - ) + def test_internal_transfer_precision_gle(self): + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + + item = make_item(properties={"valuation_method": "Moving Average"}).name + company = "_Test Company with perpetual inventory" + warehouse = "Stores - TCP1" + target = "Finished Goods - TCP1" + customer = create_internal_customer(represents_company=company) + + # average rate = 128.015 + rates = [101.45, 150.46, 138.25, 121.9] + + for rate in rates: + make_stock_entry(item_code=item, target=warehouse, qty=1, rate=rate) + + dn = create_delivery_note( + item_code=item, + company=company, + customer=customer, + qty=4, + warehouse=warehouse, + target_warehouse=target, + ) + self.assertFalse( + frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype}) + ) def test_reserve_qty_on_sales_return(self): frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) From 6190b4cf63415222562c99156c4a3d5278844a75 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Fri, 3 Mar 2023 09:42:29 -0500 Subject: [PATCH 13/15] chore: revert unrelated changes. --- .../selling/doctype/sales_order/sales_order.json | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 9559f134c1..ccea8407ab 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -17,8 +17,6 @@ "customer_name", "tax_id", "order_type", - "col_breaktest123", - "test_my_field", "column_break_7", "transaction_date", "delivery_date", @@ -250,15 +248,6 @@ "print_hide": 1, "reqd": 1 }, - { - "fieldname": "col_breaktest123", - "fieldtype": "Column Break" - }, - { - "fieldname": "test_my_field", - "fieldtype": "Data", - "label": "Test My Field" - }, { "fieldname": "column_break1", "fieldtype": "Column Break", @@ -1654,7 +1643,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2023-02-13 11:59:00.681780", + "modified": "2022-12-12 18:34:00.681780", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", @@ -1733,4 +1722,4 @@ "title_field": "customer_name", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file From 17f80801681373e7be3b502e833f75632e0f276b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 25 Mar 2023 00:24:53 -0400 Subject: [PATCH 14/15] chore: revert get_reserve_qty method --- erpnext/stock/stock_balance.py | 122 ++++++++++++--------------------- 1 file changed, 42 insertions(+), 80 deletions(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index ab6e18e967..439ed7a8e0 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -3,11 +3,7 @@ import frappe -from frappe.query_builder import DocType -from frappe.query_builder.functions import Sum -from frappe.query_builder.utils import Table from frappe.utils import cstr, flt, now, nowdate, nowtime -from pypika.queries import QueryBuilder from erpnext.controllers.stock_controller import create_repost_item_valuation_entry @@ -98,85 +94,51 @@ def get_balance_qty_from_sle(item_code, warehouse): def get_reserved_qty(item_code, warehouse): - def append_open_so_query(q: QueryBuilder, child_table: Table) -> QueryBuilder: - return ( - q.inner_join(SalesOrder) - .on(SalesOrder.name == child_table.parent) - .where(SalesOrder.docstatus == 1) - .where(SalesOrder.status.notin(["On Hold", "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) - .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 = ( - frappe.qb.from_(PackedItem) - .select(PackedItem.qty, PackedItem.parent_detail_docname, PackedItem.parent, PackedItem.name) - .where(PackedItem.item_code == item_code) - .where(PackedItem.warehouse == warehouse) - ) - dnpi = append_open_so_query(dnpi, PackedItem) - - 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), - ]: - 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) - ).as_(key) - ) - dnpi_parent = dnpi_parent.select(dnpi.parent, dnpi.name) - - dnpi_parent = dnpi_parent + tab - - q = ( - frappe.qb.from_(dnpi_parent) - .select( - Sum( - dnpi_parent.dnpi_qty - * ( + reserved_qty = frappe.db.sql( + """ + select + sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty) / so_item_qty)) + from + ( + (select + qty as dnpi_qty, ( - dnpi_parent.so_item_qty - - dnpi_parent.so_item_delivered_qty - - (dnpi_parent.so_item_returned_qty if dont_reserve_qty_on_sales_return else 0) - ) - / dnpi_parent.so_item_qty - ) - ) - ) - .where(dnpi_parent.so_item_qty >= dnpi_parent.so_item_delivered_qty) + select qty from `tabSales Order Item` + where name = dnpi.parent_detail_docname + and (delivered_by_supplier is null or delivered_by_supplier = 0) + ) as so_item_qty, + ( + select delivered_qty from `tabSales Order Item` + where name = dnpi.parent_detail_docname + and delivered_by_supplier = 0 + ) as so_item_delivered_qty, + parent, name + from + ( + select qty, parent_detail_docname, parent, name + from `tabPacked Item` dnpi_in + where item_code = %s and warehouse = %s + and parenttype='Sales Order' + and item_code != parent_item + and exists (select * from `tabSales Order` so + where name = dnpi_in.parent and docstatus = 1 and status not in ('On Hold', 'Closed')) + ) dnpi) + union + (select stock_qty as dnpi_qty, qty as so_item_qty, + delivered_qty as so_item_delivered_qty, parent, name + from `tabSales Order Item` so_item + where item_code = %s and warehouse = %s + and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0) + and exists(select * from `tabSales Order` so + where so.name = so_item.parent and so.docstatus = 1 + and so.status not in ('On Hold', 'Closed'))) + ) tab + where + so_item_qty >= so_item_delivered_qty + """, + (item_code, warehouse, item_code, warehouse), ) - reserved_qty = q.run() return flt(reserved_qty[0][0]) if reserved_qty else 0 From 3c553b0938073ee3fb4a355c884812bf6ae136f3 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Mon, 27 Mar 2023 10:24:15 -0400 Subject: [PATCH 15/15] feat: don't reserve qty on sales return --- erpnext/stock/stock_balance.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 439ed7a8e0..e3cbb43d8b 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -94,10 +94,13 @@ def get_balance_qty_from_sle(item_code, warehouse): def get_reserved_qty(item_code, warehouse): + dont_reserve_on_return = frappe.get_cached_value( + "Selling Settings", "Selling Settings", "dont_reserve_sales_order_qty_on_sales_return" + ) reserved_qty = frappe.db.sql( - """ + f""" select - sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty) / so_item_qty)) + sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty - if(dont_reserve_qty_on_return, so_item_returned_qty, 0)) / so_item_qty)) from ( (select @@ -112,6 +115,12 @@ def get_reserved_qty(item_code, warehouse): where name = dnpi.parent_detail_docname and delivered_by_supplier = 0 ) as so_item_delivered_qty, + ( + select returned_qty from `tabSales Order Item` + where name = dnpi.parent_detail_docname + and delivered_by_supplier = 0 + ) as so_item_returned_qty, + {dont_reserve_on_return} as dont_reserve_qty_on_return, parent, name from ( @@ -125,7 +134,9 @@ def get_reserved_qty(item_code, warehouse): ) dnpi) union (select stock_qty as dnpi_qty, qty as so_item_qty, - delivered_qty as so_item_delivered_qty, parent, name + delivered_qty as so_item_delivered_qty, + returned_qty as so_item_returned_qty, + {dont_reserve_on_return}, parent, name from `tabSales Order Item` so_item where item_code = %s and warehouse = %s and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0)