feat: serial no and batch traceability report

This commit is contained in:
Rohit Waghchaure
2025-08-04 19:15:36 +05:30
parent 28cbd18300
commit 7b05a2a097
14 changed files with 824 additions and 96 deletions

View File

@@ -431,3 +431,4 @@ erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
erpnext.patches.v15_0.add_company_payment_gateway_account
erpnext.patches.v16_0.update_serial_no_reference_name

View File

View File

@@ -0,0 +1,27 @@
import frappe
def execute():
# Update the reference_name, reference_doctype fields for Serial No where it is null
sabb = frappe.qb.DocType("Serial and Batch Bundle")
sabb_entry = frappe.qb.DocType("Serial and Batch Entry")
serial_no = frappe.qb.DocType("Serial No").as_("sn")
query = (
frappe.qb.update(serial_no)
.join(sabb_entry)
.on(sabb_entry.serial_no == serial_no.name)
.join(sabb)
.on(sabb.name == sabb_entry.parent)
.set(serial_no.reference_name, serial_no.purchase_document_no)
.set(serial_no.reference_doctype, sabb.voucher_type)
.set(serial_no.posting_date, sabb.posting_date)
.where(
(sabb.voucher_no == serial_no.purchase_document_no)
& (sabb.is_cancelled == 0)
& (sabb_entry.docstatus == 1)
)
)
query.run()

View File

@@ -1137,12 +1137,12 @@ class SerialandBatchBundle(Document):
def before_submit(self):
self.validate_serial_and_batch_data()
self.validate_serial_and_batch_no_for_returned()
self.set_purchase_document_no()
self.set_source_document_no()
def on_submit(self):
self.validate_serial_nos_inventory()
def set_purchase_document_no(self):
def set_source_document_no(self):
if self.flags.ignore_validate_serial_batch:
return
@@ -1154,10 +1154,9 @@ class SerialandBatchBundle(Document):
sn_table = frappe.qb.DocType("Serial No")
(
frappe.qb.update(sn_table)
.set(
sn_table.purchase_document_no,
self.voucher_no if not sn_table.purchase_document_no else self.voucher_no,
)
.set(sn_table.reference_doctype, self.voucher_type)
.set(sn_table.reference_name, self.voucher_no)
.set(sn_table.posting_date, self.posting_date)
.where(sn_table.name.isin(serial_nos))
).run()

View File

@@ -39,7 +39,11 @@
"company",
"column_break_2cmm",
"work_order",
"purchase_document_no"
"source_document_section",
"reference_doctype",
"posting_date",
"column_break_ctcx",
"reference_name"
],
"fields": [
{
@@ -262,13 +266,6 @@
"fieldname": "column_break_2cmm",
"fieldtype": "Column Break"
},
{
"fieldname": "purchase_document_no",
"fieldtype": "Data",
"label": "Creation Document No",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "customer",
"fieldtype": "Link",
@@ -277,12 +274,45 @@
"options": "Customer",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "source_document_section",
"fieldtype": "Section Break",
"label": "Source Document"
},
{
"fieldname": "reference_doctype",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Source Document Type",
"no_copy": 1,
"options": "DocType",
"read_only": 1
},
{
"fieldname": "column_break_ctcx",
"fieldtype": "Column Break"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Source Document Name",
"no_copy": 1,
"options": "reference_doctype",
"read_only": 1
},
{
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date",
"no_copy": 1,
"read_only": 1
}
],
"icon": "fa fa-barcode",
"idx": 1,
"links": [],
"modified": "2025-07-15 13:36:21.938700",
"modified": "2025-08-05 17:17:11.328682",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial No",

View File

