# Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import http from odoo.http import request from odoo.addons.base.models.ir_qweb import keep_query from odoo.addons.appointment.controllers.appointment import AppointmentController class WebsiteAppointment(AppointmentController): # ------------------------------------------------------------ # APPOINTMENT INDEX PAGE # ------------------------------------------------------------ @http.route() def appointment_type_index(self, page=1, **kwargs): """ Display the appointments to choose (the display depends of a custom option called 'Card Design') :param page: the page number displayed when the appointments are organized by cards A param filter_appointment_type_ids can be passed to display a define selection of appointments types. This param is propagated through templates to allow people to go back with the initial appointment types filter selection """ kwargs['domain'] = self._appointments_base_domain( filter_appointment_type_ids=kwargs.get('filter_appointment_type_ids'), search=kwargs.get('search'), invite_token=kwargs.get('invite_token'), additional_domain=self._appointment_website_domain(), filter_countries=True, ) available_appointment_types = self._fetch_and_check_private_appointment_types( kwargs.get('filter_appointment_type_ids'), kwargs.get('filter_staff_user_ids'), kwargs.get('filter_resource_ids'), kwargs.get('invite_token'), domain=kwargs['domain'], ) if len(available_appointment_types) == 1 and not kwargs.get('search'): # If there is only one appointment type available in the selection, skip the appointment type selection view return request.redirect('/appointment/%s?%s' % (available_appointment_types.id, keep_query('*'))) cards_layout = request.website.viewref('website_appointment.opt_appointments_list_cards').active if cards_layout: return request.render( 'website_appointment.appointments_cards_layout', self._prepare_appointments_cards_data( page, available_appointment_types, **kwargs ) ) else: return request.render( 'appointment.appointments_list_layout', self._prepare_appointments_list_data( available_appointment_types, **kwargs ) ) # ---------------------------------------------------------------- # APPOINTMENT SUBMISSION : RECAPTCHA CHECK # ---------------------------------------------------------------- @http.route() def appointment_form_submit(self, *args, **kwargs): request.env['ir.http']._verify_request_recaptcha_token('appointment_form_submission') return super().appointment_form_submit(*args, **kwargs) # ---------------------------------------------------------------- # APPOINTMENT TYPE PAGE VIEW : WITH NEW OPERATOR SELECTION VIEW # ---------------------------------------------------------------- def _get_appointment_type_resource_selection_view(self, appointment_type, page_values): """ Renders the appointment_select_operator template. This displays a card view of available staff users to select from for appointment_type, containing their picture, job description and website_description. :param appointment_type: the appointment_type that we want to access. :param page_values: dict of precomputed values in the appointment_page route. """ return request.render("website_appointment.appointment_select_operator", { 'appointment_type': appointment_type, 'available_appointments': page_values['available_appointments'], 'main_object': appointment_type, 'users_possible': page_values['users_possible'], 'resources_possible': page_values['resources_possible'], }) def _get_appointment_type_page_view(self, appointment_type, page_values, state=False, **kwargs): """ Override: when website_appointment is installed, instead of the default appointment type page, renders the operator selection template, if the condition below is met. """ # If the user skips the resource selection to see all availabilities, make sure we do not show the selection. # As the operator view is mainly user cards, we only show it if avatars are 'on'. Also, it makes no sense in # random appointment types since it is a selection screen. Moreover, the selection should not have already # been made before in order to avoid loops. Finally, in order to choose, one needs at least 2 possible users/resources. skip_resource_selection = kwargs.get('skip_resource_selection') or \ not appointment_type.active or \ appointment_type.is_auto_assign or \ appointment_type.is_date_first operator_selection = not skip_resource_selection and \ appointment_type.schedule_based_on == 'users' and \ not page_values['user_selected'] and \ len(page_values['users_possible']) > 1 resource_selection = not skip_resource_selection and \ appointment_type.schedule_based_on == 'resources' and \ not page_values['resource_selected'] and \ len(page_values['resources_possible']) > 1 if operator_selection or resource_selection: return self._get_appointment_type_resource_selection_view(appointment_type, page_values) return super()._get_appointment_type_page_view(appointment_type, page_values, state, **kwargs) def _prepare_appointment_type_page_values(self, appointment_type, staff_user_id=False, resource_selected_id=False, skip_resource_selection=False, **kwargs): """ Override: Take into account the operator selection flow. When skipping the selection, no {user,resource}_selected or user_default should be set. The display is also properly managed according to this new flow. :param skip_resource_selection: If true, skip the selection, and instead see all availabilities. No user should be selected. """ values = super()._prepare_appointment_type_page_values(appointment_type, staff_user_id, resource_selected_id, **kwargs) values['skip_resource_selection'] = skip_resource_selection if skip_resource_selection: values['user_selected'] = values['user_default'] = request.env['res.users'] values['resource_selected'] = values['resource_default'] = request.env['appointment.resource'] else: resource_or_user_selected = values['user_selected'] if appointment_type.schedule_based_on == 'users' else values['resource_selected'] values['hide_select_dropdown'] = values['hide_select_dropdown'] or ( resource_or_user_selected and not (appointment_type.is_date_first and not appointment_type.is_auto_assign)) return values # Tools / Data preparation # ------------------------------------------------------------ def _prepare_appointments_cards_data(self, page, appointment_types, **kwargs): """ Compute specific data for the cards layout like the search bar and the pager. """ APPOINTMENTS_PER_PAGE = 12 website = request.website appointment_count = len(appointment_types) pager = website.pager( url='/appointment', url_args=kwargs, total=appointment_count, page=page, step=APPOINTMENTS_PER_PAGE, scope=5, ) appointment_types = appointment_types.sorted('is_published', reverse=True)[pager['offset']:pager['offset'] + APPOINTMENTS_PER_PAGE] return { 'appointment_types': appointment_types, 'current_search': kwargs.get('search'), 'pager': pager, 'filter_appointment_type_ids': kwargs.get('filter_appointment_type_ids'), 'filter_staff_user_ids': kwargs.get('filter_staff_user_ids'), 'invite_token': kwargs.get('invite_token'), 'search_count': appointment_count, } def _get_allowed_companies(self, organizer): """ Check if the current website can be used to determine the company and fallback on the companies of the organizer if not """ companies = super()._get_allowed_companies(organizer) website_company = request.website.company_id if request.website else request.env['res.company'] return website_company if website_company in companies else companies def _get_customer_partner(self): """ Get the partner of the last calendar event of the public user's visitor if no partner has been found from the user or visitor, to avoid partner duplication if the user is not logged. """ partner = super()._get_customer_partner() if not partner: visitor = request.env['website.visitor']._get_visitor_from_request() if visitor.partner_id: partner = visitor.partner_id elif visitor and request.env.user._is_public(): partner = self.env['calendar.event'].sudo().search( [('visitor_id', '=', visitor.id), ('appointment_booker_id', '!=', False)], order='id desc', limit=1 ).appointment_booker_id return partner @staticmethod def _get_customer_country(): """ Find the country from the geoip lib or fallback on the user or the visitor """ country = AppointmentController._get_customer_country() if not country: visitor = request.env['website.visitor']._get_visitor_from_request() country = visitor.country_id return country @classmethod def _appointments_base_domain(cls, filter_appointment_type_ids, search=False, invite_token=False, additional_domain=None, filter_countries=False): domain = super()._appointments_base_domain(filter_appointment_type_ids, search, invite_token, additional_domain, filter_countries) domain &= request.website.website_domain() return domain