feat: Discount Filters

- Discount filters in filters section
- Code cleanup
This commit is contained in:
marination 2021-04-20 21:54:52 +05:30
parent 817f695c57
commit 1d94914102
11 changed files with 186 additions and 163 deletions

View File

@ -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."))

View File

@ -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

View File

@ -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

View File

@ -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 };

View File

@ -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),

View File

@ -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>

View File

@ -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 -%}

View File

@ -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

View File

@ -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>

View File

@ -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);

View File

@ -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)