diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 252deaad36..8a1f8c7f69 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -566,14 +566,14 @@ class TestStockEntry(unittest.TestCase): stock_entry = frappe.get_doc(make_stock_entry(production_order.name, "Manufacture", 1)) stock_entry.insert() self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.items]) - + def test_same_serial_nos_in_repack_or_manufacture_entries(self): s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC") serial_nos = s1.get("items")[0].serial_no - - s2 = make_stock_entry(item_code="_Test Serialized Item With Series", source="_Test Warehouse - _TC", + + s2 = make_stock_entry(item_code="_Test Serialized Item With Series", source="_Test Warehouse - _TC", qty=2, basic_rate=100, purpose="Repack", serial_no=serial_nos, do_not_save=True) - + s2.append("items", { "item_code": "_Test Serialized Item", "t_warehouse": "_Test Warehouse - _TC", @@ -584,7 +584,7 @@ class TestStockEntry(unittest.TestCase): "cost_center": "_Test Cost Center - _TC", "serial_no": serial_nos }) - + s2.submit() s2.cancel() diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 51dd8c156c..d4b0a20403 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -15,21 +15,37 @@ def reorder_item(): def _reorder_item(): material_requests = {"Purchase": {}, "Transfer": {}} - - item_warehouse_projected_qty = get_item_warehouse_projected_qty() - - warehouse_company = frappe._dict(frappe.db.sql("""select name, company from `tabWarehouse`""")) + warehouse_company = frappe._dict(frappe.db.sql("""select name, company from `tabWarehouse` + where disabled=0""")) default_company = (frappe.defaults.get_defaults().get("company") or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]) + items_to_consider = frappe.db.sql_list("""select name from `tabItem` item + where is_stock_item=1 and has_variants=0 + and (is_purchase_item=1 or is_sub_contracted_item=1) + and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s) + and ((re_order_level is not null and re_order_level > 0) + or exists (select name from `tabItem Reorder` ir where ir.parent=item.name) + or (variant_of is not null and variant_of != '' + and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of)) + )""", + {"today": nowdate()}) + + if not items_to_consider: + return + + item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider) + def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type): - if warehouse not in item_warehouse_projected_qty[item_code]: - # likely a disabled warehouse or a warehouse where BIN does not exist + if warehouse not in warehouse_company: + # a disabled warehouse return reorder_level = flt(reorder_level) reorder_qty = flt(reorder_qty) - projected_qty = item_warehouse_projected_qty[item_code][warehouse] + + # projected_qty will be 0 if Bin does not exist + projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse)) if reorder_level and projected_qty < reorder_level: deficiency = reorder_level - projected_qty @@ -44,7 +60,7 @@ def _reorder_item(): "reorder_qty": reorder_qty }) - for item_code in item_warehouse_projected_qty: + for item_code in items_to_consider: item = frappe.get_doc("Item", item_code) if item.variant_of and not item.get("reorder_levels"): @@ -62,18 +78,13 @@ def _reorder_item(): if material_requests: return create_material_request(material_requests) -def get_item_warehouse_projected_qty(): +def get_item_warehouse_projected_qty(items_to_consider): item_warehouse_projected_qty = {} for item_code, warehouse, projected_qty in frappe.db.sql("""select item_code, warehouse, projected_qty - from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != '' - and exists (select name from `tabItem` - where `tabItem`.name = `tabBin`.item_code and - is_stock_item=1 and (is_purchase_item=1 or is_sub_contracted_item=1) and - (ifnull(end_of_life, '0000-00-00')='0000-00-00' or end_of_life > %s)) - and exists (select name from `tabWarehouse` - where `tabWarehouse`.name = `tabBin`.warehouse - and ifnull(disabled, 0)=0)""", nowdate()): + from tabBin where item_code in ({0}) + and (warehouse != "" and warehouse is not null)"""\ + .format(", ".join(["%s"] * len(items_to_consider))), items_to_consider): item_warehouse_projected_qty.setdefault(item_code, {})[warehouse] = flt(projected_qty) diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py index d7dcb23199..348cca2fd3 100644 --- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py +++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py @@ -6,48 +6,58 @@ import frappe from frappe import _ def execute(filters=None): - columns = get_columns() - - data = frappe.db.sql("""select - item.name, item.item_name, description, item_group, brand, warehouse, item.stock_uom, - actual_qty, planned_qty, indented_qty, ordered_qty, reserved_qty, - projected_qty, item.re_order_level, item.re_order_qty, - (item.re_order_level - projected_qty) as shortage_qty - from `tabBin` bin, - (select name, company from tabWarehouse - {warehouse_conditions}) wh, - (select name, item_name, description, stock_uom, item_group, - brand, re_order_level, re_order_qty - from `tabItem` {item_conditions}) item - where item_code = item.name and warehouse = wh.name - order by item.name, wh.name"""\ - .format(item_conditions=get_item_conditions(filters), - warehouse_conditions=get_warehouse_conditions(filters)), filters) - - return columns, data - + filters = frappe._dict(filters or {}) + return get_columns(), get_data(filters) + def get_columns(): - return [_("Item Code") + ":Link/Item:140", _("Item Name") + "::100", _("Description") + "::200", - _("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Warehouse") + ":Link/Warehouse:120", - _("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100", - _("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100", _("Reserved Qty") + ":Float:100", - _("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100", + return [_("Item Code") + ":Link/Item:140", _("Item Name") + "::100", _("Description") + "::200", + _("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Warehouse") + ":Link/Warehouse:120", + _("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100", + _("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100", _("Reserved Qty") + ":Float:100", + _("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100", _("Shortage Qty") + ":Float:100"] - -def get_item_conditions(filters): - conditions = [] - if filters.get("item_code"): - conditions.append("name=%(item_code)s") - if filters.get("brand"): - conditions.append("brand=%(brand)s") - - return "where {}".format(" and ".join(conditions)) if conditions else "" - -def get_warehouse_conditions(filters): - conditions = [] - if filters.get("company"): - conditions.append("company=%(company)s") - if filters.get("warehouse"): - conditions.append("name=%(warehouse)s") - - return "where {}".format(" and ".join(conditions)) if conditions else "" \ No newline at end of file + +def get_data(filters): + item_map = {} + warehouse_company = {} + data = [] + + for bin in get_bin_list(filters): + item = item_map.setdefault(bin.item_code, frappe.get_doc("Item", bin.item_code)) + company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company")) + + if filters.brand and filters.brand != item.brand: + continue + + elif filters.company and filters.company != company: + continue + + re_order_level = re_order_qty = 0 + + if bin.warehouse==item.default_warehouse: + re_order_level = item.re_order_level or 0 + re_order_qty = item.re_order_qty or 0 + + for d in item.get("reorder_levels"): + if d.warehouse == bin.warehouse: + re_order_level = d.warehouse_reorder_level + re_order_qty = d.warehouse_reorder_qty + + data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse, + item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, bin.reserved_qty, + bin.projected_qty, re_order_level, re_order_qty, re_order_level - bin.projected_qty]) + + return data + +def get_bin_list(filters): + bin_filters = frappe._dict() + if filters.item_code: + bin_filters.item_code = filters.item_code + if filters.warehouse: + bin_filters.warehouse = filters.warehouse + + bin_list = frappe.get_all("Bin", fields=["item_code", "warehouse", + "actual_qty", "planned_qty", "indented_qty", "ordered_qty", "reserved_qty", "projected_qty"], + filters=bin_filters, order_by="item_code, warehouse") + + return bin_list