\ No newline at end of file
+
diff --git a/webshop/templates/includes/macros.html b/webshop/templates/includes/macros.html
index 913aa776a8..bda4176094 100644
--- a/webshop/templates/includes/macros.html
+++ b/webshop/templates/includes/macros.html
@@ -302,7 +302,7 @@
{% if values | len > 20 %}
-
+
{% endif %}
{% if values %}
diff --git a/webshop/templates/pages/cart.js b/webshop/templates/pages/cart.js
index dd03f3d1c7..f7c328f92a 100644
--- a/webshop/templates/pages/cart.js
+++ b/webshop/templates/pages/cart.js
@@ -21,18 +21,33 @@ $.extend(shopping_cart, {
shopping_cart.bind_remove_coupon_code();
},
- bind_place_order: function() {
- $(".btn-place-order").on("click", function() {
- shopping_cart.place_order(this);
+ bind_place_order: function () {
+ $(".btn-place-order").off("click").on("click", function (e) {
+ e.preventDefault();
+ const btn = this;
+ frappe.confirm(
+ "Are you sure you want to place this order?",
+ function () {
+ shopping_cart.place_order(btn);
+ }
+ );
});
},
- bind_request_quotation: function() {
- $('.btn-request-for-quotation').on('click', function() {
- shopping_cart.request_quotation(this);
+ bind_request_quotation: function () {
+ $(".btn-request-for-quotation").off("click").on("click", function (e) {
+ e.preventDefault();
+ const btn = this;
+ frappe.confirm(
+ "Are you sure you want to request a quotation?",
+ function () {
+ shopping_cart.request_quotation(btn);
+ }
+ );
});
},
+
bind_change_qty: function() {
// bind update button
$(".cart-items").on("change", ".cart-qty", function() {
diff --git a/webshop/templates/pages/product_search.py b/webshop/templates/pages/product_search.py
index ba8348e566..f176b526be 100644
--- a/webshop/templates/pages/product_search.py
+++ b/webshop/templates/pages/product_search.py
@@ -63,11 +63,30 @@ def get_product_data(search=None, start=0, limit=12):
return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep
+# @frappe.whitelist(allow_guest=True)
+# def search(query):
+# product_results = product_search(query)
+# category_results = get_category_suggestions(query)
+
+# return {
+# "product_results": product_results.get("results") or [],
+# "category_results": category_results.get("results") or [],
+# }
+
@frappe.whitelist(allow_guest=True)
def search(query):
product_results = product_search(query)
category_results = get_category_suggestions(query)
+ webshop_settings = frappe.get_single("Webshop Settings")
+ hide_unavailable = webshop_settings.hide_unavailable_items
+
+ if hide_unavailable:
+ product_results["results"] = [
+ item for item in product_results["results"]
+ if frappe.utils.cint(item.get("actual_qty") or 0) > 0
+ ]
+
return {
"product_results": product_results.get("results") or [],
"category_results": category_results.get("results") or [],
diff --git a/webshop/webshop/api.py b/webshop/webshop/api.py
index 82650d868d..37cf90d4d2 100644
--- a/webshop/webshop/api.py
+++ b/webshop/webshop/api.py
@@ -5,55 +5,232 @@
import json
import frappe
-from frappe.utils import cint
+from frappe.utils import cint, flt
from webshop.webshop.product_data_engine.filters import ProductFiltersBuilder
from webshop.webshop.product_data_engine.query import ProductQuery
from webshop.webshop.doctype.override_doctype.item_group import get_child_groups_for_website
+# @frappe.whitelist(allow_guest=True)
+# def get_product_filter_data(query_args=None):
+# """
+# Returns filtered products and discount filters.
+
+# Args:
+# query_args (dict): contains filters to get products list
+
+# Query Args filters:
+# search (str): Search Term.
+# field_filters (dict): Keys include item_group, brand, etc.
+# attribute_filters(dict): Keys include Color, Size, etc.
+# start (int): Offset items by
+# item_group (str): Valid Item Group
+# from_filters (bool): Set as True to jump to page 1
+# """
+# if isinstance(query_args, str):
+# query_args = json.loads(query_args)
+
+# query_args = frappe._dict(query_args or {})
+
+# if query_args:
+# search = query_args.get("search")
+# field_filters = query_args.get("field_filters", {})
+# attribute_filters = query_args.get("attribute_filters", {})
+# start = cint(query_args.start) if query_args.get("start") else 0
+# item_group = query_args.get("item_group")
+# from_filters = query_args.get("from_filters")
+# else:
+# search, attribute_filters, item_group, from_filters = None, None, None, None
+# field_filters = {}
+# start = 0
+
+# # if new filter is checked, reset start to show filtered items from page 1
+# if from_filters:
+# start = 0
+
+# sub_categories = []
+# if item_group:
+# sub_categories = get_child_groups_for_website(item_group, immediate=True)
+
+# engine = ProductQuery()
+
+# try:
+# result = engine.query(
+# attribute_filters,
+# field_filters,
+# search_term=search,
+# start=start,
+# item_group=item_group,
+# )
+# except Exception:
+# frappe.log_error("Product query with filter failed")
+# return {"exc": "Something went wrong!"}
+
+# # discount filter data
+# filters = {}
+# discounts = result["discounts"]
+
+# if discounts:
+# filter_engine = ProductFiltersBuilder()
+# filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
+
+# return {
+# "items": result["items"] or [],
+# "filters": filters,
+# "settings": engine.settings,
+# "sub_categories": sub_categories,
+# "items_count": result["items_count"],
+# }
+
+# ###############################################################################################
+
+# @frappe.whitelist(allow_guest=True)
+# def get_product_filter_data(query_args=None):
+# """
+# Returns filtered products and discount filters.
+
+# Args:
+# query_args (dict): contains filters to get products list
+
+# Query Args filters:
+# search (str): Search Term.
+# field_filters (dict): Keys include item_group, brand, etc.
+# attribute_filters(dict): Keys include Color, Size, etc.
+# start (int): Offset items by
+# item_group (str): Valid Item Group
+# from_filters (bool): Set as True to jump to page 1
+# """
+# if isinstance(query_args, str):
+# query_args = json.loads(query_args)
+
+# query_args = frappe._dict(query_args or {})
+
+# if query_args:
+# search = query_args.get("search")
+# field_filters = query_args.get("field_filters", {})
+# attribute_filters = query_args.get("attribute_filters", {})
+# start = cint(query_args.start) if query_args.get("start") else 0
+# item_group = query_args.get("item_group")
+# from_filters = query_args.get("from_filters")
+# else:
+# search, attribute_filters, item_group, from_filters = None, None, None, None
+# field_filters = {}
+# start = 0
+
+# # if new filter is checked, reset start to show filtered items from page 1
+# if from_filters:
+# start = 0
+
+# sub_categories = []
+# if item_group:
+# sub_categories = get_child_groups_for_website(item_group, immediate=True)
+
+# engine = ProductQuery()
+
+# try:
+# result = engine.query(
+# attribute_filters,
+# field_filters,
+# search_term=search,
+# start=start,
+# item_group=item_group,
+# )
+# except Exception:
+# frappe.log_error("Product query with filter failed")
+# return {"exc": "Something went wrong!"}
+
+# # discount filter data
+# filters = {}
+# discounts = result["discounts"]
+
+# if discounts:
+# filter_engine = ProductFiltersBuilder()
+# filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
+
+# for item in result["items"]:
+# item_code = item.get("item_code")
+# website_warehouse = item.get("website_warehouse")
+
+# if not website_warehouse:
+# website_warehouse = frappe.db.get_value("Website Item", item.get("name"), "website_warehouse")
+
+# actual_qty = 0
+# if item_code and website_warehouse:
+# actual_qty = frappe.db.get_value("Bin", {
+# "item_code": item_code,
+# "warehouse": website_warehouse
+# }, "actual_qty") or 0
+
+# item["actual_qty"] = flt(actual_qty)
+# item["stock_qty"] = flt(actual_qty)
+# item["in_stock"] = actual_qty > 0
+
+
+# return {
+# "items": result["items"] or [],
+# "filters": filters,
+# "settings": engine.settings,
+# "sub_categories": sub_categories,
+# "items_count": result["items_count"],
+# }
+
+# ###############################################################################################
+
+# @frappe.whitelist()
+# def get_product_filter_data(start=0, search=None, item_group=None, fields=None, attributes=None):
+# if isinstance(fields, str):
+# fields = json.loads(fields)
+# if isinstance(attributes, str):
+# attributes = json.loads(attributes)
+
+# from webshop.webshop.product_data_engine.query import ProductQuery
+
+# # استخدم Webshop Settings لتحديد الترتيب
+# settings = frappe.get_cached_doc("Webshop Settings")
+# # sort_by = settings.sort_by or "ranking_desc"
+# sort_by = settings.sort_by or "most_used"
+
+
+# query_engine = ProductQuery()
+# query_engine.set_sort_order(sort_by)
+
+# frappe.log_error("Using sort order: " + query_engine.sort_order)
+
+# return query_engine.query(
+# attributes=attributes,
+# fields=fields,
+# search_term=search,
+# start=start,
+# item_group=item_group,
+# )
+
+# ###############################################################################################
+
@frappe.whitelist(allow_guest=True)
def get_product_filter_data(query_args=None):
- """
- Returns filtered products and discount filters.
-
- Args:
- query_args (dict): contains filters to get products list
-
- Query Args filters:
- search (str): Search Term.
- field_filters (dict): Keys include item_group, brand, etc.
- attribute_filters(dict): Keys include Color, Size, etc.
- start (int): Offset items by
- item_group (str): Valid Item Group
- from_filters (bool): Set as True to jump to page 1
- """
if isinstance(query_args, str):
query_args = json.loads(query_args)
query_args = frappe._dict(query_args or {})
- if query_args:
- search = query_args.get("search")
- field_filters = query_args.get("field_filters", {})
- attribute_filters = query_args.get("attribute_filters", {})
- start = cint(query_args.start) if query_args.get("start") else 0
- item_group = query_args.get("item_group")
- from_filters = query_args.get("from_filters")
- else:
- search, attribute_filters, item_group, from_filters = None, None, None, None
- field_filters = {}
- start = 0
+ search = query_args.get("search")
+ field_filters = query_args.get("field_filters", {})
+ attribute_filters = query_args.get("attribute_filters", {})
+ start = cint(query_args.get("start") or 0)
+ item_group = query_args.get("item_group")
+ from_filters = query_args.get("from_filters")
- # if new filter is checked, reset start to show filtered items from page 1
if from_filters:
start = 0
- sub_categories = []
- if item_group:
- sub_categories = get_child_groups_for_website(item_group, immediate=True)
+ settings = frappe.get_cached_doc("Webshop Settings")
+ sort_by = settings.sort_by or "most_used"
engine = ProductQuery()
+ engine.set_sort_order(sort_by)
+
+ frappe.log_error("Using sort order: " + engine.sort_order)
try:
result = engine.query(
@@ -63,11 +240,10 @@ def get_product_filter_data(query_args=None):
start=start,
item_group=item_group,
)
- except Exception:
- frappe.log_error("Product query with filter failed")
+ except Exception as e:
+ frappe.log_error(f"Product query failed: {frappe.get_traceback()}")
return {"exc": "Something went wrong!"}
- # discount filter data
filters = {}
discounts = result["discounts"]
@@ -75,6 +251,22 @@ def get_product_filter_data(query_args=None):
filter_engine = ProductFiltersBuilder()
filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
+ for item in result["items"]:
+ item_code = item.get("item_code")
+ website_warehouse = item.get("website_warehouse") or frappe.db.get_value(
+ "Website Item", item.get("name"), "website_warehouse"
+ )
+
+ actual_qty = get_total_stock_qty(item_code, website_warehouse)
+
+ item["actual_qty"] = flt(actual_qty)
+ item["stock_qty"] = flt(actual_qty)
+ item["in_stock"] = actual_qty > 0
+
+ sub_categories = []
+ if item_group:
+ sub_categories = get_child_groups_for_website(item_group, immediate=True)
+
return {
"items": result["items"] or [],
"filters": filters,
@@ -84,6 +276,28 @@ def get_product_filter_data(query_args=None):
}
+def get_total_stock_qty(item_code, warehouse=None):
+ """حساب الكمية الكلية بناءً على شروط المستودع"""
+ from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+
+ # تحديد المستودعات
+ if warehouse:
+ if frappe.get_cached_value("Warehouse", warehouse, "is_group"):
+ warehouses = get_child_warehouses(warehouse)
+ else:
+ warehouses = [warehouse]
+ else:
+ warehouses = frappe.db.get_all("Warehouse", filters={"is_group": 0}, pluck="name")
+
+ total_qty = 0.0
+
+ for wh in warehouses:
+ qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": wh}, "actual_qty")
+ total_qty += flt(qty) or 0.0
+
+ return total_qty
+
+
@frappe.whitelist(allow_guest=True)
def get_guest_redirect_on_action():
return frappe.db.get_single_value("Webshop Settings", "redirect_on_action")
diff --git a/webshop/webshop/doctype/webshop_settings/webshop_settings.json b/webshop/webshop/doctype/webshop_settings/webshop_settings.json
index 024ec2efd3..737a6e3d99 100644
--- a/webshop/webshop/doctype/webshop_settings/webshop_settings.json
+++ b/webshop/webshop/doctype/webshop_settings/webshop_settings.json
@@ -15,10 +15,12 @@
"hide_variants",
"enable_variants",
"show_price",
+ "sort_by",
"column_break_9",
"login_required_to_view_products",
"show_stock_availability",
"show_quantity_in_website",
+ "hide_unavailable_items",
"allow_items_not_in_stock",
"column_break_13",
"show_apply_coupon_code_in_website",
@@ -382,12 +384,26 @@
"fieldtype": "Check",
"in_list_view": 1,
"label": "Allow Non Website Items in Cart Quotation"
+ },
+ {
+ "default": "0",
+ "depends_on": "show_stock_availability",
+ "fieldname": "hide_unavailable_items",
+ "fieldtype": "Check",
+ "label": " Hide Unavailable Items"
+ },
+ {
+ "fieldname": "sort_by",
+ "fieldtype": "Select",
+ "label": "Sort By",
+ "options": "\nLast Updated On Asc\nLast Updated On Desc\nWebsite Item Name Asc\nWebsite Item Name Desc\nID Asc\nID Desc\nCreated On Asc\nCreated On Desc\nMost Used\nRoute\nItem Code Asc\nItem Code Desc\nItem Group Asc\nItem Group Desc"
}
],
+ "grid_page_length": 50,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2025-03-22 13:01:51.906319",
+ "modified": "2025-05-19 13:38:51.119954",
"modified_by": "Administrator",
"module": "Webshop",
"name": "Webshop Settings",
@@ -408,6 +424,7 @@
"role": "All"
}
],
+ "row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
diff --git a/webshop/webshop/doctype/website_item/website_item.json b/webshop/webshop/doctype/website_item/website_item.json
index e5adbd778c..4c55e05177 100644
--- a/webshop/webshop/doctype/website_item/website_item.json
+++ b/webshop/webshop/doctype/website_item/website_item.json
@@ -184,7 +184,9 @@
"fetch_from": "item_code.item_group",
"fieldname": "item_group",
"fieldtype": "Link",
+ "in_filter": 1,
"in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Item Group",
"options": "Item Group",
"read_only": 1,
@@ -202,7 +204,6 @@
"fetch_from": "item_code.has_variants",
"fieldname": "has_variants",
"fieldtype": "Check",
- "in_standard_filter": 1,
"label": "Has Variants",
"no_copy": 1,
"read_only": 1
@@ -347,7 +348,7 @@
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
- "modified": "2024-11-05 13:41:45.347700",
+ "modified": "2025-05-19 14:39:31.456099",
"modified_by": "Administrator",
"module": "Webshop",
"name": "Website Item",
diff --git a/webshop/webshop/product_data_engine/query.py b/webshop/webshop/product_data_engine/query.py
index 4fb6c60543..5a422f5fec 100644
--- a/webshop/webshop/product_data_engine/query.py
+++ b/webshop/webshop/product_data_engine/query.py
@@ -23,6 +23,7 @@ class ProductQuery:
def __init__(self):
self.settings = frappe.get_doc("Webshop Settings")
self.page_length = self.settings.products_per_page or 20
+ self.sort_order = "ranking desc" # New
self.or_filters = []
self.filters = [["published", "=", 1]]
@@ -43,6 +44,27 @@ def __init__(self):
"on_backorder",
]
+ def set_sort_order(self, sort_by):
+ sort_map = {
+ "Last Updated On Asc": "modified asc",
+ "Last Updated On Desc": "modified desc",
+ "Website Item Name Asc": "item_name asc",
+ "Website Item Name Desc": "item_name desc",
+ "ID Asc": "name asc",
+ "ID Desc": "name desc",
+ "Created On Asc": "creation asc",
+ "Created On Desc": "creation desc",
+ "Most Used": "ranking desc",
+ "Route": "route",
+ "Item Code Asc": "item_code asc",
+ "Item Code Desc": "item_code desc",
+ "Item Group Asc": "item_group asc",
+ "Item Group Desc": "item_group desc",
+ }
+
+ self.sort_order = sort_map.get(sort_by, "ranking desc")
+
+
def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
"""
Args:
@@ -100,7 +122,8 @@ def query_items(self, start=0):
or_filters=self.or_filters,
limit_page_length=184467440737095516,
limit_start=start, # get all items from this offset for total count ahead
- order_by="ranking desc",
+ order_by=self.sort_order,
+ # order_by="ranking desc",
)
count = len(count_items)
@@ -117,7 +140,8 @@ def query_items(self, start=0):
or_filters=self.or_filters,
limit_page_length=page_length,
limit_start=start,
- order_by="ranking desc",
+ order_by=self.sort_order,
+ # order_by="ranking desc",
)
return items, count
diff --git a/webshop/webshop/redisearch_utils.py b/webshop/webshop/redisearch_utils.py
index 85e59624c3..7207026534 100644
--- a/webshop/webshop/redisearch_utils.py
+++ b/webshop/webshop/redisearch_utils.py
@@ -163,7 +163,8 @@ def delete_item_from_index(website_item_doc):
def delete_from_ac_dict(website_item_doc):
"""Removes this items's name from autocomplete dictionary"""
ac = frappe.cache().ft()
- ac.sugdel(website_item_doc.web_item_name)
+ ac.sugdel("autocomplete_key", website_item_doc.web_item_name)
+
@if_redisearch_enabled
diff --git a/webshop/webshop/shopping_cart/cart.py b/webshop/webshop/shopping_cart/cart.py
index f4ed155e42..5aeec4d959 100644
--- a/webshop/webshop/shopping_cart/cart.py
+++ b/webshop/webshop/shopping_cart/cart.py
@@ -747,6 +747,7 @@ def get_shipping_rules(quotation=None, cart_settings=None):
return shipping_rules
+
def get_address_territory(address_name):
"""Tries to match city, state and country of address to existing territory"""
territory = None
diff --git a/webshop/webshop/shopping_cart/product_info.py b/webshop/webshop/shopping_cart/product_info.py
index 97e5871f01..faddf9a206 100644
--- a/webshop/webshop/shopping_cart/product_info.py
+++ b/webshop/webshop/shopping_cart/product_info.py
@@ -76,7 +76,8 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
product_info["in_stock"] = (
stock_status.in_stock
if stock_status.is_stock_item
- else get_non_stock_item_status(item_code, "website_warehouse")
+ else
+ (item_code, "website_warehouse")
)
product_info["show_stock_qty"] = show_quantity_in_website()
diff --git a/webshop/webshop/utils/product.py b/webshop/webshop/utils/product.py
index d4b80b0468..9369a24022 100644
--- a/webshop/webshop/utils/product.py
+++ b/webshop/webshop/utils/product.py
@@ -3,65 +3,87 @@
from erpnext.stock.doctype.batch.batch import get_batch_qty
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
+from frappe.utils import flt
def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
- in_stock, stock_qty = 0, ""
+ in_stock, total_stock = 0, 0.0
+
template_item_code, is_stock_item = frappe.db.get_value(
"Item", item_code, ["variant_of", "is_stock_item"]
)
+ # أول محاولة للحصول على المخزن من Website Item
if not warehouse:
warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field)
+ # إذا ما زال لا يوجد مخزن، جرّب الصنف القالب
if not warehouse and template_item_code and template_item_code != item_code:
- warehouse = frappe.db.get_value(
- "Website Item", {"item_code": template_item_code}, item_warehouse_field
- )
+ warehouse = frappe.db.get_value("Website Item", {"item_code": template_item_code}, item_warehouse_field)
- if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1:
- warehouses = get_child_warehouses(warehouse)
+ # معالجة حالة المخزن
+ if warehouse:
+ if frappe.get_cached_value("Warehouse", warehouse, "is_group"):
+ warehouses = get_child_warehouses(warehouse)
+ else:
+ warehouses = [warehouse]
else:
- warehouses = [warehouse] if warehouse else []
-
- total_stock = 0.0
- if warehouses:
- for warehouse in warehouses:
- stock_qty = frappe.db.sql(
- """
- select S.actual_qty / IFNULL(C.conversion_factor, 1)
- from tabBin S
- inner join `tabItem` I on S.item_code = I.Item_code
- left join `tabUOM Conversion Detail` C on I.sales_uom = C.uom and C.parent = I.Item_code
- where S.item_code=%s and S.warehouse=%s""",
- (item_code, warehouse),
- )
-
- if stock_qty:
- total_stock += adjust_qty_for_expired_items(item_code, stock_qty, warehouse)
-
- in_stock = total_stock > 0 and 1 or 0
-
- return frappe._dict(
- {"in_stock": in_stock, "stock_qty": total_stock, "is_stock_item": is_stock_item}
- )
+ # إذا لا يوجد مخزن، خذ كل المخازن التي ليست مجموعة
+ warehouses = frappe.db.get_all("Warehouse", filters={"is_group": 0}, pluck="name")
+
+ # جمع الكميات
+ for wh in warehouses:
+ stock_qty = frappe.db.sql(
+ """
+ SELECT S.actual_qty / IFNULL(C.conversion_factor, 1)
+ FROM tabBin S
+ INNER JOIN `tabItem` I ON S.item_code = I.item_code
+ LEFT JOIN `tabUOM Conversion Detail` C
+ ON I.sales_uom = C.uom AND C.parent = I.item_code
+ WHERE S.item_code = %s AND S.warehouse = %s
+ """,
+ (item_code, wh),
+ )
+
+ if stock_qty and stock_qty[0][0] is not None:
+ qty = flt(stock_qty[0][0])
+ total_stock += adjust_qty_for_expired_items(item_code, qty, wh)
+
+ in_stock = 1 if total_stock > 0 else 0
+
+ return frappe._dict({
+ "in_stock": in_stock,
+ "stock_qty": total_stock,
+ "is_stock_item": is_stock_item
+ })
+
def adjust_qty_for_expired_items(item_code, stock_qty, warehouse):
+ # تحويل stock_qty إلى قيمة عددية موحدة
+ if isinstance(stock_qty, (int, float)):
+ stock_qty_value = stock_qty
+ elif isinstance(stock_qty, list):
+ # مثال: [[10.0]] → 10.0
+ stock_qty_value = stock_qty[0][0] if stock_qty and stock_qty[0] else 0
+ else:
+ stock_qty_value = 0
+
+ # استعلام الدُفعات وانتهاء الصلاحية
batches = frappe.get_all("Batch", filters=[{"item": item_code}], fields=["expiry_date", "name"])
expired_batches = get_expired_batches(batches)
- stock_qty = [list(item) for item in stock_qty]
for batch in expired_batches:
if warehouse:
- stock_qty[0][0] = max(0, stock_qty[0][0] - get_batch_qty(batch, warehouse))
+ stock_qty_value = max(0, stock_qty_value - get_batch_qty(batch, warehouse))
else:
- stock_qty[0][0] = max(0, stock_qty[0][0] - qty_from_all_warehouses(get_batch_qty(batch)))
+ stock_qty_value = max(0, stock_qty_value - qty_from_all_warehouses(get_batch_qty(batch)))
- if not stock_qty[0][0]:
+ if not stock_qty_value:
break
- return stock_qty[0][0] if stock_qty else 0
+ return stock_qty_value
+
def get_expired_batches(batches):
diff --git a/webshop/webshop/variant_selector/utils.py b/webshop/webshop/variant_selector/utils.py
index 976a6e2074..818aa75b07 100644
--- a/webshop/webshop/variant_selector/utils.py
+++ b/webshop/webshop/variant_selector/utils.py
@@ -194,7 +194,9 @@ def get_next_attribute_and_values(item_code, selected_attributes):
if warehouse and frappe.get_cached_value("Warehouse", warehouse, "is_group") == 1:
warehouses = get_child_warehouses(warehouse)
else:
- warehouses = [warehouse] if warehouse else []
+ all_warehouses = frappe.db.get_all("Warehouse", filters={"is_group": 0}, pluck="name")
+
+ warehouses = [warehouse] if warehouse else all_warehouses
for warehouse in warehouses:
available_qty += flt(
diff --git a/webshop/www/all-products/index copy.html b/webshop/www/all-products/index copy.html
new file mode 100644
index 0000000000..cbd76632b6
--- /dev/null
+++ b/webshop/www/all-products/index copy.html
@@ -0,0 +1,51 @@
+{% from "webshop/templates/includes/macros.html" import attribute_filter_section, field_filter_section, discount_range_filters %}
+{% extends "templates/web.html" %}
+
+{% block title %}{{ _("All Products") }}{% endblock %}
+{% block header %}
+