Report : Batch Item Expiry Status
Select From Date and To Date
Lists Items about to expire or already expired
Expiry Status column shows number of days in which item/batch will expire
diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py
index 3ca424d..14e7999 100644
--- a/erpnext/config/stock.py
+++ b/erpnext/config/stock.py
@@ -264,6 +264,12 @@
{
"type": "report",
"is_query_report": True,
+ "name": "Batch Item Expiry Status",
+ "doctype": "Stock Ledger Entry"
+ },
+ {
+ "type": "report",
+ "is_query_report": True,
"name": "Item Prices",
"doctype": "Price List"
},
diff --git a/erpnext/stock/report/batch_item_expiry_status/__init__.py b/erpnext/stock/report/batch_item_expiry_status/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/batch_item_expiry_status/__init__.py
diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.js b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.js
new file mode 100644
index 0000000..eeeb9ad
--- /dev/null
+++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.js
@@ -0,0 +1,21 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Batch Item Expiry Status"] = {
+ "filters": [
+ {
+ "fieldname":"from_date",
+ "label": __("From Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "default": sys_defaults.year_start_date,
+ },
+ {
+ "fieldname":"to_date",
+ "label": __("To Date"),
+ "fieldtype": "Date",
+ "width": "80",
+ "default": frappe.datetime.get_today()
+ }
+ ]
+}
diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.json b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.json
new file mode 100644
index 0000000..d5d23ba
--- /dev/null
+++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.json
@@ -0,0 +1,18 @@
+{
+ "add_total_row": 0,
+ "apply_user_permissions": 1,
+ "creation": "2016-12-21 11:29:01.884436",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2016-12-21 11:29:01.884436",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Batch Item Expiry Status",
+ "owner": "Administrator",
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Batch Item Expiry Status",
+ "report_type": "Script Report"
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
new file mode 100644
index 0000000..1ab78a9
--- /dev/null
+++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+from frappe.utils import flt, cint, getdate
+
+def execute(filters=None):
+ if not filters: filters = {}
+
+ float_precision = cint(frappe.db.get_default("float_precision")) or 3
+
+ columns = get_columns(filters)
+ item_map = get_item_details(filters)
+ iwb_map = get_item_warehouse_batch_map(filters, float_precision)
+
+ data = []
+ for item in sorted(iwb_map):
+ for wh in sorted(iwb_map[item]):
+ for batch in sorted(iwb_map[item][wh]):
+ qty_dict = iwb_map[item][wh][batch]
+
+ data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch,
+ frappe.db.get_value('Batch', batch, 'expiry_date'), qty_dict.expiry_status
+ ])
+
+
+ return columns, data
+
+def get_columns(filters):
+ """return columns based on filters"""
+
+ columns = [_("Item") + ":Link/Item:100"] + [_("Item Name") + "::150"] + [_("Description") + "::150"] + \
+ [_("Warehouse") + ":Link/Warehouse:100"] + [_("Batch") + ":Link/Batch:100"] + [_("Expires On") + ":Date:90"] + \
+ [_("Expiry Status") + ":Data:120"]
+
+ return columns
+
+def get_conditions(filters):
+ conditions = ""
+ if not filters.get("from_date"):
+ frappe.throw(_("'From Date' is required"))
+
+ if filters.get("to_date"):
+ conditions += " and posting_date <= '%s'" % filters["to_date"]
+ else:
+ frappe.throw(_("'To Date' is required"))
+
+ return conditions
+
+def get_stock_ledger_entries(filters):
+ conditions = get_conditions(filters)
+ return frappe.db.sql("""select item_code, batch_no, warehouse,
+ posting_date, actual_qty
+ from `tabStock Ledger Entry`
+ where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
+ conditions, as_dict=1)
+
+def get_item_warehouse_batch_map(filters, float_precision):
+ sle = get_stock_ledger_entries(filters)
+ iwb_map = {}
+
+ from_date = getdate(filters["from_date"])
+ to_date = getdate(filters["to_date"])
+
+ for d in sle:
+ iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {})\
+ .setdefault(d.batch_no, frappe._dict({
+ "expires_on": None, "expiry_status": None}))
+
+ qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
+
+ expiry_date_unicode = frappe.db.get_value('Batch', d.batch_no, 'expiry_date')
+ qty_dict.expires_on = expiry_date_unicode
+
+ exp_date = frappe.utils.data.getdate(expiry_date_unicode)
+ qty_dict.expires_on = exp_date
+
+ expires_in_days = (exp_date - frappe.utils.datetime.date.today()).days
+
+ if expires_in_days > 0:
+ qty_dict.expiry_status = "Expires in {} day(s)".format(expires_in_days)
+ else:
+ qty_dict.expiry_status = 'Expired'
+
+ return iwb_map
+
+def get_item_details(filters):
+ item_map = {}
+ for d in frappe.db.sql("select name, item_name, description from tabItem", as_dict=1):
+ item_map.setdefault(d.name, d)
+
+ return item_map