Skip to content

Commit

Permalink
perf: timeout for auto material request through reorder level
Browse files Browse the repository at this point in the history
  • Loading branch information
rohitwaghchaure committed Jan 31, 2024
1 parent 6b8f046 commit 60f2393
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 41 deletions.
42 changes: 42 additions & 0 deletions erpnext/stock/doctype/stock_entry/test_stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,48 @@ def test_negative_batch(self):

self.assertRaises(frappe.ValidationError, se1.cancel)

def test_auto_reorder_level(self):
from erpnext.stock.reorder_item import reorder_item

item_doc = make_item(
"Test Auto Reorder Item - 001",
properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
uoms=[{"uom": "Nos", "conversion_factor": 5}],
)

if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
item_doc.append(
"reorder_levels",
{
"warehouse_reorder_level": 0,
"warehouse_reorder_qty": 10,
"warehouse": "_Test Warehouse - _TC",
"material_request_type": "Purchase",
},
)

item_doc.save(ignore_permissions=True)

frappe.db.set_single_value("Stock Settings", "auto_indent", 1)

mr_list = reorder_item()

frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
mrs = frappe.get_all(
"Material Request Item",
fields=["qty", "stock_uom", "stock_qty"],
filters={"item_code": item_doc.name, "uom": "Nos"},
)

for mri in mrs:
self.assertEqual(mri.stock_uom, "Kg")
self.assertEqual(mri.stock_qty, 10)
self.assertEqual(mri.qty, 2)

for mr in mr_list:
mr.cancel()
mr.delete()


def make_serialized_item(**args):
args = frappe._dict(args)
Expand Down
4 changes: 3 additions & 1 deletion erpnext/stock/get_item_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,9 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not item:
item = frappe.get_doc("Item", args.get("item_code"))

if item.variant_of and not item.taxes:
if (
item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
):
item.update_template_tables()

item_defaults = get_item_defaults(item.name, args.company)
Expand Down
165 changes: 125 additions & 40 deletions erpnext/stock/reorder_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,73 +34,157 @@ def _reorder_item():
erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
)

items_to_consider = frappe.db.sql_list(
"""select name from `tabItem` item
where is_stock_item=1 and has_variants=0
and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
or (variant_of is not null and variant_of != ''
and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
)""",
{"today": nowdate()},
)
items_to_consider = get_items_for_reorder()

if not items_to_consider:
return

item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)

def add_to_material_request(
item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
):
if warehouse not in warehouse_company:
def add_to_material_request(**kwargs):
if isinstance(kwargs, dict):
kwargs = frappe._dict(kwargs)

if kwargs.warehouse not in warehouse_company:
# a disabled warehouse
return

reorder_level = flt(reorder_level)
reorder_qty = flt(reorder_qty)
reorder_level = flt(kwargs.reorder_level)
reorder_qty = flt(kwargs.reorder_qty)

# projected_qty will be 0 if Bin does not exist
if warehouse_group:
projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
if kwargs.warehouse_group:
projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
)
else:
projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
)

if (reorder_level or reorder_qty) and projected_qty <= reorder_level:
deficiency = reorder_level - projected_qty
if deficiency > reorder_qty:
reorder_qty = deficiency

company = warehouse_company.get(warehouse) or default_company
company = warehouse_company.get(kwargs.warehouse) or default_company

material_requests[material_request_type].setdefault(company, []).append(
{"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
material_requests[kwargs.material_request_type].setdefault(company, []).append(
{
"item_code": kwargs.item_code,
"warehouse": kwargs.warehouse,
"reorder_qty": reorder_qty,
"item_details": kwargs.item_details,
}
)

for item_code in items_to_consider:
item = frappe.get_doc("Item", item_code)

if item.variant_of and not item.get("reorder_levels"):
item.update_template_tables()

if item.get("reorder_levels"):
for d in item.get("reorder_levels"):
add_to_material_request(
item_code,
d.warehouse,
d.warehouse_reorder_level,
d.warehouse_reorder_qty,
d.material_request_type,
warehouse_group=d.warehouse_group,
)
for item_code, reorder_levels in items_to_consider.items():
for d in reorder_levels:
if d.has_variants:
continue

add_to_material_request(
item_code=item_code,
warehouse=d.warehouse,
reorder_level=d.warehouse_reorder_level,
reorder_qty=d.warehouse_reorder_qty,
material_request_type=d.material_request_type,
warehouse_group=d.warehouse_group,
item_details=frappe._dict(
{
"item_code": item_code,
"name": item_code,
"item_name": d.item_name,
"item_group": d.item_group,
"brand": d.brand,
"description": d.description,
"stock_uom": d.stock_uom,
"purchase_uom": d.purchase_uom,
}
),
)

if material_requests:
return create_material_request(material_requests)


def get_items_for_reorder() -> dict[str, list]:
reorder_table = frappe.qb.DocType("Item Reorder")
item_table = frappe.qb.DocType("Item")

query = (
frappe.qb.from_(reorder_table)
.inner_join(item_table)
.on(reorder_table.parent == item_table.name)
.select(
reorder_table.warehouse,
reorder_table.warehouse_group,
reorder_table.material_request_type,
reorder_table.warehouse_reorder_level,
reorder_table.warehouse_reorder_qty,
item_table.name,
item_table.stock_uom,
item_table.purchase_uom,
item_table.description,
item_table.item_name,
item_table.item_group,
item_table.brand,
item_table.variant_of,
item_table.has_variants,
)
.where(
(item_table.disabled == 0)
& (item_table.is_stock_item == 1)
& (
(item_table.end_of_life.isnull())
| (item_table.end_of_life > nowdate())
| (item_table.end_of_life == "0000-00-00")
)
)
)

data = query.run(as_dict=True)
itemwise_reorder = frappe._dict({})
for d in data:
itemwise_reorder.setdefault(d.name, []).append(d)

itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)

return itemwise_reorder


def get_reorder_levels_for_variants(itemwise_reorder):
item_table = frappe.qb.DocType("Item")

query = (
frappe.qb.from_(item_table)
.select(
item_table.name,
item_table.variant_of,
)
.where(
(item_table.disabled == 0)
& (item_table.is_stock_item == 1)
& (
(item_table.end_of_life.isnull())
| (item_table.end_of_life > nowdate())
| (item_table.end_of_life == "0000-00-00")
)
& (item_table.variant_of.notnull())
)
)

variants_item = query.run(as_dict=True)
for row in variants_item:
if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))

return itemwise_reorder


def get_item_warehouse_projected_qty(items_to_consider):
item_warehouse_projected_qty = {}
items_to_consider = list(items_to_consider.keys())

for item_code, warehouse, projected_qty in frappe.db.sql(
"""select item_code, warehouse, projected_qty
Expand Down Expand Up @@ -164,7 +248,7 @@ def _log_exception(mr):

for d in items:
d = frappe._dict(d)
item = frappe.get_doc("Item", d.item_code)
item = d.get("item_details")
uom = item.stock_uom
conversion_factor = 1.0

Expand All @@ -190,6 +274,7 @@ def _log_exception(mr):
"item_code": d.item_code,
"schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
"qty": qty,
"conversion_factor": conversion_factor,
"uom": uom,
"stock_uom": item.stock_uom,
"warehouse": d.warehouse,
Expand Down

0 comments on commit 60f2393

Please sign in to comment.