Merge pull request #35224 from rohitwaghchaure/fixed-inventory-dimension-for-material-transfer-not-working
fix: inventory dimension for material transfer not working
This commit is contained in:
commit
e7bb6ad31c
@ -442,7 +442,29 @@ class StockController(AccountsController):
|
|||||||
if not dimension:
|
if not dimension:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if row.get(dimension.source_fieldname):
|
if self.doctype in [
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Delivery Note",
|
||||||
|
"Stock Entry",
|
||||||
|
]:
|
||||||
|
if (sl_dict.actual_qty > 0 and self.doctype in ["Purchase Invoice", "Purchase Receipt"]) or (
|
||||||
|
sl_dict.actual_qty < 0 and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"]
|
||||||
|
):
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||||
|
else:
|
||||||
|
fieldname_start_with = "to"
|
||||||
|
if self.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||||
|
fieldname_start_with = "from"
|
||||||
|
|
||||||
|
fieldname = f"{fieldname_start_with}_{dimension.source_fieldname}"
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(fieldname)
|
||||||
|
|
||||||
|
if not sl_dict.get(dimension.target_fieldname):
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||||
|
|
||||||
|
elif row.get(dimension.source_fieldname):
|
||||||
sl_dict[dimension.target_fieldname] = 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:
|
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
|
||||||
|
@ -75,7 +75,16 @@ class InventoryDimension(Document):
|
|||||||
self.delete_custom_fields()
|
self.delete_custom_fields()
|
||||||
|
|
||||||
def delete_custom_fields(self):
|
def delete_custom_fields(self):
|
||||||
filters = {"fieldname": self.source_fieldname}
|
filters = {
|
||||||
|
"fieldname": (
|
||||||
|
"in",
|
||||||
|
[
|
||||||
|
self.source_fieldname,
|
||||||
|
f"to_{self.source_fieldname}",
|
||||||
|
f"from_{self.source_fieldname}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if self.document_type:
|
if self.document_type:
|
||||||
filters["dt"] = self.document_type
|
filters["dt"] = self.document_type
|
||||||
@ -88,6 +97,8 @@ class InventoryDimension(Document):
|
|||||||
|
|
||||||
def reset_value(self):
|
def reset_value(self):
|
||||||
if self.apply_to_all_doctypes:
|
if self.apply_to_all_doctypes:
|
||||||
|
self.type_of_transaction = ""
|
||||||
|
|
||||||
self.istable = 0
|
self.istable = 0
|
||||||
for field in ["document_type", "condition"]:
|
for field in ["document_type", "condition"]:
|
||||||
self.set(field, None)
|
self.set(field, None)
|
||||||
@ -111,12 +122,35 @@ class InventoryDimension(Document):
|
|||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.add_custom_fields()
|
self.add_custom_fields()
|
||||||
|
|
||||||
def add_custom_fields(self):
|
@staticmethod
|
||||||
dimension_fields = [
|
def get_insert_after_fieldname(doctype):
|
||||||
|
return frappe.get_all(
|
||||||
|
"DocField",
|
||||||
|
fields=["fieldname"],
|
||||||
|
filters={"parent": doctype},
|
||||||
|
order_by="idx desc",
|
||||||
|
limit=1,
|
||||||
|
)[0].fieldname
|
||||||
|
|
||||||
|
def get_dimension_fields(self, doctype=None):
|
||||||
|
if not doctype:
|
||||||
|
doctype = self.document_type
|
||||||
|
|
||||||
|
label_start_with = ""
|
||||||
|
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
|
||||||
|
label_start_with = "Target"
|
||||||
|
elif doctype in ["Sales Invoice Item", "Delivery Note Item", "Stock Entry Detail"]:
|
||||||
|
label_start_with = "Source"
|
||||||
|
|
||||||
|
label = self.dimension_name
|
||||||
|
if label_start_with:
|
||||||
|
label = f"{label_start_with} {self.dimension_name}"
|
||||||
|
|
||||||
|
return [
|
||||||
dict(
|
dict(
|
||||||
fieldname="inventory_dimension",
|
fieldname="inventory_dimension",
|
||||||
fieldtype="Section Break",
|
fieldtype="Section Break",
|
||||||
insert_after="warehouse",
|
insert_after=self.get_insert_after_fieldname(doctype),
|
||||||
label="Inventory Dimension",
|
label="Inventory Dimension",
|
||||||
collapsible=1,
|
collapsible=1,
|
||||||
),
|
),
|
||||||
@ -125,24 +159,37 @@ class InventoryDimension(Document):
|
|||||||
fieldtype="Link",
|
fieldtype="Link",
|
||||||
insert_after="inventory_dimension",
|
insert_after="inventory_dimension",
|
||||||
options=self.reference_document,
|
options=self.reference_document,
|
||||||
label=self.dimension_name,
|
label=label,
|
||||||
reqd=self.reqd,
|
reqd=self.reqd,
|
||||||
mandatory_depends_on=self.mandatory_depends_on,
|
mandatory_depends_on=self.mandatory_depends_on,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def add_custom_fields(self):
|
||||||
custom_fields = {}
|
custom_fields = {}
|
||||||
|
|
||||||
|
dimension_fields = []
|
||||||
if self.apply_to_all_doctypes:
|
if self.apply_to_all_doctypes:
|
||||||
for doctype in get_inventory_documents():
|
for doctype in get_inventory_documents():
|
||||||
if not field_exists(doctype[0], self.source_fieldname):
|
if field_exists(doctype[0], self.source_fieldname):
|
||||||
|
continue
|
||||||
|
|
||||||
|
dimension_fields = self.get_dimension_fields(doctype[0])
|
||||||
|
self.add_transfer_field(doctype[0], dimension_fields)
|
||||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||||
elif not field_exists(self.document_type, self.source_fieldname):
|
elif not field_exists(self.document_type, self.source_fieldname):
|
||||||
|
dimension_fields = self.get_dimension_fields()
|
||||||
|
|
||||||
|
self.add_transfer_field(self.document_type, dimension_fields)
|
||||||
custom_fields.setdefault(self.document_type, dimension_fields)
|
custom_fields.setdefault(self.document_type, dimension_fields)
|
||||||
|
|
||||||
if not frappe.db.get_value(
|
if (
|
||||||
|
dimension_fields
|
||||||
|
and not frappe.db.get_value(
|
||||||
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
||||||
) and not field_exists("Stock Ledger Entry", self.target_fieldname):
|
)
|
||||||
|
and not field_exists("Stock Ledger Entry", self.target_fieldname)
|
||||||
|
):
|
||||||
dimension_field = dimension_fields[1]
|
dimension_field = dimension_fields[1]
|
||||||
dimension_field["mandatory_depends_on"] = ""
|
dimension_field["mandatory_depends_on"] = ""
|
||||||
dimension_field["reqd"] = 0
|
dimension_field["reqd"] = 0
|
||||||
@ -152,6 +199,53 @@ class InventoryDimension(Document):
|
|||||||
if custom_fields:
|
if custom_fields:
|
||||||
create_custom_fields(custom_fields)
|
create_custom_fields(custom_fields)
|
||||||
|
|
||||||
|
def add_transfer_field(self, doctype, dimension_fields):
|
||||||
|
if doctype not in [
|
||||||
|
"Stock Entry Detail",
|
||||||
|
"Sales Invoice Item",
|
||||||
|
"Delivery Note Item",
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
"Purchase Receipt Item",
|
||||||
|
]:
|
||||||
|
return
|
||||||
|
|
||||||
|
fieldname_start_with = "to"
|
||||||
|
label_start_with = "Target"
|
||||||
|
display_depends_on = ""
|
||||||
|
|
||||||
|
if doctype in ["Purchase Invoice Item", "Purchase Receipt Item"]:
|
||||||
|
fieldname_start_with = "from"
|
||||||
|
label_start_with = "Source"
|
||||||
|
display_depends_on = "eval:parent.is_internal_supplier == 1"
|
||||||
|
elif doctype != "Stock Entry Detail":
|
||||||
|
display_depends_on = "eval:parent.is_internal_customer == 1"
|
||||||
|
elif doctype == "Stock Entry Detail":
|
||||||
|
display_depends_on = "eval:parent.purpose != 'Material Issue'"
|
||||||
|
|
||||||
|
fieldname = f"{fieldname_start_with}_{self.source_fieldname}"
|
||||||
|
label = f"{label_start_with} {self.dimension_name}"
|
||||||
|
|
||||||
|
if field_exists(doctype, fieldname):
|
||||||
|
return
|
||||||
|
|
||||||
|
dimension_fields.extend(
|
||||||
|
[
|
||||||
|
dict(
|
||||||
|
fieldname="inventory_dimension_col_break",
|
||||||
|
fieldtype="Column Break",
|
||||||
|
insert_after=self.source_fieldname,
|
||||||
|
),
|
||||||
|
dict(
|
||||||
|
fieldname=fieldname,
|
||||||
|
fieldtype="Link",
|
||||||
|
insert_after="inventory_dimension_col_break",
|
||||||
|
options=self.reference_document,
|
||||||
|
label=label,
|
||||||
|
depends_on=display_depends_on,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def field_exists(doctype, fieldname) -> str or None:
|
def field_exists(doctype, fieldname) -> str or None:
|
||||||
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
||||||
@ -185,6 +279,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
|
|||||||
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
|
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
|
||||||
filter_dimensions = []
|
filter_dimensions = []
|
||||||
for row in dimensions:
|
for row in dimensions:
|
||||||
|
if row.type_of_transaction:
|
||||||
if (
|
if (
|
||||||
row.type_of_transaction == "Inward"
|
row.type_of_transaction == "Inward"
|
||||||
if doc.docstatus == 1
|
if doc.docstatus == 1
|
||||||
|
@ -12,6 +12,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
|||||||
DoNotChangeError,
|
DoNotChangeError,
|
||||||
delete_dimension,
|
delete_dimension,
|
||||||
)
|
)
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
@ -20,6 +21,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
|||||||
class TestInventoryDimension(FrappeTestCase):
|
class TestInventoryDimension(FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
prepare_test_data()
|
prepare_test_data()
|
||||||
|
create_store_dimension()
|
||||||
|
|
||||||
def test_validate_inventory_dimension(self):
|
def test_validate_inventory_dimension(self):
|
||||||
# Can not be child doc
|
# Can not be child doc
|
||||||
@ -73,6 +75,8 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
self.assertFalse(custom_field)
|
self.assertFalse(custom_field)
|
||||||
|
|
||||||
def test_inventory_dimension(self):
|
def test_inventory_dimension(self):
|
||||||
|
frappe.local.document_wise_inventory_dimensions = {}
|
||||||
|
|
||||||
warehouse = "Shelf Warehouse - _TC"
|
warehouse = "Shelf Warehouse - _TC"
|
||||||
item_code = "_Test Item"
|
item_code = "_Test Item"
|
||||||
|
|
||||||
@ -143,6 +147,8 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
||||||
|
|
||||||
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
||||||
|
frappe.local.document_wise_inventory_dimensions = {}
|
||||||
|
|
||||||
inv_dimension = create_inventory_dimension(
|
inv_dimension = create_inventory_dimension(
|
||||||
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
|
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
|
||||||
)
|
)
|
||||||
@ -250,6 +256,97 @@ class TestInventoryDimension(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_for_purchase_sales_and_stock_transaction(self):
|
||||||
|
create_inventory_dimension(
|
||||||
|
reference_document="Store",
|
||||||
|
type_of_transaction="Outward",
|
||||||
|
dimension_name="Store",
|
||||||
|
apply_to_all_doctypes=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
item_code = "Test Inventory Dimension Item"
|
||||||
|
create_item(item_code)
|
||||||
|
warehouse = create_warehouse("Store Warehouse")
|
||||||
|
|
||||||
|
# Purchase Receipt -> Inward in Store 1
|
||||||
|
pr_doc = make_purchase_receipt(
|
||||||
|
item_code=item_code, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
|
||||||
|
)
|
||||||
|
|
||||||
|
pr_doc.items[0].store = "Store 1"
|
||||||
|
pr_doc.save()
|
||||||
|
pr_doc.submit()
|
||||||
|
|
||||||
|
entries = get_voucher_sl_entries(pr_doc.name, ["warehouse", "store", "incoming_rate"])
|
||||||
|
|
||||||
|
self.assertEqual(entries[0].warehouse, warehouse)
|
||||||
|
self.assertEqual(entries[0].store, "Store 1")
|
||||||
|
|
||||||
|
# Stock Entry -> Transfer from Store 1 to Store 2
|
||||||
|
se_doc = make_stock_entry(
|
||||||
|
item_code=item_code, qty=10, from_warehouse=warehouse, to_warehouse=warehouse, do_not_save=True
|
||||||
|
)
|
||||||
|
|
||||||
|
se_doc.items[0].store = "Store 1"
|
||||||
|
se_doc.items[0].to_store = "Store 2"
|
||||||
|
|
||||||
|
se_doc.save()
|
||||||
|
se_doc.submit()
|
||||||
|
|
||||||
|
entries = get_voucher_sl_entries(
|
||||||
|
se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
self.assertEqual(entry.warehouse, warehouse)
|
||||||
|
if entry.actual_qty > 0:
|
||||||
|
self.assertEqual(entry.store, "Store 2")
|
||||||
|
self.assertEqual(entry.incoming_rate, 100.0)
|
||||||
|
else:
|
||||||
|
self.assertEqual(entry.store, "Store 1")
|
||||||
|
|
||||||
|
# Delivery Note -> Outward from Store 2
|
||||||
|
|
||||||
|
dn_doc = create_delivery_note(item_code=item_code, qty=10, warehouse=warehouse, do_not_save=True)
|
||||||
|
|
||||||
|
dn_doc.items[0].store = "Store 2"
|
||||||
|
dn_doc.save()
|
||||||
|
dn_doc.submit()
|
||||||
|
|
||||||
|
entries = get_voucher_sl_entries(dn_doc.name, ["warehouse", "store", "actual_qty"])
|
||||||
|
|
||||||
|
self.assertEqual(entries[0].warehouse, warehouse)
|
||||||
|
self.assertEqual(entries[0].store, "Store 2")
|
||||||
|
self.assertEqual(entries[0].actual_qty, -10.0)
|
||||||
|
|
||||||
|
|
||||||
|
def get_voucher_sl_entries(voucher_no, fields):
|
||||||
|
return frappe.get_all(
|
||||||
|
"Stock Ledger Entry", filters={"voucher_no": voucher_no}, fields=fields, order_by="creation"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_store_dimension():
|
||||||
|
if not frappe.db.exists("DocType", "Store"):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "DocType",
|
||||||
|
"name": "Store",
|
||||||
|
"module": "Stock",
|
||||||
|
"custom": 1,
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"autoname": "field:store_name",
|
||||||
|
"fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}],
|
||||||
|
"permissions": [
|
||||||
|
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
for store in ["Store 1", "Store 2"]:
|
||||||
|
if not frappe.db.exists("Store", store):
|
||||||
|
frappe.get_doc({"doctype": "Store", "store_name": store}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def prepare_test_data():
|
def prepare_test_data():
|
||||||
if not frappe.db.exists("DocType", "Shelf"):
|
if not frappe.db.exists("DocType", "Shelf"):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user