@@ -48,8 +48,10 @@ class SerialNo(StockController):
item_name: DF.Data | None
location: DF.Link | None
maintenance_status: DF.Literal["", "Under Warranty", "Out of Warranty", "Under AMC", "Out of AMC"]
purchase_document_no: DF.Data | None
posting_date: DF.Date | None
purchase_rate: DF.Float
reference_doctype: DF.Link | None
reference_name: DF.DynamicLink | None
serial_no: DF.Data
status: DF.Literal["", "Active", "Inactive", "Consumed", "Delivered", "Expired"]
warehouse: DF.Link | None

View File

@@ -144,7 +144,15 @@ def get_columns(filters, data):
)
if not item_details or item_details.get("has_serial_no"):
columns.append({"label": _("Serial No"), "fieldname": "serial_no", "fieldtype": "Data", "width": 120})
columns.append(
{
"label": _("Serial No"),
"fieldname": "serial_no",
"fieldtype": "Link",
"width": 120,
"options": "Serial No",
}
)
if not item_details or item_details.get("has_batch_no"):
columns.extend(

View File

@@ -0,0 +1,112 @@
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.query_reports["Serial No and Batch Traceability"] = {
filters: [
{
fieldname: "item_code",
label: __("Item Code"),
options: "Item",
fieldtype: "Link",
get_query: () => {
return {
query: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.item_query",
};
},
},
{
fieldname: "batches",
label: __("Batch No"),
fieldtype: "MultiSelectList",
options: "Batch",
get_data: (txt) => {
let filters = {
disabled: 0,
};
let item_code = frappe.query_report.get_filter_value("item_code");
if (item_code?.length) {
filters.item = ["in", item_code];
}
return frappe.db.get_link_options("Batch", txt, filters);
},
},
{
fieldname: "serial_nos",
label: __("Serial No"),
fieldtype: "MultiSelectList",
options: "Serial No",
get_data: (txt) => {
let filters = {};
let item_code = frappe.query_report.get_filter_value("item_code");
if (item_code?.length) {
filters.item_code = ["in", item_code];
}
return frappe.db.get_link_options("Serial No", txt, filters);
},
},
{
fieldname: "traceability_direction",
label: __("Tracebility Direction"),
fieldtype: "Select",
options: "Backward\nForward\nBoth",
default: "Backward",
},
],
formatter: function (value, row, column, data, default_formatter) {
if (column.fieldname === "qty" && !data.item_code) {
return "";
}
return custom_formatter(value, row, column, data, default_formatter);
},
};
function getTraceabilityLink({ type, value, original_value, item_code, data, filter_values }) {
if (!value) return value;
const base_url = type === "batch_no" ? "/app/batch/" : "/app/serial-no/";
const filter_list = filter_values[type]; // either batches or serial_nos
let css_class = "ellipsis";
if (filter_list?.length && !filter_list.includes(original_value)) {
// value not in filtered list
css_class = "ellipsis";
} else if (item_code && data.item_code && data.item_code !== item_code) {
// mismatch in item code
css_class = "ellipsis";
} else {
// color by direction
css_class = data.direction === "Backward" ? "ellipsis text-success" : "ellipsis text-danger";
}
return `<a class="${css_class}" href="${base_url}${original_value}">${original_value}</a>`;
}
function custom_formatter(value, row, column, data, default_formatter) {
let original_value = value;
let filter_values = {
batch_no: frappe.query_report.get_filter_value("batches"),
serial_no: frappe.query_report.get_filter_value("serial_nos"),
};
let item_code = frappe.query_report.get_filter_value("item_code");
value = default_formatter(value, row, column, data);
if (["batch_no", "serial_no"].includes(column.fieldname) && value) {
value = getTraceabilityLink({
type: column.fieldname,
value,
original_value,
item_code,
data,
filter_values,
});
}
return value;
}

View File

@@ -0,0 +1,52 @@
{
"add_total_row": 0,
"add_translate_data": 0,
"columns": [],
"creation": "2025-08-05 13:41:28.654684",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letterhead": null,
"modified": "2025-08-05 13:41:28.654684",
"modified_by": "Administrator",
"module": "Stock",
"name": "Serial No and Batch Traceability",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Serial and Batch Bundle",
"report_name": "Serial No and Batch Traceability",
"report_type": "Script Report",
"roles": [
{
"role": "System Manager"
},
{
"role": "Purchase User"
},
{
"role": "Purchase Manager"
},
{
"role": "Stock User"
},
{
"role": "Stock Manager"
},
{
"role": "Delivery User"
},
{
"role": "Delivery Manager"
},
{
"role": "Manufacturing User"
},
{
"role": "Manufacturing Manager"
}
],
"timeout": 0
}

View File

@@ -0,0 +1,462 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.query_builder import Case
def execute(filters: dict | None = None):
report = ReportData(filters)
data = report.get_data()
columns = report.get_columns()
return columns, data
class ReportData:
def __init__(self, filters):
self.filters = filters
self.doctype_name = self.get_doctype()
def get_data(self):
result_data = []
if self.filters.get("traceability_direction") in ["Backward", "Both"]:
data = self.get_serial_no_batches()
source_data = self.prepare_source_data(data)
for key in source_data:
sabb_data = source_data[key]
if sabb_data.reference_doctype != "Stock Entry":
continue
self.set_backward_data(sabb_data)
self.parse_batch_details(source_data, result_data, "Backward")
if self.filters.get("traceability_direction") in ["Forward", "Both"]:
data = self.get_serial_no_batches()
batch_details = frappe._dict({})
for row in data:
value = row.serial_no or row.batch_no
self.set_forward_data(value, batch_details)
self.parse_batch_details(batch_details, result_data, "Forward")
return result_data
def parse_batch_details(self, sabb_data_details, data, direction, indent=0):
for key in sabb_data_details:
sabb = sabb_data_details[key]
row = {
"item_code": sabb.item_code,
"batch_no": sabb.batch_no,
"serial_no": sabb.serial_no,
"warehouse": sabb.warehouse,
"qty": sabb.qty,
"reference_doctype": sabb.reference_doctype,
"reference_name": sabb.reference_name,
"item_name": sabb.item_name,
"posting_date": sabb.posting_date,
"indent": indent,
"direction": direction,
}
if data and indent == 0:
data.append({})
if direction == "Forward" and row["qty"] > 0:
row["direction"] = "Backward"
if sabb.reference_doctype == "Purchase Receipt":
row["supplier"] = frappe.db.get_value(
"Purchase Receipt",
sabb.reference_name,
"supplier",
)
elif sabb.reference_doctype == "Stock Entry":
row["work_order"] = frappe.db.get_value(
"Stock Entry",
sabb.reference_name,
"work_order",
)
data.append(row)
raw_materials = sabb.get("raw_materials")
if raw_materials:
self.parse_batch_details(raw_materials, data, direction, indent + 1)
return data
def prepare_source_data(self, data):
source_data = frappe._dict({})
for row in data:
key = (row.item_code, row.reference_name)
if self.doctype_name == "Batch":
sabb_details = self.get_qty_from_sabb(row)
row.update(sabb_details)
else:
row.qty = 1
if key not in source_data:
row["raw_materials"] = frappe._dict({})
source_data[key] = row
return source_data
def get_qty_from_sabb(self, row):
sabb = frappe.qb.DocType("Serial and Batch Bundle")
sabb_entry = frappe.qb.DocType("Serial and Batch Entry")
query = (
frappe.qb.from_(sabb)
.inner_join(sabb_entry)
.on(sabb.name == sabb_entry.parent)
.select(
sabb_entry.qty,
sabb_entry.warehouse,
)
.where(
(sabb_entry.batch_no == row.batch_no)
& (sabb.voucher_type == row.reference_doctype)
& (sabb.voucher_no == row.reference_name)
& (sabb.is_cancelled == 0)
& (sabb_entry.docstatus == 1)
)
)
results = query.run(as_dict=True)
return results[0] if results else {}
def set_backward_data(self, sabb_data, qty=None):
if qty:
sabb_data.qty = qty
if "raw_materials" not in sabb_data:
sabb_data.raw_materials = frappe._dict({})
materials = self.get_materials(sabb_data)
for material in materials:
key = (material.item_code, material.batch_no)
# Recursive: batch has sub-components
if material.serial_no or material.batch_no:
value = material.serial_no or material.batch_no
if key not in sabb_data.raw_materials:
details = self.get_serial_no_batches(value)
if details:
details.update(self.get_qty_from_sabb(details))
sabb_data.raw_materials[key] = details
self.set_backward_data(sabb_data.raw_materials[key], material.qty)
else:
sub_key = material.item_code
if sub_key not in sabb_data.raw_materials:
sabb_data.raw_materials[sub_key] = frappe._dict(
{
"item_code": material.item_code,
"item_name": material.item_name,
"qty": material.qty or material.quantity,
"warehouse": material.warehouse,
}
)
return sabb_data
def get_serial_no_batches(self, name=None):
batches = self.filters.get("batches", [])
serial_nos = self.filters.get("serial_nos", [])
doctype = frappe.qb.DocType(self.doctype_name)
query = frappe.qb.from_(doctype).select(
doctype.reference_doctype,
doctype.reference_name,
doctype.item_name,
)
if self.doctype_name == "Batch":
query = query.select(
doctype.item.as_("item_code"),
doctype.name.as_("batch_no"),
doctype.manufacturing_date.as_("posting_date"),
)
else:
query = query.select(
doctype.item_code,
doctype.name.as_("serial_no"),
doctype.posting_date,
)
if name:
query = query.where(doctype.name == name)
data = query.run(as_dict=True)
return data[0] if data else {}
if batches:
query = query.where(doctype.name.isin(batches))
elif serial_nos:
query = query.where(doctype.name.isin(serial_nos))
if self.filters.get("item_code"):
if self.doctype_name == "Serial No":
query = query.where(doctype.item_code == self.filters.item_code)
else:
query = query.where(doctype.item == self.filters.item_code)
return query.run(as_dict=True)
def get_doctype(self):
if self.filters.item_code:
item_details = frappe.get_cached_value(
"Item",
self.filters.item_code,
["has_batch_no", "has_serial_no"],
as_dict=True,
)
if item_details.has_serial_no:
return "Serial No"
elif item_details.has_batch_no:
return "Batch"
elif self.filters.get("serial_nos"):
return "Serial No"
return "Batch"
def get_materials(self, sabb_data):
stock_entry = frappe.qb.DocType("Stock Entry")
stock_entry_detail = frappe.qb.DocType("Stock Entry Detail")
sabb_entry = frappe.qb.DocType("Serial and Batch Entry")
query = (
frappe.qb.from_(stock_entry)
.inner_join(stock_entry_detail)
.on(stock_entry.name == stock_entry_detail.parent)
.left_join(sabb_entry)
.on(
(stock_entry_detail.serial_and_batch_bundle == sabb_entry.parent)
& (sabb_entry.docstatus == 1)
)
.select(
stock_entry_detail.s_warehouse.as_("warehouse"),
stock_entry_detail.item_code,
stock_entry_detail.item_name,
stock_entry_detail.parenttype.as_("reference_doctype"),
stock_entry.name.as_("reference_name"),
(
(
stock_entry_detail.qty
/ Case().when(stock_entry.fg_completed_qty > 0, stock_entry.fg_completed_qty).else_(1)
)
* sabb_data.qty
).as_("qty"),
sabb_entry.batch_no,
sabb_entry.serial_no,
sabb_entry.qty.as_("quantity"),
)
.where(
(stock_entry.docstatus == 1)
& (stock_entry.purpose.isin(["Manufacture", "Repack"]))
& (stock_entry.name == sabb_data.reference_name)
& (stock_entry_detail.s_warehouse.isnotnull())
)
)
return query.run(as_dict=True)
def set_forward_data(self, value, sabb_data):
outward_entries = self.get_outward_sabb_entries(value)
for row in outward_entries:
if row.reference_doctype == "Stock Entry":
self.process_manufacture_or_repack_entry(row, sabb_data)
else:
self.add_direct_outward_entry(row, sabb_data)
def add_direct_outward_entry(self, row, batch_details):
key = (row.item_code, row.reference_name)
if key not in batch_details:
row["indent"] = 0
batch_details[key] = row
def get_outward_sabb_entries(self, value):
SABB = frappe.qb.DocType("Serial and Batch Bundle")
SABE = frappe.qb.DocType("Serial and Batch Entry")
query = (
frappe.qb.from_(SABB)
.inner_join(SABE)
.on(SABB.name == SABE.parent)
.select(
SABB.voucher_type.as_("reference_doctype"),
SABB.voucher_no.as_("reference_name"),
SABE.batch_no,
SABE.serial_no,
SABE.qty,
SABB.item_code,
SABB.item_name,
SABB.posting_date,
SABB.warehouse,
)
.where((SABB.is_cancelled == 0) & (SABE.docstatus == 1) & (SABB.type_of_transaction == "Outward"))
)
if self.doctype_name == "Serial No":
query = query.where(SABE.serial_no == value)
else:
query = query.where(SABE.batch_no == value)
return query.run(as_dict=True)
def process_manufacture_or_repack_entry(self, row, batch_details):
ste = frappe.db.get_value("Stock Entry", row.reference_name, ["purpose", "work_order"], as_dict=True)
if ste and ste.purpose in ["Manufacture", "Repack"]:
fg_item = self.get_finished_item_from_stock_entry(row.reference_name)
if not fg_item:
return
key = (fg_item.item_code, row.reference_name)
if key not in batch_details:
serial_no, batch_no = self.get_serial_batch_no(fg_item.serial_and_batch_bundle)
fg_item.update(
{
"work_order": ste.work_order,
"posting_date": row.posting_date,
"serial_no": serial_no,
"batch_no": batch_no,
"indent": 0,
"warehouse": fg_item.warehouse,
"raw_materials": frappe._dict({(row.item_code, row.reference_name): row}),
}
)
batch_details[key] = fg_item
def get_finished_item_from_stock_entry(self, reference_name):
return frappe.db.get_value(
"Stock Entry Detail",
{"parent": reference_name, "is_finished_item": 1},
[
"item_code",
"item_name",
"serial_and_batch_bundle",
"qty",
"parenttype as reference_doctype",
"parent as reference_name",
"t_warehouse as warehouse",
],
as_dict=True,
)
def get_serial_batch_no(self, serial_and_batch_bundle):
sabb_details = frappe.db.get_value(
"Serial and Batch Entry",
{"parent": serial_and_batch_bundle},
["batch_no", "serial_no"],
as_dict=True,
)
return (sabb_details.serial_no, sabb_details.batch_no) if sabb_details else (None, None)
def get_columns(self):
columns = [
{
"fieldname": "item_code",
"label": _("Item Code"),
"fieldtype": "Link",
"options": "Item",
"width": 180,
},
{
"fieldname": "item_name",
"label": _("Item Name"),
"fieldtype": "Data",
"width": 120,
},
]
if self.doctype_name == "Serial No":
columns.append(
{
"fieldname": "serial_no",
"label": _("Serial No"),
"fieldtype": "Link",
"options": "Serial No",
"width": 150,
}
)
else:
columns.append(
{
"fieldname": "batch_no",
"label": _("Batch No"),
"fieldtype": "Link",
"options": "Batch",
"width": 140,
}
)
columns.extend(
[
{
"fieldname": "qty",
"label": _("Quantity"),
"fieldtype": "Float",
"width": 90,
},
{
"fieldname": "reference_doctype",
"label": _("Voucher Type"),
"fieldtype": "Data",
"width": 130,
},
{
"fieldname": "reference_name",
"label": _("Source Document No"),
"fieldtype": "Dynamic Link",
"options": "reference_doctype",
"width": 200,
},
{
"fieldname": "warehouse",
"label": _("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse",
"width": 120,
},
{
"fieldname": "posting_date",
"label": _("Posting Date"),
"fieldtype": "Date",
"width": 120,
},
{
"fieldname": "work_order",
"label": _("Work Order"),
"fieldtype": "Link",
"options": "Work Order",
"width": 160,
},
{
"fieldname": "supplier",
"label": _("Supplier"),
"fieldtype": "Link",
"options": "Supplier",
"width": 150,
},
{
"fieldname": "customer",
"label": _("Customer"),
"fieldtype": "Link",
"options": "Customer",
"width": 150,
},
]
)
return columns

View File

@@ -423,7 +423,7 @@ class SerialBatchBundle:
"Active"
if warehouse
else status
if (sn_table.purchase_document_no != sle.voucher_no or sle.is_cancelled != 1)
if (sn_table.reference_name != sle.voucher_no or sle.is_cancelled != 1)
else "Inactive",
)
.set(sn_table.company, sle.company)
@@ -1263,6 +1263,10 @@ class SerialBatchCreation:
if self.get("voucher_no"):
voucher_no = self.get("voucher_no")
voucher_type = ""
if self.get("voucher_type"):
voucher_type = self.get("voucher_type")
for _i in range(abs(cint(self.actual_qty))):
serial_no = make_autoname(self.serial_no_series, "Serial No")
sr_nos.append(serial_no)
@@ -1280,6 +1284,7 @@ class SerialBatchCreation:
self.item_name,
self.description,
"Active",
voucher_type,
voucher_no,
self.batch_no,
)
@@ -1299,7 +1304,8 @@ class SerialBatchCreation:
"item_name",
"description",
"status",
"purchase_document_no",
"reference_doctype",
"reference_name",
"batch_no",
]

