From 2a89a502f07ef0b5895f1991c3e9cadbd29240db Mon Sep 17 00:00:00 2001 From: Britlog Date: Fri, 17 Nov 2017 07:11:07 +0100 Subject: [PATCH] [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 --- .../products_settings/products_settings.json | 53 ++++++++++++++++++- .../test_products_settings.js | 23 ++++++++ .../test_products_settings.py | 10 ++++ .../setup/doctype/item_group/item_group.py | 30 ++++++----- .../templates/includes/products_as_grid.html | 9 +++- erpnext/templates/pages/product_search.py | 24 +++++---- erpnext/utilities/product.py | 2 +- 7 files changed, 121 insertions(+), 30 deletions(-) create mode 100644 erpnext/portal/doctype/products_settings/test_products_settings.js create mode 100644 erpnext/portal/doctype/products_settings/test_products_settings.py diff --git a/erpnext/portal/doctype/products_settings/products_settings.json b/erpnext/portal/doctype/products_settings/products_settings.json index 90de96c844..2d025cfe65 100644 --- a/erpnext/portal/doctype/products_settings/products_settings.json +++ b/erpnext/portal/doctype/products_settings/products_settings.json @@ -1,17 +1,23 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2016-04-22 09:11:55.272398", "custom": 0, "docstatus": 0, "doctype": "DocType", "document_type": "", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "If checked, the Home page will be the default Item Group for the website", "fieldname": "home_page_is_products", "fieldtype": "Check", @@ -19,7 +25,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Home Page is Products", "length": 0, "no_copy": 0, @@ -28,6 +36,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -35,16 +44,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "products_as_list", "fieldtype": "Check", "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": "Show Products as a List", "length": 0, "no_copy": 0, @@ -53,6 +66,39 @@ "print_hide": 0, "print_hide_if_no_value": 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, "reqd": 0, "search_index": 0, @@ -60,16 +106,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, + "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-04-22 09:11:59.537639", + "modified": "2017-11-07 19:34:33.055048", "modified_by": "Administrator", "module": "Portal", "name": "Products Settings", @@ -100,7 +147,9 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.js b/erpnext/portal/doctype/products_settings/test_products_settings.js new file mode 100644 index 0000000000..b7049b37e1 --- /dev/null +++ b/erpnext/portal/doctype/products_settings/test_products_settings.js @@ -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() + ]); + +}); diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.py b/erpnext/portal/doctype/products_settings/test_products_settings.py new file mode 100644 index 0000000000..d04a009882 --- /dev/null +++ b/erpnext/portal/doctype/products_settings/test_products_settings.py @@ -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 diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index a4c377e8cc..364f21abd5 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -57,7 +57,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): def get_context(self, context): 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' 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)]) # base query - query = """select name, item_name, item_code, route, image, website_image, thumbnail, item_group, - description, web_long_description as website_description - from `tabItem` - where show_in_website = 1 - and disabled=0 - and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s) - and (variant_of = '' or variant_of is null) - and (item_group in ({child_groups}) - or name in (select parent from `tabWebsite Item Group` where item_group in ({child_groups}))) + query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group, + I.description, I.web_long_description as website_description, + case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock + from `tabItem` I + left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse + where I.show_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) + 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) # search term condition if search: - query += """ and (web_long_description like %(search)s - or item_name like %(search)s - or name like %(search)s)""" + query += """ and (I.web_long_description like %(search)s + or I.item_name like %(search)s + or I.name like %(search)s)""" 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) diff --git a/erpnext/templates/includes/products_as_grid.html b/erpnext/templates/includes/products_as_grid.html index 7a15c1ffe3..b2437d3393 100644 --- a/erpnext/templates/includes/products_as_grid.html +++ b/erpnext/templates/includes/products_as_grid.html @@ -3,8 +3,13 @@
- {{ product_image_square(thumbnail or website_image) }} -
{{ item_name }}
+ {{ product_image_square(thumbnail or website_image) }} +
{{ item_name }}
+ {% if in_stock %} +
{{ _("In stock") }}
+ {% else %} +
{{ _("Not in stock") }}
+ {% endif %}
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py index 49f321dd9a..a872f19510 100644 --- a/erpnext/templates/pages/product_search.py +++ b/erpnext/templates/pages/product_search.py @@ -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 # base query - query = """select name, item_name, item_code, route, website_image, thumbnail, item_group, - description, web_long_description as website_description - from `tabItem` - where (show_in_website = 1 or show_variant_in_website = 1) - and disabled=0 - and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)""" + query = """select I.name, I.item_name, I.item_code, I.route, I.website_image, I.thumbnail, I.item_group, + I.description, I.web_long_description as website_description, + case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock + from `tabItem` I + left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse + 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 if search: - query += """ and (web_long_description like %(search)s - or description like %(search)s - or item_name like %(search)s - or name like %(search)s)""" + query += """ and (I.web_long_description like %(search)s + or I.description like %(search)s + or I.item_name like %(search)s + or I.name like %(search)s)""" search = "%" + cstr(search) + "%" # 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, { "search": search, diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 1ad8b6e5ee..10366be6ec 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -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) 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)) if stock_qty: in_stock = stock_qty[0][0] > 0 and 1 or 0