From c14f1f145b7e30b71104c96498c6fb708511b95f Mon Sep 17 00:00:00 2001 From: tundebabzy Date: Sat, 27 Jan 2018 06:28:29 +0100 Subject: [PATCH 1/5] adjust stock_qty for expired quantities adjust based on warehouse add parameters to `get_qty_in_stock` so it can be useful in other parts of the code base --- .../setup/doctype/item_group/item_group.py | 22 +++++++- erpnext/utilities/product.py | 50 +++++++++++++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 39172d7d11..bb1910720d 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -9,6 +9,7 @@ from frappe.utils.nestedset import NestedSet from frappe.website.website_generator import WebsiteGenerator from frappe.website.render import clear_cache from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow +from erpnext.utilities.product import get_qty_in_stock class ItemGroup(NestedSet, WebsiteGenerator): @@ -83,7 +84,8 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non # base query query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group, I.description, I.web_long_description as website_description, I.is_stock_item, - case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock + case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock, I.website_warehouse, + I.has_batch_no from `tabItem` I left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse where I.show_in_website = 1 @@ -104,8 +106,26 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1) + data = adjust_for_expired_items(data) + return [get_item_for_list_in_html(r) for r in data] + +def adjust_for_expired_items(data): + adjusted_data = [] + + for item in data: + if item.get('has_batch_no') and item.get('website_warehouse'): + stock_qty_dict = get_qty_in_stock( + item.get('name'), 'website_warehouse', item.get('website_warehouse')) + qty = stock_qty_dict.stock_qty[0][0] + item['in_stock'] = 1 if qty else 0 + adjusted_data.append(item) + + return adjusted_data + + + def get_child_groups(item_group_name): item_group = frappe.get_doc("Item Group", item_group_name) return frappe.db.sql("""select name diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 9d5c056ba7..f088e3267d 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -4,14 +4,17 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, fmt_money, flt +from frappe.utils import cint, fmt_money, flt, nowdate, getdate from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item +from erpnext.stock.doctype.batch.batch import get_batch_qty -def get_qty_in_stock(item_code, item_warehouse_field): +def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None): in_stock, stock_qty = 0, '' template_item_code, is_stock_item = frappe.db.get_value("Item", item_code, ["variant_of", "is_stock_item"]) - warehouse = frappe.db.get_value("Item", item_code, item_warehouse_field) + if not warehouse: + warehouse = frappe.db.get_value("Item", item_code, item_warehouse_field) + if not warehouse and template_item_code and template_item_code != item_code: warehouse = frappe.db.get_value("Item", template_item_code, item_warehouse_field) @@ -21,8 +24,49 @@ def get_qty_in_stock(item_code, item_warehouse_field): if stock_qty: in_stock = stock_qty[0][0] > 0 and 1 or 0 + if stock_qty[0][0]: + stock_qty = adjust_for_expired_items(item_code, stock_qty, warehouse) + + if stock_qty[0][0]: + in_stock = stock_qty[0][0] > 0 and 1 or 0 + return frappe._dict({"in_stock": in_stock, "stock_qty": stock_qty, "is_stock_item": is_stock_item}) + +def adjust_for_expired_items(item_code, stock_qty, warehouse): + batches = frappe.get_list('Batch', filters=[{'item': item_code}], fields=['expiry_date', 'name']) + expired_batches = get_expired_batches(batches) + stock_qty = [list(item) for item in stock_qty] + + if expired_batches: + for batch in expired_batches: + if warehouse: + stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse)) + else: + stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch))) + + if not stock_qty[0][0]: + break + return stock_qty + + +def get_expired_batches(batches): + """ + :param batches: A list of dict in the form [{'expiry_date': datetime.date(20XX, 1, 1), 'name': 'batch_id'}, ...] + """ + return [b.name for b in batches if b.expiry_date and b.expiry_date <= getdate(nowdate())] + + +def qty_from_all_warehouses(batch_info): + """ + :param batch_info: A list of dict in the form [{u'warehouse': u'Stores - I', u'qty': 0.8}, ...] + """ + qty = 0 + for batch in batch_info: + qty = qty + batch.qty + + return qty + def get_price(item_code, price_list, customer_group, company, qty=1): template_item_code = frappe.db.get_value("Item", item_code, "variant_of") From c7c1defe648310bb1971735f71a8fc9e5a24aa75 Mon Sep 17 00:00:00 2001 From: tundebabzy Date: Sat, 27 Jan 2018 06:30:56 +0100 Subject: [PATCH 2/5] after adjusting stock_qty for expired, set in_stock flag --- erpnext/utilities/product.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index f088e3267d..5a9322bb38 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -21,8 +21,6 @@ def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None): if warehouse: stock_qty = frappe.db.sql("""select GREATEST(actual_qty - reserved_qty, 0) from tabBin where item_code=%s and warehouse=%s""", (item_code, warehouse)) - if stock_qty: - in_stock = stock_qty[0][0] > 0 and 1 or 0 if stock_qty[0][0]: stock_qty = adjust_for_expired_items(item_code, stock_qty, warehouse) @@ -30,6 +28,9 @@ def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None): if stock_qty[0][0]: in_stock = stock_qty[0][0] > 0 and 1 or 0 + if stock_qty: + in_stock = stock_qty[0][0] > 0 and 1 or 0 + return frappe._dict({"in_stock": in_stock, "stock_qty": stock_qty, "is_stock_item": is_stock_item}) From 29c8142678d9f81a7098266bdff0e762360f9bab Mon Sep 17 00:00:00 2001 From: tundebabzy Date: Wed, 31 Jan 2018 10:36:31 +0100 Subject: [PATCH 3/5] refactor `adjust_for_expired_items` and others as per code review use get_all instead of get_list rename `adjust_for_expired_items` to `adjust_qty_for_expired_items` --- .../setup/doctype/item_group/item_group.py | 4 ++-- erpnext/utilities/product.py | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index bb1910720d..14a478d7e4 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -106,12 +106,12 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1) - data = adjust_for_expired_items(data) + data = adjust_qty_for_expired_items(data) return [get_item_for_list_in_html(r) for r in data] -def adjust_for_expired_items(data): +def adjust_qty_for_expired_items(data): adjusted_data = [] for item in data: diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 5a9322bb38..5901b6031d 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -23,7 +23,7 @@ def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None): item_code=%s and warehouse=%s""", (item_code, warehouse)) if stock_qty[0][0]: - stock_qty = adjust_for_expired_items(item_code, stock_qty, warehouse) + stock_qty = adjust_qty_for_expired_items(item_code, stock_qty, warehouse) if stock_qty[0][0]: in_stock = stock_qty[0][0] > 0 and 1 or 0 @@ -34,20 +34,20 @@ def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None): return frappe._dict({"in_stock": in_stock, "stock_qty": stock_qty, "is_stock_item": is_stock_item}) -def adjust_for_expired_items(item_code, stock_qty, warehouse): - batches = frappe.get_list('Batch', filters=[{'item': item_code}], fields=['expiry_date', 'name']) +def adjust_qty_for_expired_items(item_code, stock_qty, warehouse): + batches = frappe.get_all('Batch', filters=[{'item': item_code}], fields=['expiry_date', 'name']) expired_batches = get_expired_batches(batches) stock_qty = [list(item) for item in stock_qty] - if expired_batches: - for batch in expired_batches: - if warehouse: - stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse)) - else: - stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch))) + for batch in expired_batches: + if warehouse: + stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse)) + else: + stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch))) + + if not stock_qty[0][0]: + break - if not stock_qty[0][0]: - break return stock_qty From 4990cf7783fbf7b800e36967ac82d43296027ffe Mon Sep 17 00:00:00 2001 From: tundebabzy Date: Wed, 31 Jan 2018 11:35:56 +0100 Subject: [PATCH 4/5] remove stray code --- erpnext/stock/doctype/batch/batch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 760643c4ec..3e5d351650 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -47,7 +47,6 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None): :param batch_no: Optional - give qty for this batch no :param warehouse: Optional - give qty for this warehouse :param item_code: Optional - give qty for this item''' - frappe.has_permission('Batch', throw=True) out = 0 if batch_no and warehouse: out = float(frappe.db.sql("""select sum(actual_qty) From 5df64d84a1b18cc8730430a60423d3e8dc312cb6 Mon Sep 17 00:00:00 2001 From: tundebabzy Date: Thu, 1 Feb 2018 17:05:54 +0100 Subject: [PATCH 5/5] code review fix --- erpnext/utilities/product.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 5901b6031d..415056d80b 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -22,10 +22,10 @@ def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None): stock_qty = frappe.db.sql("""select GREATEST(actual_qty - reserved_qty, 0) from tabBin where item_code=%s and warehouse=%s""", (item_code, warehouse)) - if stock_qty[0][0]: + if stock_qty: stock_qty = adjust_qty_for_expired_items(item_code, stock_qty, warehouse) - if stock_qty[0][0]: + if stock_qty: in_stock = stock_qty[0][0] > 0 and 1 or 0 if stock_qty: