[Website] Items list and stock (#11480)
* Add stock availability in items list and a parameter to set the number of products per page * Substract reserved_qty from actual_qty for website stock
This commit is contained in:
parent
82b200e497
commit
2a89a502f0
@ -1,17 +1,23 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
|
"beta": 0,
|
||||||
"creation": "2016-04-22 09:11:55.272398",
|
"creation": "2016-04-22 09:11:55.272398",
|
||||||
"custom": 0,
|
"custom": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
"document_type": "",
|
||||||
|
"editable_grid": 0,
|
||||||
|
"engine": "InnoDB",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"description": "If checked, the Home page will be the default Item Group for the website",
|
"description": "If checked, the Home page will be the default Item Group for the website",
|
||||||
"fieldname": "home_page_is_products",
|
"fieldname": "home_page_is_products",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
@ -19,7 +25,9 @@
|
|||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Home Page is Products",
|
"label": "Home Page is Products",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -28,6 +36,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -35,16 +44,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "products_as_list",
|
"fieldname": "products_as_list",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Show Products as a List",
|
"label": "Show Products as a List",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -53,6 +66,39 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"bold": 0,
|
||||||
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
|
"default": "6",
|
||||||
|
"fieldname": "products_per_page",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"ignore_xss_filter": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
|
"label": "Products per Page",
|
||||||
|
"length": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"options": "",
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"print_hide_if_no_value": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -60,16 +106,17 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 0,
|
"hide_toolbar": 0,
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
|
"image_view": 0,
|
||||||
"in_create": 0,
|
"in_create": 0,
|
||||||
"in_dialog": 0,
|
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2016-04-22 09:11:59.537639",
|
"modified": "2017-11-07 19:34:33.055048",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Portal",
|
"module": "Portal",
|
||||||
"name": "Products Settings",
|
"name": "Products Settings",
|
||||||
@ -100,7 +147,9 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
|
"show_name_in_global_search": 0,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1,
|
||||||
"track_seen": 0
|
"track_seen": 0
|
||||||
}
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// rename this file from _test_[name] to test_[name] to activate
|
||||||
|
// and remove above this line
|
||||||
|
|
||||||
|
QUnit.test("test: Products Settings", function (assert) {
|
||||||
|
let done = assert.async();
|
||||||
|
|
||||||
|
// number of asserts
|
||||||
|
assert.expect(1);
|
||||||
|
|
||||||
|
frappe.run_serially([
|
||||||
|
// insert a new Products Settings
|
||||||
|
() => frappe.tests.make('Products Settings', [
|
||||||
|
// values to be set
|
||||||
|
{key: 'value'}
|
||||||
|
]),
|
||||||
|
() => {
|
||||||
|
assert.equal(cur_frm.doc.key, 'value');
|
||||||
|
},
|
||||||
|
() => done()
|
||||||
|
]);
|
||||||
|
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestProductsSettings(unittest.TestCase):
|
||||||
|
pass
|
||||||
@ -57,7 +57,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
|||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
context.show_search=True
|
context.show_search=True
|
||||||
context.page_length = 6
|
context.page_length = cint(frappe.db.get_single_value('Products Settings', 'products_per_page')) or 6
|
||||||
context.search_link = '/product_search'
|
context.search_link = '/product_search'
|
||||||
|
|
||||||
start = int(frappe.form_dict.start or 0)
|
start = int(frappe.form_dict.start or 0)
|
||||||
@ -81,24 +81,26 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non
|
|||||||
child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(product_group)])
|
child_groups = ", ".join(['"' + i[0] + '"' for i in get_child_groups(product_group)])
|
||||||
|
|
||||||
# base query
|
# base query
|
||||||
query = """select name, item_name, item_code, route, image, website_image, thumbnail, item_group,
|
query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
|
||||||
description, web_long_description as website_description
|
I.description, I.web_long_description as website_description,
|
||||||
from `tabItem`
|
case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock
|
||||||
where show_in_website = 1
|
from `tabItem` I
|
||||||
and disabled=0
|
left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse
|
||||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
|
where I.show_in_website = 1
|
||||||
and (variant_of = '' or variant_of is null)
|
and I.disabled = 0
|
||||||
and (item_group in ({child_groups})
|
and (I.end_of_life is null or I.end_of_life='0000-00-00' or I.end_of_life > %(today)s)
|
||||||
or name in (select parent from `tabWebsite Item Group` where item_group in ({child_groups})))
|
and (I.variant_of = '' or I.variant_of is null)
|
||||||
|
and (I.item_group in ({child_groups})
|
||||||
|
or I.name in (select parent from `tabWebsite Item Group` where item_group in ({child_groups})))
|
||||||
""".format(child_groups=child_groups)
|
""".format(child_groups=child_groups)
|
||||||
# search term condition
|
# search term condition
|
||||||
if search:
|
if search:
|
||||||
query += """ and (web_long_description like %(search)s
|
query += """ and (I.web_long_description like %(search)s
|
||||||
or item_name like %(search)s
|
or I.item_name like %(search)s
|
||||||
or name like %(search)s)"""
|
or I.name like %(search)s)"""
|
||||||
search = "%" + cstr(search) + "%"
|
search = "%" + cstr(search) + "%"
|
||||||
|
|
||||||
query += """order by weightage desc, item_name, modified desc limit %s, %s""" % (start, limit)
|
query += """order by I.weightage desc, in_stock desc, I.item_name limit %s, %s""" % (start, limit)
|
||||||
|
|
||||||
data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1)
|
data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1)
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,13 @@
|
|||||||
<a class="product-link" href="{{ route|abs_url }}">
|
<a class="product-link" href="{{ route|abs_url }}">
|
||||||
<div class="col-sm-4 col-xs-4 product-image-wrapper">
|
<div class="col-sm-4 col-xs-4 product-image-wrapper">
|
||||||
<div class="product-image-img">
|
<div class="product-image-img">
|
||||||
{{ product_image_square(thumbnail or website_image) }}
|
{{ product_image_square(thumbnail or website_image) }}
|
||||||
<div class="product-text" itemprop="name">{{ item_name }}</div>
|
<div class="product-text" itemprop="name">{{ item_name }}</div>
|
||||||
|
{% if in_stock %}
|
||||||
|
<div style='color: green'> <i class='fa fa-check'></i> {{ _("In stock") }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -17,23 +17,25 @@ def get_product_list(search=None, start=0, limit=12):
|
|||||||
# limit = 12 because we show 12 items in the grid view
|
# limit = 12 because we show 12 items in the grid view
|
||||||
|
|
||||||
# base query
|
# base query
|
||||||
query = """select name, item_name, item_code, route, website_image, thumbnail, item_group,
|
query = """select I.name, I.item_name, I.item_code, I.route, I.website_image, I.thumbnail, I.item_group,
|
||||||
description, web_long_description as website_description
|
I.description, I.web_long_description as website_description,
|
||||||
from `tabItem`
|
case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock
|
||||||
where (show_in_website = 1 or show_variant_in_website = 1)
|
from `tabItem` I
|
||||||
and disabled=0
|
left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse
|
||||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)"""
|
where (I.show_in_website = 1 or I.show_variant_in_website = 1)
|
||||||
|
and I.disabled = 0
|
||||||
|
and (I.end_of_life is null or I.end_of_life='0000-00-00' or I.end_of_life > %(today)s)"""
|
||||||
|
|
||||||
# search term condition
|
# search term condition
|
||||||
if search:
|
if search:
|
||||||
query += """ and (web_long_description like %(search)s
|
query += """ and (I.web_long_description like %(search)s
|
||||||
or description like %(search)s
|
or I.description like %(search)s
|
||||||
or item_name like %(search)s
|
or I.item_name like %(search)s
|
||||||
or name like %(search)s)"""
|
or I.name like %(search)s)"""
|
||||||
search = "%" + cstr(search) + "%"
|
search = "%" + cstr(search) + "%"
|
||||||
|
|
||||||
# order by
|
# order by
|
||||||
query += """ order by weightage desc, idx desc, modified desc limit %s, %s""" % (cint(start), cint(limit))
|
query += """ order by I.weightage desc, in_stock desc, I.item_name limit %s, %s""" % (cint(start), cint(limit))
|
||||||
|
|
||||||
data = frappe.db.sql(query, {
|
data = frappe.db.sql(query, {
|
||||||
"search": search,
|
"search": search,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ def get_qty_in_stock(item_code, item_warehouse_field):
|
|||||||
warehouse = frappe.db.get_value("Item", template_item_code, item_warehouse_field)
|
warehouse = frappe.db.get_value("Item", template_item_code, item_warehouse_field)
|
||||||
|
|
||||||
if warehouse:
|
if warehouse:
|
||||||
stock_qty = frappe.db.sql("""select actual_qty from tabBin where
|
stock_qty = frappe.db.sql("""select GREATEST(actual_qty - reserved_qty, 0) from tabBin where
|
||||||
item_code=%s and warehouse=%s""", (item_code, warehouse))
|
item_code=%s and warehouse=%s""", (item_code, warehouse))
|
||||||
if stock_qty:
|
if stock_qty:
|
||||||
in_stock = stock_qty[0][0] > 0 and 1 or 0
|
in_stock = stock_qty[0][0] > 0 and 1 or 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user