feat: added report for mismatch in serial nos and stock quantity in warehouse (#22669)

* feat: added report for mismatch in serial nos and stock quantity in warehouse

* style: removed print statement

* fix: rename of reports and refactor of code

* fix: handled null condition for actual qty and added report link in stock desk page

* style: breaking code in multiple lines
diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json
index 1bf81f7..2fba5fa 100644
--- a/erpnext/stock/desk_page/stock/stock.json
+++ b/erpnext/stock/desk_page/stock/stock.json
@@ -33,7 +33,7 @@
   {
    "hidden": 0,
    "label": "Key Reports",
-   "links": "[\n    {\n        \"dependencies\": [\n            \"Item Price\"\n        ],\n        \"doctype\": \"Item Price\",\n        \"is_query_report\": false,\n        \"label\": \"Item-wise Price List Rate\",\n        \"name\": \"Item-wise Price List Rate\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Stock Entry\"\n        ],\n        \"doctype\": \"Stock Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Analytics\",\n        \"name\": \"Stock Analytics\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Delivery Note\"\n        ],\n        \"doctype\": \"Delivery Note\",\n        \"is_query_report\": true,\n        \"label\": \"Delivery Note Trends\",\n        \"name\": \"Delivery Note Trends\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Purchase Receipt\"\n        ],\n        \"doctype\": \"Purchase Receipt\",\n        \"is_query_report\": true,\n        \"label\": \"Purchase Receipt Trends\",\n        \"name\": \"Purchase Receipt Trends\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Sales Order\"\n        ],\n        \"doctype\": \"Sales Order\",\n        \"is_query_report\": true,\n        \"label\": \"Sales Order Analysis\",\n        \"name\": \"Sales Order Analysis\",\n        \"type\": \"report\"\n    },\n   {\n         \"dependencies\": [\n            \"Purchase Order\"\n        ],\n        \"doctype\": \"Purchase Order\",\n        \"is_query_report\": true,\n        \"label\": \"Purchase Order Analysis\",\n        \"name\": \"Purchase Order Analysis\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Bin\"\n        ],\n        \"doctype\": \"Bin\",\n        \"is_query_report\": true,\n        \"label\": \"Item Shortage Report\",\n        \"name\": \"Item Shortage Report\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Batch\"\n        ],\n        \"doctype\": \"Batch\",\n        \"is_query_report\": true,\n        \"label\": \"Batch-Wise Balance History\",\n        \"name\": \"Batch-Wise Balance History\",\n        \"type\": \"report\"\n    }\n]"
+   "links": "[\n    {\n        \"dependencies\": [\n            \"Item Price\"\n        ],\n        \"doctype\": \"Item Price\",\n        \"is_query_report\": false,\n        \"label\": \"Item-wise Price List Rate\",\n        \"name\": \"Item-wise Price List Rate\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Stock Entry\"\n        ],\n        \"doctype\": \"Stock Entry\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Analytics\",\n        \"name\": \"Stock Analytics\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Item\"\n        ],\n        \"doctype\": \"Item\",\n        \"is_query_report\": true,\n        \"label\": \"Stock Qty vs Serial No Count\",\n        \"name\": \"Stock Qty vs Serial No Count\",\n        \"onboard\": 1,\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Delivery Note\"\n        ],\n        \"doctype\": \"Delivery Note\",\n        \"is_query_report\": true,\n        \"label\": \"Delivery Note Trends\",\n        \"name\": \"Delivery Note Trends\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Purchase Receipt\"\n        ],\n        \"doctype\": \"Purchase Receipt\",\n        \"is_query_report\": true,\n        \"label\": \"Purchase Receipt Trends\",\n        \"name\": \"Purchase Receipt Trends\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Sales Order\"\n        ],\n        \"doctype\": \"Sales Order\",\n        \"is_query_report\": true,\n        \"label\": \"Sales Order Analysis\",\n        \"name\": \"Sales Order Analysis\",\n        \"type\": \"report\"\n    },\n   {\n         \"dependencies\": [\n            \"Purchase Order\"\n        ],\n        \"doctype\": \"Purchase Order\",\n        \"is_query_report\": true,\n        \"label\": \"Purchase Order Analysis\",\n        \"name\": \"Purchase Order Analysis\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Bin\"\n        ],\n        \"doctype\": \"Bin\",\n        \"is_query_report\": true,\n        \"label\": \"Item Shortage Report\",\n        \"name\": \"Item Shortage Report\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Batch\"\n        ],\n        \"doctype\": \"Batch\",\n        \"is_query_report\": true,\n        \"label\": \"Batch-Wise Balance History\",\n        \"name\": \"Batch-Wise Balance History\",\n        \"type\": \"report\"\n    }\n]"
   },
   {
    "hidden": 0,
@@ -58,7 +58,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Stock",
- "modified": "2020-05-30 17:32:11.062681",
+ "modified": "2020-08-11 17:29:32.626067",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock",
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/__init__.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/__init__.py
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js
new file mode 100644
index 0000000..2a0fd40
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js
@@ -0,0 +1,42 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Stock Qty vs Serial No Count"] = {
+	"filters": [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		},
+		{
+			"fieldname":"warehouse",
+			"label": __("Warehouse"),
+			"fieldtype": "Link",
+			"options": "Warehouse",
+			"get_query": function() {
+				const company = frappe.query_report.get_filter_value('company');
+				return {
+					filters: { 'company': company }
+				}
+			},
+			"reqd": 1
+		},
+	],
+
+	"formatter": function (value, row, column, data, default_formatter) {
+		value = default_formatter(value, row, column, data);
+		if (column.fieldname == "difference" && data) {
+			if (data.difference > 0) {
+				value = "<span style='color:red'>" + value + "</span>";
+			}
+			else if (data.difference < 0) {
+				value = "<span style='color:red'>" + value + "</span>";
+			}
+		}
+		return value;
+	}
+};
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.json b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.json
new file mode 100644
index 0000000..c7108b5
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "creation": "2020-07-23 19:31:32.395011",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-07-23 19:32:02.168185",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Stock Qty vs Serial No Count",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Item",
+ "report_name": "Stock Qty vs Serial No Count",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Stock Manager"
+  },
+  {
+   "role": "Stock User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
new file mode 100644
index 0000000..55f041c
--- /dev/null
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
@@ -0,0 +1,80 @@
+# 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 _
+
+def execute(filters=None):
+	columns = get_columns()
+	data = get_data(filters.warehouse)
+	return columns, data
+
+def get_columns():
+	columns = [
+		{
+			"label": _("Item Code"),
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 200
+		},
+		{
+			"label": _("Item Name"),
+			"fieldname": "item_name",
+			"fieldtype": "Data",
+			"width": 200
+		},
+		{
+			"label": _("Serial No Count"),
+			"fieldname": "total",
+			"fieldtype": "Float",
+			"width": 150
+		},
+		{
+			"label": _("Stock Qty"),
+			"fieldname": "stock_qty",
+			"fieldtype": "Float",
+			"width": 150
+		},
+		{
+			"label": _("Difference"),
+			"fieldname": "difference",
+			"fieldtype": "Float",
+			"width": 150
+		},
+	]
+
+	return columns
+
+def get_data(warehouse):
+	serial_item_list = frappe.get_all("Item", filters={
+		'has_serial_no': True,
+	}, fields=['item_code', 'item_name'])
+	
+	status_list = ['Active', 'Expired']
+	data = []
+	for item in serial_item_list:
+		total_serial_no = frappe.db.count("Serial No", 
+			filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse})
+
+		actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'], 
+			filters={"warehouse": warehouse, "item_code": item.item_code})
+
+		# frappe.db.get_value returns null if no record exist.
+		if not actual_qty:
+			actual_qty = 0
+
+		difference = total_serial_no - actual_qty
+
+		row = {
+			"item_code": item.item_code,
+			"item_name": item.item_name,
+			"total": total_serial_no,
+			"stock_qty": actual_qty,
+			"difference": difference,
+		}
+
+		data.append(row)
+
+	return data
\ No newline at end of file