test: test cases for inventory dimension
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index da7f5e2..40bc1aa 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -367,14 +367,13 @@
)
sl_dict.update(args)
- if self.docstatus == 1:
- self.update_inventory_dimensions(d, sl_dict)
+ self.update_inventory_dimensions(d, sl_dict)
return sl_dict
def update_inventory_dimensions(self, row, sl_dict) -> None:
dimension = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
- if dimension:
+ if dimension and row.get(dimension.source_fieldname):
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
index 1256659..91a21f4 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
@@ -32,25 +32,15 @@
frm.trigger('render_traget_field');
},
- map_with_existing_field(frm) {
- frm.trigger('render_traget_field');
- },
+ refresh(frm) {
+ if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
+ && frm.doc.__onload.has_stock_ledger.length) {
+ let msg = __('Stock transactions exists against this dimension, user can not update document.');
+ frm.dashboard.add_comment(msg, 'blue', true);
- render_traget_field(frm) {
- if (frm.doc.map_with_existing_field && !frm.doc.disabled) {
- frappe.call({
- method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_source_fieldnames',
- args: {
- reference_document: frm.doc.reference_document,
- ignore_document: frm.doc.name
- },
- callback: function(r) {
- if (r.message && r.message.length) {
- frm.set_df_property('stock_ledger_dimension', 'options', r.message);
- } else {
- frm.set_value("map_with_existing_field", 0);
- frappe.msgprint(__('Inventory Dimensions not found'));
- }
+ frm.fields.forEach((field) => {
+ if (field.df.fieldname !== 'disabled') {
+ frm.set_df_property(field.df.fieldname, "read_only", "1");
}
});
}
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
index 7e5df42..cfac5cd 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
@@ -10,21 +10,22 @@
"dimension_details_tab",
"dimension_name",
"reference_document",
+ "column_break_4",
"disabled",
"section_break_7",
"field_mapping_section",
"source_fieldname",
- "target_fieldname",
"column_break_9",
- "map_with_existing_field",
- "stock_ledger_dimension",
+ "target_fieldname",
"applicable_for_documents_tab",
"apply_to_all_doctypes",
"document_type",
"istable",
"type_of_transaction",
"column_break_16",
- "condition"
+ "condition",
+ "applicable_condition_example_section",
+ "html_19"
],
"fields": [
{
@@ -81,7 +82,7 @@
"label": "Applicable Condition"
},
{
- "default": "1",
+ "default": "0",
"fieldname": "apply_to_all_doctypes",
"fieldtype": "Check",
"label": "Apply to All Document Types"
@@ -115,31 +116,35 @@
"label": "Field Mapping"
},
{
- "default": "0",
- "fieldname": "map_with_existing_field",
- "fieldtype": "Check",
- "label": "Map with existing field"
- },
- {
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
- "depends_on": "map_with_existing_field",
- "fieldname": "stock_ledger_dimension",
- "fieldtype": "Select",
- "label": "Stock Ledger Dimension"
- },
- {
"fieldname": "type_of_transaction",
"fieldtype": "Select",
"label": "Type of Transaction",
"options": "\nInward\nOutward"
+ },
+ {
+ "fieldname": "html_19",
+ "fieldtype": "HTML",
+ "options": "<table class=\"table table-bordered table-condensed\">\n<thead>\n <tr>\n <th class=\"table-sr\" style=\"width: 50%;\">Child Document</th>\n <th class=\"table-sr\" style=\"width: 50%;\">Non Child Document</th>\n </tr>\n</thead>\n<tbody>\n<tr>\n <td>\n <p> To access parent document field use parent.fieldname and to access child table document field use doc.fieldname </p>\n\n </td>\n <td>\n <p>To access document field use doc.fieldname </p>\n </td>\n</tr>\n<tr>\n <td>\n <p><b>Example: </b> parent.doctype == \"Stock Entry\" and doc.item_code == \"Test\" </p>\n\n </td>\n <td>\n <p><b>Example: </b> doc.doctype == \"Stock Entry\" and doc.purpose == \"Manufacture\"</p> \n </td>\n</tr>\n\n</tbody>\n</table>\n\n\n\n\n\n\n"
+ },
+ {
+ "collapsible": 1,
+ "depends_on": "eval:!doc.apply_to_all_doctypes",
+ "fieldname": "applicable_condition_example_section",
+ "fieldtype": "Section Break",
+ "label": "Applicable Condition Examples"
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-06-22 10:56:43.753713",
+ "modified": "2022-07-05 15:33:37.270373",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index fd143da..3b9a84a 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -2,36 +2,67 @@
# For license information, please see license.txt
import frappe
-from frappe import _, scrub
+from frappe import _, bold, scrub
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.model.document import Document
+class DoNotChangeError(frappe.ValidationError):
+ pass
+
+
class InventoryDimension(Document):
+ def onload(self):
+ if not self.is_new() and frappe.db.has_column("Stock Ledger Entry", self.target_fieldname):
+ self.set_onload("has_stock_ledger", self.has_stock_ledger())
+
+ def has_stock_ledger(self) -> str:
+ if not self.target_fieldname:
+ return
+
+ return frappe.get_all(
+ "Stock Ledger Entry", filters={self.target_fieldname: ("is", "set"), "is_cancelled": 0}, limit=1
+ )
+
def validate(self):
+ self.do_not_update_document()
self.reset_value()
self.validate_reference_document()
self.set_source_and_target_fieldname()
+ def do_not_update_document(self):
+ if self.is_new() or not self.has_stock_ledger():
+ return
+
+ old_doc = self._doc_before_save
+ for field in frappe.get_meta("Inventory Dimension").fields:
+ if field.fieldname != "disabled" and old_doc.get(field.fieldname) != self.get(field.fieldname):
+ msg = f"""The user can not change value of the field {bold(field.label)} because
+ stock transactions exists against the dimension {bold(self.name)}."""
+
+ frappe.throw(_(msg), DoNotChangeError)
+
def reset_value(self):
if self.apply_to_all_doctypes:
self.istable = 0
- for field in ["document_type", "parent_field", "condition", "type_of_transaction"]:
+ for field in ["document_type", "condition"]:
self.set(field, None)
def validate_reference_document(self):
if frappe.get_cached_value("DocType", self.reference_document, "istable") == 1:
- frappe.throw(_(f"The reference document {self.reference_document} can not be child table."))
+ msg = f"The reference document {self.reference_document} can not be child table."
+ frappe.throw(_(msg))
if self.reference_document in ["Batch", "Serial No", "Warehouse", "Item"]:
- frappe.throw(
- _(f"The reference document {self.reference_document} can not be an Inventory Dimension.")
- )
+ msg = f"The reference document {self.reference_document} can not be an Inventory Dimension."
+ frappe.throw(_(msg))
- def set_source_and_target_fieldname(self):
- self.source_fieldname = scrub(self.dimension_name)
- if not self.map_with_existing_field:
- self.target_fieldname = self.source_fieldname
+ def set_source_and_target_fieldname(self) -> None:
+ if not self.source_fieldname:
+ self.source_fieldname = scrub(self.dimension_name)
+
+ if not self.target_fieldname:
+ self.target_fieldname = scrub(self.reference_document)
def on_update(self):
self.add_custom_fields()
@@ -107,7 +138,11 @@
) and sl_dict.actual_qty > 0:
continue
- if frappe.safe_eval(row.condition, {"doc": doc, "parent_doc": parent_doc}):
+ evals = {"doc": doc}
+ if parent_doc:
+ evals["parent"] = parent_doc
+
+ if frappe.safe_eval(row.condition, evals):
return row
@@ -115,7 +150,7 @@
if not hasattr(frappe.local, "document_wise_inventory_dimensions"):
frappe.local.document_wise_inventory_dimensions = {}
- if doctype not in frappe.local.document_wise_inventory_dimensions:
+ if not frappe.local.document_wise_inventory_dimensions.get(doctype):
dimensions = frappe.get_all(
"Inventory Dimension",
fields=["name", "source_fieldname", "condition", "target_fieldname", "type_of_transaction"],
@@ -129,20 +164,6 @@
@frappe.whitelist()
-def get_source_fieldnames(reference_document, ignore_document):
- return frappe.get_all(
- "Inventory Dimension",
- fields=["source_fieldname as value", "dimension_name as label"],
- filters={
- "disabled": 0,
- "map_with_existing_field": 0,
- "name": ("!=", ignore_document),
- "reference_document": reference_document,
- },
- )
-
-
-@frappe.whitelist()
def get_inventory_dimensions():
if not hasattr(frappe.local, "inventory_dimensions"):
frappe.local.inventory_dimensions = {}
@@ -152,10 +173,9 @@
"Inventory Dimension",
fields=[
"distinct target_fieldname as fieldname",
- "dimension_name as label",
"reference_document as doctype",
],
- filters={"disabled": 0, "map_with_existing_field": 0},
+ filters={"disabled": 0},
)
frappe.local.inventory_dimensions = dimensions
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index 8d727b2..a79de1a 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -1,9 +1,112 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-# import frappe
+import frappe
from frappe.tests.utils import FrappeTestCase
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
class TestInventoryDimension(FrappeTestCase):
- pass
+ def setUp(self):
+ prepare_test_data()
+
+ def test_inventory_dimension(self):
+ warehouse = "Shelf Warehouse - _TC"
+ item_code = "_Test Item"
+
+ create_inventory_dimension(
+ reference_document="Shelf",
+ type_of_transaction="Outward",
+ dimension_name="Shelf",
+ apply_to_all_doctypes=0,
+ document_type="Stock Entry Detail",
+ condition="parent.purpose == 'Material Issue'",
+ )
+
+ create_inventory_dimension(
+ reference_document="Shelf",
+ type_of_transaction="Inward",
+ dimension_name="To Shelf",
+ apply_to_all_doctypes=0,
+ document_type="Stock Entry Detail",
+ condition="parent.purpose == 'Material Receipt'",
+ )
+
+ inward = make_stock_entry(
+ item_code=item_code,
+ target=warehouse,
+ qty=5,
+ basic_rate=10,
+ do_not_save=True,
+ purpose="Material Receipt",
+ )
+
+ inward.items[0].to_shelf = "Shelf 1"
+ inward.save()
+ inward.submit()
+ inward.load_from_db()
+ print(inward.name)
+
+ sle_data = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": inward.name}, ["shelf", "warehouse"], as_dict=1
+ )
+
+ self.assertEqual(inward.items[0].to_shelf, "Shelf 1")
+ self.assertEqual(sle_data.warehouse, warehouse)
+ self.assertEqual(sle_data.shelf, "Shelf 1")
+
+ outward = make_stock_entry(
+ item_code=item_code,
+ source=warehouse,
+ qty=3,
+ basic_rate=10,
+ do_not_save=True,
+ purpose="Material Issue",
+ )
+
+ outward.items[0].shelf = "Shelf 1"
+ outward.save()
+ outward.submit()
+ outward.load_from_db()
+
+ sle_shelf = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": outward.name}, "shelf")
+ self.assertEqual(sle_shelf, "Shelf 1")
+
+
+def prepare_test_data():
+ if not frappe.db.exists("DocType", "Shelf"):
+ frappe.get_doc(
+ {
+ "doctype": "DocType",
+ "name": "Shelf",
+ "module": "Stock",
+ "custom": 1,
+ "naming_rule": "By fieldname",
+ "autoname": "field:shelf_name",
+ "fields": [{"label": "Shelf Name", "fieldname": "shelf_name", "fieldtype": "Data"}],
+ "permissions": [
+ {"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
+ ],
+ }
+ ).insert(ignore_permissions=True)
+
+ for shelf in ["Shelf 1", "Shelf 2"]:
+ if not frappe.db.exists("Shelf", shelf):
+ frappe.get_doc({"doctype": "Shelf", "shelf_name": shelf}).insert(ignore_permissions=True)
+
+ create_warehouse("Shelf Warehouse")
+
+
+def create_inventory_dimension(**args):
+ args = frappe._dict(args)
+
+ if frappe.db.exists("Inventory Dimension", args.dimension_name):
+ return frappe.get_doc("Inventory Dimension", args.dimension_name)
+
+ doc = frappe.new_doc("Inventory Dimension")
+ doc.update(args)
+ doc.insert(ignore_permissions=True)
+
+ return doc
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 3492b05..a1e1030 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -146,7 +146,7 @@
for dimension in get_inventory_dimensions():
columns.append(
{
- "label": _(dimension.label),
+ "label": _(dimension.doctype),
"fieldname": dimension.fieldname,
"fieldtype": "Link",
"options": dimension.doctype,
@@ -320,9 +320,8 @@
inventory_dimension_fields = get_inventory_dimension_fields()
if inventory_dimension_fields:
- query = query.select(", ".join(inventory_dimension_fields))
-
for fieldname in inventory_dimension_fields:
+ query = query.select(fieldname)
if fieldname in filters and filters.get(fieldname):
query = query.where(sle[fieldname].isin(filters.get(fieldname)))
@@ -347,7 +346,7 @@
inventory_dimensions = get_inventory_dimension_fields()
for d in sle:
- group_by_key = get_group_by_key(d, inventory_dimensions)
+ group_by_key = get_group_by_key(d, filters, inventory_dimensions)
if group_by_key not in iwb_map:
iwb_map[group_by_key] = frappe._dict(
{
@@ -399,16 +398,18 @@
return iwb_map
-def get_group_by_key(row, inventory_dimension_fields) -> tuple:
+def get_group_by_key(row, filters, inventory_dimension_fields) -> tuple:
group_by_key = [row.company, row.item_code, row.warehouse]
for fieldname in inventory_dimension_fields:
- group_by_key.append(row.get(fieldname))
+ if filters.get(fieldname):
+ group_by_key.append(row.get(fieldname))
return tuple(group_by_key)
def filter_items_with_no_transactions(iwb_map, float_precision: float, inventory_dimensions: list):
+ pop_keys = []
for group_by_key in iwb_map:
qty_dict = iwb_map[group_by_key]
@@ -423,7 +424,10 @@
no_transactions = False
if no_transactions:
- iwb_map.pop(group_by_key)
+ pop_keys.append(group_by_key)
+
+ for key in pop_keys:
+ iwb_map.pop(key)
return iwb_map
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 1104983..807b800 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -224,7 +224,7 @@
for dimension in get_inventory_dimensions():
columns.append(
{
- "label": _(dimension.label),
+ "label": _(dimension.doctype),
"fieldname": dimension.fieldname,
"fieldtype": "Link",
"options": dimension.doctype,