feat: Discount Filters
- Discount filters in filters section - Code cleanup
This commit is contained in:
parent
817f695c57
commit
1d94914102
@ -75,4 +75,4 @@ def get_customer():
|
||||
if customer:
|
||||
return frappe.db.get_value("Customer", customer)
|
||||
else:
|
||||
frappe.throw("You are not verified to write a review yet. Please contact us for verification.")
|
||||
frappe.throw(_("You are not verified to write a review yet. Please contact us for verification."))
|
@ -2,7 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from frappe import _dict
|
||||
from frappe.utils import floor, ceil, flt
|
||||
|
||||
class ProductFiltersBuilder:
|
||||
def __init__(self, item_group=None):
|
||||
@ -88,3 +89,19 @@ class ProductFiltersBuilder:
|
||||
for name, values in attribute_value_map.items():
|
||||
out.append(frappe._dict(name=name, item_attribute_values=values))
|
||||
return out
|
||||
|
||||
def get_discount_filters(self, discounts):
|
||||
discount_filters = []
|
||||
|
||||
# [25.89, 60.5]
|
||||
min_discount, max_discount = discounts[0], discounts[1]
|
||||
# [25, 60]
|
||||
min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount)
|
||||
min_range = int(min_discount - (min_range_absolute%10)) # 20
|
||||
max_range = int(max_discount - (max_range_absolute%10)) # 60
|
||||
|
||||
for discount in range(min_range, (max_range + 1), 10):
|
||||
label = f"{discount}% and above"
|
||||
discount_filters.append([discount, label])
|
||||
|
||||
return discount_filters
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
|
||||
|
||||
|
||||
@ -39,25 +41,15 @@ class ProductQuery:
|
||||
Returns:
|
||||
list: List of results with set fields
|
||||
"""
|
||||
result, discount_list = [], []
|
||||
|
||||
if fields:
|
||||
self.build_fields_filters(fields)
|
||||
if search_term:
|
||||
self.build_search_filters(search_term)
|
||||
|
||||
if self.settings.hide_variants:
|
||||
self.conditions += " and wi.variant_of is null"
|
||||
|
||||
result = []
|
||||
website_item_groups = []
|
||||
|
||||
# if from item group page consider website item group table
|
||||
if item_group:
|
||||
website_item_groups = frappe.db.get_all(
|
||||
"Item",
|
||||
fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
|
||||
filters=[["Website Item Group", "item_group", "=", item_group]]
|
||||
)
|
||||
|
||||
if attributes:
|
||||
result = self.query_items_with_attributes(attributes, start)
|
||||
else:
|
||||
@ -67,33 +59,50 @@ class ProductQuery:
|
||||
# add price and availability info in results
|
||||
for item in result:
|
||||
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
|
||||
|
||||
if product_info and product_info['price']:
|
||||
item.formatted_mrp = product_info['price'].get('formatted_mrp')
|
||||
item.formatted_price = product_info['price'].get('formatted_price')
|
||||
if item.formatted_mrp:
|
||||
item.discount = product_info['price'].get('formatted_discount_percent') or \
|
||||
product_info['price'].get('formatted_discount_rate')
|
||||
item.price = product_info['price'].get('price_list_rate')
|
||||
self.get_price_discount_info(item, product_info['price'], discount_list)
|
||||
|
||||
if self.settings.show_stock_availability:
|
||||
if item.get("website_warehouse"):
|
||||
stock_qty = frappe.utils.flt(
|
||||
frappe.db.get_value("Bin",
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"warehouse": item.get("website_warehouse")
|
||||
},
|
||||
"actual_qty")
|
||||
)
|
||||
item.in_stock = "green" if stock_qty else "red"
|
||||
elif not frappe.db.get_value("Item", item.item_code, "is_stock_item"):
|
||||
item.in_stock = "green" # non-stock item will always be available
|
||||
self.get_stock_availability(item)
|
||||
|
||||
item.wished = False
|
||||
if frappe.db.exists("Wishlist Items", {"item_code": item.item_code, "parent": frappe.session.user}):
|
||||
item.wished = True
|
||||
|
||||
return result
|
||||
discounts = []
|
||||
if discount_list:
|
||||
discounts = [min(discount_list), max(discount_list)]
|
||||
|
||||
if fields and "discount" in fields:
|
||||
discount_percent = frappe.utils.flt(fields["discount"][0])
|
||||
result = [row for row in result if row.get("discount_percent") and row.discount_percent >= discount_percent]
|
||||
|
||||
return result, discounts
|
||||
|
||||
def get_price_discount_info(self, item, price_object, discount_list):
|
||||
"""Modify item object and add price details."""
|
||||
item.formatted_mrp = price_object.get('formatted_mrp')
|
||||
item.formatted_price = price_object.get('formatted_price')
|
||||
|
||||
if price_object.get('discount_percent'):
|
||||
item.discount_percent = flt(price_object.discount_percent)
|
||||
discount_list.append(price_object.discount_percent)
|
||||
|
||||
if item.formatted_mrp:
|
||||
item.discount = price_object.get('formatted_discount_percent') or \
|
||||
price_object.get('formatted_discount_rate')
|
||||
item.price = price_object.get('price_list_rate')
|
||||
|
||||
def get_stock_availability(self, item):
|
||||
"""Modify item object and add stock details."""
|
||||
if item.get("website_warehouse"):
|
||||
stock_qty = frappe.utils.flt(
|
||||
frappe.db.get_value("Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")},
|
||||
"actual_qty"))
|
||||
item.in_stock = "green" if stock_qty else "red"
|
||||
elif not frappe.db.get_value("Item", item.item_code, "is_stock_item"):
|
||||
item.in_stock = "green" # non-stock item will always be available
|
||||
|
||||
def query_items(self, conditions, or_conditions, substitutions, start=0):
|
||||
"""Build a query to fetch Website Items based on field filters."""
|
||||
@ -150,7 +159,7 @@ class ProductQuery:
|
||||
filters (dict): Filters
|
||||
"""
|
||||
for field, values in filters.items():
|
||||
if not values:
|
||||
if not values or field == "discount":
|
||||
continue
|
||||
|
||||
# handle multiselect fields in filter addition
|
||||
|
@ -48,7 +48,7 @@ $.extend(wishlist, {
|
||||
});
|
||||
|
||||
let success_action = function() {
|
||||
const $card_wrapper = $move_to_cart_btn.closest(".item-card");
|
||||
const $card_wrapper = $move_to_cart_btn.closest(".wishlist-card");
|
||||
$card_wrapper.addClass("wish-removed");
|
||||
};
|
||||
let args = { item_code: item_code };
|
||||
@ -63,7 +63,7 @@ $.extend(wishlist, {
|
||||
let item_code = $remove_wish_btn.data("item-code");
|
||||
|
||||
let success_action = function() {
|
||||
const $card_wrapper = $remove_wish_btn.closest(".item-card");
|
||||
const $card_wrapper = $remove_wish_btn.closest(".wishlist-card");
|
||||
$card_wrapper.addClass("wish-removed");
|
||||
};
|
||||
let args = { item_code: item_code };
|
||||
|
@ -93,12 +93,14 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
||||
field_filters['item_group'] = self.name
|
||||
|
||||
engine = ProductQuery()
|
||||
context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
|
||||
context.items, discounts = engine.query(attribute_filters, field_filters, search, start)
|
||||
|
||||
filter_engine = ProductFiltersBuilder(self.name)
|
||||
|
||||
context.field_filters = filter_engine.get_field_filters()
|
||||
context.attribute_filters = filter_engine.get_attribute_filters()
|
||||
if discounts:
|
||||
context.discount_filters = filter_engine.get_discount_filters(discounts)
|
||||
|
||||
context.update({
|
||||
"parents": get_parent_item_groups(self.parent_item_group),
|
||||
|
@ -1,3 +1,4 @@
|
||||
{% from "erpnext/templates/includes/macros.html" import field_filter_section, attribute_filter_section, discount_range_filters %}
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block header %}
|
||||
@ -63,68 +64,16 @@
|
||||
<div class="mb-4 filters-title" > {{ _('Filters') }} </div>
|
||||
<a class="mb-4 clear-filters" href="/{{ doc.route }}">{{ _('Clear All') }}</a>
|
||||
</div>
|
||||
{% for field_filter in field_filters %}
|
||||
{%- set item_field = field_filter[0] %}
|
||||
{%- set values = field_filter[1] %}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ item_field.label }}</div>
|
||||
<!-- field filters -->
|
||||
{{ field_filter_section(field_filters) }}
|
||||
|
||||
{% if values | len > 20 %}
|
||||
<!-- show inline filter if values more than 20 -->
|
||||
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||
{% endif %}
|
||||
<!-- attribute filters -->
|
||||
{{ attribute_filter_section(attribute_filters) }}
|
||||
|
||||
{% if values %}
|
||||
<div class="filter-options">
|
||||
{% for value in values %}
|
||||
<div class="checkbox" data-value="{{ value }}">
|
||||
<label for="{{value}}">
|
||||
<input type="checkbox"
|
||||
class="product-filter field-filter"
|
||||
id="{{value}}"
|
||||
data-filter-name="{{ item_field.fieldname }}"
|
||||
data-filter-value="{{ value }}"
|
||||
>
|
||||
<span class="label-area">{{ value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<i class="text-muted">{{ _('No values') }}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for attribute in attribute_filters %}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ attribute.name}}</div>
|
||||
{% if values | len > 20 %}
|
||||
<!-- show inline filter if values more than 20 -->
|
||||
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||
{% endif %}
|
||||
|
||||
{% if attribute.item_attribute_values %}
|
||||
<div class="filter-options">
|
||||
{% for attr_value in attribute.item_attribute_values %}
|
||||
<div class="checkbox">
|
||||
<label data-value="{{ value }}">
|
||||
<input type="checkbox"
|
||||
class="product-filter attribute-filter"
|
||||
id="{{attr_value.name}}"
|
||||
data-attribute-name="{{ attribute.name }}"
|
||||
data-attribute-value="{{ attr_value.attribute_value }}"
|
||||
{% if attr_value.checked %} checked {% endif %}>
|
||||
<span class="label-area">{{ attr_value.attribute_value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<i class="text-muted">{{ _('No values') }}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- discount filters -->
|
||||
{% if discount_filters %}
|
||||
{{ discount_range_filters(discount_filters) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
@ -315,3 +315,91 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro field_filter_section(filters)-%}
|
||||
{% for field_filter in filters %}
|
||||
{%- set item_field = field_filter[0] %}
|
||||
{%- set values = field_filter[1] %}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ item_field.label }}</div>
|
||||
|
||||
{% if values | len > 20 %}
|
||||
<!-- show inline filter if values more than 20 -->
|
||||
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||
{% endif %}
|
||||
|
||||
{% if values %}
|
||||
<div class="filter-options">
|
||||
{% for value in values %}
|
||||
<div class="checkbox" data-value="{{ value }}">
|
||||
<label for="{{value}}">
|
||||
<input type="checkbox"
|
||||
class="product-filter field-filter"
|
||||
id="{{value}}"
|
||||
data-filter-name="{{ item_field.fieldname }}"
|
||||
data-filter-value="{{ value }}"
|
||||
>
|
||||
<span class="label-area">{{ value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<i class="text-muted">{{ _('No values') }}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro attribute_filter_section(filters)-%}
|
||||
{% for attribute in filters %}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ attribute.name}}</div>
|
||||
{% if values | len > 20 %}
|
||||
<!-- show inline filter if values more than 20 -->
|
||||
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||
{% endif %}
|
||||
|
||||
{% if attribute.item_attribute_values %}
|
||||
<div class="filter-options">
|
||||
{% for attr_value in attribute.item_attribute_values %}
|
||||
<div class="checkbox">
|
||||
<label data-value="{{ value }}">
|
||||
<input type="checkbox"
|
||||
class="product-filter attribute-filter"
|
||||
id="{{attr_value.name}}"
|
||||
data-attribute-name="{{ attribute.name }}"
|
||||
data-attribute-value="{{ attr_value.attribute_value }}"
|
||||
{% if attr_value.checked %} checked {% endif %}>
|
||||
<span class="label-area">{{ attr_value.attribute_value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<i class="text-muted">{{ _('No values') }}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{%- endmacro -%}
|
||||
|
||||
{%- macro discount_range_filters(filters)-%}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ _("Discounts") }}</div>
|
||||
<div class="filter-options">
|
||||
{% for entry in filters %}
|
||||
<div class="checkbox">
|
||||
<label data-value="{{ entry[0] }}">
|
||||
<input type="radio" class="product-filter discount-filter"
|
||||
name="discount" id="{{ entry[0] }}"
|
||||
data-filter-name="discount" data-filter-value="{{ entry[0] }}"
|
||||
>
|
||||
<span class="label-area" for="{{ entry[0] }}">
|
||||
{{ entry[1] }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro -%}
|
||||
|
@ -15,7 +15,7 @@ def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
|
||||
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:
|
||||
stock_qty = frappe.db.sql("""
|
||||
@ -97,6 +97,7 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
|
||||
mrp = price_obj.price_list_rate or 0
|
||||
|
||||
if pricing_rule.pricing_rule_for == "Discount Percentage":
|
||||
price_obj.discount_percent = pricing_rule.discount_percentage
|
||||
price_obj.formatted_discount_percent = str(flt(pricing_rule.discount_percentage, 0)) + "%"
|
||||
price_obj.price_list_rate = flt(price_obj.price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0)))
|
||||
|
||||
@ -139,6 +140,6 @@ def get_non_stock_item_status(item_code, item_warehouse_field):
|
||||
if frappe.db.exists("Product Bundle", item_code):
|
||||
items = frappe.get_doc("Product Bundle", item_code).get_all_children()
|
||||
bundle_warehouse = frappe.db.get_value('Item', item_code, item_warehouse_field)
|
||||
return all([ get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items ])
|
||||
return all([get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items])
|
||||
else:
|
||||
return 1
|
||||
|
@ -1,4 +1,6 @@
|
||||
{% from "erpnext/templates/includes/macros.html" import attribute_filter_section, field_filter_section, discount_range_filters %}
|
||||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block title %}{{ _('Products') }}{% endblock %}
|
||||
{% block header %}
|
||||
<div class="mb-6">{{ _('Products') }}</div>
|
||||
@ -53,71 +55,19 @@
|
||||
<div class="mb-4 filters-title" > {{ _('Filters') }} </div>
|
||||
<a class="mb-4 clear-filters" href="/all-products">{{ _('Clear All') }}</a>
|
||||
</div>
|
||||
<!-- field filters -->
|
||||
{% if field_filters %}
|
||||
{% for field_filter in field_filters %}
|
||||
{%- set item_field = field_filter[0] %}
|
||||
{%- set values = field_filter[1] %}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ item_field.label }}</div>
|
||||
|
||||
{% if values | len > 20 %}
|
||||
<!-- show inline filter if values more than 20 -->
|
||||
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||
{% endif %}
|
||||
|
||||
{% if values %}
|
||||
<div class="filter-options">
|
||||
{% for value in values %}
|
||||
<div class="checkbox" data-value="{{ value }}">
|
||||
<label for="{{value}}">
|
||||
<input type="checkbox"
|
||||
class="product-filter field-filter"
|
||||
id="{{value}}"
|
||||
data-filter-name="{{ item_field.fieldname }}"
|
||||
data-filter-value="{{ value }}"
|
||||
>
|
||||
<span class="label-area">{{ value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<i class="text-muted">{{ _('No values') }}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{{ field_filter_section(field_filters) }}
|
||||
{% endif %}
|
||||
|
||||
<!-- attribute filters -->
|
||||
{% if attribute_filters %}
|
||||
{% for attribute in attribute_filters %}
|
||||
<div class="mb-4 filter-block pb-5">
|
||||
<div class="filter-label mb-3">{{ attribute.name}}</div>
|
||||
{% if values | len > 20 %}
|
||||
<!-- show inline filter if values more than 20 -->
|
||||
<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
|
||||
{% endif %}
|
||||
{{ attribute_filter_section(attribute_filters) }}
|
||||
{% endif %}
|
||||
|
||||
{% if attribute.item_attribute_values %}
|
||||
<div class="filter-options">
|
||||
{% for attr_value in attribute.item_attribute_values %}
|
||||
<div class="checkbox">
|
||||
<label data-value="{{ value }}">
|
||||
<input type="checkbox"
|
||||
class="product-filter attribute-filter"
|
||||
id="{{attr_value.name}}"
|
||||
data-attribute-name="{{ attribute.name }}"
|
||||
data-attribute-value="{{ attr_value.attribute_value }}"
|
||||
{% if attr_value.checked %} checked {% endif %}>
|
||||
<span class="label-area">{{ attr_value.attribute_value }}</span>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<i class="text-muted">{{ _('No values') }}</i>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- discount filters -->
|
||||
{% if discount_filters %}
|
||||
{{ discount_range_filters(discount_filters) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -32,12 +32,16 @@ $(() => {
|
||||
if (this.attribute_filters[attribute_name].length === 0) {
|
||||
delete this.attribute_filters[attribute_name];
|
||||
}
|
||||
} else if ($checkbox.is('.field-filter')) {
|
||||
} else if ($checkbox.is('.field-filter') || $checkbox.is('.discount-filter')) {
|
||||
const {
|
||||
filterName: filter_name,
|
||||
filterValue: filter_value
|
||||
} = $checkbox.data();
|
||||
|
||||
if ($checkbox.is('.discount-filter')) {
|
||||
// clear previous discount filter to accomodate new
|
||||
delete this.field_filters["discount"];
|
||||
}
|
||||
if (is_checked) {
|
||||
this.field_filters[filter_name] = this.field_filters[filter_name] || [];
|
||||
this.field_filters[filter_name].push(filter_value);
|
||||
|
@ -17,7 +17,7 @@ def get_context(context):
|
||||
start = 0
|
||||
|
||||
engine = ProductQuery()
|
||||
context.items = engine.query(attribute_filters, field_filters, search, start)
|
||||
context.items, discounts = engine.query(attribute_filters, field_filters, search, start)
|
||||
|
||||
# Add homepage as parent
|
||||
context.parents = [{"name": frappe._("Home"), "route":"/"}]
|
||||
@ -26,6 +26,8 @@ def get_context(context):
|
||||
|
||||
context.field_filters = filter_engine.get_field_filters()
|
||||
context.attribute_filters = filter_engine.get_attribute_filters()
|
||||
if discounts:
|
||||
context.discount_filters = filter_engine.get_discount_filters(discounts)
|
||||
|
||||
context.e_commerce_settings = engine.settings
|
||||
context.page_length = engine.settings.products_per_page or 20
|
||||
@ -39,12 +41,13 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None):
|
||||
attribute_filters = frappe.parse_json(attribute_filters)
|
||||
|
||||
engine = ProductQuery()
|
||||
items = engine.query(attribute_filters, field_filters, search_term=None, start=0)
|
||||
items, discounts = engine.query(attribute_filters, field_filters, search_term=None, start=0)
|
||||
|
||||
item_html = []
|
||||
for item in items:
|
||||
item_html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
|
||||
'item': item
|
||||
'item': item,
|
||||
'e_commerce_settings': None
|
||||
}))
|
||||
html = ''.join(item_html)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user