chore: fifo queue vs qty after transaction comparison report
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/__init__.py b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/__init__.py
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js
new file mode 100644
index 0000000..0b8f496
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.js
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+const DIFFERNCE_FIELD_NAMES = [
+ "fifo_qty_diff",
+ "fifo_value_diff",
+];
+
+frappe.query_reports["FIFO Queue vs Qty After Transaction Comparison"] = {
+ "filters": [
+ {
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "label": "Item",
+ "options": "Item",
+ get_query: function() {
+ return {
+ filters: {is_stock_item: 1, has_serial_no: 0}
+ }
+ }
+ },
+ {
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "label": "Item Group",
+ "options": "Item Group",
+ },
+ {
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "label": "Warehouse",
+ "options": "Warehouse",
+ },
+ {
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "label": "From Posting Date",
+ },
+ {
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "label": "From Posting Date",
+ }
+ ],
+ formatter (value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+ if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) {
+ value = "<span style='color:red'>" + value + "</span>";
+ }
+ return value;
+ },
+};
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.json b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.json
new file mode 100644
index 0000000..5e958aa
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.json
@@ -0,0 +1,27 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2022-05-11 04:09:13.460652",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letter_head": "abc",
+ "modified": "2022-05-11 04:09:20.232177",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "FIFO Queue vs Qty After Transaction Comparison",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Ledger Entry",
+ "report_name": "FIFO Queue vs Qty After Transaction Comparison",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Administrator"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py
new file mode 100644
index 0000000..9e14033
--- /dev/null
+++ b/erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py
@@ -0,0 +1,212 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import json
+
+import frappe
+from frappe import _
+from frappe.utils import flt
+from frappe.utils.nestedset import get_descendants_of
+
+SLE_FIELDS = (
+ "name",
+ "item_code",
+ "warehouse",
+ "posting_date",
+ "posting_time",
+ "creation",
+ "voucher_type",
+ "voucher_no",
+ "actual_qty",
+ "qty_after_transaction",
+ "stock_queue",
+ "batch_no",
+ "stock_value",
+ "valuation_rate",
+)
+
+
+def execute(filters=None):
+ columns = get_columns()
+ data = get_data(filters)
+ return columns, data
+
+
+def get_data(filters):
+ if not any([filters.warehouse, filters.item_code, filters.item_group]):
+ frappe.throw(_("Any one of following filters required: warehouse, Item Code, Item Group"))
+ sles = get_stock_ledger_entries(filters)
+ return find_first_bad_queue(sles)
+
+
+def get_stock_ledger_entries(filters):
+
+ sle_filters = {"is_cancelled": 0}
+
+ if filters.warehouse:
+ children = get_descendants_of("Warehouse", filters.warehouse)
+ sle_filters["warehouse"] = ("in", children + [filters.warehouse])
+
+ if filters.item_code:
+ sle_filters["item_code"] = filters.item_code
+ elif filters.get("item_group"):
+ item_group = filters.get("item_group")
+ children = get_descendants_of("Item Group", item_group)
+ item_group_filter = {"item_group": ("in", children + [item_group])}
+ sle_filters["item_code"] = (
+ "in",
+ frappe.get_all("Item", filters=item_group_filter, pluck="name", order_by=None),
+ )
+
+ if filters.from_date:
+ sle_filters["posting_date"] = (">=", filters.from_date)
+ if filters.to_date:
+ sle_filters["posting_date"] = ("<=", filters.to_date)
+
+ return frappe.get_all(
+ "Stock Ledger Entry",
+ fields=SLE_FIELDS,
+ filters=sle_filters,
+ order_by="timestamp(posting_date, posting_time), creation",
+ )
+
+
+def find_first_bad_queue(sles):
+ item_warehouse_sles = {}
+ for sle in sles:
+ item_warehouse_sles.setdefault((sle.item_code, sle.warehouse), []).append(sle)
+
+ data = []
+
+ for _item_wh, sles in item_warehouse_sles.items():
+ for idx, sle in enumerate(sles):
+ queue = json.loads(sle.stock_queue or "[]")
+
+ sle.fifo_queue_qty = 0.0
+ sle.fifo_stock_value = 0.0
+ for qty, rate in queue:
+ sle.fifo_queue_qty += flt(qty)
+ sle.fifo_stock_value += flt(qty) * flt(rate)
+
+ sle.fifo_qty_diff = sle.qty_after_transaction - sle.fifo_queue_qty
+ sle.fifo_value_diff = sle.stock_value - sle.fifo_stock_value
+
+ if sle.batch_no:
+ sle.use_batchwise_valuation = frappe.db.get_value(
+ "Batch", sle.batch_no, "use_batchwise_valuation", cache=True
+ )
+
+ if abs(sle.fifo_qty_diff) > 0.001 or abs(sle.fifo_value_diff) > 0.1:
+ if idx:
+ data.append(sles[idx - 1])
+ data.append(sle)
+ data.append({})
+ break
+
+ return data
+
+
+def get_columns():
+ return [
+ {
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "label": _("Stock Ledger Entry"),
+ "options": "Stock Ledger Entry",
+ },
+ {
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "label": _("Item Code"),
+ "options": "Item",
+ },
+ {
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ },
+ {
+ "fieldname": "posting_date",
+ "fieldtype": "Data",
+ "label": _("Posting Date"),
+ },
+ {
+ "fieldname": "posting_time",
+ "fieldtype": "Data",
+ "label": _("Posting Time"),
+ },
+ {
+ "fieldname": "creation",
+ "fieldtype": "Data",
+ "label": _("Creation"),
+ },
+ {
+ "fieldname": "voucher_type",
+ "fieldtype": "Link",
+ "label": _("Voucher Type"),
+ "options": "DocType",
+ },
+ {
+ "fieldname": "voucher_no",
+ "fieldtype": "Dynamic Link",
+ "label": _("Voucher No"),
+ "options": "voucher_type",
+ },
+ {
+ "fieldname": "batch_no",
+ "fieldtype": "Link",
+ "label": _("Batch"),
+ "options": "Batch",
+ },
+ {
+ "fieldname": "use_batchwise_valuation",
+ "fieldtype": "Check",
+ "label": _("Batchwise Valuation"),
+ },
+ {
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "label": _("Qty Change"),
+ },
+ {
+ "fieldname": "qty_after_transaction",
+ "fieldtype": "Float",
+ "label": _("(A) Qty After Transaction"),
+ },
+ {
+ "fieldname": "stock_queue",
+ "fieldtype": "Data",
+ "label": _("FIFO/LIFO Queue"),
+ },
+ {
+ "fieldname": "fifo_queue_qty",
+ "fieldtype": "Float",
+ "label": _("(C) Total qty in queue"),
+ },
+ {
+ "fieldname": "fifo_qty_diff",
+ "fieldtype": "Float",
+ "label": _("A - C"),
+ },
+ {
+ "fieldname": "stock_value",
+ "fieldtype": "Float",
+ "label": _("(D) Balance Stock Value"),
+ },
+ {
+ "fieldname": "fifo_stock_value",
+ "fieldtype": "Float",
+ "label": _("(E) Balance Stock Value in Queue"),
+ },
+ {
+ "fieldname": "fifo_value_diff",
+ "fieldtype": "Float",
+ "label": _("D - E"),
+ },
+ {
+ "fieldname": "valuation_rate",
+ "fieldtype": "Float",
+ "label": _("(H) Valuation Rate"),
+ },
+ ]
diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py
index 55b9104..d118d8e 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -65,6 +65,8 @@
("Delayed Item Report", {"based_on": "Delivery Note"}),
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
("Stock Ledger Invariant Check", {"warehouse": "_Test Warehouse - _TC", "item": "_Test Item"}),
+ ("FIFO Queue vs Qty After Transaction Comparison", {"warehouse": "_Test Warehouse - _TC"}),
+ ("FIFO Queue vs Qty After Transaction Comparison", {"item_group": "All Item Groups"}),
]
OPTIONAL_FILTERS = {