feat: (minor) Backorder indicator and fixed inconsistencies
- Checkbox in website item to indicate if item is on backorder - Indicator on listing on full page if availbale on backorder. - fix: Allow provision to add any valid field from Website Item in Search Index - fix: Settings filter fields are as per Item, make as per Website Item - "Add to quote/ Go to Quote" if cart checkout is disabled
This commit is contained in:
parent
45f64bd930
commit
bbcbcf7a20
@ -25,9 +25,9 @@ frappe.ui.form.on("E Commerce Settings", {
|
||||
}
|
||||
|
||||
frappe.model.with_doctype("Item", () => {
|
||||
const item_meta = frappe.get_meta('Item');
|
||||
const web_item_meta = frappe.get_meta('Website Item');
|
||||
|
||||
const valid_fields = item_meta.fields.filter(
|
||||
const valid_fields = web_item_meta.fields.filter(
|
||||
df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden
|
||||
).map(df => ({ label: df.label, value: df.fieldname }));
|
||||
|
||||
|
@ -91,7 +91,7 @@
|
||||
"depends_on": "enable_field_filters",
|
||||
"fieldname": "filter_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Item Fields",
|
||||
"label": "Website Item Fields",
|
||||
"options": "Website Filter Field"
|
||||
},
|
||||
{
|
||||
@ -370,7 +370,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-31 12:23:06.187619",
|
||||
"modified": "2021-09-02 14:02:44.785824",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "E Commerce Settings",
|
||||
|
@ -29,11 +29,14 @@
|
||||
"column_break_13",
|
||||
"slideshow",
|
||||
"thumbnail",
|
||||
"stock_information_section",
|
||||
"website_warehouse",
|
||||
"column_break_24",
|
||||
"on_backorder",
|
||||
"section_break_17",
|
||||
"short_description",
|
||||
"web_long_description",
|
||||
"column_break_27",
|
||||
"website_warehouse",
|
||||
"website_specifications",
|
||||
"copy_from_item_group",
|
||||
"display_additional_information_section",
|
||||
@ -326,13 +329,29 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Recommended/Similar Items",
|
||||
"options": "Recommended Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_information_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stock Information"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Indicate that Item is available on backorder and not usually pre-stocked",
|
||||
"fieldname": "on_backorder",
|
||||
"fieldtype": "Check",
|
||||
"label": "On Backorder"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-12 21:00:04.065803",
|
||||
"modified": "2021-09-02 13:08:41.942726",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "Website Item",
|
||||
|
@ -26,9 +26,11 @@ class ProductQuery:
|
||||
|
||||
self.or_filters = []
|
||||
self.filters = [["published", "=", 1]]
|
||||
self.fields = ['web_item_name', 'name', 'item_name', 'item_code', 'website_image',
|
||||
'variant_of', 'has_variants', 'item_group', 'image', 'web_long_description',
|
||||
'short_description', 'route', 'website_warehouse', 'ranking']
|
||||
self.fields = [
|
||||
"web_item_name", "name", "item_name", "item_code", "website_image",
|
||||
"variant_of", "has_variants", "item_group", "image", "web_long_description",
|
||||
"short_description", "route", "website_warehouse", "ranking", "on_backorder"
|
||||
]
|
||||
|
||||
def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
|
||||
"""
|
||||
@ -239,6 +241,9 @@ class ProductQuery:
|
||||
warehouse = item.get("website_warehouse")
|
||||
is_stock_item = frappe.get_cached_value("Item", item.item_code, "is_stock_item")
|
||||
|
||||
if item.get("on_backorder"):
|
||||
return
|
||||
|
||||
if not is_stock_item:
|
||||
if warehouse:
|
||||
# product bundle case
|
||||
|
@ -142,9 +142,22 @@ erpnext.ProductGrid = class {
|
||||
}
|
||||
|
||||
get_stock_availability(item, settings) {
|
||||
if (settings.show_stock_availability && !item.has_variants && !item.in_stock) {
|
||||
return `<span class="out-of-stock mb-2 mt-1">${ __("Out of stock") }</span>`;
|
||||
if (settings.show_stock_availability && !item.has_variants) {
|
||||
if (item.on_backorder) {
|
||||
return `
|
||||
<span class="out-of-stock mb-2 mt-1" style="color: var(--primary-color)">
|
||||
${ __("Available on backorder") }
|
||||
</span>
|
||||
`;
|
||||
} else if (!item.in_stock) {
|
||||
return `
|
||||
<span class="out-of-stock mb-2 mt-1">
|
||||
${ __("Out of stock") }
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
return ``;
|
||||
}
|
||||
|
||||
@ -168,7 +181,7 @@ erpnext.ProductGrid = class {
|
||||
<use href="#icon-assets"></use>
|
||||
</svg>
|
||||
</span>
|
||||
${ __('Add to Cart') }
|
||||
${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') }
|
||||
</div>
|
||||
|
||||
<a href="/cart">
|
||||
@ -177,7 +190,7 @@ erpnext.ProductGrid = class {
|
||||
w-100 mt-4 go-to-cart-grid
|
||||
${ item.in_cart ? '' : 'hidden' }"
|
||||
data-item-code="${ item.item_code }">
|
||||
${ __('Go to Cart') }
|
||||
${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') }
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
|
@ -125,11 +125,20 @@ erpnext.ProductList = class {
|
||||
}
|
||||
|
||||
get_stock_availability(item, settings) {
|
||||
if (settings.show_stock_availability && !item.has_variants && !item.in_stock) {
|
||||
return `
|
||||
<br>
|
||||
<span class="out-of-stock mt-2">${ __("Out of stock") }</span>
|
||||
`;
|
||||
if (settings.show_stock_availability && !item.has_variants) {
|
||||
if (item.on_backorder) {
|
||||
return `
|
||||
<br>
|
||||
<span class="out-of-stock mt-2" style="color: var(--primary-color)">
|
||||
${ __("Available on backorder") }
|
||||
</span>
|
||||
`;
|
||||
} else if (!item.in_stock) {
|
||||
return `
|
||||
<br>
|
||||
<span class="out-of-stock mt-2">${ __("Out of stock") }</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
return ``;
|
||||
}
|
||||
@ -169,7 +178,7 @@ erpnext.ProductList = class {
|
||||
<use href="#icon-assets"></use>
|
||||
</svg>
|
||||
</span>
|
||||
${ __('Add to Cart') }
|
||||
${ settings.enable_checkout ? __('Add to Cart') : __('Add to Quote') }
|
||||
</div>
|
||||
|
||||
<div class="cart-indicator list-indicator ${item.in_cart ? '' : 'hidden'}">
|
||||
@ -183,7 +192,7 @@ erpnext.ProductList = class {
|
||||
${ item.in_cart ? '' : 'hidden' }"
|
||||
data-item-code="${ item.item_code }"
|
||||
style="padding: 0.25rem 1rem; min-width: 135px;">
|
||||
${ __('Go to Cart') }
|
||||
${ settings.enable_checkout ? __('Go to Cart') : __('Go to Quote') }
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
|
@ -10,15 +10,17 @@ WEBSITE_ITEM_KEY_PREFIX = 'website_item:'
|
||||
WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict'
|
||||
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict'
|
||||
|
||||
ALLOWED_INDEXABLE_FIELDS_SET = {
|
||||
'web_item_name',
|
||||
'item_code',
|
||||
'item_name',
|
||||
'item_group',
|
||||
'brand',
|
||||
'description',
|
||||
'web_long_description'
|
||||
}
|
||||
def get_indexable_web_fields():
|
||||
"Return valid fields from Website Item that can be searched for."
|
||||
web_item_meta = frappe.get_meta("Website Item", cached=True)
|
||||
valid_fields = filter(
|
||||
lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"),
|
||||
web_item_meta.fields)
|
||||
|
||||
return [df.fieldname for df in valid_fields]
|
||||
|
||||
ALLOWED_INDEXABLE_FIELDS_SET = get_indexable_web_fields()
|
||||
|
||||
|
||||
def is_search_module_loaded():
|
||||
cache = frappe.cache()
|
||||
@ -30,8 +32,8 @@ def is_search_module_loaded():
|
||||
|
||||
return "search" in parsed_output
|
||||
|
||||
# Decorator for checking wether Redisearch is there or not
|
||||
def if_redisearch_loaded(function):
|
||||
"Decorator to check if Redisearch is loaded."
|
||||
def wrapper(*args, **kwargs):
|
||||
if is_search_module_loaded():
|
||||
func = function(*args, **kwargs)
|
||||
@ -45,7 +47,8 @@ def make_key(key):
|
||||
|
||||
@if_redisearch_loaded
|
||||
def create_website_items_index():
|
||||
'''Creates Index Definition'''
|
||||
"Creates Index Definition."
|
||||
|
||||
# CREATE index
|
||||
client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
|
||||
|
||||
@ -197,7 +200,7 @@ def get_fields_indexed():
|
||||
)
|
||||
fields_to_index = fields_to_index.split(',') if fields_to_index else []
|
||||
|
||||
mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail']
|
||||
mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail', 'ranking']
|
||||
fields_to_index = fields_to_index + mandatory_fields
|
||||
|
||||
return fields_to_index
|
||||
|
@ -38,8 +38,13 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
)
|
||||
|
||||
stock_status = None
|
||||
|
||||
if cart_settings.show_stock_availability:
|
||||
stock_status = get_web_item_qty_in_stock(item_code, "website_warehouse")
|
||||
on_backorder = frappe.get_cached_value("Website Item", {"item_code": item_code}, "on_backorder")
|
||||
if on_backorder:
|
||||
stock_status = frappe._dict({"on_backorder": True})
|
||||
else:
|
||||
stock_status = get_web_item_qty_in_stock(item_code, "website_warehouse")
|
||||
|
||||
product_info = {
|
||||
"price": price,
|
||||
@ -49,9 +54,12 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
|
||||
}
|
||||
|
||||
if stock_status:
|
||||
product_info["stock_qty"] = stock_status.stock_qty
|
||||
product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse")
|
||||
product_info["show_stock_qty"] = show_quantity_in_website()
|
||||
if stock_status.on_backorder:
|
||||
product_info["on_backorder"] = True
|
||||
else:
|
||||
product_info["stock_qty"] = stock_status.stock_qty
|
||||
product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse")
|
||||
product_info["show_stock_qty"] = show_quantity_in_website()
|
||||
|
||||
if product_info["price"]:
|
||||
if frappe.session.user != "Guest":
|
||||
|
@ -1354,8 +1354,12 @@ body.product-page {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.has-stock {
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.out-of-stock {
|
||||
font-weight: 500;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #F47A7A;
|
||||
|
@ -34,17 +34,21 @@
|
||||
|
||||
{% if cart_settings.show_stock_availability %}
|
||||
<div class="mt-2">
|
||||
{% if product_info.in_stock == 0 %}
|
||||
<span class="no-stock out-of-stock">
|
||||
{{ _('Out of stock') }}
|
||||
</span>
|
||||
{% if product_info.get("on_backorder") %}
|
||||
<span class="no-stock out-of-stock" style="color: var(--primary-color);">
|
||||
{{ _('Available on backorder') }}
|
||||
</span>
|
||||
{% elif product_info.in_stock == 0 %}
|
||||
<span class="no-stock out-of-stock">
|
||||
{{ _('Out of stock') }}
|
||||
</span>
|
||||
{% elif product_info.in_stock == 1 %}
|
||||
<span class="in-green has-stock">
|
||||
{{ _('In stock') }}
|
||||
{% if product_info.show_stock_qty and product_info.stock_qty %}
|
||||
({{ product_info.stock_qty[0][0] }})
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="in-green has-stock">
|
||||
{{ _('In stock') }}
|
||||
{% if product_info.show_stock_qty and product_info.stock_qty %}
|
||||
({{ product_info.stock_qty[0][0] }})
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -88,17 +92,21 @@
|
||||
<div class="mb-4 d-flex">
|
||||
<!-- Add to Cart -->
|
||||
{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
|
||||
<a href="/cart" class="btn btn-light btn-view-in-cart hidden mr-2 font-md" role="button">
|
||||
{{ _("View in Cart") }}
|
||||
</a>
|
||||
<button data-item-code="{{item_code}}" class="btn btn-primary btn-add-to-cart mr-2 w-30-40">
|
||||
<span class="mr-2">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-assets"></use>
|
||||
</svg>
|
||||
</span>
|
||||
{{ _("Add to Cart") }}
|
||||
</button>
|
||||
<a href="/cart" class="btn btn-light btn-view-in-cart hidden mr-2 font-md"
|
||||
role="button">
|
||||
{{ _("View in Cart") if cart_settings.enable_checkout else _("View in Quote") }}
|
||||
</a>
|
||||
<button
|
||||
data-item-code="{{item_code}}"
|
||||
class="btn btn-primary btn-add-to-cart mr-2 w-30-40"
|
||||
>
|
||||
<span class="mr-2">
|
||||
<svg class="icon icon-md">
|
||||
<use href="#icon-assets"></use>
|
||||
</svg>
|
||||
</span>
|
||||
{{ _("Add to Cart") if cart_settings.enable_checkout else _("Add to Quote") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<!-- Contact Us -->
|
||||
|
@ -52,7 +52,7 @@ def get_product_data(search=None, start=0, limit=12):
|
||||
search = "%" + cstr(search) + "%"
|
||||
|
||||
# order by
|
||||
query += """ ORDER BY ranking asc, modified desc limit %s, %s""" % (cint(start), cint(limit))
|
||||
query += """ ORDER BY ranking desc, modified desc limit %s, %s""" % (cint(start), cint(limit))
|
||||
|
||||
return frappe.db.sql(query, {
|
||||
"search": search
|
||||
@ -102,6 +102,7 @@ def product_search(query, limit=10, fuzzy_search=True):
|
||||
|
||||
results = client.search(q)
|
||||
search_results['results'] = list(map(convert_to_dict, results.docs))
|
||||
search_results['results'] = sorted(search_results['results'], key=lambda k: frappe.utils.cint(k['ranking']), reverse=True)
|
||||
|
||||
return search_results
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user