test: test cases for inventory dimension
This commit is contained in:
parent
dbec5cff00
commit
e576f7f07e
@ -367,14 +367,13 @@ class StockController(AccountsController):
|
||||
)
|
||||
|
||||
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):
|
||||
|
@ -32,25 +32,15 @@ frappe.ui.form.on('Inventory Dimension', {
|
||||
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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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"
|
||||
@ -114,32 +115,36 @@
|
||||
"fieldtype": "Section Break",
|
||||
"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",
|
||||
|
@ -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 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None) -> dict:
|
||||
) 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 @@ def get_document_wise_inventory_dimensions(doctype) -> dict:
|
||||
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"],
|
||||
@ -128,20 +163,6 @@ def get_document_wise_inventory_dimensions(doctype) -> dict:
|
||||
return frappe.local.document_wise_inventory_dimensions[doctype]
|
||||
|
||||
|
||||
@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"):
|
||||
@ -152,10 +173,9 @@ def get_inventory_dimensions():
|
||||
"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
|
||||
|
@ -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
|
||||
|
@ -146,7 +146,7 @@ def get_columns(filters: StockBalanceFilter):
|
||||
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 @@ def get_stock_ledger_entries(filters: StockBalanceFilter, items: List[str]) -> L
|
||||
|
||||
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 @@ def get_item_warehouse_map(filters: StockBalanceFilter, sle: List[SLEntry]):
|
||||
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 @@ def get_item_warehouse_map(filters: StockBalanceFilter, sle: List[SLEntry]):
|
||||
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 @@ def filter_items_with_no_transactions(iwb_map, float_precision: float, inventory
|
||||
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
|
||||
|
||||
|
@ -224,7 +224,7 @@ def get_columns(filters):
|
||||
for dimension in get_inventory_dimensions():
|
||||
columns.append(
|
||||
{
|
||||
"label": _(dimension.label),
|
||||
"label": _(dimension.doctype),
|
||||
"fieldname": dimension.fieldname,
|
||||
"fieldtype": "Link",
|
||||
"options": dimension.doctype,
|
||||
|
Loading…
x
Reference in New Issue
Block a user