fix: inventory dimension for material transfer not working
This commit is contained in:
parent
0723fbc108
commit
6798b900ef
@ -442,7 +442,29 @@ class StockController(AccountsController):
|
||||
if not dimension:
|
||||
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)
|
||||
|
||||
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
|
||||
|
@ -75,7 +75,16 @@ class InventoryDimension(Document):
|
||||
self.delete_custom_fields()
|
||||
|
||||
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:
|
||||
filters["dt"] = self.document_type
|
||||
@ -88,6 +97,8 @@ class InventoryDimension(Document):
|
||||
|
||||
def reset_value(self):
|
||||
if self.apply_to_all_doctypes:
|
||||
self.type_of_transaction = ""
|
||||
|
||||
self.istable = 0
|
||||
for field in ["document_type", "condition"]:
|
||||
self.set(field, None)
|
||||
@ -111,12 +122,35 @@ class InventoryDimension(Document):
|
||||
def on_update(self):
|
||||
self.add_custom_fields()
|
||||
|
||||
def add_custom_fields(self):
|
||||
dimension_fields = [
|
||||
@staticmethod
|
||||
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(
|
||||
fieldname="inventory_dimension",
|
||||
fieldtype="Section Break",
|
||||
insert_after="warehouse",
|
||||
insert_after=self.get_insert_after_fieldname(doctype),
|
||||
label="Inventory Dimension",
|
||||
collapsible=1,
|
||||
),
|
||||
@ -125,24 +159,37 @@ class InventoryDimension(Document):
|
||||
fieldtype="Link",
|
||||
insert_after="inventory_dimension",
|
||||
options=self.reference_document,
|
||||
label=self.dimension_name,
|
||||
label=label,
|
||||
reqd=self.reqd,
|
||||
mandatory_depends_on=self.mandatory_depends_on,
|
||||
),
|
||||
]
|
||||
|
||||
def add_custom_fields(self):
|
||||
custom_fields = {}
|
||||
|
||||
dimension_fields = []
|
||||
if self.apply_to_all_doctypes:
|
||||
for doctype in get_inventory_documents():
|
||||
if not field_exists(doctype[0], self.source_fieldname):
|
||||
custom_fields.setdefault(doctype[0], dimension_fields)
|
||||
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)
|
||||
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)
|
||||
|
||||
if not frappe.db.get_value(
|
||||
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
||||
) and not field_exists("Stock Ledger Entry", self.target_fieldname):
|
||||
if (
|
||||
dimension_fields
|
||||
and not frappe.db.get_value(
|
||||
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
|
||||
)
|
||||
and not field_exists("Stock Ledger Entry", self.target_fieldname)
|
||||
):
|
||||
dimension_field = dimension_fields[1]
|
||||
dimension_field["mandatory_depends_on"] = ""
|
||||
dimension_field["reqd"] = 0
|
||||
@ -152,6 +199,53 @@ class InventoryDimension(Document):
|
||||
if 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:
|
||||
return frappe.db.get_value("DocField", {"parent": doctype, "fieldname": fieldname}, "name")
|
||||
@ -185,18 +279,19 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
|
||||
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
|
||||
filter_dimensions = []
|
||||
for row in dimensions:
|
||||
if (
|
||||
row.type_of_transaction == "Inward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Inward"
|
||||
) and sl_dict.actual_qty < 0:
|
||||
continue
|
||||
elif (
|
||||
row.type_of_transaction == "Outward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Outward"
|
||||
) and sl_dict.actual_qty > 0:
|
||||
continue
|
||||
if row.type_of_transaction:
|
||||
if (
|
||||
row.type_of_transaction == "Inward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Inward"
|
||||
) and sl_dict.actual_qty < 0:
|
||||
continue
|
||||
elif (
|
||||
row.type_of_transaction == "Outward"
|
||||
if doc.docstatus == 1
|
||||
else row.type_of_transaction != "Outward"
|
||||
) and sl_dict.actual_qty > 0:
|
||||
continue
|
||||
|
||||
evals = {"doc": doc}
|
||||
if parent_doc:
|
||||
|
@ -12,6 +12,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
||||
DoNotChangeError,
|
||||
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.stock_entry.stock_entry_utils import make_stock_entry
|
||||
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):
|
||||
def setUp(self):
|
||||
prepare_test_data()
|
||||
create_store_dimension()
|
||||
|
||||
def test_validate_inventory_dimension(self):
|
||||
# Can not be child doc
|
||||
@ -73,6 +75,8 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
self.assertFalse(custom_field)
|
||||
|
||||
def test_inventory_dimension(self):
|
||||
frappe.local.document_wise_inventory_dimensions = {}
|
||||
|
||||
warehouse = "Shelf Warehouse - _TC"
|
||||
item_code = "_Test Item"
|
||||
|
||||
@ -143,6 +147,8 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
self.assertRaises(DoNotChangeError, inv_dim1.save)
|
||||
|
||||
def test_inventory_dimension_for_purchase_receipt_and_delivery_note(self):
|
||||
frappe.local.document_wise_inventory_dimensions = {}
|
||||
|
||||
inv_dimension = create_inventory_dimension(
|
||||
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():
|
||||
if not frappe.db.exists("DocType", "Shelf"):
|
||||
|
Loading…
x
Reference in New Issue
Block a user