View File

@@ -1,11 +1,12 @@
{
"app": "erpnext",
"charts": [
{
"chart_name": "Warehouse wise Stock Value",
"label": "Warehouse wise Stock Value"
}
],
"content": "[{\"id\":\"BJTnTemGjc\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"id\":\"WKeeHLcyXI\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Stock Value\",\"col\":4}},{\"id\":\"6nVoOHuy5w\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Warehouses\",\"col\":4}},{\"id\":\"OUex5VED7d\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Active Items\",\"col\":4}},{\"id\":\"A3svBa974t\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"id\":\"wwAoBx30p3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"LkqrpJHM9X\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"id\":\"OR8PYiYspy\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"KP1A22WjDl\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"id\":\"0EYKOrx6U1\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"id\":\"cqotiphmhZ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"id\":\"Xhjqnm-JxZ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"id\":\"yxCx6Tay4Z\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"id\":\"o3sdEnNy34\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"id\":\"m9O0HUUDS5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"NwWcNC_xNj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Inventory Management\",\"col\":3}},{\"id\":\"9AmAh9LnPI\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"3SmmwBbOER\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters &amp; Reports</b></span>\",\"col\":12}},{\"id\":\"OAGNH9njt7\",\"type\":\"card\",\"data\":{\"card_name\":\"Items Catalogue\",\"col\":4}},{\"id\":\"jF9eKz0qr0\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"id\":\"tyTnQo-MIS\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"id\":\"dJaJw6YNPU\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"rQf5vK4N_T\",\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"id\":\"7oM7hFL4v8\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"ve3L6ZifkB\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"8Kfvu3umw7\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"content": "[{\"id\":\"WKeeHLcyXI\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Stock Value\",\"col\":4}},{\"id\":\"6nVoOHuy5w\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Warehouses\",\"col\":4}},{\"id\":\"OUex5VED7d\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Active Items\",\"col\":4}},{\"id\":\"A3svBa974t\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"id\":\"wwAoBx30p3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"LkqrpJHM9X\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"id\":\"OR8PYiYspy\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"KP1A22WjDl\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"id\":\"0EYKOrx6U1\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"id\":\"cqotiphmhZ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"id\":\"Xhjqnm-JxZ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"id\":\"yxCx6Tay4Z\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"id\":\"o3sdEnNy34\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"id\":\"m9O0HUUDS5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"NwWcNC_xNj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Inventory Management\",\"col\":3}},{\"id\":\"9AmAh9LnPI\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"3SmmwBbOER\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters &amp; Reports</b></span>\",\"col\":12}},{\"id\":\"OAGNH9njt7\",\"type\":\"card\",\"data\":{\"card_name\":\"Items Catalogue\",\"col\":4}},{\"id\":\"jF9eKz0qr0\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"id\":\"tyTnQo-MIS\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"id\":\"dJaJw6YNPU\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"rQf5vK4N_T\",\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"id\":\"7oM7hFL4v8\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"ve3L6ZifkB\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"8Kfvu3umw7\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:10.096528",
"custom_blocks": [],
"docstatus": 0,
@@ -286,80 +287,6 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Serial No and Batch",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No",
"link_count": 0,
"link_to": "Serial No",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Batch",
"link_count": 0,
"link_to": "Batch",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Installation Note",
"link_count": 0,
"link_to": "Installation Note",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Serial No",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No Service Contract Expiry",
"link_count": 0,
"link_to": "Serial No Service Contract Expiry",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Serial No",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No Status",
"link_count": 0,
"link_to": "Serial No Status",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Serial No",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No Warranty Expiry",
"link_count": 0,
"link_to": "Serial No Warranty Expiry",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -601,6 +528,7 @@
"link_to": "Warehouse Wise Stock Balance",
"link_type": "Report",
"onboard": 0,
"report_ref_doctype": "Stock Ledger Entry",
"type": "Link"
},
{
@@ -761,9 +689,107 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Serial No and Batch",
"link_count": 8,
"link_type": "DocType",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No",
"link_count": 0,
"link_to": "Serial No",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Batch",
"link_count": 0,
"link_to": "Batch",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Serial No and Batch Traceability",
"link_count": 0,
"link_to": "Serial No and Batch Traceability",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Serial No Ledger",
"link_count": 0,
"link_to": "Serial No Ledger",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Installation Note",
"link_count": 0,
"link_to": "Installation Note",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Serial No",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No Service Contract Expiry",
"link_count": 0,
"link_to": "Serial No Service Contract Expiry",
"link_type": "Report",
"onboard": 0,
"report_ref_doctype": "Serial No",
"type": "Link"
},
{
"dependencies": "Serial No",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No Status",
"link_count": 0,
"link_to": "Serial No Status",
"link_type": "Report",
"onboard": 0,
"report_ref_doctype": "Serial No",
"type": "Link"
},
{
"dependencies": "Serial No",
"hidden": 0,
"is_query_report": 0,
"label": "Serial No Warranty Expiry",
"link_count": 0,
"link_to": "Serial No Warranty Expiry",
"link_type": "Report",
"onboard": 0,
"report_ref_doctype": "Serial No",
"type": "Link"
}
],
"modified": "2023-07-04 14:38:14.988756",
"modified": "2025-08-06 13:22:35.414711",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock",
@@ -836,11 +862,13 @@
{
"label": "Stock Ledger",
"link_to": "Stock Ledger",
"report_ref_doctype": "Stock Ledger Entry",
"type": "Report"
},
{
"label": "Stock Balance",
"link_to": "Stock Balance",
"report_ref_doctype": "Stock Ledger Entry",
"type": "Report"
},
{
@@ -849,5 +877,6 @@
"type": "Dashboard"
}
],
"title": "Stock"
"title": "Stock",
"type": "Workspace"
}