feat: added reports to check incorrect qty and valuation for serialized items
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/__init__.py
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js
new file mode 100644
index 0000000..bf11277
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.js
@@ -0,0 +1,27 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Incorrect Balance Qty After Transaction"] = {
+ "filters": [
+ {
+ label: __("Company"),
+ fieldtype: "Link",
+ fieldname: "company",
+ options: "Company",
+ default: frappe.defaults.get_user_default("Company"),
+ reqd: 1
+ },
+ {
+ label: __('Item Code'),
+ fieldtype: 'Link',
+ fieldname: 'item_code',
+ options: 'Item'
+ },
+ {
+ label: __('Warehouse'),
+ fieldtype: 'Link',
+ fieldname: 'warehouse'
+ }
+ ]
+};
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json
new file mode 100644
index 0000000..a5815bc
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-05-12 16:47:58.717853",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-05-12 16:48:28.347575",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Incorrect Balance Qty After Transaction",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Incorrect Balance Qty After Transaction",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Stock Manager"
+ },
+ {
+ "role": "Purchase User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
new file mode 100644
index 0000000..cf174c9
--- /dev/null
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from six import iteritems
+from frappe.utils import flt
+
+def execute(filters=None):
+ columns, data = [], []
+ columns = get_columns()
+ data = get_data(filters)
+ return columns, data
+
+def get_data(filters):
+ data = get_stock_ledger_entries(filters)
+ itewise_balance_qty = {}
+
+ for row in data:
+ key = (row.item_code, row.warehouse)
+ itewise_balance_qty.setdefault(key, []).append(row)
+
+ res = validate_data(itewise_balance_qty)
+ return res
+
+def validate_data(itewise_balance_qty):
+ res = []
+ for key, data in iteritems(itewise_balance_qty):
+ row = get_incorrect_data(data)
+ if row:
+ res.append(row)
+ res.append({})
+
+ return res
+
+def get_incorrect_data(data):
+ balance_qty = 0.0
+ for row in data:
+ balance_qty += row.actual_qty
+ if row.voucher_type == "Stock Reconciliation" and not row.batch_no:
+ balance_qty = flt(row.qty_after_transaction)
+
+ row.expected_balance_qty = balance_qty
+ if abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction)) > 0.5:
+ row.differnce = abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction))
+ return row
+
+def get_stock_ledger_entries(report_filters):
+ filters = {}
+ fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'actual_qty',
+ 'posting_date', 'posting_time', 'company', 'warehouse', 'qty_after_transaction', 'batch_no']
+
+ for field in ['warehouse', 'item_code', 'company']:
+ if report_filters.get(field):
+ filters[field] = report_filters.get(field)
+
+ return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters,
+ order_by = 'timestamp(posting_date, posting_time) asc, creation asc')
+
+def get_columns():
+ return [{
+ 'label': _('Id'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'name',
+ 'options': 'Stock Ledger Entry',
+ 'width': 120
+ }, {
+ 'label': _('Posting Date'),
+ 'fieldtype': 'Date',
+ 'fieldname': 'posting_date',
+ 'width': 110
+ }, {
+ 'label': _('Voucher Type'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'voucher_type',
+ 'options': 'DocType',
+ 'width': 120
+ }, {
+ 'label': _('Voucher No'),
+ 'fieldtype': 'Dynamic Link',
+ 'fieldname': 'voucher_no',
+ 'options': 'voucher_type',
+ 'width': 120
+ }, {
+ 'label': _('Item Code'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'item_code',
+ 'options': 'Item',
+ 'width': 120
+ }, {
+ 'label': _('Warehouse'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'warehouse',
+ 'options': 'Warehouse',
+ 'width': 120
+ }, {
+ 'label': _('Expected Balance Qty'),
+ 'fieldtype': 'Float',
+ 'fieldname': 'expected_balance_qty',
+ 'width': 170
+ }, {
+ 'label': _('Actual Balance Qty'),
+ 'fieldtype': 'Float',
+ 'fieldname': 'qty_after_transaction',
+ 'width': 150
+ }, {
+ 'label': _('Difference'),
+ 'fieldtype': 'Float',
+ 'fieldname': 'differnce',
+ 'width': 110
+ }]
\ No newline at end of file
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py b/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/__init__.py
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js
new file mode 100644
index 0000000..c62d480
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.js
@@ -0,0 +1,35 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Incorrect Serial No Valuation"] = {
+ "filters": [
+ {
+ label: __('Item Code'),
+ fieldtype: 'Link',
+ fieldname: 'item_code',
+ options: 'Item',
+ get_query: function() {
+ return {
+ filters: {
+ 'has_serial_no': 1
+ }
+ }
+ }
+ },
+ {
+ label: __('From Date'),
+ fieldtype: 'Date',
+ fieldname: 'from_date',
+ reqd: 1,
+ default: frappe.defaults.get_user_default("year_start_date")
+ },
+ {
+ label: __('To Date'),
+ fieldtype: 'Date',
+ fieldname: 'to_date',
+ reqd: 1,
+ default: frappe.defaults.get_user_default("year_end_date")
+ }
+ ]
+};
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json
new file mode 100644
index 0000000..cc384a5
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.json
@@ -0,0 +1,36 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-05-13 13:07:00.767845",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "modified": "2021-05-13 13:07:00.767845",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Incorrect Serial No Valuation",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "Incorrect Serial No Valuation",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Stock User"
+ },
+ {
+ "role": "Accounts Manager"
+ },
+ {
+ "role": "Accounts User"
+ },
+ {
+ "role": "Stock Manager"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
new file mode 100644
index 0000000..e54cf4c
--- /dev/null
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+import copy
+from frappe import _
+from six import iteritems
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+def execute(filters=None):
+ columns, data = [], []
+ columns = get_columns()
+ data = get_data(filters)
+ return columns, data
+
+def get_data(filters):
+ data = get_stock_ledger_entries(filters)
+ serial_nos_data = prepare_serial_nos(data)
+ data = get_incorrect_serial_nos(serial_nos_data)
+
+ return data
+
+def prepare_serial_nos(data):
+ serial_no_wise_data = {}
+ for row in data:
+ if not row.serial_nos:
+ continue
+
+ for serial_no in get_serial_nos(row.serial_nos):
+ sle = copy.deepcopy(row)
+ sle.serial_no = serial_no
+ sle.qty = 1 if sle.actual_qty > 0 else -1
+ sle.valuation_rate = sle.valuation_rate if sle.actual_qty > 0 else sle.valuation_rate * -1
+ serial_no_wise_data.setdefault(serial_no, []).append(sle)
+
+ return serial_no_wise_data
+
+def get_incorrect_serial_nos(serial_nos_data):
+ result = []
+
+ total_value = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Balance'))})
+
+ for serial_no, data in iteritems(serial_nos_data):
+ total_dict = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Total'))})
+
+ if check_incorrect_serial_data(data, total_dict):
+ result.extend(data)
+
+ total_value.qty += total_dict.qty
+ total_value.valuation_rate += total_dict.valuation_rate
+
+ result.append(total_dict)
+ result.append({})
+
+ result.append(total_value)
+
+ return result
+
+def check_incorrect_serial_data(data, total_dict):
+ incorrect_data = False
+ for row in data:
+ total_dict.qty += row.qty
+ total_dict.valuation_rate += row.valuation_rate
+
+ if ((total_dict.qty == 0 and abs(total_dict.valuation_rate) > 0) or total_dict.qty < 0):
+ incorrect_data = True
+
+ return incorrect_data
+
+def get_stock_ledger_entries(report_filters):
+ fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'serial_no as serial_nos', 'actual_qty',
+ 'posting_date', 'posting_time', 'company', 'warehouse', '(stock_value_difference / actual_qty) as valuation_rate']
+
+ filters = {'serial_no': ("is", "set")}
+
+ if report_filters.get('item_code'):
+ filters['item_code'] = report_filters.get('item_code')
+
+ if report_filters.get('from_date') and report_filters.get('to_date'):
+ filters['posting_date'] = ('between', [report_filters.get('from_date'), report_filters.get('to_date')])
+
+ return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters,
+ order_by = 'timestamp(posting_date, posting_time) asc, creation asc')
+
+def get_columns():
+ return [{
+ 'label': _('Company'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'company',
+ 'options': 'Company',
+ 'width': 120
+ }, {
+ 'label': _('Id'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'name',
+ 'options': 'Stock Ledger Entry',
+ 'width': 120
+ }, {
+ 'label': _('Posting Date'),
+ 'fieldtype': 'Date',
+ 'fieldname': 'posting_date',
+ 'width': 90
+ }, {
+ 'label': _('Posting Time'),
+ 'fieldtype': 'Time',
+ 'fieldname': 'posting_time',
+ 'width': 90
+ }, {
+ 'label': _('Voucher Type'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'voucher_type',
+ 'options': 'DocType',
+ 'width': 100
+ }, {
+ 'label': _('Voucher No'),
+ 'fieldtype': 'Dynamic Link',
+ 'fieldname': 'voucher_no',
+ 'options': 'voucher_type',
+ 'width': 110
+ }, {
+ 'label': _('Item Code'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'item_code',
+ 'options': 'Item',
+ 'width': 120
+ }, {
+ 'label': _('Warehouse'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'warehouse',
+ 'options': 'Warehouse',
+ 'width': 120
+ }, {
+ 'label': _('Serial No'),
+ 'fieldtype': 'Link',
+ 'fieldname': 'serial_no',
+ 'options': 'Serial No',
+ 'width': 100
+ }, {
+ 'label': _('Qty'),
+ 'fieldtype': 'Float',
+ 'fieldname': 'qty',
+ 'width': 80
+ }, {
+ 'label': _('Valuation Rate (In / Out)'),
+ 'fieldtype': 'Currency',
+ 'fieldname': 'valuation_rate',
+ 'width': 110
+ }]
\ No newline at end of file
diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json
index 3221dc4..529ce8e 100644
--- a/erpnext/stock/workspace/stock/stock.json
+++ b/erpnext/stock/workspace/stock/stock.json
@@ -15,6 +15,7 @@
"hide_custom": 0,
"icon": "stock",
"idx": 0,
+ "is_default": 0,
"is_standard": 1,
"label": "Stock",
"links": [
@@ -653,9 +654,44 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Incorrect Data Report",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Card Break"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Incorrect Serial No Qty and Valuation",
+ "link_to": "Incorrect Serial No Valuation",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Incorrect Balance Qty After Transaction",
+ "link_to": "Incorrect Balance Qty After Transaction",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Stock and Account Value Comparison",
+ "link_to": "Stock and Account Value Comparison",
+ "link_type": "Report",
+ "onboard": 0,
+ "type": "Link"
}
],
- "modified": "2020-12-01 13:38:36.282890",
+ "modified": "2021-05-13 13:10:24.914983",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",