feat: Stock Ledger Variance
report (#37165)
* feat: `Stock Ledger Variance` report * refactor: `get_data()`
This commit is contained in:
parent
f0859ecc60
commit
acda72d616
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
const DIFFERENCE_FIELD_NAMES = [
|
||||||
|
"difference_in_qty",
|
||||||
|
"fifo_qty_diff",
|
||||||
|
"fifo_value_diff",
|
||||||
|
"fifo_valuation_diff",
|
||||||
|
"valuation_diff",
|
||||||
|
"fifo_difference_diff",
|
||||||
|
"diff_value_diff"
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.query_reports["Stock Ledger Variance"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item",
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {is_stock_item: 1, has_serial_no: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"options": "Warehouse",
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {is_group: 0, disabled: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_in",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Difference In",
|
||||||
|
"options": [
|
||||||
|
"",
|
||||||
|
"Qty",
|
||||||
|
"Value",
|
||||||
|
"Valuation",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "include_disabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Include Disabled",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
formatter (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (DIFFERENCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
|
get_datatable_options(options) {
|
||||||
|
return Object.assign(options, {
|
||||||
|
checkboxColumn: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onload(report) {
|
||||||
|
report.page.add_inner_button(__('Create Reposting Entries'), () => {
|
||||||
|
let message = `
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Reposting Entries will change the value of
|
||||||
|
accounts Stock In Hand, and Stock Expenses
|
||||||
|
in the Trial Balance report and will also change
|
||||||
|
the Balance Value in the Stock Balance report.
|
||||||
|
</p>
|
||||||
|
<p>Are you sure you want to create Reposting Entries?</p>
|
||||||
|
</div>`;
|
||||||
|
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
|
||||||
|
let selected_rows = indexes.map(i => frappe.query_report.data[i]);
|
||||||
|
|
||||||
|
if (!selected_rows.length) {
|
||||||
|
frappe.throw(__("Please select rows to create Reposting Entries"));
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.confirm(__(message), () => {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check.create_reposting_entries',
|
||||||
|
args: {
|
||||||
|
rows: selected_rows,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2023-09-20 10:44:19.414449",
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"letterhead": null,
|
||||||
|
"modified": "2023-09-20 10:44:19.414449",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Stock Ledger Variance",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Stock Ledger Entry",
|
||||||
|
"report_name": "Stock Ledger Variance",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": []
|
||||||
|
}
|
@ -0,0 +1,279 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
|
from erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check import (
|
||||||
|
get_data as stock_ledger_invariant_check,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns, data = [], []
|
||||||
|
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"fieldname": "name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Stock Ledger Entry"),
|
||||||
|
"options": "Stock Ledger Entry",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("Posting Date"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_time",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("Posting Time"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "creation",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": _("Creation"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Item"),
|
||||||
|
"options": "Item",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": _("Warehouse"),
|
||||||
|
"options": "Warehouse",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "incoming_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("Incoming Rate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumption_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("Consumption Rate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(A) Qty After Transaction"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expected_qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(B) Expected Qty After Transaction"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_in_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("A - B"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": "stock_value_difference",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(F) Change in Stock Value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value_from_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(G) Sum of Change in Stock Value"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "diff_value_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("G - D"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(H) Change in Stock Value (FIFO Queue)"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_difference_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("H - F"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(I) Valuation Rate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(J) Valuation Rate as per FIFO"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("I - J"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "balance_value_by_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("(K) Valuation = Value (D) ÷ Qty (A)"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": _("I - K"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters=None):
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
item_warehouse_map = get_item_warehouse_combinations(filters)
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if item_warehouse_map:
|
||||||
|
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
|
||||||
|
|
||||||
|
for item_warehouse in item_warehouse_map:
|
||||||
|
report_data = stock_ledger_invariant_check(item_warehouse)
|
||||||
|
|
||||||
|
if not report_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for row in report_data:
|
||||||
|
if has_difference(row, precision, filters.difference_in):
|
||||||
|
data.append(add_item_warehouse_details(row, item_warehouse))
|
||||||
|
break
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_item_warehouse_combinations(filters: dict = None) -> dict:
|
||||||
|
filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
|
bin = frappe.qb.DocType("Bin")
|
||||||
|
item = frappe.qb.DocType("Item")
|
||||||
|
warehouse = frappe.qb.DocType("Warehouse")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(bin)
|
||||||
|
.inner_join(item)
|
||||||
|
.on(bin.item_code == item.name)
|
||||||
|
.inner_join(warehouse)
|
||||||
|
.on(bin.warehouse == warehouse.name)
|
||||||
|
.select(
|
||||||
|
bin.item_code,
|
||||||
|
bin.warehouse,
|
||||||
|
)
|
||||||
|
.where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
if filters.item_code:
|
||||||
|
query = query.where(item.name == filters.item_code)
|
||||||
|
if filters.warehouse:
|
||||||
|
query = query.where(warehouse.name == filters.warehouse)
|
||||||
|
if not filters.include_disabled:
|
||||||
|
query = query.where((item.disabled == 0) & (warehouse.disabled == 0))
|
||||||
|
|
||||||
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
|
def has_difference(row, precision, difference_in):
|
||||||
|
has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
|
||||||
|
has_value_difference = (
|
||||||
|
flt(row.diff_value_diff, precision)
|
||||||
|
or flt(row.fifo_value_diff, precision)
|
||||||
|
or flt(row.fifo_difference_diff, precision)
|
||||||
|
)
|
||||||
|
has_valuation_difference = flt(row.valuation_diff, precision) or flt(
|
||||||
|
row.fifo_valuation_diff, precision
|
||||||
|
)
|
||||||
|
|
||||||
|
if difference_in == "Qty" and has_qty_difference:
|
||||||
|
return True
|
||||||
|
elif difference_in == "Value" and has_value_difference:
|
||||||
|
return True
|
||||||
|
elif difference_in == "Valuation" and has_valuation_difference:
|
||||||
|
return True
|
||||||
|
elif difference_in not in ["Qty", "Value", "Valuation"] and (
|
||||||
|
has_qty_difference or has_value_difference or has_valuation_difference
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def add_item_warehouse_details(row, item_warehouse):
|
||||||
|
row.update(
|
||||||
|
{
|
||||||
|
"item_code": item_warehouse.item_code,
|
||||||
|
"warehouse": item_warehouse.warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return row
|
Loading…
x
Reference in New Issue
Block a user