diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py index 3ca424dbe2..14e79999f0 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -261,6 +261,12 @@ def get_data(): "name": "Batch-Wise Balance History", "doctype": "Batch" }, + { + "type": "report", + "is_query_report": True, + "name": "Batch Item Expiry Status", + "doctype": "Stock Ledger Entry" + }, { "type": "report", "is_query_report": True, 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 0000000000..e69de29bb2 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 0000000000..eeeb9adc84 --- /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 0000000000..d5d23ba1a5 --- /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 0000000000..7354eee413 --- /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 (In Days)") + ":Int: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_days + else: + qty_dict.expiry_status = 0 + + 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