|
|
@ -1,10 +1,10 @@ |
|
|
|
# -*- coding: utf-8 -*- |
|
|
|
################################################################################### |
|
|
|
################################################################################ |
|
|
|
# |
|
|
|
# Cybrosys Technologies Pvt. Ltd. |
|
|
|
# |
|
|
|
# Copyright (C) 2022-TODAY Cybrosys Technologies (<https://www.cybrosys.com>). |
|
|
|
# Author: Neeraj Krishnan V M (<https://www.cybrosys.com>) |
|
|
|
# Copyright (C) 2022-TODAY Cybrosys Technologies (<https://www.cybrosys.com>) |
|
|
|
# Author: Neeraj Krishnan V M , Saneen K(<https://www.cybrosys.com>) |
|
|
|
# |
|
|
|
# This program is free software: you can modify |
|
|
|
# it under the terms of the GNU Affero General Public License (AGPL) as |
|
|
@ -19,18 +19,16 @@ |
|
|
|
# You should have received a copy of the GNU Affero General Public License |
|
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
|
|
# |
|
|
|
################################################################################### |
|
|
|
|
|
|
|
############################################################################### |
|
|
|
from werkzeug.exceptions import NotFound |
|
|
|
from odoo.addons.http_routing.models.ir_http import slug |
|
|
|
from odoo.addons.website.controllers.main import QueryURL |
|
|
|
from odoo.addons.website_sale.controllers.main import TableCompute |
|
|
|
from odoo import http |
|
|
|
from odoo import fields, http, tools |
|
|
|
from ast import literal_eval |
|
|
|
from odoo.http import request |
|
|
|
from odoo.addons.website.models.ir_http import sitemap_qs2dom |
|
|
|
from odoo.addons.website_sale.controllers.main import WebsiteSale |
|
|
|
|
|
|
|
from odoo.osv import expression |
|
|
|
from datetime import datetime |
|
|
|
from odoo.tools import lazy |
|
|
@ -51,21 +49,22 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
|
|
|
|
def reset_domain(self, search, categories, available_products, |
|
|
|
attrib_values, search_in_description=True): |
|
|
|
''' |
|
|
|
""" |
|
|
|
Function returns a domain consist of filter conditions |
|
|
|
:param search: search variable |
|
|
|
:param categories: list of category available |
|
|
|
:param available_products: list of available product ids from product.template |
|
|
|
:param available_products: list of available product ids from |
|
|
|
product.template |
|
|
|
:param attrib_values:product attiribute values |
|
|
|
:param search_in_description: boolean filed showing there is search variable exist or not''' |
|
|
|
:param search_in_description: boolean filed showing there is search |
|
|
|
variable exist or not""" |
|
|
|
|
|
|
|
domains = [request.website.sale_product_domain()] |
|
|
|
if search: |
|
|
|
for srch in search.split(" "): |
|
|
|
subdomains = [ |
|
|
|
[('name', 'ilike', srch)], |
|
|
|
[('product_variant_ids.default_code', 'ilike', srch)] |
|
|
|
] |
|
|
|
[('product_variant_ids.default_code', 'ilike', srch)]] |
|
|
|
if search_in_description: |
|
|
|
subdomains.append([('description', 'ilike', srch)]) |
|
|
|
subdomains.append([('description_sale', 'ilike', srch)]) |
|
|
@ -74,34 +73,16 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
domains.append([('id', 'in', available_products.ids)]) |
|
|
|
if categories: |
|
|
|
domains.append([('public_categ_ids', 'child_of', categories.ids)]) |
|
|
|
if attrib_values: |
|
|
|
print("hello world....") |
|
|
|
# attrib = None |
|
|
|
# ids = [] |
|
|
|
# print("hello...", ids) |
|
|
|
# for value in attrib_values: |
|
|
|
# if not attrib: |
|
|
|
# attrib = value[0] |
|
|
|
# ids.append(value[1]) |
|
|
|
# elif value[0] == attrib: |
|
|
|
# ids.append(value[1]) |
|
|
|
# else: |
|
|
|
# domains.append([('attribute_line_ids.value_ids', 'in', ids)]) |
|
|
|
# attrib = value[0] |
|
|
|
# ids = [value[1]] |
|
|
|
# if attrib: |
|
|
|
# domains.append([('attribute_line_ids.value_ids', 'in', ids)]) |
|
|
|
|
|
|
|
return expression.AND(domains) |
|
|
|
|
|
|
|
@http.route([ |
|
|
|
'''/shop''', |
|
|
|
'''/shop/page/<int:page>''', |
|
|
|
'''/shop/category/<model("product.public.category"):category>''', |
|
|
|
'''/shop/category/<model("product.public.category"):category>/page/<int:page>''' |
|
|
|
'/shop', |
|
|
|
'/shop/page/<int:page>', |
|
|
|
'/shop/category/<model("product.public.category"):category>', |
|
|
|
'/shop/category/<model("product.public.category"):category>/page/<int:page>', |
|
|
|
], type='http', auth="public", website=True, sitemap=sitemap_shop) |
|
|
|
def shop(self, page=0, category=None, search='', ppg=False, **post): |
|
|
|
''''Override shop function.''' |
|
|
|
def shop(self, page=0, category=None, search='', min_price=0.0, |
|
|
|
max_price=0.0, ppg=False, **post): |
|
|
|
available_categ = available_products = '' |
|
|
|
user = request.env['res.users'].sudo().search( |
|
|
|
[('id', '=', request.env.user.id)]) |
|
|
@ -125,12 +106,10 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
[('id', '=', user.partner_id.id)]) |
|
|
|
mode = partner.filter_mode |
|
|
|
if mode == 'product_only': |
|
|
|
available_products = self.availavle_products() |
|
|
|
available_products = self.available_products() |
|
|
|
available_categ = partner.website_available_cat_ids |
|
|
|
|
|
|
|
Category_avail = [] |
|
|
|
Category = request.env['product.public.category'] |
|
|
|
|
|
|
|
for ids in available_categ: |
|
|
|
if not ids.parent_id.id in available_categ.ids: |
|
|
|
Category_avail.append(ids.id) |
|
|
@ -138,15 +117,21 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
[('id', 'in', Category_avail)]) |
|
|
|
if mode == 'product_only': |
|
|
|
categ = Category.search([('parent_id', '=', False), ( |
|
|
|
'product_tmpl_ids', 'in', available_products.ids)]) |
|
|
|
|
|
|
|
'product_tmpl_ids', 'in', available_products.ids)]) |
|
|
|
# supering shop*** |
|
|
|
|
|
|
|
if not available_categ and not available_products: |
|
|
|
return super(ProductVisibilityCon, self).shop(page, category, |
|
|
|
search, ppg, **post) |
|
|
|
add_qty = int(post.get('add_qty', 1)) |
|
|
|
|
|
|
|
try: |
|
|
|
min_price = float(min_price) |
|
|
|
except ValueError: |
|
|
|
min_price = 0 |
|
|
|
try: |
|
|
|
max_price = float(max_price) |
|
|
|
except ValueError: |
|
|
|
max_price = 0 |
|
|
|
Category = request.env['product.public.category'] |
|
|
|
if category: |
|
|
|
category = Category.search([('id', '=', int(category))], limit=1) |
|
|
|
if not category or not category.can_access_from_current_website(): |
|
|
@ -174,49 +159,99 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
domain_pro = self.reset_domain(search, category, available_products, |
|
|
|
attrib_values) |
|
|
|
Product = Product.search(domain_pro) |
|
|
|
keep = QueryURL('/shop', category=category and int(category), |
|
|
|
search=search, attrib=attrib_list, |
|
|
|
order=post.get('order')) |
|
|
|
|
|
|
|
# pricelist_context, pricelist = self._get_pricelist_context() |
|
|
|
# request.context = dict(request.context, pricelist=pricelist.id, partner=request.env.user.partner_id) |
|
|
|
|
|
|
|
keep = QueryURL('/shop', **self._shop_get_query_url_kwargs( |
|
|
|
category and int(category), search, min_price, max_price, **post)) |
|
|
|
now = datetime.timestamp(datetime.now()) |
|
|
|
pricelist = request.env['product.pricelist'].browse( |
|
|
|
request.session.get('website_sale_current_pl')) |
|
|
|
if not pricelist or request.session.get('website_sale_pricelist_time', |
|
|
|
0) < now - 60 * 60: # test: 1 hour in session |
|
|
|
0) < now - 60 * 60: |
|
|
|
# test: 1 hour in session |
|
|
|
pricelist = website.get_current_pricelist() |
|
|
|
request.session['website_sale_pricelist_time'] = now |
|
|
|
request.session['website_sale_current_pl'] = pricelist.id |
|
|
|
|
|
|
|
request.update_context(pricelist=pricelist.id, |
|
|
|
partner=request.env.user.partner_id) |
|
|
|
|
|
|
|
filter_by_price_enabled = website.is_view_active( |
|
|
|
'website_sale.filter_products_price') |
|
|
|
if filter_by_price_enabled: |
|
|
|
company_currency = website.company_id.currency_id |
|
|
|
conversion_rate = request.env['res.currency']._get_conversion_rate( |
|
|
|
company_currency, pricelist.currency_id, |
|
|
|
request.website.company_id, fields.Date.today()) |
|
|
|
else: |
|
|
|
conversion_rate = 1 |
|
|
|
url = "/shop" |
|
|
|
if search: |
|
|
|
post["search"] = search |
|
|
|
if attrib_list: |
|
|
|
post['attrib'] = attrib_list |
|
|
|
options = self._get_search_options( |
|
|
|
category=category, |
|
|
|
attrib_values=attrib_values, |
|
|
|
pricelist=pricelist, |
|
|
|
min_price=min_price, |
|
|
|
max_price=max_price, |
|
|
|
conversion_rate=conversion_rate, |
|
|
|
**post |
|
|
|
) |
|
|
|
fuzzy_search_term, product_count, search_product = self._shop_lookup_products( |
|
|
|
attrib_set, options, post, search, website) |
|
|
|
filter_by_price_enabled = website.is_view_active( |
|
|
|
'website_sale.filter_products_price') |
|
|
|
if filter_by_price_enabled: |
|
|
|
# TODO Find an alternative way to obtain the domain through the search metadata. |
|
|
|
Product = request.env['product.template'].with_context( |
|
|
|
bin_size=True) |
|
|
|
domain = self._get_search_domain(search, category, attrib_values) |
|
|
|
# This is ~4 times more efficient than a search for the cheapest and most expensive products |
|
|
|
from_clause, where_clause, where_params = Product._where_calc( |
|
|
|
domain).get_sql() |
|
|
|
query = f""" |
|
|
|
SELECT COALESCE(MIN(list_price), 0) * {conversion_rate}, COALESCE(MAX(list_price), 0) * {conversion_rate} |
|
|
|
FROM {from_clause} |
|
|
|
WHERE {where_clause} |
|
|
|
""" |
|
|
|
request.env.cr.execute(query, where_params) |
|
|
|
available_min_price, available_max_price = request.env.cr.fetchone() |
|
|
|
if min_price or max_price: |
|
|
|
# The if/else condition in the min_price / max_price value assignment |
|
|
|
# tackles the case where we switch to a list of products with different |
|
|
|
# available min / max prices than the ones set in the previous page. |
|
|
|
# In order to have logical results and not yield empty product lists, the |
|
|
|
# price filter is set to their respective available prices when the specified |
|
|
|
# min exceeds the max, and / or the specified max is lower than the available min. |
|
|
|
if min_price: |
|
|
|
min_price = min_price if min_price <= available_max_price else available_min_price |
|
|
|
post['min_price'] = min_price |
|
|
|
if max_price: |
|
|
|
max_price = max_price if max_price >= available_min_price else available_max_price |
|
|
|
post['max_price'] = max_price |
|
|
|
website_domain = website.website_domain() |
|
|
|
categs_domain = [('parent_id', '=', False)] + website_domain |
|
|
|
if not category: |
|
|
|
domain = self.reset_domain(search, available_categ, |
|
|
|
available_products, attrib_values) |
|
|
|
search_product = Product.search(domain) |
|
|
|
website_domain = request.website.website_domain() |
|
|
|
categs_domain = [('parent_id', '=', False), ( |
|
|
|
'product_tmpl_ids', 'in', search_product.ids)] + website_domain |
|
|
|
'product_tmpl_ids', 'in', search_product.ids)] + website_domain |
|
|
|
if search: |
|
|
|
search_categories = Category.search( |
|
|
|
[('product_tmpl_ids', 'in', |
|
|
|
search_product.ids)] + website_domain).parents_and_self |
|
|
|
[( |
|
|
|
'product_tmpl_ids', 'in', |
|
|
|
search_product.ids)] + website_domain |
|
|
|
).parents_and_self |
|
|
|
categs_domain.append(('id', 'in', search_categories.ids)) |
|
|
|
else: |
|
|
|
search_categories = available_categ |
|
|
|
categs = lazy(lambda: Category.search(categs_domain)) |
|
|
|
if category: |
|
|
|
url = "/shop/category/%s" % slug(category) |
|
|
|
product_count = len(search_product) |
|
|
|
pager = request.website.pager(url=url, total=product_count, page=page, |
|
|
|
step=ppg, scope=7, url_args=post) |
|
|
|
pager = website.pager(url=url, total=product_count, page=page, step=ppg, |
|
|
|
scope=7, url_args=post) |
|
|
|
offset = pager['offset'] |
|
|
|
products = Product.search(domain, limit=ppg, offset=pager['offset'], |
|
|
|
order=self._get_search_order(post)) |
|
|
|
if not category: |
|
|
@ -240,23 +275,24 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
ProductAttribute = request.env['product.attribute'] |
|
|
|
if products: |
|
|
|
# get all products without limit |
|
|
|
attributes = ProductAttribute.search( |
|
|
|
[('product_tmpl_ids', 'in', search_product.ids)]) |
|
|
|
attributes = lazy(lambda: ProductAttribute.search([ |
|
|
|
('product_tmpl_ids', 'in', search_product.ids), |
|
|
|
('visibility', '=', 'visible'), |
|
|
|
])) |
|
|
|
else: |
|
|
|
attributes = ProductAttribute.browse(attributes_ids) |
|
|
|
|
|
|
|
attributes = lazy(lambda: ProductAttribute.browse(attributes_ids)) |
|
|
|
layout_mode = request.session.get('website_sale_shop_layout_mode') |
|
|
|
if not layout_mode: |
|
|
|
if request.website.viewref( |
|
|
|
'website_sale.products_list_view').active: |
|
|
|
if website.viewref('website_sale.products_list_view').active: |
|
|
|
layout_mode = 'list' |
|
|
|
else: |
|
|
|
layout_mode = 'grid' |
|
|
|
request.session['website_sale_shop_layout_mode'] = layout_mode |
|
|
|
|
|
|
|
products_prices = lazy(lambda: products._get_sales_prices(pricelist)) |
|
|
|
values = { |
|
|
|
'search': search, |
|
|
|
'search': fuzzy_search_term or search, |
|
|
|
'original_search': fuzzy_search_term and search, |
|
|
|
'order': post.get('order', ''), |
|
|
|
'category': category, |
|
|
|
'attrib_values': attrib_values, |
|
|
|
'attrib_set': attrib_set, |
|
|
@ -264,6 +300,7 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
'pricelist': pricelist, |
|
|
|
'add_qty': add_qty, |
|
|
|
'products': products, |
|
|
|
'search_product': search_product, |
|
|
|
'search_count': product_count, # common for all searchbox |
|
|
|
'bins': TableCompute().process(products, ppg, ppr), |
|
|
|
'ppg': ppg, |
|
|
@ -276,14 +313,22 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
'products_prices': products_prices, |
|
|
|
'get_product_prices': lambda product: lazy( |
|
|
|
lambda: products_prices[product.id]), |
|
|
|
'float_round': tools.float_round, |
|
|
|
} |
|
|
|
|
|
|
|
if filter_by_price_enabled: |
|
|
|
values['min_price'] = min_price or available_min_price |
|
|
|
values['max_price'] = max_price or available_max_price |
|
|
|
values['available_min_price'] = tools.float_round( |
|
|
|
available_min_price, 2) |
|
|
|
values['available_max_price'] = tools.float_round( |
|
|
|
available_max_price, 2) |
|
|
|
if category: |
|
|
|
values['main_object'] = category |
|
|
|
values.update(self._get_additional_shop_values(values)) |
|
|
|
return request.render("website_sale.products", values) |
|
|
|
|
|
|
|
def availavle_products(self): |
|
|
|
''''Returns the available product (product.template) ids''' |
|
|
|
def available_products(self): |
|
|
|
"""Returns the available product (product.template) ids""" |
|
|
|
user = request.env['res.users'].sudo().search( |
|
|
|
[('id', '=', request.env.user.id)]) |
|
|
|
partner = request.env['res.partner'].sudo().search( |
|
|
@ -299,7 +344,6 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
def products_autocomplete(self, term, options={}, **kwargs): |
|
|
|
""" |
|
|
|
Returns list of products according to the term and product options |
|
|
|
|
|
|
|
Params: |
|
|
|
term (str): search term written by the user |
|
|
|
options (dict) |
|
|
@ -309,7 +353,6 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
- 'order' (str) |
|
|
|
- 'max_nb_chars' (int): max number of characters for the |
|
|
|
description if returned |
|
|
|
|
|
|
|
Returns: |
|
|
|
dict (or False if no result) |
|
|
|
- 'products' (list): products (only their needed field values) |
|
|
@ -318,7 +361,6 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
- 'products_count' (int): the number of products in the database |
|
|
|
that matched the search query |
|
|
|
""" |
|
|
|
|
|
|
|
user = request.env['res.users'].sudo().search( |
|
|
|
[('id', '=', request.env.user.id)]) |
|
|
|
available_categ = available_products = '' |
|
|
@ -343,7 +385,7 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
[('id', '=', user.partner_id.id)]) |
|
|
|
mode = partner.filter_mode |
|
|
|
if mode != 'categ_only': |
|
|
|
available_products = self.availavle_products() |
|
|
|
available_products = self.available_products() |
|
|
|
available_categ = partner.website_available_cat_ids |
|
|
|
ProductTemplate = request.env['product.template'] |
|
|
|
display_description = options.get('display_description', True) |
|
|
@ -352,7 +394,6 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
max_nb_chars = options.get('max_nb_chars', 999) |
|
|
|
category = options.get('category') |
|
|
|
attrib_values = options.get('attrib_values') |
|
|
|
|
|
|
|
if not available_products and not available_categ: |
|
|
|
domain = self._get_search_domain(term, category, attrib_values, |
|
|
|
display_description) |
|
|
@ -368,19 +409,16 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
fields = ['id', 'name', 'website_url'] |
|
|
|
if display_description: |
|
|
|
fields.append('description_sale') |
|
|
|
|
|
|
|
res = { |
|
|
|
'products': products.read(fields), |
|
|
|
'products_count': ProductTemplate.search_count(domain), |
|
|
|
} |
|
|
|
|
|
|
|
if display_description: |
|
|
|
for res_product in res['products']: |
|
|
|
desc = res_product['description_sale'] |
|
|
|
if desc and len(desc) > max_nb_chars: |
|
|
|
res_product['description_sale'] = "%s..." % desc[:( |
|
|
|
max_nb_chars - 3)] |
|
|
|
|
|
|
|
max_nb_chars - 3)] |
|
|
|
if display_price: |
|
|
|
FieldMonetary = request.env['ir.qweb.field.monetary'] |
|
|
|
monetary_options = { |
|
|
@ -394,5 +432,4 @@ class ProductVisibilityCon(WebsiteSale): |
|
|
|
res_product['list_price'], monetary_options) |
|
|
|
res_product['price'] = FieldMonetary.value_to_html( |
|
|
|
res_product['price'], monetary_options) |
|
|
|
|
|
|
|
return res |
|
|
|