diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index e27718a9b4cda..440271cac7457 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -374,9 +374,24 @@ def get_sl_entries(self, d, args): def update_inventory_dimensions(self, row, sl_dict) -> None: dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self) for dimension in dimensions: - if dimension and row.get(dimension.source_fieldname): + if not dimension: + continue + + if row.get(dimension.source_fieldname): sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname) + if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent: + sl_dict[dimension.target_fieldname] = self.get(dimension.fetch_from_parent) + + # Get value based on doctype name + if not sl_dict.get(dimension.target_fieldname): + fieldname = frappe.get_cached_value( + "DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" + ) + + if fieldname and self.get(fieldname): + sl_dict[dimension.target_fieldname] = self.get(fieldname) + def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False): from erpnext.stock.stock_ledger import make_sl_entries diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js index 91a21f4e7225b..3b88d521eff57 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js @@ -35,14 +35,39 @@ frappe.ui.form.on('Inventory Dimension', { 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); + let allow_to_edit_fields = ['disabled', 'fetch_from_parent', + 'type_of_transaction', 'condition']; frm.fields.forEach((field) => { - if (field.df.fieldname !== 'disabled') { + if (!in_list(allow_to_edit_fields, field.df.fieldname)) { frm.set_df_property(field.df.fieldname, "read_only", "1"); } }); } + + if (!frm.is_new()) { + frm.add_custom_button(__('Delete Dimension'), () => { + frm.trigger('delete_dimension'); + }); + } + }, + + delete_dimension(frm) { + let msg = (` + Custom fields related to this dimension will be deleted on deletion of dimension. +
Do you want to delete {0} dimension? + `); + + frappe.confirm(__(msg, [frm.doc.name.bold()]), () => { + frappe.call({ + method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension', + args: { + dimension: frm.doc.name + }, + callback: function(r) { + frappe.set_route('List', 'Inventory Dimension'); + } + }) + }); } }); diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json index 8b334d13d7d0f..03e7fda841178 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "autoname": "field:dimension_name", "creation": "2022-06-17 13:04:16.554051", "doctype": "DocType", @@ -22,6 +21,7 @@ "document_type", "istable", "type_of_transaction", + "fetch_from_parent", "column_break_16", "condition", "applicable_condition_example_section", @@ -101,12 +101,14 @@ "fieldname": "target_fieldname", "fieldtype": "Data", "label": "Target Fieldname (Stock Ledger Entry)", + "no_copy": 1, "read_only": 1 }, { "fieldname": "source_fieldname", "fieldtype": "Data", "label": "Source Fieldname", + "no_copy": 1, "read_only": 1 }, { @@ -123,7 +125,7 @@ "fieldname": "type_of_transaction", "fieldtype": "Select", "label": "Type of Transaction", - "options": "\nInward\nOutward" + "options": "\nInward\nOutward\nBoth" }, { "fieldname": "html_19", @@ -140,11 +142,18 @@ { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "depends_on": "istable", + "description": "Set fieldname or DocType name like Supplier, Customer etc.", + "fieldname": "fetch_from_parent", + "fieldtype": "Data", + "label": "Fetch Value From Parent Form" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-07-19 21:06:11.824976", + "modified": "2022-08-17 11:43:24.722441", "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 5a9541f060aba..4ff8f33b40954 100644 --- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py @@ -43,13 +43,37 @@ def do_not_update_document(self): return old_doc = self._doc_before_save + allow_to_edit_fields = [ + "disabled", + "fetch_from_parent", + "type_of_transaction", + "condition", + ] + for field in frappe.get_meta("Inventory Dimension").fields: - if field.fieldname != "disabled" and old_doc.get(field.fieldname) != self.get(field.fieldname): + if field.fieldname not in allow_to_edit_fields 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 on_trash(self): + self.delete_custom_fields() + + def delete_custom_fields(self): + filters = {"fieldname": self.source_fieldname} + + if self.document_type: + filters["dt"] = self.document_type + + for field in frappe.get_all("Custom Field", filters=filters): + frappe.delete_doc("Custom Field", field.name) + + msg = f"Deleted custom fields related to the dimension {self.name}" + frappe.msgprint(_(msg)) + def reset_value(self): if self.apply_to_all_doctypes: self.istable = 0 @@ -76,30 +100,35 @@ def on_update(self): self.add_custom_fields() def add_custom_fields(self): - dimension_field = dict( - fieldname=self.source_fieldname, - fieldtype="Link", - insert_after="warehouse", - options=self.reference_document, - label=self.dimension_name, - ) + dimension_fields = [ + dict( + fieldname="inventory_dimension", + fieldtype="Section Break", + insert_after="warehouse", + label="Inventory Dimension", + collapsible=1, + ), + dict( + fieldname=self.source_fieldname, + fieldtype="Link", + insert_after="inventory_dimension", + options=self.reference_document, + label=self.dimension_name, + ), + ] custom_fields = {} if self.apply_to_all_doctypes: for doctype in get_inventory_documents(): - if not frappe.db.get_value( - "Custom Field", {"dt": doctype[0], "fieldname": self.source_fieldname} - ): - custom_fields.setdefault(doctype[0], dimension_field) - elif not frappe.db.get_value( - "Custom Field", {"dt": self.document_type, "fieldname": self.source_fieldname} - ): - custom_fields.setdefault(self.document_type, dimension_field) + custom_fields.setdefault(doctype[0], dimension_fields) + else: + custom_fields.setdefault(self.document_type, dimension_fields) if not frappe.db.get_value( "Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname} ): + dimension_field = dimension_fields[1] dimension_field["fieldname"] = self.target_fieldname custom_fields["Stock Ledger Entry"] = dimension_field @@ -143,7 +172,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None): elif ( row.type_of_transaction == "Outward" if doc.docstatus == 1 - else row.type_of_transaction != "Inward" + else row.type_of_transaction != "Outward" ) and sl_dict.actual_qty > 0: continue @@ -166,7 +195,14 @@ def get_document_wise_inventory_dimensions(doctype) -> dict: 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"], + fields=[ + "name", + "source_fieldname", + "condition", + "target_fieldname", + "type_of_transaction", + "fetch_from_parent", + ], filters={"disabled": 0}, or_filters={"document_type": doctype, "apply_to_all_doctypes": 1}, ) @@ -194,3 +230,9 @@ def get_inventory_dimensions(): frappe.local.inventory_dimensions = dimensions return frappe.local.inventory_dimensions + + +@frappe.whitelist() +def delete_dimension(dimension): + doc = frappe.get_doc("Inventory Dimension", dimension) + doc.delete() diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py index 998a0e9d6a6df..7a45567de7959 100644 --- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py +++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py @@ -8,6 +8,7 @@ CanNotBeChildDoc, CanNotBeDefaultDimension, DoNotChangeError, + delete_dimension, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse @@ -42,6 +43,30 @@ def test_validate_inventory_dimension(self): self.assertRaises(CanNotBeDefaultDimension, inv_dim1.insert) + def test_delete_inventory_dimension(self): + inv_dim1 = create_inventory_dimension( + reference_document="Shelf", + type_of_transaction="Outward", + dimension_name="From Shelf", + apply_to_all_doctypes=0, + document_type="Stock Entry Detail", + condition="parent.purpose == 'Material Issue'", + ) + + custom_field = frappe.db.get_value( + "Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name" + ) + + self.assertTrue(custom_field) + + delete_dimension(inv_dim1.name) + + custom_field = frappe.db.get_value( + "Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name" + ) + + self.assertFalse(custom_field) + def test_inventory_dimension(self): warehouse = "Shelf Warehouse - _TC" item_code = "_Test Item"