Merge pull request #32662 from s-aga-r/refactor/stock/remaining-reports
refactor: rewrite stock reports queries in qb
This commit is contained in:
commit
b6ade62211
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Abs, Sum
|
||||||
from frappe.utils import flt, getdate
|
from frappe.utils import flt, getdate
|
||||||
|
|
||||||
|
|
||||||
@ -11,8 +12,6 @@ def execute(filters=None):
|
|||||||
filters = {}
|
filters = {}
|
||||||
float_precision = frappe.db.get_default("float_precision")
|
float_precision = frappe.db.get_default("float_precision")
|
||||||
|
|
||||||
condition = get_condition(filters)
|
|
||||||
|
|
||||||
avg_daily_outgoing = 0
|
avg_daily_outgoing = 0
|
||||||
diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
|
diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
|
||||||
if diff <= 0:
|
if diff <= 0:
|
||||||
@ -20,8 +19,8 @@ def execute(filters=None):
|
|||||||
|
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
items = get_item_info(filters)
|
items = get_item_info(filters)
|
||||||
consumed_item_map = get_consumed_items(condition)
|
consumed_item_map = get_consumed_items(filters)
|
||||||
delivered_item_map = get_delivered_items(condition)
|
delivered_item_map = get_delivered_items(filters)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for item in items:
|
for item in items:
|
||||||
@ -71,76 +70,86 @@ def get_columns():
|
|||||||
def get_item_info(filters):
|
def get_item_info(filters):
|
||||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||||
|
|
||||||
conditions = [get_item_group_condition(filters.get("item_group"))]
|
item = frappe.qb.DocType("Item")
|
||||||
if filters.get("brand"):
|
query = (
|
||||||
conditions.append("item.brand=%(brand)s")
|
frappe.qb.from_(item)
|
||||||
conditions.append("is_stock_item = 1")
|
.select(
|
||||||
|
item.name,
|
||||||
return frappe.db.sql(
|
item.item_name,
|
||||||
"""select name, item_name, description, brand, item_group,
|
item.description,
|
||||||
safety_stock, lead_time_days from `tabItem` item where {}""".format(
|
item.brand,
|
||||||
" and ".join(conditions)
|
item.item_group,
|
||||||
),
|
item.safety_stock,
|
||||||
filters,
|
item.lead_time_days,
|
||||||
as_dict=1,
|
)
|
||||||
|
.where(item.is_stock_item == 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if brand := filters.get("brand"):
|
||||||
|
query = query.where(item.brand == brand)
|
||||||
|
|
||||||
def get_consumed_items(condition):
|
if conditions := get_item_group_condition(filters.get("item_group"), item):
|
||||||
|
query = query.where(conditions)
|
||||||
|
|
||||||
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_consumed_items(filters):
|
||||||
purpose_to_exclude = [
|
purpose_to_exclude = [
|
||||||
"Material Transfer for Manufacture",
|
"Material Transfer for Manufacture",
|
||||||
"Material Transfer",
|
"Material Transfer",
|
||||||
"Send to Subcontractor",
|
"Send to Subcontractor",
|
||||||
]
|
]
|
||||||
|
|
||||||
condition += """
|
se = frappe.qb.DocType("Stock Entry")
|
||||||
and (
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
purpose is NULL
|
query = (
|
||||||
or purpose not in ({})
|
frappe.qb.from_(sle)
|
||||||
|
.left_join(se)
|
||||||
|
.on(sle.voucher_no == se.name)
|
||||||
|
.select(sle.item_code, Abs(Sum(sle.actual_qty)).as_("consumed_qty"))
|
||||||
|
.where(
|
||||||
|
(sle.actual_qty < 0)
|
||||||
|
& (sle.is_cancelled == 0)
|
||||||
|
& (sle.voucher_type.notin(["Delivery Note", "Sales Invoice"]))
|
||||||
|
& ((se.purpose.isnull()) | (se.purpose.notin(purpose_to_exclude)))
|
||||||
)
|
)
|
||||||
""".format(
|
.groupby(sle.item_code)
|
||||||
", ".join(f"'{p}'" for p in purpose_to_exclude)
|
|
||||||
)
|
)
|
||||||
condition = condition.replace("posting_date", "sle.posting_date")
|
query = get_filtered_query(filters, sle, query)
|
||||||
|
|
||||||
consumed_items = frappe.db.sql(
|
consumed_items = query.run(as_dict=True)
|
||||||
"""
|
|
||||||
select item_code, abs(sum(actual_qty)) as consumed_qty
|
|
||||||
from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
|
|
||||||
on sle.voucher_no = se.name
|
|
||||||
where
|
|
||||||
actual_qty < 0
|
|
||||||
and is_cancelled = 0
|
|
||||||
and voucher_type not in ('Delivery Note', 'Sales Invoice')
|
|
||||||
%s
|
|
||||||
group by item_code"""
|
|
||||||
% condition,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items}
|
consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items}
|
||||||
return consumed_items_map
|
return consumed_items_map
|
||||||
|
|
||||||
|
|
||||||
def get_delivered_items(condition):
|
def get_delivered_items(filters):
|
||||||
dn_items = frappe.db.sql(
|
parent = frappe.qb.DocType("Delivery Note")
|
||||||
"""select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty
|
child = frappe.qb.DocType("Delivery Note Item")
|
||||||
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
|
query = (
|
||||||
where dn.name = dn_item.parent and dn.docstatus = 1 %s
|
frappe.qb.from_(parent)
|
||||||
group by dn_item.item_code"""
|
.from_(child)
|
||||||
% (condition),
|
.select(child.item_code, Sum(child.stock_qty).as_("dn_qty"))
|
||||||
as_dict=1,
|
.where((parent.name == child.parent) & (parent.docstatus == 1))
|
||||||
|
.groupby(child.item_code)
|
||||||
)
|
)
|
||||||
|
query = get_filtered_query(filters, parent, query)
|
||||||
|
|
||||||
si_items = frappe.db.sql(
|
dn_items = query.run(as_dict=True)
|
||||||
"""select si_item.item_code, sum(si_item.stock_qty) as si_qty
|
|
||||||
from `tabSales Invoice` si, `tabSales Invoice Item` si_item
|
parent = frappe.qb.DocType("Sales Invoice")
|
||||||
where si.name = si_item.parent and si.docstatus = 1 and
|
child = frappe.qb.DocType("Sales Invoice Item")
|
||||||
si.update_stock = 1 %s
|
query = (
|
||||||
group by si_item.item_code"""
|
frappe.qb.from_(parent)
|
||||||
% (condition),
|
.from_(child)
|
||||||
as_dict=1,
|
.select(child.item_code, Sum(child.stock_qty).as_("si_qty"))
|
||||||
|
.where((parent.name == child.parent) & (parent.docstatus == 1) & (parent.update_stock == 1))
|
||||||
|
.groupby(child.item_code)
|
||||||
)
|
)
|
||||||
|
query = get_filtered_query(filters, parent, query)
|
||||||
|
|
||||||
|
si_items = query.run(as_dict=True)
|
||||||
|
|
||||||
dn_item_map = {}
|
dn_item_map = {}
|
||||||
for item in dn_items:
|
for item in dn_items:
|
||||||
@ -152,13 +161,10 @@ def get_delivered_items(condition):
|
|||||||
return dn_item_map
|
return dn_item_map
|
||||||
|
|
||||||
|
|
||||||
def get_condition(filters):
|
def get_filtered_query(filters, table, query):
|
||||||
conditions = ""
|
|
||||||
if filters.get("from_date") and filters.get("to_date"):
|
if filters.get("from_date") and filters.get("to_date"):
|
||||||
conditions += " and posting_date between '%s' and '%s'" % (
|
query = query.where(table.posting_date.between(filters["from_date"], filters["to_date"]))
|
||||||
filters["from_date"],
|
|
||||||
filters["to_date"],
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("From and To dates required"))
|
frappe.throw(_("From and To dates are required"))
|
||||||
return conditions
|
|
||||||
|
return query
|
||||||
|
|||||||
@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import IfNull
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||||
|
|
||||||
@ -123,43 +125,65 @@ def get_items(filters):
|
|||||||
pb_details = frappe._dict()
|
pb_details = frappe._dict()
|
||||||
item_details = frappe._dict()
|
item_details = frappe._dict()
|
||||||
|
|
||||||
conditions = get_parent_item_conditions(filters)
|
item = frappe.qb.DocType("Item")
|
||||||
parent_item_details = frappe.db.sql(
|
pb = frappe.qb.DocType("Product Bundle")
|
||||||
"""
|
|
||||||
select item.name as item_code, item.item_name, pb.description, item.item_group, item.brand, item.stock_uom
|
query = (
|
||||||
from `tabItem` item
|
frappe.qb.from_(item)
|
||||||
inner join `tabProduct Bundle` pb on pb.new_item_code = item.name
|
.inner_join(pb)
|
||||||
where ifnull(item.disabled, 0) = 0 {0}
|
.on(pb.new_item_code == item.name)
|
||||||
""".format(
|
.select(
|
||||||
conditions
|
item.name.as_("item_code"),
|
||||||
),
|
item.item_name,
|
||||||
filters,
|
pb.description,
|
||||||
as_dict=1,
|
item.item_group,
|
||||||
) # nosec
|
item.brand,
|
||||||
|
item.stock_uom,
|
||||||
|
)
|
||||||
|
.where(IfNull(item.disabled, 0) == 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_code := filters.get("item_code"):
|
||||||
|
query = query.where(item.item_code == item_code)
|
||||||
|
else:
|
||||||
|
if brand := filters.get("brand"):
|
||||||
|
query = query.where(item.brand == brand)
|
||||||
|
if item_group := filters.get("item_group"):
|
||||||
|
if conditions := get_item_group_condition(item_group, item):
|
||||||
|
query = query.where(conditions)
|
||||||
|
|
||||||
|
parent_item_details = query.run(as_dict=True)
|
||||||
|
|
||||||
parent_items = []
|
parent_items = []
|
||||||
for d in parent_item_details:
|
for d in parent_item_details:
|
||||||
parent_items.append(d.item_code)
|
parent_items.append(d.item_code)
|
||||||
item_details[d.item_code] = d
|
item_details[d.item_code] = d
|
||||||
|
|
||||||
if parent_items:
|
|
||||||
child_item_details = frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select
|
|
||||||
pb.new_item_code as parent_item, pbi.item_code, item.item_name, pbi.description, item.item_group, item.brand,
|
|
||||||
item.stock_uom, pbi.uom, pbi.qty
|
|
||||||
from `tabProduct Bundle Item` pbi
|
|
||||||
inner join `tabProduct Bundle` pb on pb.name = pbi.parent
|
|
||||||
inner join `tabItem` item on item.name = pbi.item_code
|
|
||||||
where pb.new_item_code in ({0})
|
|
||||||
""".format(
|
|
||||||
", ".join(["%s"] * len(parent_items))
|
|
||||||
),
|
|
||||||
parent_items,
|
|
||||||
as_dict=1,
|
|
||||||
) # nosec
|
|
||||||
else:
|
|
||||||
child_item_details = []
|
child_item_details = []
|
||||||
|
if parent_items:
|
||||||
|
item = frappe.qb.DocType("Item")
|
||||||
|
pb = frappe.qb.DocType("Product Bundle")
|
||||||
|
pbi = frappe.qb.DocType("Product Bundle Item")
|
||||||
|
|
||||||
|
child_item_details = (
|
||||||
|
frappe.qb.from_(pbi)
|
||||||
|
.inner_join(pb)
|
||||||
|
.on(pb.name == pbi.parent)
|
||||||
|
.inner_join(item)
|
||||||
|
.on(item.name == pbi.item_code)
|
||||||
|
.select(
|
||||||
|
pb.new_item_code.as_("parent_item"),
|
||||||
|
pbi.item_code,
|
||||||
|
item.item_name,
|
||||||
|
pbi.description,
|
||||||
|
item.item_group,
|
||||||
|
item.brand,
|
||||||
|
item.stock_uom,
|
||||||
|
pbi.uom,
|
||||||
|
pbi.qty,
|
||||||
|
)
|
||||||
|
.where(pb.new_item_code.isin(parent_items))
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
child_items = set()
|
child_items = set()
|
||||||
for d in child_item_details:
|
for d in child_item_details:
|
||||||
@ -184,58 +208,42 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
if not items:
|
if not items:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
item_conditions_sql = " and sle.item_code in ({})".format(
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
", ".join(frappe.db.escape(i) for i in items)
|
sle2 = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(sle)
|
||||||
|
.force_index("posting_sort_index")
|
||||||
|
.left_join(sle2)
|
||||||
|
.on(
|
||||||
|
(sle.item_code == sle2.item_code)
|
||||||
|
& (sle.warehouse == sle2.warehouse)
|
||||||
|
& (sle.posting_date < sle2.posting_date)
|
||||||
|
& (sle.posting_time < sle2.posting_time)
|
||||||
|
& (sle.name < sle2.name)
|
||||||
|
)
|
||||||
|
.select(sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company)
|
||||||
|
.where((sle2.name.isnull()) & (sle.docstatus < 2) & (sle.item_code.isin(items)))
|
||||||
)
|
)
|
||||||
|
|
||||||
conditions = get_sle_conditions(filters)
|
if date := filters.get("date"):
|
||||||
|
query = query.where(sle.posting_date <= date)
|
||||||
return frappe.db.sql(
|
|
||||||
"""
|
|
||||||
select
|
|
||||||
sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company
|
|
||||||
from
|
|
||||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
|
||||||
left join `tabStock Ledger Entry` sle2 on
|
|
||||||
sle.item_code = sle2.item_code and sle.warehouse = sle2.warehouse
|
|
||||||
and (sle.posting_date, sle.posting_time, sle.name) < (sle2.posting_date, sle2.posting_time, sle2.name)
|
|
||||||
where sle2.name is null and sle.docstatus < 2 %s %s"""
|
|
||||||
% (item_conditions_sql, conditions),
|
|
||||||
as_dict=1,
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
def get_parent_item_conditions(filters):
|
|
||||||
conditions = []
|
|
||||||
|
|
||||||
if filters.get("item_code"):
|
|
||||||
conditions.append("item.item_code = %(item_code)s")
|
|
||||||
else:
|
else:
|
||||||
if filters.get("brand"):
|
|
||||||
conditions.append("item.brand=%(brand)s")
|
|
||||||
if filters.get("item_group"):
|
|
||||||
conditions.append(get_item_group_condition(filters.get("item_group")))
|
|
||||||
|
|
||||||
conditions = " and ".join(conditions)
|
|
||||||
return "and {0}".format(conditions) if conditions else ""
|
|
||||||
|
|
||||||
|
|
||||||
def get_sle_conditions(filters):
|
|
||||||
conditions = ""
|
|
||||||
if not filters.get("date"):
|
|
||||||
frappe.throw(_("'Date' is required"))
|
frappe.throw(_("'Date' is required"))
|
||||||
|
|
||||||
conditions += " and sle.posting_date <= %s" % frappe.db.escape(filters.get("date"))
|
|
||||||
|
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
warehouse_details = frappe.db.get_value(
|
warehouse_details = frappe.db.get_value(
|
||||||
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
|
||||||
)
|
)
|
||||||
if warehouse_details:
|
|
||||||
conditions += (
|
|
||||||
" and exists (select name from `tabWarehouse` wh \
|
|
||||||
where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"
|
|
||||||
% (warehouse_details.lft, warehouse_details.rgt)
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
return conditions
|
if warehouse_details:
|
||||||
|
wh = frappe.qb.DocType("Warehouse")
|
||||||
|
query = query.where(
|
||||||
|
ExistsCriterion(
|
||||||
|
frappe.qb.from_(wh)
|
||||||
|
.select(wh.name)
|
||||||
|
.where((wh.lft >= warehouse_details.lft) & (wh.rgt <= warehouse_details.rgt))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.run(as_dict=True)
|
||||||
|
|||||||
@ -305,20 +305,25 @@ def get_inventory_dimension_fields():
|
|||||||
|
|
||||||
|
|
||||||
def get_items(filters):
|
def get_items(filters):
|
||||||
|
item = frappe.qb.DocType("Item")
|
||||||
|
query = frappe.qb.from_(item).select(item.name)
|
||||||
conditions = []
|
conditions = []
|
||||||
if filters.get("item_code"):
|
|
||||||
conditions.append("item.name=%(item_code)s")
|
if item_code := filters.get("item_code"):
|
||||||
|
conditions.append(item.name == item_code)
|
||||||
else:
|
else:
|
||||||
if filters.get("brand"):
|
if brand := filters.get("brand"):
|
||||||
conditions.append("item.brand=%(brand)s")
|
conditions.append(item.brand == brand)
|
||||||
if filters.get("item_group"):
|
if item_group := filters.get("item_group"):
|
||||||
conditions.append(get_item_group_condition(filters.get("item_group")))
|
if condition := get_item_group_condition(item_group, item):
|
||||||
|
conditions.append(condition)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
if conditions:
|
if conditions:
|
||||||
items = frappe.db.sql_list(
|
for condition in conditions:
|
||||||
"""select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters
|
query = query.where(condition)
|
||||||
)
|
items = [r[0] for r in query.run()]
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
@ -330,30 +335,23 @@ def get_item_details(items, sl_entries, include_uom):
|
|||||||
if not items:
|
if not items:
|
||||||
return item_details
|
return item_details
|
||||||
|
|
||||||
cf_field = cf_join = ""
|
item = frappe.qb.DocType("Item")
|
||||||
if include_uom:
|
query = (
|
||||||
cf_field = ", ucd.conversion_factor"
|
frappe.qb.from_(item)
|
||||||
cf_join = (
|
.select(item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom)
|
||||||
"left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s"
|
.where(item.name.isin(items))
|
||||||
% frappe.db.escape(include_uom)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
res = frappe.db.sql(
|
if include_uom:
|
||||||
"""
|
ucd = frappe.qb.DocType("UOM Conversion Detail")
|
||||||
select
|
query = (
|
||||||
item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field}
|
query.left_join(ucd)
|
||||||
from
|
.on((ucd.parent == item.name) & (ucd.uom == include_uom))
|
||||||
`tabItem` item
|
.select(ucd.conversion_factor)
|
||||||
{cf_join}
|
|
||||||
where
|
|
||||||
item.name in ({item_codes})
|
|
||||||
""".format(
|
|
||||||
cf_field=cf_field, cf_join=cf_join, item_codes=",".join(["%s"] * len(items))
|
|
||||||
),
|
|
||||||
items,
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
res = query.run(as_dict=True)
|
||||||
|
|
||||||
for item in res:
|
for item in res:
|
||||||
item_details.setdefault(item.name, item)
|
item_details.setdefault(item.name, item)
|
||||||
|
|
||||||
@ -427,17 +425,29 @@ def get_warehouse_condition(warehouse):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def get_item_group_condition(item_group):
|
def get_item_group_condition(item_group, item_table=None):
|
||||||
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
|
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
|
||||||
if item_group_details:
|
if item_group_details:
|
||||||
|
if item_table:
|
||||||
|
ig = frappe.qb.DocType("Item Group")
|
||||||
|
return item_table.item_group.isin(
|
||||||
|
(
|
||||||
|
frappe.qb.from_(ig)
|
||||||
|
.select(ig.name)
|
||||||
|
.where(
|
||||||
|
(ig.lft >= item_group_details.lft)
|
||||||
|
& (ig.rgt <= item_group_details.rgt)
|
||||||
|
& (item_table.item_group == ig.name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
return (
|
return (
|
||||||
"item.item_group in (select ig.name from `tabItem Group` ig \
|
"item.item_group in (select ig.name from `tabItem Group` ig \
|
||||||
where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
|
where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
|
||||||
% (item_group_details.lft, item_group_details.rgt)
|
% (item_group_details.lft, item_group_details.rgt)
|
||||||
)
|
)
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def check_inventory_dimension_filters_applied(filters) -> bool:
|
def check_inventory_dimension_filters_applied(filters) -> bool:
|
||||||
for dimension in get_inventory_dimensions():
|
for dimension in get_inventory_dimensions():
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user