mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 08:24:47 +00:00
Merge pull request #48729 from frappe/mergify/bp/version-15-hotfix/pr-48382
This commit is contained in:
@@ -3669,7 +3669,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
columns, data = execute(
|
columns, data = execute(
|
||||||
filters=frappe._dict(
|
filters=frappe._dict(
|
||||||
{"item_code": item_code, "warehouse": pr.items[0].warehouse, "company": pr.company}
|
{"item_code": [item_code], "warehouse": [pr.items[0].warehouse], "company": pr.company}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -242,19 +242,35 @@ def get_warehouses_based_on_account(account, company=None):
|
|||||||
|
|
||||||
# Will be use for frappe.qb
|
# Will be use for frappe.qb
|
||||||
def apply_warehouse_filter(query, sle, filters):
|
def apply_warehouse_filter(query, sle, filters):
|
||||||
if warehouse := filters.get("warehouse"):
|
if not (warehouses := filters.get("warehouse")):
|
||||||
warehouse_table = frappe.qb.DocType("Warehouse")
|
return query
|
||||||
|
|
||||||
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
|
warehouse_table = frappe.qb.DocType("Warehouse")
|
||||||
chilren_subquery = (
|
|
||||||
frappe.qb.from_(warehouse_table)
|
if isinstance(warehouses, str):
|
||||||
.select(warehouse_table.name)
|
warehouses = [warehouses]
|
||||||
.where(
|
|
||||||
(warehouse_table.lft >= lft)
|
warehouse_range = frappe.get_all(
|
||||||
& (warehouse_table.rgt <= rgt)
|
"Warehouse",
|
||||||
& (warehouse_table.name == sle.warehouse)
|
filters={
|
||||||
)
|
"name": ("in", warehouses),
|
||||||
)
|
},
|
||||||
query = query.where(ExistsCriterion(chilren_subquery))
|
fields=["lft", "rgt"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
child_query = frappe.qb.from_(warehouse_table).select(warehouse_table.name)
|
||||||
|
|
||||||
|
range_conditions = [
|
||||||
|
(warehouse_table.lft >= lft) & (warehouse_table.rgt <= rgt) for lft, rgt in warehouse_range
|
||||||
|
]
|
||||||
|
|
||||||
|
combined_condition = range_conditions[0]
|
||||||
|
for condition in range_conditions[1:]:
|
||||||
|
combined_condition = combined_condition | condition
|
||||||
|
|
||||||
|
child_query = child_query.where(combined_condition).where(warehouse_table.name == sle.warehouse)
|
||||||
|
|
||||||
|
query = query.where(ExistsCriterion(child_query))
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -36,38 +36,57 @@ frappe.query_reports["Stock Balance"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_code",
|
fieldname: "item_code",
|
||||||
label: __("Item"),
|
label: __("Items"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
width: "80",
|
width: "80",
|
||||||
options: "Item",
|
options: "Item",
|
||||||
get_query: function () {
|
get_data: async function (txt) {
|
||||||
let item_group = frappe.query_report.get_filter_value("item_group");
|
let item_group = frappe.query_report.get_filter_value("item_group");
|
||||||
|
|
||||||
return {
|
let filters = {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
...(item_group && { item_group }),
|
||||||
filters: {
|
is_stock_item: 1,
|
||||||
...(item_group && { item_group }),
|
|
||||||
is_stock_item: 1,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let { message: data } = await frappe.call({
|
||||||
|
method: "erpnext.controllers.queries.item_query",
|
||||||
|
args: {
|
||||||
|
doctype: "Item",
|
||||||
|
txt: txt,
|
||||||
|
searchfield: "name",
|
||||||
|
start: 0,
|
||||||
|
page_len: 10,
|
||||||
|
filters: filters,
|
||||||
|
as_dict: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
data = data.map(({ name, description }) => {
|
||||||
|
return {
|
||||||
|
value: name,
|
||||||
|
description: description,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return data || [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "warehouse",
|
fieldname: "warehouse",
|
||||||
label: __("Warehouse"),
|
label: __("Warehouses"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
width: "80",
|
width: "80",
|
||||||
options: "Warehouse",
|
options: "Warehouse",
|
||||||
get_query: () => {
|
get_data: (txt) => {
|
||||||
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
||||||
let company = frappe.query_report.get_filter_value("company");
|
let company = frappe.query_report.get_filter_value("company");
|
||||||
|
|
||||||
return {
|
let filters = {
|
||||||
filters: {
|
...(warehouse_type && { warehouse_type }),
|
||||||
...(warehouse_type && { warehouse_type }),
|
...(company && { company }),
|
||||||
...(company && { company }),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return frappe.db.get_link_options("Warehouse", txt, filters);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ class StockBalanceFilter(TypedDict):
|
|||||||
from_date: str
|
from_date: str
|
||||||
to_date: str
|
to_date: str
|
||||||
item_group: str | None
|
item_group: str | None
|
||||||
item: str | None
|
item: list[str] | None
|
||||||
warehouse: str | None
|
warehouse: list[str] | None
|
||||||
warehouse_type: str | None
|
warehouse_type: str | None
|
||||||
include_uom: str | None # include extra info in converted UOM
|
include_uom: str | None # include extra info in converted UOM
|
||||||
show_stock_ageing_data: bool
|
show_stock_ageing_data: bool
|
||||||
@@ -283,8 +283,11 @@ class StockBalanceReport:
|
|||||||
)
|
)
|
||||||
|
|
||||||
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
|
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
|
||||||
if self.filters.get(fieldname):
|
if value := self.filters.get(fieldname):
|
||||||
query = query.where(table[fieldname] == self.filters.get(fieldname))
|
if isinstance(value, list | tuple):
|
||||||
|
query = query.where(table[fieldname].isin(value))
|
||||||
|
else:
|
||||||
|
query = query.where(table[fieldname] == value)
|
||||||
|
|
||||||
return query.run(as_dict=True)
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
@@ -347,6 +350,7 @@ class StockBalanceReport:
|
|||||||
|
|
||||||
if self.filters.get("warehouse"):
|
if self.filters.get("warehouse"):
|
||||||
query = apply_warehouse_filter(query, sle, self.filters)
|
query = apply_warehouse_filter(query, sle, self.filters)
|
||||||
|
|
||||||
elif warehouse_type := self.filters.get("warehouse_type"):
|
elif warehouse_type := self.filters.get("warehouse_type"):
|
||||||
query = (
|
query = (
|
||||||
query.join(warehouse_table)
|
query.join(warehouse_table)
|
||||||
@@ -361,13 +365,11 @@ class StockBalanceReport:
|
|||||||
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
|
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
|
||||||
query = query.where(item_table.item_group.isin([*children, item_group]))
|
query = query.where(item_table.item_group.isin([*children, item_group]))
|
||||||
|
|
||||||
for field in ["item_code", "brand"]:
|
if item_codes := self.filters.get("item_code"):
|
||||||
if not self.filters.get(field):
|
query = query.where(item_table.name.isin(item_codes))
|
||||||
continue
|
|
||||||
elif field == "item_code":
|
if brand := self.filters.get("brand"):
|
||||||
query = query.where(item_table.name == self.filters.get(field))
|
query = query.where(item_table.brand == brand)
|
||||||
else:
|
|
||||||
query = query.where(item_table[field] == self.filters.get(field))
|
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class TestStockBalance(FrappeTestCase):
|
|||||||
self.filters = _dict(
|
self.filters = _dict(
|
||||||
{
|
{
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"item_code": self.item.name,
|
"item_code": [self.item.name],
|
||||||
"from_date": "2020-01-01",
|
"from_date": "2020-01-01",
|
||||||
"to_date": str(today()),
|
"to_date": str(today()),
|
||||||
}
|
}
|
||||||
@@ -165,6 +165,6 @@ class TestStockBalance(FrappeTestCase):
|
|||||||
variant.save()
|
variant.save()
|
||||||
|
|
||||||
self.generate_stock_ledger(variant.name, [_dict(qty=5, rate=10)])
|
self.generate_stock_ledger(variant.name, [_dict(qty=5, rate=10)])
|
||||||
rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": variant.name}))
|
rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": [variant.name]}))
|
||||||
self.assertPartialDictEq(attributes, rows[0])
|
self.assertPartialDictEq(attributes, rows[0])
|
||||||
self.assertInvariants(rows)
|
self.assertInvariants(rows)
|
||||||
|
|||||||
@@ -27,25 +27,44 @@ frappe.query_reports["Stock Ledger"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "warehouse",
|
fieldname: "warehouse",
|
||||||
label: __("Warehouse"),
|
label: __("Warehouses"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
options: "Warehouse",
|
options: "Warehouse",
|
||||||
get_query: function () {
|
get_data: function (txt) {
|
||||||
const company = frappe.query_report.get_filter_value("company");
|
const company = frappe.query_report.get_filter_value("company");
|
||||||
return {
|
|
||||||
filters: { company: company },
|
return frappe.db.get_link_options("Warehouse", txt, {
|
||||||
};
|
company: company,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_code",
|
fieldname: "item_code",
|
||||||
label: __("Item"),
|
label: __("Items"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
options: "Item",
|
options: "Item",
|
||||||
get_query: function () {
|
get_data: async function (txt) {
|
||||||
return {
|
let { message: data } = await frappe.call({
|
||||||
query: "erpnext.controllers.queries.item_query",
|
method: "erpnext.controllers.queries.item_query",
|
||||||
};
|
args: {
|
||||||
|
doctype: "Item",
|
||||||
|
txt: txt,
|
||||||
|
searchfield: "name",
|
||||||
|
start: 0,
|
||||||
|
page_len: 10,
|
||||||
|
filters: {},
|
||||||
|
as_dict: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
data = data.map(({ name, description }) => {
|
||||||
|
return {
|
||||||
|
value: name,
|
||||||
|
description: description,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return data || [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -456,19 +456,23 @@ def get_items(filters):
|
|||||||
query = frappe.qb.from_(item).select(item.name)
|
query = frappe.qb.from_(item).select(item.name)
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
if item_code := filters.get("item_code"):
|
if item_codes := filters.get("item_code"):
|
||||||
conditions.append(item.name == item_code)
|
conditions.append(item.name.isin(item_codes))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if brand := filters.get("brand"):
|
if brand := filters.get("brand"):
|
||||||
conditions.append(item.brand == brand)
|
conditions.append(item.brand == brand)
|
||||||
if item_group := filters.get("item_group"):
|
|
||||||
if condition := get_item_group_condition(item_group, item):
|
if filters.get("item_group") and (
|
||||||
conditions.append(condition)
|
condition := get_item_group_condition(filters.get("item_group"), item)
|
||||||
|
):
|
||||||
|
conditions.append(condition)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
if conditions:
|
if conditions:
|
||||||
for condition in conditions:
|
for condition in conditions:
|
||||||
query = query.where(condition)
|
query = query.where(condition)
|
||||||
|
|
||||||
items = [r[0] for r in query.run()]
|
items = [r[0] for r in query.run()]
|
||||||
|
|
||||||
return items
|
return items
|
||||||
@@ -505,6 +509,7 @@ def get_item_details(items, sl_entries, include_uom):
|
|||||||
return item_details
|
return item_details
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: THIS IS NOT USED
|
||||||
def get_sle_conditions(filters):
|
def get_sle_conditions(filters):
|
||||||
conditions = []
|
conditions = []
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
@@ -535,8 +540,8 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for fields in ["item_code", "warehouse"]:
|
for fields in ["item_code", "warehouse"]:
|
||||||
if filters.get(fields):
|
if value := filters.get(fields):
|
||||||
query_filters[fields] = filters.get(fields)
|
query_filters[fields] = ("in", value)
|
||||||
|
|
||||||
opening_data = frappe.get_all(
|
opening_data = frappe.get_all(
|
||||||
"Stock Ledger Entry",
|
"Stock Ledger Entry",
|
||||||
@@ -567,8 +572,16 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for field in ["item_code", "warehouse", "company"]:
|
for field in ["item_code", "warehouse", "company"]:
|
||||||
if filters.get(field):
|
value = filters.get(field)
|
||||||
query = query.where(table[field] == filters.get(field))
|
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(value, list | tuple):
|
||||||
|
query = query.where(table[field].isin(value))
|
||||||
|
|
||||||
|
else:
|
||||||
|
query = query.where(table[field] == value)
|
||||||
|
|
||||||
bundle_data = query.run(as_dict=True)
|
bundle_data = query.run(as_dict=True)
|
||||||
|
|
||||||
@@ -623,13 +636,34 @@ def get_opening_balance(filters, columns, sl_entries):
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
def get_warehouse_condition(warehouse):
|
def get_warehouse_condition(warehouses):
|
||||||
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
|
if not warehouses:
|
||||||
if warehouse_details:
|
return ""
|
||||||
return f" exists (select name from `tabWarehouse` wh \
|
|
||||||
where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
|
|
||||||
|
|
||||||
return ""
|
if isinstance(warehouses, str):
|
||||||
|
warehouses = [warehouses]
|
||||||
|
|
||||||
|
warehouse_range = frappe.get_all(
|
||||||
|
"Warehouse",
|
||||||
|
filters={
|
||||||
|
"name": ("in", warehouses),
|
||||||
|
},
|
||||||
|
fields=["lft", "rgt"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not warehouse_range:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
alias = "wh"
|
||||||
|
conditions = []
|
||||||
|
for lft, rgt in warehouse_range:
|
||||||
|
conditions.append(f"({alias}.lft >= {lft} and {alias}.rgt <= {rgt})")
|
||||||
|
|
||||||
|
conditions = " or ".join(conditions)
|
||||||
|
|
||||||
|
return f" exists (select name from `tabWarehouse` {alias} \
|
||||||
|
where ({conditions}) and warehouse = {alias}.name)"
|
||||||
|
|
||||||
|
|
||||||
def get_item_group_condition(item_group, item_table=None):
|
def get_item_group_condition(item_group, item_table=None):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class TestStockLedgerReeport(FrappeTestCase):
|
|||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
from_date=today(),
|
from_date=today(),
|
||||||
to_date=add_days(today(), 30),
|
to_date=add_days(today(), 30),
|
||||||
item_code="_Test Stock Report Serial Item",
|
item_code=["_Test Stock Report Serial Item"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
|
|||||||
@@ -17,8 +17,15 @@ batch = get_random("Batch")
|
|||||||
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
||||||
("Stock Ledger", {"_optional": True}),
|
("Stock Ledger", {"_optional": True}),
|
||||||
("Stock Ledger", {"batch_no": batch}),
|
("Stock Ledger", {"batch_no": batch}),
|
||||||
("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}),
|
("Stock Ledger", {"item_code": ["_Test Item"], "warehouse": ["_Test Warehouse - _TC"]}),
|
||||||
("Stock Balance", {"_optional": True}),
|
(
|
||||||
|
"Stock Balance",
|
||||||
|
{
|
||||||
|
"item_code": ["_Test Item"],
|
||||||
|
"warehouse": ["_Test Warehouse - _TC"],
|
||||||
|
"item_group": "_Test Item Group",
|
||||||
|
},
|
||||||
|
),
|
||||||
("Stock Projected Qty", {"_optional": True}),
|
("Stock Projected Qty", {"_optional": True}),
|
||||||
("Batch-Wise Balance History", {}),
|
("Batch-Wise Balance History", {}),
|
||||||
("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}),
|
("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}),
|
||||||
|
|||||||
@@ -1670,8 +1670,20 @@ def get_stock_ledger_entries(
|
|||||||
):
|
):
|
||||||
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||||
conditions = f" and posting_datetime {operator} %(posting_datetime)s"
|
conditions = f" and posting_datetime {operator} %(posting_datetime)s"
|
||||||
if previous_sle.get("warehouse"):
|
|
||||||
conditions += " and warehouse = %(warehouse)s"
|
if item_code := previous_sle.get("item_code"):
|
||||||
|
if isinstance(item_code, list | tuple):
|
||||||
|
conditions += " and item_code in %(item_code)s"
|
||||||
|
else:
|
||||||
|
conditions += " and item_code = %(item_code)s"
|
||||||
|
|
||||||
|
if warehouse := previous_sle.get("warehouse"):
|
||||||
|
if isinstance(warehouse, list | tuple):
|
||||||
|
conditions += " and warehouse in %(warehouse)s"
|
||||||
|
|
||||||
|
else:
|
||||||
|
conditions += " and warehouse = %(warehouse)s"
|
||||||
|
|
||||||
elif previous_sle.get("warehouse_condition"):
|
elif previous_sle.get("warehouse_condition"):
|
||||||
conditions += " and " + previous_sle.get("warehouse_condition")
|
conditions += " and " + previous_sle.get("warehouse_condition")
|
||||||
|
|
||||||
@@ -1714,8 +1726,7 @@ def get_stock_ledger_entries(
|
|||||||
"""
|
"""
|
||||||
select *, posting_datetime as "timestamp"
|
select *, posting_datetime as "timestamp"
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where item_code = %(item_code)s
|
where is_cancelled = 0
|
||||||
and is_cancelled = 0
|
|
||||||
{conditions}
|
{conditions}
|
||||||
order by posting_datetime {order}, creation {order}
|
order by posting_datetime {order}, creation {order}
|
||||||
{limit} {for_update}""".format(
|
{limit} {for_update}""".format(
|
||||||
|
|||||||
Reference in New Issue
Block a user