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:
marination 2021-09-02 14:07:59 +05:30
parent 45f64bd930
commit bbcbcf7a20
11 changed files with 129 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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