# -*- coding: utf-8 -*- ############################################################################### # # Cybrosys Technologies Pvt. Ltd. # # Copyright (C) 2024-TODAY Cybrosys Technologies() # Author: Ammu Raj (odoo@cybrosys.com) # # You can modify it under the terms of the GNU AFFERO # GENERAL PUBLIC LICENSE (AGPL v3), Version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. # # You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE # (AGPL v3) along with this program. # If not, see . # ############################################################################### import logging from datetime import datetime from werkzeug.exceptions import NotFound from odoo import fields from odoo.http import request, route from odoo.osv import expression from odoo.tools import float_round, groupby, lazy, SQL from odoo.addons.website_sale.controllers.main import WebsiteSale from odoo.addons.website.controllers.main import QueryURL from odoo.addons.website.models.ir_http import sitemap_qs2dom from odoo.addons.website_sale.controllers.main import TableCompute _logger = logging.getLogger(__name__) class WebsiteSales(WebsiteSale): def sitemap_shop(env, rule, qs): if not qs or qs.lower() in '/shop': yield {'loc': '/shop'} Category = env['product.public.category'] dom = sitemap_qs2dom(qs, '/shop/category', Category._rec_name) dom += env['website'].get_current_website().website_domain() for cat in Category.search(dom): loc = '/shop/category/%s' % env['ir.http']._slug(cat) if not qs or qs.lower() in loc: yield {'loc': loc} @route([ '/shop', '/shop/page/', '/shop/category/', '/shop/category//page/', '''/shop/brand/''' ], type='http', auth="public", website=True, sitemap=sitemap_shop) def shop(self, page=0, category=None, search='', min_price=0.0, max_price=0.0, ppg=False, brand=None, **post): """Overrides the function for adding Brand""" if not request.website.has_ecommerce_access(): return request.redirect('/web/login') try: min_price = float(min_price) except ValueError: min_price = 0 try: max_price = float(max_price) except ValueError: max_price = 0 compute_brand = brand 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(): raise NotFound() else: category = Category product_brand = request.env['product.brand'] if not brand: brand = product_brand website = request.env['website'].get_current_website() website_domain = website.website_domain() if ppg: try: ppg = int(ppg) post['ppg'] = ppg except ValueError: ppg = False if not ppg: ppg = website.shop_ppg or 20 ppr = website.shop_ppr or 4 request_args = request.httprequest.args attrib_list = request_args.getlist('attribute_value') attrib_values = [[int(x) for x in v.split("-")] for v in attrib_list if v] attributes_ids = {v[0] for v in attrib_values} attrib_set = {v[1] for v in attrib_values} filter_by_tags_enabled = website.is_view_active( 'website_sale.filter_products_tags') if filter_by_tags_enabled: tags = request_args.getlist('tags') # Allow only numeric tag values to avoid internal error. if tags and all(tag.isnumeric() for tag in tags): post['tags'] = tags tags = {int(tag) for tag in tags} else: post['tags'] = None tags = {} 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 = website.pricelist_id if 'website_sale_pricelist_time' in request.session: # Check if we need to refresh the cached pricelist pricelist_save_time = request.session['website_sale_pricelist_time'] if pricelist_save_time < now - 60 * 60: request.session.pop('website_sale_current_pl', None) website.invalidate_recordset(['pricelist_id']) pricelist = website.pricelist_id request.session['website_sale_pricelist_time'] = now request.session['website_sale_current_pl'] = pricelist.id else: request.session['website_sale_pricelist_time'] = now request.session['website_sale_current_pl'] = pricelist.id filter_by_price_enabled = website.is_view_active( 'website_sale.filter_products_price') if filter_by_price_enabled: company_currency = website.company_id.sudo().currency_id conversion_rate = request.env['res.currency']._get_conversion_rate( company_currency, website.currency_id, request.website.company_id, fields.Date.today()) else: conversion_rate = 1 url = '/shop' if search: post['search'] = search options = self._get_search_options( category=category, attrib_values=attrib_values, min_price=min_price, max_price=max_price, conversion_rate=conversion_rate, display_currency=website.currency_id, **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_shop_domain(search, category, attrib_values) # This is ~4 times more efficient than a search for the cheapest and most expensive products query = Product._where_calc(domain) Product._apply_ir_rules(query, 'read') sql = query.select( SQL( "COALESCE(MIN(list_price), 0) * %(conversion_rate)s, COALESCE(MAX(list_price), 0) * %(conversion_rate)s", conversion_rate=conversion_rate, ) ) available_min_price, available_max_price = \ request.env.execute_query(sql)[0] 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 ProductTag = request.env['product.tag'] if filter_by_tags_enabled and search_product: all_tags = ProductTag.search( expression.AND([ [('product_ids.is_published', '=', True), ('visible_on_ecommerce', '=', True)], website_domain ]) ) else: all_tags = ProductTag categs_domain = [('parent_id', '=', False)] + website_domain if search: search_categories = Category.search( [( 'product_tmpl_ids', 'in', search_product.ids)] + website_domain ).parents_and_self categs_domain.append(('id', 'in', search_categories.ids)) else: search_categories = Category categs = lazy(lambda: Category.search(categs_domain)) if category: url = "/shop/category/%s" % request.env['ir.http']._slug(category) pager = website.pager(url=url, total=product_count, page=page, step=ppg, scope=5, url_args=post) offset = pager['offset'] products = search_product[offset:offset + ppg] ProductAttribute = request.env['product.attribute'] if products: # get all products without limit attributes = lazy(lambda: ProductAttribute.search([ ('product_tmpl_ids', 'in', search_product.ids), ('visibility', '=', 'visible'), ])) else: attributes = lazy(lambda: ProductAttribute.browse(attributes_ids)) layout_mode = request.session.get('website_sale_shop_layout_mode') if not layout_mode: 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(website)) products_prices_brand = lazy( lambda: search_product._get_sales_prices(website)) product_brand = request.env['product.brand'].search([]) products_brand = request.env['product.template'].search( ['&', '&', ('brand_id', '=', brand.id), ('sale_ok', '=', True), ('is_published', '=', True)]) product_brand_count = len(products_brand) pager_brand = website.pager(url=url, total=product_brand_count, page=page, step=ppg, scope=5, url_args=post) attributes_values = request.env['product.attribute.value'].browse( attrib_set) sorted_attributes_values = attributes_values.sorted('sequence') multi_attributes_values = sorted_attributes_values.filtered( lambda av: av.display_type == 'multi') single_attributes_values = sorted_attributes_values - multi_attributes_values grouped_attributes_values = list( groupby(single_attributes_values, lambda av: av.attribute_id.id)) grouped_attributes_values.extend( [(av.attribute_id.id, [av]) for av in multi_attributes_values]) selected_attributes_hash = grouped_attributes_values and "#attribute_values=%s" % ( ','.join(str(v[0].id) for k, v in grouped_attributes_values) ) or '' values = { 'brand': brand, '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, 'pager': pager_brand, 'products': products, 'search_product': search_product, 'search_count': product_brand_count, # common for all searchbox 'bins': lazy( lambda: TableCompute().process(products_brand, ppg, ppr)), 'ppg': ppg, 'ppr': ppr, 'categories': categs, 'attributes': attributes, 'keep': keep, 'selected_attributes_hash': selected_attributes_hash, 'search_categories_ids': search_categories.ids, 'layout_mode': layout_mode, 'brands': product_brand, 'products_prices': products_prices, 'get_product_prices': lambda product: lazy( lambda: products_prices_brand[product.id]), 'float_round': 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'] = float_round(available_min_price, 2) values['available_max_price'] = float_round(available_max_price, 2) if filter_by_tags_enabled: values.update({'all_tags': all_tags, 'tags': tags}) if category: values['main_object'] = category values.update(self._get_additional_extra_shop_values(values, **post)) return request.render("website_sale.products", values)