diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 46da5abb3..656aab0bb 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -97,7 +97,7 @@ class HotelFolio(models.Model): readonly=True, copy=False, index=True, track_visibility='onchange', default='draft') - + # Partner fields for being used directly in the Folio views--------- email = fields.Char('E-mail', related='partner_id.email') @@ -165,7 +165,7 @@ class HotelFolio(models.Model): has_checkout_to_send = fields.Boolean( compute='_compute_has_checkout_to_send') - #Generic Fields----------------------------------------------------- + #Generic Fields----------------------------------------------------- internal_comment = fields.Text(string='Internal Folio Notes') cancelled_reason = fields.Text('Cause of cancelled') closure_reason_id = fields.Many2one('room.closure.reason') @@ -349,7 +349,7 @@ class HotelFolio(models.Model): ).next_by_code('hotel.folio') or _('New') else: vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New') - + # Makes sure partner_invoice_id' and 'pricelist_id' are defined lfields = ('partner_invoice_id', 'partner_shipping_id', 'pricelist_id') @@ -402,7 +402,7 @@ class HotelFolio(models.Model): self.pricelist_id.is_staff, self.reservation_type)} self.update(values) - + @api.model def calcule_reservation_type(self, is_staff, current_type): @@ -753,13 +753,13 @@ class HotelFolio(models.Model): info_grouped = [] for rline in self.room_lines: if (import_all or rline.to_send) and \ - not rline.parent_reservation and rline.state == state: - dates = rline.get_real_checkin_checkout() + not rline.parent_reservation and rline.state == state: + dates = (rline.real_checkin, rline.real_checkout) vals = { 'num': len( self.room_lines.filtered( - lambda r: r.get_real_checkin_checkout()[0] == dates[0] and \ - r.get_real_checkin_checkout()[1] == dates[1] and \ + lambda r: r.real_checkin == dates[0] and \ + r.real_checkout == dates[1] and \ r.room_type_id.id == rline.room_type_id.id and \ (r.to_send or import_all) and not r.parent_reservation and \ r.state == rline.state) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 222b2030f..2a43ded84 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -151,6 +151,10 @@ class HotelReservation(models.Model): checkout = fields.Date('Check Out', required=True, default=_get_default_checkout, track_visibility='onchange') + real_checkin = fields.Date('Real Check In', required=True, + track_visibility='onchange') + real_checkout = fields.Date('Real Check Out', required=True, + track_visibility='onchange') arrival_hour = fields.Char('Arrival Hour', default=_get_default_arrival_hour, help="Default Arrival Hour (HH:MM)") @@ -309,7 +313,11 @@ class HotelReservation(models.Model): vals.update(self.prepare_reservation_lines( vals['checkin'], days_diff, - vals=vals)) #REVISAR el unlink + vals=vals)) # REVISAR el unlink + if 'checkin' in vals and 'checkout' in vals \ + and 'real_checkin' not in vals and 'real_checkout' not in vals: + vals['real_checkin'] = vals['checkin'] + vals['real_checkout'] = vals['checkout'] record = super(HotelReservation, self).create(vals) #~ if (record.state == 'draft' and record.folio_id.state == 'sale') or \ #~ record.preconfirm: @@ -325,6 +333,13 @@ class HotelReservation(models.Model): for record in self: checkin = vals['checkin'] if 'checkin' in vals else record.checkin checkout = vals['checkout'] if 'checkout' in vals else record.checkout + + if not record.splitted and not vals.get('splitted', False): + if 'checkin' in vals: + vals['real_checkin'] = vals['checkin'] + if 'checkout' in vals: + vals['real_checkout'] = vals['checkout'] + days_diff = ( fields.Date.from_string(checkout) - \ fields.Date.from_string(checkin) @@ -485,6 +500,8 @@ class HotelReservation(models.Model): 'splitted': self.splitted, 'room_type_id': self.room_type_id.id, 'room_id': self.room_id.id, + 'real_checkin': self.real_checkin, + 'real_checkout': self.real_checkout, } @api.constrains('adults') @@ -952,39 +969,13 @@ class HotelReservation(models.Model): RESERVATION SPLITTED ----------------------------------------------- """ - @api.multi - def get_real_checkin_checkout(self): - self.ensure_one() - if not self.splitted: - return (self.checkin, self.checkout) - - master_reservation = self.parent_reservation or self - splitted_reservs = self.env['hotel.reservation'].search([ - '|', - ('splitted', '=', True), - ('id', '=', master_reservation.id), # This here because can create a splitted reserv before set as splitted the parent reservation (master) - ('folio_id', '=', self.folio_id.id), - '|', - ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id) - ]) - last_checkout = splitted_reservs[0].checkout - first_checkin = splitted_reservs[0].checkin - for reserv in splitted_reservs: - if last_checkout < reserv.checkout: - last_checkout = reserv.checkout - if first_checkin > reserv.checkin: - first_checkin = reserv.checkin - return (first_checkin, last_checkout) - @api.multi def split(self, nights): for record in self: date_start_dt = fields.Date.from_string(record.checkin) date_end_dt = fields.Date.from_string(record.checkout) date_diff = abs((date_end_dt - date_start_dt).days) - new_start_date_dt = date_start_dt + \ - timedelta(days=date_diff-nights) + new_start_date_dt = date_start_dt + timedelta(days=date_diff-nights) if nights >= date_diff or nights < 1: raise ValidationError(_("Invalid Nights! Max is \ '%d'") % (date_diff-1)) @@ -1058,19 +1049,24 @@ class HotelReservation(models.Model): @api.model def unify_books(self, splitted_reservs): - master_reservation = splitted_reservs[0].parent_reservation or splitted_reservs[0] + parent_reservation = splitted_reservs[0].parent_reservation or splitted_reservs[0] room_type_ids = splitted_reservs.mapped('room_type_id.id') if len(room_type_ids) > 1 or \ (len(room_type_ids) == 1 - and master_reservation.room_type_id.id != room_type_ids[0]): + and parent_reservation.room_type_id.id != room_type_ids[0]): raise ValidationError(_("This reservation can't be unified: They \ all need to be in the same room")) # Search checkout last_checkout = splitted_reservs[0].checkout + first_checkin = splitted_reservs[0].checkin + master_reservation = splitted_reservs[0] for reserv in splitted_reservs: if last_checkout < reserv.checkout: last_checkout = reserv.checkout + if first_checkin > reserv.checkin: + first_checkin = reserv.checkin + master_reservation = reserv # Agrupate reservation lines reservation_line_ids = splitted_reservs.mapped('reservation_line_ids') @@ -1088,9 +1084,15 @@ class HotelReservation(models.Model): osplitted_reservs = splitted_reservs - master_reservation osplitted_reservs.sudo().unlink() + _logger.info("========== UNIFY") + _logger.info(master_reservation.real_checkin) + _logger.info(first_checkin) + _logger.info(master_reservation.real_checkout) + _logger.info(last_checkout) + master_reservation.write({ 'checkout': last_checkout, - 'splitted': master_reservation.get_real_checkin_checkout()[1] != last_checkout, + 'splitted': master_reservation.real_checkin != first_checkin or master_reservation.real_checkout != last_checkout, 'reservation_line_ids': rlines, 'price_total': tprice, }) diff --git a/hotel_calendar/__manifest__.py b/hotel_calendar/__manifest__.py index 0f123751b..b85d29a60 100644 --- a/hotel_calendar/__manifest__.py +++ b/hotel_calendar/__manifest__.py @@ -25,7 +25,6 @@ 'views/inherited_res_users_views.xml', 'views/inherited_hotel_room_type_views.xml', 'views/inherited_hotel_room_views.xml', - 'views/room_pricelist_cached_views.xml', 'views/hotel_reservation_views.xml', 'views/hotel_calendar_management_views.xml', 'views/hotel_calendar_views.xml', diff --git a/hotel_calendar/data/menus.xml b/hotel_calendar/data/menus.xml index 6ef1e3d96..69f7251b6 100644 --- a/hotel_calendar/data/menus.xml +++ b/hotel_calendar/data/menus.xml @@ -22,9 +22,6 @@ web_icon="hotel_calendar,static/description/icon_calendar_configurator.png" action="action_hotel_calendar_management" groups="hotel.group_hotel_manager" /> - - # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -from datetime import datetime, timedelta +from datetime import timedelta from odoo import models, fields, api, _ from odoo.exceptions import ValidationError -from odoo.tools import ( - DEFAULT_SERVER_DATE_FORMAT, - DEFAULT_SERVER_DATETIME_FORMAT) +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT _logger = logging.getLogger(__name__) @@ -18,10 +16,6 @@ class HotelReservation(models.Model): reserve_color_text = fields.Char(compute='_compute_color', string='Color', store=True) - """ - COMPUTE RESERVE COLOR ---------------------------------------------- - """ - @api.multi def _generate_color(self): self.ensure_one() @@ -79,54 +73,48 @@ class HotelReservation(models.Model): json_reservation_tooltips = {} for reserv in reservations: json_reservations.append({ - 'room_id': reserv.room_id.id, - 'id': reserv.id, - 'name': reserv.folio_id.closure_reason_id.name or _('Out of service') if reserv.folio_id.reservation_type == 'out' - else reserv.folio_id.partner_id.name, - 'adults': reserv.adults, - 'childrens': reserv.children, - 'checkin': reserv.checkin, - 'checkout': reserv.checkout, - 'folio_id': reserv.folio_id.id, - 'bgcolor': reserv.reserve_color, - 'color': reserv.reserve_color_text, - 'splitted': reserv.splitted, - 'parent_reservation': reserv.parent_reservation and reserv.parent_reservation.id or False, + 'room_id': reserv['room_id'], + 'id': reserv['id'], + 'name': reserv['closure_reason'] or _('Out of service') + if reserv['reservation_type'] == 'out' + else reserv['partner_name'], + 'adults': reserv['adults'], + 'childrens': reserv['children'], + 'checkin': reserv['checkin'], + 'checkout': reserv['checkout'], + 'folio_id': reserv['folio_id'], + 'bgcolor': reserv['reserve_color'], + 'color': reserv['reserve_color_text'], + 'splitted': reserv['splitted'], + 'parent_reservation': reserv['parent_reservation'] or False, 'read_only': False, # Read-Only - 'fix_days': reserv.splitted, # Fix Days + 'fix_days': reserv['splitted'], # Fix Days 'fix_room': False, # Fix Rooms - 'overbooking': reserv.overbooking, - 'state': reserv.state, - 'real_dates': reserv.get_real_checkin_checkout()}) - num_split = 0 - if reserv.splitted: - master_reserv = reserv.parent_reservation or reserv - num_split = self.search_count([ - ('folio_id', '=', reserv.folio_id.id), - '|', ('parent_reservation', '=', master_reserv.id), - ('id', '=', master_reserv.id), - ('splitted', '=', True), - ]) + 'overbooking': reserv['overbooking'], + 'state': reserv['state'], + 'real_dates': [reserv['real_checkin'], reserv['real_checkout']]}) json_reservation_tooltips.update({ - reserv.id: { - 'folio_name': reserv.folio_id.name, - 'name': _('Out of service') if reserv.folio_id.reservation_type == 'out' else reserv.folio_id.partner_id.name, - 'phone': reserv.mobile or reserv.phone or _('Phone not provided'), - 'email': reserv.email or _('Email not provided'), - 'room_type_name': reserv.room_type_id.name, - 'adults': reserv.adults, - 'children': reserv.children, - 'checkin': reserv.checkin, - 'checkout': reserv.checkout, - 'arrival_hour': reserv.arrival_hour, - 'departure_hour': reserv.departure_hour, - 'num_split': num_split, - 'amount_total': reserv.folio_id.amount_total, - 'pending_amount': reserv.folio_id.pending_amount, - 'amount_paid': reserv.folio_id.amount_total - reserv.folio_id.pending_amount, - 'type': reserv.reservation_type or 'normal', - 'out_service_description': reserv.out_service_description or - _('No reason given'), + reserv['id']: { + 'folio_name': reserv['folio_id'], + 'name': _('Out of service') + if reserv['reservation_type'] == 'out' + else reserv['partner_name'], + 'phone': reserv['mobile'] or reserv['phone'] + or _('Phone not provided'), + 'email': reserv['email'] or _('Email not provided'), + 'room_type_name': reserv['room_type'], + 'adults': reserv['adults'], + 'children': reserv['children'], + 'checkin': reserv['checkin'], + 'checkout': reserv['checkout'], + 'arrival_hour': reserv['arrival_hour'], + 'departure_hour': reserv['departure_hour'], + 'amount_total': reserv['amount_total'], + 'pending_amount': reserv['pending_amount'], + 'amount_paid': reserv['amount_total'] - (reserv['pending_amount'] or 0.0), + 'type': reserv['reservation_type'] or 'normal', + 'out_service_description': reserv['out_service_description'] + or _('No reason given'), # TODO: Add Board Services and Extra Service as Cradle, Bed, ... } }) @@ -138,9 +126,8 @@ class HotelReservation(models.Model): 'res.config.settings', 'default_pricelist_id') if pricelist_id: pricelist_id = int(pricelist_id) - json_rooms = [] - for room in rooms: - json_rooms.append({ + json_rooms = [ + { 'id': room.id, 'name': room.name, 'capacity': room.capacity, @@ -148,163 +135,190 @@ class HotelReservation(models.Model): 'class_id': room.room_type_id.class_id.id, 'shared': room.shared_room, 'price': room.room_type_id - and ['pricelist', room.room_type_id.id, pricelist_id, - room.room_type_id.name] or 0, + and ['pricelist', room.room_type_id.id, pricelist_id, + room.room_type_id.name] or 0, 'room_type_name': room.room_type_id.name, 'room_type_id': room.room_type_id.id, 'floor_id': room.floor_id.id, 'amentity_ids': room.room_type_id.room_amenity_ids.ids, - }) + } for room in rooms] return json_rooms @api.model def _hcalendar_calendar_data(self, calendars): - json_calendars = [] - for calendar in calendars: - json_calendars.append({ + return [ + { 'id': calendar.id, 'name': calendar.name, 'segmentation_ids': calendar.segmentation_ids.ids, 'location_ids': calendar.location_ids.ids, 'amenity_ids': calendar.amenity_ids.ids, 'room_type_ids': calendar.room_type_ids.ids, - }) - return json_calendars + } for calendar in calendars] @api.model def _hcalendar_event_data(self, events): - json_events = [] - for event in events: - json_events.append({ + json_events = [ + { 'id': event.id, 'name': event.name, 'date': event.start, 'location': event.location, - }) + } for event in events] return json_events @api.model - def get_hcalendar_reservations_data(self, dfrom, dto, rooms): - date_start = fields.Date.from_string(dfrom) - timedelta(days=1) - date_start_str = date_start.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - reservations_raw = self.env['hotel.reservation'].search( - [('room_id', 'in', rooms.ids)], - order="checkin DESC, checkout ASC, adults DESC, children DESC") - reservations_ll = self.env['hotel.reservation'].search([ - ('checkin', '<=', dto), - ('checkout', '>=', date_start_str) - ]) - reservations_lr = self.env['hotel.reservation'].search([ - ('checkin', '>=', date_start_str), - ('checkout', '<=', dto) - ]) - reservations = (reservations_ll | reservations_lr) & reservations_raw - return self._hcalendar_reservation_data(reservations) + def get_hcalendar_calendar_data(self): + calendars = self.env['hotel.calendar'].search([]) + res = self._hcalendar_calendar_data(calendars) + return res @api.model - def get_hcalendar_pricelist_data(self, dfrom, dto): + def get_hcalendar_reservations_data(self, dfrom_dt, dto_dt, rooms): + rdfrom_dt = dfrom_dt + timedelta(days=1) # Ignore checkout + rdfrom_str = rdfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + dto_str = dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + self.env.cr.execute(''' + SELECT + hr.id, hr.room_id, hr.adults, hr.children, hr.checkin, hr.checkout, hr.reserve_color, hr.reserve_color_text, + hr.splitted, hr.parent_reservation, hr.overbooking, hr.state, hr.real_checkin, hr.real_checkout, + hr.out_service_description, hr.arrival_hour, hr.departure_hour, + + hf.id as folio_id, hf.name as folio_name, hf.reservation_type, hf.amount_total, hf.pending_amount, + + rp.mobile, rp.phone, rp.email, rp.name as partner_name, + + pt.name as room_type, + + rcr.name as closure_reason + FROM hotel_reservation AS hr + LEFT JOIN hotel_folio AS hf ON hr.folio_id = hf.id + LEFT JOIN hotel_room_type AS hrt ON hr.room_type_id = hrt.id + LEFT JOIN product_product AS pp ON hrt.product_id = pp.id + LEFT JOIN product_template AS pt ON pp.product_tmpl_id = pt.id + LEFT JOIN res_partner AS rp ON hf.partner_id = rp.id + LEFT JOIN room_closure_reason as rcr + ON hf.closure_reason_id = rcr.id + WHERE room_id IN %s AND ( + (checkin <= %s AND checkout >= %s AND checkout <= %s) + OR (checkin >= %s AND checkout <= %s) + OR (checkin >= %s AND checkin <= %s AND checkout >= %s) + OR (checkin <= %s AND checkout >= %s)) + ORDER BY checkin DESC, checkout ASC, adults DESC, children DESC + ''', (tuple(rooms.ids), + rdfrom_str, rdfrom_str, dto_str, + rdfrom_str, dto_str, + rdfrom_str, dto_str, dto_str, + rdfrom_str, dto_str)) + return self._hcalendar_reservation_data(self.env.cr.dictfetchall()) + + @api.model + def get_hcalendar_pricelist_data(self, dfrom_dt, dto_dt): pricelist_id = self.env['ir.default'].sudo().get( 'res.config.settings', 'default_pricelist_id') if pricelist_id: pricelist_id = int(pricelist_id) - date_start = fields.Date.from_string(dfrom) - timedelta(days=1) - date_end = fields.Date.from_string(dto) - date_diff = abs((date_end - date_start).days) + 1 - # Get Prices - json_rooms_prices = {pricelist_id: []} - room_typed_ids = self.env['hotel.room.type'].search( - [], - order='hcal_sequence ASC') - room_pr_cached_obj = self.env['room.pricelist.cached'] - for room_type_id in room_typed_ids: - days = {} - for i in range(0, date_diff): - ndate = date_start + timedelta(days=i) - ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT) - prod_price_id = room_pr_cached_obj.search([ - ('room_id', '=', room_type_id.id), - ('date', '=', ndate_str) - ], limit=1) - days.update({ - ndate.strftime("%d/%m/%Y"): prod_price_id and - prod_price_id.price or - room_type_id.product_id.with_context( - quantity=1, - date=ndate_str, - pricelist=pricelist_id).price + room_types_ids = self.env['hotel.room.type'].search([]) + + dfrom_str = dfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + dto_str = dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) + + self.env.cr.execute(''' + WITH RECURSIVE gen_table_days AS ( + SELECT hrt.id, %s::Date AS date + FROM hotel_room_type AS hrt + UNION ALL + SELECT hrt.id, (td.date + INTERVAL '1 day')::Date + FROM gen_table_days as td + LEFT JOIN hotel_room_type AS hrt ON hrt.id=td.id + WHERE td.date < %s + ) + SELECT + TO_CHAR(gtd.date, 'DD/MM/YYYY') as date, gtd.id as room_type_id, + pt.name, ppi.fixed_price as price, pt.list_price + FROM gen_table_days AS gtd + LEFT JOIN hotel_room_type AS hrt ON hrt.id = gtd.id + LEFT JOIN product_product AS pp ON pp.id = hrt.product_id + LEFT JOIN product_template AS pt ON pt.id = pp.product_tmpl_id + LEFT JOIN product_pricelist_item AS ppi ON ppi.date_start = gtd.date AND ppi.date_end = gtd.date AND ppi.product_tmpl_id = pt.id + WHERE gtd.id IN %s + ORDER BY gtd.id ASC, gtd.date ASC + ''', (dfrom_str, dto_str, tuple(room_types_ids.ids))) + query_results = self.env.cr.dictfetchall() + + json_data = {} + for results in query_results: + if results['room_type_id'] not in json_data: + json_data.setdefault(results['room_type_id'], {}).update({ + 'title': results['name'], + 'room': results['room_type_id'], }) - json_rooms_prices[pricelist_id].append({ - 'room': room_type_id.id, - 'days': days, - 'title': room_type_id.name, + json_data[results['room_type_id']].setdefault('days', {}).update({ + results['date']: results['price'] or results['list_price'] }) + + json_rooms_prices = {} + for prices in list(json_data.values()): + json_rooms_prices.setdefault(pricelist_id, []).append(prices) return json_rooms_prices @api.model - def get_hcalendar_restrictions_data(self, dfrom, dto): + def get_hcalendar_restrictions_data(self, dfrom_dt, dto_dt): restriction_id = self.env['ir.default'].sudo().get( 'res.config.settings', 'default_restriction_id') if restriction_id: restriction_id = int(restriction_id) - date_start = fields.Date.from_string(dfrom) - timedelta(days=1) - date_end = fields.Date.from_string(dto) - date_diff = abs((date_end - date_start).days) + 1 - # Get Prices + + # Get Restrictions json_rooms_rests = {} - room_types = self.env['hotel.room.type'].search( + room_typed_ids = self.env['hotel.room.type'].search( [], order='hcal_sequence ASC') room_type_rest_obj = self.env['hotel.room.type.restriction.item'] - for room_type in room_types: + rtype_rest_ids = room_type_rest_obj.search([ + ('room_type_id', 'in', room_typed_ids.ids), + ('date', '>=', dfrom_dt), + ('date', '<=', dto_dt), + ('restriction_id', '=', restriction_id) + ]) + + for room_type in room_typed_ids: days = {} - for i in range(0, date_diff): - ndate = date_start + timedelta(days=i) - ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT) - rest_id = room_type_rest_obj.search([ - ('room_type_id', '=', room_type.id), - ('date', '=', ndate_str), - ('restriction_id', '=', restriction_id) - ], limit=1) - if rest_id and (rest_id.min_stay or rest_id.min_stay_arrival or - rest_id.max_stay or rest_id.max_stay_arrival or - rest_id.closed or rest_id.closed_arrival or - rest_id.closed_departure): - days.update({ - ndate.strftime("%d/%m/%Y"): ( - rest_id.min_stay, - rest_id.min_stay_arrival, - rest_id.max_stay, - rest_id.max_stay_arrival, - rest_id.closed, - rest_id.closed_arrival, - rest_id.closed_departure) - }) + rest_ids = rtype_rest_ids.filtered( + lambda x: x.room_type_id == room_type) + for rest_id in rest_ids: + days.update({ + fields.Date.from_string(rest_id.date).strftime("%d/%m/%Y"): ( + rest_id.min_stay, + rest_id.min_stay_arrival, + rest_id.max_stay, + rest_id.max_stay_arrival, + rest_id.closed, + rest_id.closed_arrival, + rest_id.closed_departure) + }) json_rooms_rests.update({room_type.id: days}) return json_rooms_rests @api.model - def get_hcalendar_events_data(self, dfrom, dto): - date_start = fields.Date.from_string(dfrom) - timedelta(days=1) - date_start_str = date_start.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + def get_hcalendar_events_data(self, dfrom_dt, dto_dt): user_id = self.env['res.users'].browse(self.env.uid) - domain = [] + domain = [ + '|', '&', + ('start', '<=', dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)), + ('stop', '>=', dfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)), + '&', + ('start', '>=', dfrom_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)), + ('stop', '<=', dto_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) + ] if user_id.pms_allowed_events_tags: domain.append(('categ_ids', 'in', user_id.pms_allowed_events_tags)) if user_id.pms_denied_events_tags: domain.append( ('categ_ids', 'not in', user_id.pms_denied_events_tags)) events_raw = self.env['calendar.event'].search(domain) - events_ll = self.env['calendar.event'].search([ - ('start', '<=', dto), - ('stop', '>=', date_start_str) - ]) - events_lr = self.env['calendar.event'].search([ - ('start', '>=', date_start_str), - ('stop', '<=', dto) - ]) - events = (events_ll | events_lr) & events_raw - return self._hcalendar_event_data(events) + return self._hcalendar_event_data(events_raw) @api.model def get_hcalendar_settings(self): @@ -332,19 +346,23 @@ class HotelReservation(models.Model): if not dfrom or not dto: raise ValidationError(_('Input Error: No dates defined!')) + dfrom_dt = fields.Date.from_string(dfrom) + dto_dt = fields.Date.from_string(dto) rooms = self.env['hotel.room'].search([], order='hcal_sequence ASC') - calendars = self.env['hotel.calendar'].search([]) + json_res, json_res_tooltips = self.get_hcalendar_reservations_data( - dfrom, dto, rooms) + dfrom_dt, dto_dt, rooms) vals = { 'rooms': withRooms and self._hcalendar_room_data(rooms) or [], 'reservations': json_res, 'tooltips': json_res_tooltips, - 'pricelist': self.get_hcalendar_pricelist_data(dfrom, dto), - 'restrictions': self.get_hcalendar_restrictions_data(dfrom, dto), - 'events': self.get_hcalendar_events_data(dfrom, dto), - 'calendars': self._hcalendar_calendar_data(calendars) + 'pricelist': self.get_hcalendar_pricelist_data(dfrom_dt, dto_dt), + 'restrictions': self.get_hcalendar_restrictions_data(dfrom_dt, + dto_dt), + 'events': self.get_hcalendar_events_data(dfrom_dt, dto_dt), + 'calendars': withRooms and self.get_hcalendar_calendar_data() + or [] } return vals @@ -359,7 +377,7 @@ class HotelReservation(models.Model): 'room_id': self.room_id.id, 'reserv_id': self.id, 'partner_name': (self.closure_reason_id.name or _('Out of service')) - if self.reservation_type == 'out' else self.partner_id.name, + if self.reservation_type == 'out' else self.partner_id.name, 'adults': self.adults, 'children': self.children, 'checkin': self.checkin, @@ -370,12 +388,12 @@ class HotelReservation(models.Model): 'reserve_color': self.reserve_color, 'reserve_color_text': self.reserve_color_text, 'splitted': self.splitted, - 'parent_reservation': self.parent_reservation and - self.parent_reservation.id or 0, + 'parent_reservation': self.parent_reservation + and self.parent_reservation.id or 0, 'room_name': self.room_id.name, 'room_type_name': self.room_type_id.name, 'partner_phone': self.partner_id.mobile - or self.partner_id.phone or _('Undefined'), + or self.partner_id.phone or _('Undefined'), 'partner_email': self.partner_id.email or _('Undefined'), 'state': self.state, 'fix_days': self.splitted, @@ -385,8 +403,9 @@ class HotelReservation(models.Model): 'amount_paid': self.folio_id.amount_total - self.folio_id.pending_amount, 'reservation_type': self.reservation_type or 'normal', 'closure_reason_id': self.closure_reason_id, - 'out_service_description': self.out_service_description or _('No reason given'), - 'real_dates': self.get_real_checkin_checkout(), + 'out_service_description': self.out_service_description + or _('No reason given'), + 'real_dates': [self.real_checkin, self.real_checkout], } @api.multi @@ -441,24 +460,24 @@ class HotelReservation(models.Model): @api.multi def write(self, vals): - ret = super(HotelReservation, self).write(vals) _logger.info("RESERV WRITE") + ret = super(HotelReservation, self).write(vals) + if 'partner_id' in vals or 'checkin' in vals or \ 'checkout' in vals or 'product_id' in vals or \ 'adults' in vals or 'children' in vals or \ 'state' in vals or 'splitted' in vals or \ 'closure_reason_id' in vals or 'out_service_description' in vals or \ 'reservation_type' in vals or \ - 'reserve_color' in vals or \ - 'reserve_color_text' in vals or 'price_total' in vals or \ + 'price_total' in vals or \ 'parent_reservation' in vals or 'overbooking' in vals or \ 'room_type_id' in vals: for record in self: record.send_bus_notification( 'write', (record.state == 'cancelled') and 'warn' or 'notify', - (record.state == 'cancelled') and - _("Reservation Cancelled") or _("Reservation Changed") + (record.state == 'cancelled') + and _("Reservation Cancelled") or _("Reservation Changed") ) elif not any(vals) or 'to_read' in vals or 'to_assign' in vals: self.send_bus_notification('write', 'noshow') diff --git a/hotel_calendar/models/inherited_hotel_room_type.py b/hotel_calendar/models/inherited_hotel_room_type.py index eb254cc50..e72d30496 100644 --- a/hotel_calendar/models/inherited_hotel_room_type.py +++ b/hotel_calendar/models/inherited_hotel_room_type.py @@ -1,21 +1,9 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields, api +from odoo import models, fields class HotelRoomType(models.Model): _inherit = 'hotel.room.type' hcal_sequence = fields.Integer('Calendar Sequence', default=0) - - @api.multi - def unlink(self): - room_type_pr_cached_obj = self.env['room.pricelist.cached'] - for record in self: - pr_chached = room_type_pr_cached_obj.search([ - ('room_id', '=', record.id) - ]) - # Because 'pricelist.cached' is an isolated model, - # doesn't trigger 'ondelete'. Need call 'unlink' instead. - pr_chached.unlink() - return super(HotelRoomType, self).unlink() diff --git a/hotel_calendar/models/inherited_product_pricelist_item.py b/hotel_calendar/models/inherited_product_pricelist_item.py index 0c025e27e..ad93492dc 100644 --- a/hotel_calendar/models/inherited_product_pricelist_item.py +++ b/hotel_calendar/models/inherited_product_pricelist_item.py @@ -1,6 +1,6 @@ # Copyright 2018 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields, api +from odoo import models, api class ProductPricelistItem(models.Model): @@ -33,20 +33,6 @@ class ProductPricelistItem(models.Model): 'price': prod_price, 'id': self.id, }) - - room_pr_cached_obj = self.env['room.pricelist.cached'] - room_pr_cached_id = room_pr_cached_obj.search([ - ('room_id', '=', room_type.id), - ('date', '=', date_start), - ], limit=1) - if room_pr_cached_id: - room_pr_cached_id.write({'price': prod_price}) - else: - room_pr_cached_obj.create({ - 'room_id': room_type.id, - 'date': date_start, - 'price': prod_price, - }) return res @api.multi @@ -57,7 +43,6 @@ class ProductPricelistItem(models.Model): pricelist_default_id = int(pricelist_default_id) ret_vals = super(ProductPricelistItem, self).write(vals) - room_pr_cached_obj = self.env['room.pricelist.cached'] bus_calendar_obj = self.env['bus.hotel.calendar'] room_type_obj = self.env['hotel.room.type'] if vals.get('fixed_price'): @@ -87,19 +72,6 @@ class ProductPricelistItem(models.Model): 'price': prod_price, 'id': record.id, }) - - room_pr_cached_id = room_pr_cached_obj.search([ - ('room_id', '=', room_type.id), - ('date', '=', date_start), - ], limit=1) - if room_pr_cached_id: - room_pr_cached_id.write({'price': prod_price}) - else: - room_pr_cached_obj.create({ - 'room_id': room_type.id, - 'date': date_start, - 'price': prod_price, - }) return ret_vals @api.multi @@ -125,7 +97,6 @@ class ProductPricelistItem(models.Model): # Do Normal Stuff res = super(ProductPricelistItem, self).unlink() # Do extra operations - room_pr_cached_obj = self.env['room.pricelist.cached'] bus_calendar_obj = self.env['bus.hotel.calendar'] for vals in unlink_vals: pricelist_id = vals['pricelist_id'] @@ -144,12 +115,4 @@ class ProductPricelistItem(models.Model): 'price': prod.price, 'id': vals['id'], }) - - # Remove records from cache model - room_pr_cached_id = room_pr_cached_obj.search([ - ('room_id', '=', room_type.id), - ('date', '=', date_start), - ], limit=1) - if room_pr_cached_id: - room_pr_cached_id.unlink() return res diff --git a/hotel_calendar/models/ir_default.py b/hotel_calendar/models/ir_default.py deleted file mode 100644 index baf735d36..000000000 --- a/hotel_calendar/models/ir_default.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields, api -from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT - - -class IrDefault(models.Model): - _inherit = 'ir.default' - - @api.model - def set(self, model_name, field_name, value, user_id=False, company_id=False, condition=False): - super(IrDefault, self).set(model_name, field_name, value, user_id, company_id, condition) - if model_name == 'res.config.settings' and field_name == 'default_pricelist_id': - pricelist_id = int(value) - self.env['room.pricelist.cached'].search([]).unlink() - - pricelist_items = self.env['product.pricelist.item'].search([ - ('pricelist_id', '=', pricelist_id) - ]) - room_type_obj = self.env['hotel.room.type'] - room_pr_cached_obj = self.env['room.pricelist.cached'] - for pitem in pricelist_items: - date_start = pitem.date_start - product_tmpl_id = pitem.product_tmpl_id.id - fixed_price = pitem.fixed_price - room_type = room_type_obj.search([ - ('product_id.product_tmpl_id', '=', product_tmpl_id), - ], limit=1) - if room_type: - room_pr_cached_obj.create({ - 'room_id': room_type.id, - 'date': date_start, - 'price': fixed_price, - }) diff --git a/hotel_calendar/models/room_pricelist_cached.py b/hotel_calendar/models/room_pricelist_cached.py deleted file mode 100644 index 3514150b3..000000000 --- a/hotel_calendar/models/room_pricelist_cached.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields, api -from odoo.exceptions import ValidationError - - -class RoomPricelistCached(models.Model): - ''' - Cached Pricelist. Used only for Calendar Values - ''' - - _name = 'room.pricelist.cached' - - room_id = fields.Many2one('hotel.room.type', 'Virtual Room', - required=True, track_visibility='always') - price = fields.Float('Price', default=0.0) - date = fields.Date('Date', required=True, track_visibility='always') diff --git a/hotel_calendar/security/ir.model.access.csv b/hotel_calendar/security/ir.model.access.csv index 66ba9fac2..c8305754e 100644 --- a/hotel_calendar/security/ir.model.access.csv +++ b/hotel_calendar/security/ir.model.access.csv @@ -1,6 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_room_price_cache_user,hotel_calendar.model_room_pricelist_cached_user,hotel_calendar.model_room_pricelist_cached,hotel.group_hotel_user,1,1,1,1 -access_room_price_cache_call,hotel_calendar.model_room_pricelist_cached_call,hotel_calendar.model_room_pricelist_cached,hotel.group_hotel_call,1,1,1,1 access_hotel_product_pricelist_item_call,hotel_calendar.pricelist_item_call,hotel_calendar.model_product_pricelist_item,hotel.group_hotel_call,1,1,1,1 access_hotel_product_pricelist_item_user,hotel_calendar.pricelist_item_use,hotel_calendar.model_product_pricelist_item,hotel.group_hotel_user,1,1,1,1 access_hotel_calendar,access_hotel_calendar,model_hotel_calendar,base.group_user,1,0,0,0 diff --git a/hotel_calendar/static/src/css/view.css b/hotel_calendar/static/src/css/view.css index 6bcf10640..daf1b6801 100644 --- a/hotel_calendar/static/src/css/view.css +++ b/hotel_calendar/static/src/css/view.css @@ -22,8 +22,10 @@ } #pms-menu { - padding: 0 0.2em !important; overflow: auto; + background-color: #f8f8f8; + height: 100%; + padding: 0 2.5em; } #pms-menu .input-group span, #pms-menu input { @@ -41,6 +43,32 @@ overflow: auto; } +#pms-search { + position: fixed; + z-index: 9; +} + +#hcal_widget { + max-height: 100%; +} + +#multicalendar_panels { + background-color: white; + border-left: 1px solid #ddd; +} + +.nav-tabs > li > a { + border-radius: 0; +} + +.nav-tabs { + padding-top: 4px; +} + +.navbar-default { + border-color: #f8f8f8; +} + button .led { position: absolute; display: inline-block; @@ -90,6 +118,13 @@ input#bookings_search { margin-top: 1em; } +#pms-menu .menu-filter-box h4 { + cursor: pointer; +} +#pms-menu .menu-filter-box h4 i { + transition: all 0.5s ease; +} + #pms-menu .button-box { text-align: left; min-height: 3.5em; @@ -173,7 +208,7 @@ input#bookings_search { max-width: 550px; } .popover .container { - max-width: -webkit-fill-available; + max-width: 100%; } .popover .container p { margin-top: 3px; @@ -230,4 +265,4 @@ input#bookings_search { display: flex; } -/* TODO: Use Odoo Colours based on http://www.odoo.com/openerp_website/static/src/less/variables.less */ \ No newline at end of file +/* TODO: Use Odoo Colours based on http://www.odoo.com/openerp_website/static/src/less/variables.less */ diff --git a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_controller.js b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_controller.js index 1b3f68524..ba1b41648 100644 --- a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_controller.js +++ b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_controller.js @@ -104,9 +104,10 @@ var PMSCalendarController = AbstractController.extend({ /** DO MAGIC **/ var hcal_dates = this.renderer.get_view_filter_dates(); var oparams = [ - hcal_dates[0].format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), + hcal_dates[0].subtract(1, 'd').format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), hcal_dates[1].format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT) ]; + this.model.get_calendar_data(oparams).then(function(results){ self._multi_calendar._days_tooltips = results['events']; self._multi_calendar._reserv_tooltips = results['tooltips']; @@ -191,7 +192,7 @@ var PMSCalendarController = AbstractController.extend({ self._view_options['days'] = 7; } - var date_begin = moment().startOf('day'); + var date_begin = moment().local().startOf('day'); var days = self._view_options['days']; if (self._view_options['days'] === 'month') { days = date_begin.daysInMonth(); @@ -205,7 +206,6 @@ var PMSCalendarController = AbstractController.extend({ $dateTimePickerBegin.data("DateTimePicker").date(date_begin); $dateEndDays.val(self._view_options['days']); - //self.renderer.init_calendar_view(); self._load_calendars(); self._assign_view_events(); }); @@ -213,23 +213,24 @@ var PMSCalendarController = AbstractController.extend({ _reload_active_calendar: function() { var self = this; - var filterDates = this.renderer.get_view_filter_dates(); var active_calendar = this._multi_calendar.get_active_calendar(); + var filterDates = active_calendar.getDates(); // Clip dates var dfrom = filterDates[0].clone(), dto = filterDates[1].clone(); if (filterDates[0].isBetween(this._last_dates[0], this._last_dates[1], 'days') && filterDates[1].isAfter(this._last_dates[1], 'day')) { - dfrom = this._last_dates[1].clone().local().startOf('day').utc(); + dfrom = this._last_dates[1].clone(); } else if (this._last_dates[0].isBetween(filterDates[0], filterDates[1], 'days') && this._last_dates[1].isAfter(filterDates[0], 'day')) { - dto = this._last_dates[0].clone().local().endOf('day').utc(); + dto = this._last_dates[0].clone(); } var oparams = [ - dfrom.format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), + dfrom.subtract(1, 'd').format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), dto.format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), false ]; + this.model.get_calendar_data(oparams).then(function(results){ var reservs = []; for (var r of results['reservations']) { @@ -238,12 +239,14 @@ var PMSCalendarController = AbstractController.extend({ } self._multi_calendar._reserv_tooltips = _.extend(this._multi_calendar._reserv_tooltips, results['tooltips']); - self._multi_calendar.merge_days_tooltips(results['events']); - self._multi_calendar.merge_pricelist(results['pricelist'], active_calendar); - self._multi_calendar.merge_restrictions(results['restrictions'], active_calendar); - self._multi_calendar.merge_reservations(reservs, active_calendar); + _.defer(function(){ + self._multi_calendar.merge_days_tooltips(results['events']); + self._multi_calendar.merge_pricelist(results['pricelist'], active_calendar); + self._multi_calendar.merge_restrictions(results['restrictions'], active_calendar); + self._multi_calendar.merge_reservations(reservs, active_calendar); - self._multi_calendar._assign_extra_info(active_calendar); + self._multi_calendar._assign_extra_info(active_calendar); + }); }.bind(this)).then(function(){ self._last_dates = filterDates; }); @@ -255,10 +258,10 @@ var PMSCalendarController = AbstractController.extend({ var $dateEndDays = this.renderer.$el.find('#pms-menu #date_end_days'); $dateTimePickerBegin.on("dp.change", function (e) { $dateTimePickerBegin.data("DateTimePicker").hide(); - self._on_change_filter_date(true); + self._on_change_filter_date(); }); $dateEndDays.on("change", function (e) { - self._on_change_filter_date(false); + self._on_change_filter_date(); }); this.renderer.$el.find("#btn_swap button").on('click', function(ev){ @@ -278,6 +281,7 @@ var PMSCalendarController = AbstractController.extend({ } else { active_calendar.setSwapMode(HotelCalendar.MODE.NONE); $("#btn_swap span.ntext").html(_t("Start Swap")); + $led.removeClass('led-green'); $led.removeClass('led-blue'); $led.addClass('led-disabled'); } @@ -286,7 +290,6 @@ var PMSCalendarController = AbstractController.extend({ this.renderer.$el.find('#pms-menu #btn_action_overbooking button').on('click', function(ev){ var active_calendar = self._multi_calendar.get_active_calendar(); active_calendar.toggleOverbookingsVisibility(); - active_calendar.addReservations(self._multi_calendar._dataset['reservations']); if (active_calendar.options.showOverbookings) { $(this).find('.led').removeClass('led-disabled'); $(this).find('.led').addClass('led-enabled'); @@ -294,12 +297,12 @@ var PMSCalendarController = AbstractController.extend({ $(this).find('.led').addClass('led-disabled'); $(this).find('.led').removeClass('led-enabled'); } + active_calendar.addReservations(_.reject(self._multi_calendar._dataset['reservations'], {overbooking:false})); }); this.renderer.$el.find('#pms-menu #btn_action_cancelled button').on('click', function(ev){ var active_calendar = self._multi_calendar.get_active_calendar(); active_calendar.toggleCancelledVisibility(); - active_calendar.addReservations(self._multi_calendar._dataset['reservations']); if (active_calendar.options.showCancelled) { $(this).find('.led').removeClass('led-disabled'); $(this).find('.led').addClass('led-enabled'); @@ -307,6 +310,7 @@ var PMSCalendarController = AbstractController.extend({ $(this).find('.led').addClass('led-disabled'); $(this).find('.led').removeClass('led-enabled'); } + active_calendar.addReservations(_.reject(self._multi_calendar._dataset['reservations'], {cancelled:false})); }); this.renderer.$el.find('#pms-menu #btn_action_divide button').on('click', function(ev){ @@ -362,6 +366,12 @@ var PMSCalendarController = AbstractController.extend({ }); }); + this.renderer.$el.find('#pms-menu .menu-filter-box #filters').on('show.bs.collapse', function(ev){ + self.renderer.$el.find('#pms-menu .menu-filter-box h4 i.fa').css({transform: 'rotate(90deg)'}); + }).on('hide.bs.collapse', function(ev){ + self.renderer.$el.find('#pms-menu .menu-filter-box h4 i.fa').css({transform: 'rotate(0deg)'}); + }); + this._multi_calendar.on('tab_changed', function(ev, active_index){ if (active_index) { self._refresh_view_options(active_index); @@ -374,26 +384,43 @@ var PMSCalendarController = AbstractController.extend({ this._multi_calendar.on_calendar('hcalOnSavePricelist', function(ev){ self.savePricelist(ev.detail.calendar_obj, ev.detail.pricelist_id, ev.detail.pricelist); }); + $('.hcal-reservation noselect').popover(); + var _destroy_and_clear_popover_mark = function(ev){ + $(".marked-as-having-a-popover").popover('destroy'); + $('.hcal-reservation').removeClass("marked-as-having-a-popover"); + }; + this._multi_calendar.on_calendar('hcalOnClickReservation', function(ev){ + var active_calendar = self._multi_calendar.get_active_calendar(); + if ( active_calendar.getSelectionMode() !== HotelCalendar.MODE.NONE + || active_calendar.getSwapMode() !== HotelCalendar.MODE.NONE ) + { + return; + } if (ev.detail.reservationObj) { var tp = self._multi_calendar._reserv_tooltips[ev.detail.reservationObj.id]; var qdict = self._generate_reservation_tooltip_dict(tp); $(".marked-as-having-a-popover").popover('destroy'); $(ev.detail.reservationDiv).addClass('marked-as-having-a-popover'); var $reservationPopover = $(ev.detail.reservationDiv).popover({ - trigger: 'click focus', + trigger: 'manual', container: 'body', animation: false, html: true, placement: 'bottom', - /* title: "Come'n popovers!", */ content: QWeb.render('HotelCalendar.TooltipReservation', qdict) }).popover('show'); + /* destroy popover if mouse click is done out the popover */ + $(document).click(function(e){ + if( $(e.target).closest(".popover-content").length == 0 && $(e.target).hasClass("marked-as-having-a-popover") == false ) { + _destroy_and_clear_popover_mark(); + } + }); /* add actions */ $reservationPopover.data('bs.popover').tip().find(".btn_popover_open_folio").on('click', {folio_id: ev.detail.reservationObj._userData.folio_id}, function(ev){ - $(".marked-as-having-a-popover").popover('destroy'); + _destroy_and_clear_popover_mark(); self.do_action({ type: 'ir.actions.act_window', res_model: 'hotel.folio', @@ -403,7 +430,7 @@ var PMSCalendarController = AbstractController.extend({ }); $reservationPopover.data('bs.popover').tip().find(".btn_popover_open_reservation").on('click', {reservation_id: ev.detail.reservationObj.id}, function(ev){ - $(".marked-as-having-a-popover").popover('destroy'); + _destroy_and_clear_popover_mark(); self.do_action({ type: 'ir.actions.act_window', res_model: 'hotel.reservation', @@ -412,7 +439,7 @@ var PMSCalendarController = AbstractController.extend({ }); }); $reservationPopover.data('bs.popover').tip().find(".btn_popover_close").on('click', function(ev){ - $(".marked-as-having-a-popover").popover('destroy'); + _destroy_and_clear_popover_mark(); }); } }); @@ -725,7 +752,6 @@ var PMSCalendarController = AbstractController.extend({ 'checkout_day_of_week': HotelCalendar.toMomentUTC(tp['checkout'], '').format("dddd"), 'arrival_hour': tp['arrival_hour'], 'departure_hour': tp['departure_hour'], - 'num_split': tp['num_split'], 'amount_total': Number(tp['amount_total']).toLocaleString(), 'pending_amount': Number(tp['pending_amount']).toLocaleString(), 'amount_paid': Number(tp['amount_paid']).toLocaleString(), @@ -848,19 +874,18 @@ var PMSCalendarController = AbstractController.extend({ this._multi_calendar.get_active_calendar().setDomain(HotelCalendar.DOMAIN.ROOMS, domain); }, - _on_change_filter_date: function(isStartDate) { - isStartDate = isStartDate || false; + _on_change_filter_date: function() { var $dateTimePickerBegin = this.renderer.$el.find('#pms-menu #date_begin'); var $dateEndDays = this.renderer.$el.find('#pms-menu #date_end_days'); // FIXME: Hackish onchange ignore (Used when change dates from code) if ($dateTimePickerBegin.data("ignore_onchange") || $dateEndDays.data("ignore_onchange")) { $dateTimePickerBegin.data("ignore_onchange", false); - $dateEndDays.data("ignore_onchange", false) + $dateEndDays.data("ignore_onchange", false); return true; } - var date_begin = $dateTimePickerBegin.data("DateTimePicker").date().set({'hour': 0, 'minute': 0, 'second': 0}).clone().utc(); + var date_begin = $dateTimePickerBegin.data("DateTimePicker").date().set({'hour': 0, 'minute': 0, 'second': 0}).clone(); var active_calendar = this._multi_calendar.get_active_calendar(); if (active_calendar && date_begin) { @@ -869,7 +894,7 @@ var PMSCalendarController = AbstractController.extend({ days = date_begin.daysInMonth(); } var date_end = date_begin.clone().add(days, 'd'); - if (!date_begin.isSame(this._last_dates[0].clone().utc(), 'd') || !date_end.isSame(this._last_dates[1].clone().utc(), 'd')) { + if (!date_begin.isSame(this._last_dates[0].clone(), 'd') || !date_end.isSame(this._last_dates[1].clone(), 'd')) { active_calendar.setStartDate(date_begin, $dateEndDays.val(), false, function(){ this._reload_active_calendar(); }.bind(this)); diff --git a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js index a236e01ff..d8bfa791b 100644 --- a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js +++ b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_model.js @@ -20,15 +20,6 @@ return AbstractModel.extend({ this.modelManagementName = 'hotel.calendar.management' }, - save_pricelist: function(params) { - return this._rpc({ - model: this.modelManagementName, - method: 'save_changes', - args: params, - context: Session.user_context, - }); - }, - swap_reservations: function(fromIds, toIds) { return this._rpc({ model: this.modelName, @@ -39,11 +30,33 @@ return AbstractModel.extend({ }, get_calendar_data: function(oparams) { + var dialog = bootbox.dialog({ + message: '
Getting Calendar Data From Server...
', + onEscape: false, + closeButton: false, + size: 'small', + backdrop: false, + }); return this._rpc({ model: this.modelName, method: 'get_hcalendar_all_data', args: oparams, context: Session.user_context, + }, { + xhr: function () { + var xhr = new window.XMLHttpRequest(); + //Download progress + xhr.addEventListener("readystatechange", function() { + if (this.readyState == this.DONE) { + console.log(`[HotelCalendar] Downloaded ${(parseInt(xhr.getResponseHeader("Content-Length"), 10)/1024).toFixed(3)}KiB of data`); + } + }, false); + return xhr; + }, + success: function() { + dialog.modal('hide'); + }, + shadow: true, }); }, @@ -151,13 +164,15 @@ return AbstractModel.extend({ }, save_changes: function(params) { - params.splice(0, 0, false); // FIXME: ID=False because first parameter its an integer + //params.splice(0, 0, false); // FIXME: ID=False because first parameter its an integer + //console.log(params); return this._rpc({ model: 'hotel.calendar.management', method: 'save_changes', args: params, - context: Session.user_context, + //context: Session.user_context, }) } }); + }); diff --git a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_renderer.js b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_renderer.js index 94f7cb35c..b3b70e51d 100644 --- a/hotel_calendar/static/src/js/views/calendar/hotel_calendar_renderer.js +++ b/hotel_calendar/static/src/js/views/calendar/hotel_calendar_renderer.js @@ -48,12 +48,12 @@ var HotelCalendarView = AbstractRenderer.extend({ get_view_filter_dates: function () { var $dateTimePickerBegin = this.$el.find('#pms-menu #date_begin'); var $dateEndDays = this.$el.find('#pms-menu #date_end_days'); - var date_begin = $dateTimePickerBegin.data("DateTimePicker").date().set({'hour': 0, 'minute': 0, 'second': 0}).clone().utc(); + var date_begin = $dateTimePickerBegin.data("DateTimePicker").date().clone(); var days = $dateEndDays.val(); if (days === 'month') { days = date_begin.daysInMonth(); } - var date_end = date_begin.clone().add(days, 'd').set({'hour': 23, 'minute': 59, 'second': 59}).clone().utc(); + var date_end = date_begin.clone().add(days, 'd'); return [date_begin, date_end]; }, @@ -112,6 +112,10 @@ var HotelCalendarView = AbstractRenderer.extend({ //language : moment.locale(), locale : moment.locale(), format : HotelConstants.L10N_DATE_MOMENT_FORMAT, + widgetPositioning:{ + horizontal: 'auto', + vertical: 'bottom' + } }; var $dateTimePickerBegin = this.$el.find('#pms-menu #date_begin'); var $dateEndDays = this.$el.find('#pms-menu #date_end_days'); diff --git a/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_model.js b/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_model.js index bdbd9f358..4e89fb0c8 100644 --- a/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_model.js +++ b/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_model.js @@ -21,12 +21,12 @@ return AbstractModel.extend({ }, save_changes: function (params) { - params.splice(0, 0, false); // FIXME: ID=False because first parameter its an integer + //params.splice(0, 0, false); // FIXME: ID=False because first parameter its an integer return this._rpc({ model: this.modelName, method: 'save_changes', args: params, - context: Session.user_context, + //context: Session.user_context, }); }, diff --git a/hotel_calendar/static/src/js/widgets/MultiCalendar.js b/hotel_calendar/static/src/js/widgets/MultiCalendar.js index 58c701eff..086cde1f5 100644 --- a/hotel_calendar/static/src/js/widgets/MultiCalendar.js +++ b/hotel_calendar/static/src/js/widgets/MultiCalendar.js @@ -97,12 +97,8 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) { var active_calendar = this.get_active_calendar(); if (active_calendar) { setTimeout(function(calendar){ - for (var reserv of calendar._reservations) { - var style = window.getComputedStyle(reserv._html, null); - if (parseInt(style.width, 10) < 15 || parseInt(style.height, 10) < 15 || parseInt(style.top, 10) === 0) { - this.get_active_calendar()._updateReservation(reserv); - } - } + calendar._updateOffsets(); + calendar._updateReservations(false); }.bind(this, active_calendar), 200); } }, @@ -333,28 +329,27 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) { _assign_extra_info: function(calendar) { var self = this; - $(calendar.etable).find('.hcal-cell-room-type-group-item.btn-hcal-3d').on("mouseenter", function(){ - var $this = $(this); - var room = calendar.getRoom($this.parent().data("hcalRoomObjId")); - if (room.overbooking) { - $this.tooltip({ - animation: true, - html: true, - placement: 'right', - title: QWeb.render('HotelCalendar.TooltipRoomOverbooking', {'name': room.number}) - }).tooltip('show'); - return; + $(calendar.etable).find('.hcal-cell-room-type-group-item.btn-hcal-left').on("mouseenter", function(){ + var $this = $(this); + var room = calendar.getRoom($this.parent().data("hcalRoomObjId")); + if (room.overbooking) { + $this.tooltip({ + animation: true, + html: true, + placement: 'right', + title: QWeb.render('HotelCalendar.TooltipRoomOverbooking', {'name': room.number}) + }).tooltip('show'); } else { - var qdict = { - 'room_type_name': room.getUserData('room_type_name'), - 'name': room.number - }; - $this.tooltip({ - animation: true, - html: true, - placement: 'right', - title: QWeb.render('HotelCalendar.TooltipRoom', qdict) - }).tooltip('show'); + var qdict = { + 'room_type_name': room.getUserData('room_type_name'), + 'name': room.number + }; + $this.tooltip({ + animation: true, + html: true, + placement: 'right', + title: QWeb.render('HotelCalendar.TooltipRoom', qdict) + }).tooltip('show'); } }); diff --git a/hotel_calendar/static/src/lib/hcalendar/css/hcalendar.css b/hotel_calendar/static/src/lib/hcalendar/css/hcalendar.css index 941c7da5d..0f9b6def8 100644 --- a/hotel_calendar/static/src/lib/hcalendar/css/hcalendar.css +++ b/hotel_calendar/static/src/lib/hcalendar/css/hcalendar.css @@ -41,25 +41,15 @@ vertical-align: middle; } -#pms-search { - position: fixed; - z-index: 9; -} - -#pms-menu { - background-color: white; - height: 100%; -} - -#hcal_widget { - max-height: 100%; - background-color: white; -} - #cal-pag-prev-plus, #cal-pag-prev, #cal-pag-selector, #cal-pag-next, #cal-pag-next-plus { min-height: 0px; } +.hcalendar-container { + display: flex; + flex-flow: column; +} + .table-reservations-header { order: 1; flex-grow: 1; @@ -85,6 +75,7 @@ flex-grow: 2; overflow-y: scroll; overflow-x: hidden; + font-size: 12px; max-height: 20vh; } .table-calcs input, .table-reservations-header input { @@ -96,17 +87,13 @@ text-align: center; } -.hcal-event-day { - background-color: #6ebcff !important; -} - .btn-hcal { } .btn-hcal.hcal-cell-current-day { background-color: #7c7bad66; color: #654a37; } .btn-hcal.hcal-cell-end-week { - background-color: #CBE5F8; + background-color: #EDEDED83; } .btn-hcal-3d { border: 1px solid #eaeaea; @@ -175,7 +162,7 @@ height: 100%; border-collapse: collapse !important; border: 0 dotted #eaeaea; - border-width: 2px 0; + border-width: 1px 0; } /*.hcal-table-day tr:first-child td{ border: 1px solid #727272 !important; @@ -191,21 +178,21 @@ }*/ .hcal-table-day td { padding: 2px; - height: 2.3em; + height: 3em; font-size: 7px; vertical-align: middle; font-weight: bold; - border: 1px solid #eaeaea !important; + border: 0.5px solid #eaeaea !important; } .hcal-table-day td:hover:not(.hcal-cell-highlight):not(.hcal-cell-invalid) { background-color: #FCFEE1 !important; } .hcal-cell-current-day { - background-color: #FAFFD9; + background-color: #7C7BADA5; } .hcal-cell-end-week { - background-color: #ECF3FF; + background-color: #EDEDED83; } .hcal-cell-day-selector { @@ -233,6 +220,7 @@ text-align: center !important; vertical-align: middle; white-space: nowrap; + font-size: 12px; } .hcal-cell-month:nth-child(n+3) { border-left-width: 2px !important; @@ -303,7 +291,7 @@ color: white; white-space: nowrap; overflow: hidden; - z-index:8; + z-index: 8; } .hcal-reservation:hover { background-color: #4e97bf; @@ -407,21 +395,25 @@ } .hcal-reservation-divide-l { - background-color: transparent !important; + background-color: transparent; border: 2px dashed black; cursor: copy; pointer-events: none; - border-color: black !important; - border-right-style: solid !important; + border-color: black; + border-right-style: solid; + position: absolute; + z-index: 9; } .hcal-reservation-divide-r { - background-color: transparent !important; + background-color: transparent; border: 2px dashed black; cursor: copy; pointer-events: none; - border-color: black !important; - border-left-style: solid !important; + border-color: black; + border-left-style: solid; + position: absolute; + z-index: 9; } .hcal-row-room-type-group-item { @@ -474,6 +466,7 @@ td.hcal-cell-room-type-group-item { vertical-align: middle; font-size: x-small; white-space: nowrap; + text-overflow: ellipsis; } td.hcal-cell-room-type-group-item:last-child { border-right-width: 2px; @@ -491,6 +484,7 @@ td.hcal-cell-room-type-group-item:last-child { td.hcal-cell-header-day { padding: 0; vertical-align: middle; + font-size: 10px; } td.hcal-cell-month-day-occupied { @@ -522,8 +516,7 @@ td.hcal-cell-month-day-occupied { } .hcal-unused-zone { - border: 1px solid #444; - border-radius: 0px; + border-radius: 0px; } .input-price { diff --git a/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js b/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js index cb938b527..b340493fa 100644 --- a/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js +++ b/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js @@ -88,6 +88,31 @@ function HotelCalendar(/*String*/querySelector, /*Dictionary*/options, /*List*/p this._lazyModeReservationsSelection = false; // Store Info About Timer for Selection Action this._domains = {}; // Store domains for filter rooms & reservations this._divideDivs = false; + this._extraRowIndicators = ['EX-', '/#']; + + // Support + var self = this; + this._supportsPassive = false; + try { + var opts = Object.defineProperty({}, 'passive', { + get: function() { + self._supportsPassive = true; + } + }); + window.addEventListener("testPassive", null, opts); + window.removeEventListener("testPassive", null, opts); + } catch (e) {} + + // Calculate Capacities + this._roomCapacityTotal = 0; + this._roomCapacities = {}; + this._roomsMap = _.groupBy(this.options.rooms, 'type'); + var room_types = this.getRoomTypes(); + for (var rt of room_types) { + this._roomsMap[rt] = _.filter(this._roomsMap[rt], {overbooking: false, cancelled: false}); + this._roomCapacities[rt] = _.reduce(this._roomsMap[rt], function(memo, tr){ return memo + (tr.shared?tr.capacity:1); }, 0); + this._roomCapacityTotal += this._roomCapacities[rt]; + } /***/ this._reset_action_reservation(); @@ -222,6 +247,12 @@ HotelCalendar.prototype = { } }, + _updateOffsets: function() { + this._etableOffset = this.loopedOffsetOptimized(this.etable); + this._eOffset = this.loopedOffsetOptimized(this.e); + this._edivrOffset = this.loopedOffsetOptimized(this.edivr); + }, + //==== DOMAINS setDomain: function(/*Int*/section, /*Array*/domain) { if (this._domains[section] !== domain) { @@ -276,6 +307,7 @@ HotelCalendar.prototype = { if (reservations.length > 0 && !(reservations[0] instanceof HReservation)) { console.warn("[HotelCalendar][addReservations] Invalid Reservation definition!"); } else { + var isCalendarEmpty = (this._reservations.length>0); // Merge var addedReservations = []; for (var r of reservations) { @@ -287,6 +319,7 @@ HotelCalendar.prototype = { continue; } + var hasCreatedExtraRows = false; r = r.clone(); // HOT-FIX: Multi-Calendar Support r.room = this.getRoom(r.room_id, r.overbooking || r.cancelled, r.id); // need create a overbooking row? @@ -297,6 +330,7 @@ HotelCalendar.prototype = { cancelled: r.cancelled, }); this.createExtraRoomRow(r.room); + hasCreatedExtraRows = true; } else { console.warn(`Can't found the room '${r.room_id}' for the reservation '${r.id}' (${r.title})!`); continue; @@ -324,6 +358,9 @@ HotelCalendar.prototype = { // Create & Render New Reservations _.defer(function(reservs){ + // Update offsets (New Rooms change positions?) + this._updateOffsets(); + var unusedZones = this._createUnusedZones(reservs); // Add Unused Zones this._reservations = this._reservations.concat(unusedZones); @@ -396,14 +433,14 @@ HotelCalendar.prototype = { return day.isBetween(item.startDate, item.endDate, 'day', inclusivity) && (typeof nbed === 'undefined' || item._beds.includes(nbed)) && ((includeUnusedZones && item.unusedZone) || !item.unusedZone) && - item !== ignoreThis; + item !== ignoreThis && !item.overbooking && !item.cancelled; }); } else { return _.filter(this._reservations, function(item){ return day.isBetween(item.startDate, item.endDate, 'day', inclusivity) && (typeof nbed === 'undefined' || item._beds.includes(nbed)) && ((includeUnusedZones && item.unusedZone) || !item.unusedZone) && - item !== ignoreThis; + item !== ignoreThis && !item.overbooking && !item.cancelled; }); } }, @@ -496,21 +533,25 @@ HotelCalendar.prototype = { var numBeds = +limits.right.dataset.hcalBedNum - +limits.left.dataset.hcalBedNum; var ndate = reservation.startDate.clone().local(); for (var i=0; i 0) - humantext += `Min. Stay: ${restr[0]}\n`; - if (restr[1] > 0) - humantext += `Min. Stay Arrival: ${restr[1]}\n`; - if (restr[2] > 0) - humantext += `Max. Stay: ${restr[2]}\n`; - if (restr[3] > 0) - humantext += `Max. Stay Arrival: ${restr[3]}\n`; - if (restr[4]) - humantext += `Closed: ${restr[4]}\n`; - if (restr[5]) - humantext += `Closed Arrival: ${restr[5]}\n`; - if (restr[6]) - humantext += `Closed Departure: ${restr[6]}`; - cell.title = humantext; - } - else { - cell.classList.remove('hcal-restriction-room-day'); - cell.title = ''; - } + var date = this.options.startDate.clone().startOf('day'); + for (var i=0; i<=this.options.days; ++i) { + var dd = date.add(1, 'd'); + var date_str = dd.format(HotelCalendar.DATE_FORMAT_SHORT_); + if (date_str in this._restrictions[room.price[1]]) { + var restr = this._restrictions[room.price[1]][date_str]; + if (restr) { + var cell = this.getMainCell(dd, room.type, room.number); + if (cell) { + if (restr[0] || restr[1] || restr[2] || restr[3] || restr[4] || restr[5] || restr[6]) { + cell.classList.add('hcal-restriction-room-day'); + var humantext = "Restrictions:\n"; + if (restr[0] > 0) + humantext += `Min. Stay: ${restr[0]}\n`; + if (restr[1] > 0) + humantext += `Min. Stay Arrival: ${restr[1]}\n`; + if (restr[2] > 0) + humantext += `Max. Stay: ${restr[2]}\n`; + if (restr[3] > 0) + humantext += `Max. Stay Arrival: ${restr[3]}\n`; + if (restr[4]) + humantext += `Closed: ${restr[4]}\n`; + if (restr[5]) + humantext += `Closed Arrival: ${restr[5]}\n`; + if (restr[6]) + humantext += `Closed Departure: ${restr[6]}`; + cell.title = humantext; + } + else { + cell.classList.remove('hcal-restriction-room-day'); + cell.title = ''; } } } @@ -855,51 +869,22 @@ HotelCalendar.prototype = { //==== DETAIL CALCS calcDayRoomTypeReservations: function(/*String,MomentObject*/day, /*String*/room_type) { var day = HotelCalendar.toMoment(day); - if (!day) { - return false; - } - - var reservs = this.getDayRoomTypeReservations(day, room_type); - var num_rooms = this.getRoomsCapacityByType(room_type); - for (var r of reservs) { - if (r.unusedZone || r.overbooking || r.cancelled) { - continue; - } - num_rooms -= (r.room && r.room.shared)?r.getTotalPersons(false):1; - } + if (!day) { return false; } + var num_rooms = this._roomCapacities[room_type]; + num_rooms -= _.reduce(this.getDayRoomTypeReservations(day, room_type), function(memo, r){ return memo + ((r.room && r.room.shared)?r.getTotalPersons(false):1); }, 0); return num_rooms; }, calcDayRoomTotalReservations: function(/*String,MomentObject*/day) { var day = HotelCalendar.toMoment(day); - if (!day) { - return false; - } - - var reservs = this.getReservationsByDay(day, true); - var num_rooms = this.getRoomsCapacityTotal(); - for (var r of reservs) { - if (r.unusedZone || r.overbooking || r.cancelled) { - continue; - } - num_rooms -= (r.room && r.room.shared)?r.getTotalPersons(false):1; - } + if (!day) { return false; } + var num_rooms = this._roomCapacityTotal; + num_rooms -= _.reduce(this.getReservationsByDay(day, true), function(memo, r){ return memo + ((r.room && r.room.shared)?r.getTotalPersons(false):1); }, 0); return num_rooms; }, - calcReservationOccupation: function(/*String,MomentObject*/day, /*String*/room_type) { - var day = HotelCalendar.toMoment(day); - if (!day) { - return false; - } - - var reservs = this.getReservationsByDay(day, true); - return Math.round(reservs.length/_.filter(this.options.rooms, function(item){ return !item.overbooking && !item.cancelled; }).length*100.0); - }, - - /** PRIVATE MEMBERS **/ //==== MAIN FUNCTIONS @@ -946,17 +931,21 @@ HotelCalendar.prototype = { var scrollThrottle = _.throttle(this._updateOBIndicators.bind(this), 100); + + this.edivcontainer = document.createElement("div"); + this.edivcontainer.classList.add('hcalendar-container'); + // Reservations Table this.edivrh = document.createElement("div"); this.edivrh.classList.add('table-reservations-header'); - this.e.appendChild(this.edivrh); + this.edivcontainer.appendChild(this.edivrh); this.etableHeader = document.createElement("table"); this.etableHeader.classList.add('hcal-table'); this.etableHeader.classList.add('noselect'); this.edivrh.appendChild(this.etableHeader); this.edivr = document.createElement("div"); this.edivr.classList.add('table-reservations'); - this.e.appendChild(this.edivr); + this.edivcontainer.appendChild(this.edivr); this.etable = document.createElement("table"); this.etable.classList.add('hcal-table'); this.etable.classList.add('noselect'); @@ -965,14 +954,14 @@ HotelCalendar.prototype = { // Detail Calcs Table this.edivch = document.createElement("div"); this.edivch.classList.add('table-calcs-header'); - this.e.appendChild(this.edivch); + this.edivcontainer.appendChild(this.edivch); this.edtableHeader = document.createElement("table"); this.edtableHeader.classList.add('hcal-table'); this.edtableHeader.classList.add('noselect'); this.edivch.appendChild(this.edtableHeader); this.edivc = document.createElement("div"); this.edivc.classList.add('table-calcs'); - this.e.appendChild(this.edivc); + this.edivcontainer.appendChild(this.edivc); this.edtable = document.createElement("table"); this.edtable.classList.add('hcal-table'); this.edtable.classList.add('noselect'); @@ -983,7 +972,10 @@ HotelCalendar.prototype = { }); observer.observe(this.edivr, { childList: true }); + this.e.appendChild(this.edivcontainer); + this._updateView(); + //_.defer(function(self){ self._updateView(); }, this); this._tableCreated = true; return true; @@ -1151,10 +1143,7 @@ HotelCalendar.prototype = { row.classList.add('hcal-row-room-type-group-item'); if ((this.options.showOverbookings && itemRoom.overbooking) || (this.options.showCancelled && itemRoom.cancelled)) { var reservId = this.parseExtraRoomId(itemRoom.id)[0]; - var cnumber = itemRoom.number; - var isf = cnumber.search('EX-'); - var isfb = cnumber.search('/#'); - if (isf != -1 && isfb != -1) { cnumber = cnumber.substr(isf+3, isfb-(isf+3)); } + var cnumber = this.getExtraRoomRealNumber(itemRoom); row.setAttribute('id', this._sanitizeId(`ROW_${cnumber}_${itemRoom.type}_EXTRA${reservId}`)); row.classList.add('hcal-row-room-type-group-overbooking-item'); } else { @@ -1207,8 +1196,7 @@ HotelCalendar.prototype = { var cheight = 0.0; for (var i=0; i mainBounds.bottom) { + var eOffset = this._eOffset; + var bounds = this.loopedOffsetOptimized(reserv._html); + if (bounds.top > mainBounds.height) { var warnDiv = this.e.querySelector(`div.hcal-warn-ob-indicator[data-hcal-reservation-obj-id='${reserv.id}']`); if (!warnDiv) { var warnDiv = document.createElement("DIV"); @@ -1482,12 +1450,12 @@ HotelCalendar.prototype = { warnDiv.classList.add('hcal-warn-ob-indicator'); warnDiv.style.borderTopLeftRadius = warnDiv.style.borderTopRightRadius = "50px"; warnDiv.dataset.hcalReservationObjId = reserv.id; - this.e.appendChild(warnDiv); + this.edivcontainer.appendChild(warnDiv); var warnComputedStyle = window.getComputedStyle(warnDiv, null); - warnDiv.style.top = `${mainBounds.bottom - eOffset.top - parseInt(warnComputedStyle.getPropertyValue("height"), 10)}px`; + warnDiv.style.top = `${mainBounds.height - eOffset.top - parseInt(warnComputedStyle.getPropertyValue("height"), 10)}px`; warnDiv.style.left = `${(bounds.left + (bounds.right - bounds.left)/2.0 - parseInt(warnComputedStyle.getPropertyValue("width"), 10)/2.0) - mainBounds.left}px`; } - } else if (bounds.bottom < mainBounds.top) { + } else if (bounds.height < mainBounds.top) { var warnDiv = this.e.querySelector(`div.hcal-warn-ob-indicator[data-hcal-reservation-obj-id='${reserv.id}']`); if (!warnDiv) { var warnDiv = document.createElement("DIV"); @@ -1496,7 +1464,7 @@ HotelCalendar.prototype = { warnDiv.style.borderBottomLeftRadius = warnDiv.style.borderBottomRightRadius = "50px"; warnDiv.style.top = `${mainBounds.top - eOffset.top}px`; warnDiv.dataset.hcalReservationObjId = reserv.id; - this.e.appendChild(warnDiv); + this.edivcontainer.appendChild(warnDiv); var warnComputedStyle = window.getComputedStyle(warnDiv, null); warnDiv.style.left = `${(bounds.left + (bounds.right - bounds.left)/2.0 - parseInt(warnComputedStyle.getPropertyValue("width"), 10)/2.0) - mainBounds.left}px`; } @@ -1652,14 +1620,14 @@ HotelCalendar.prototype = { }, _updateScroll: function(/*HTMLObject*/reservationDiv) { - var reservBounds = reservationDiv.getBoundingClientRect(); - var mainBounds = this.edivr.getBoundingClientRect(); - var eOffset = this.e.getBoundingClientRect(); - var bottom = mainBounds.bottom - eOffset.top; + var reservBounds = this.loopedOffsetOptimized(reservationDiv); + var mainBounds = this._edivrOffset; + var eOffset = this._eOffset; + var bottom = mainBounds.height - eOffset.top; var top = mainBounds.top + eOffset.top; var offset = 10.0; var scrollDisp = 10.0; - if (reservBounds.bottom >= bottom-offset) { + if (reservBounds.height >= bottom-offset) { this.edivr.scrollBy(0, scrollDisp); } else if (reservBounds.top <= top+offset) { @@ -1756,55 +1724,71 @@ HotelCalendar.prototype = { } if (!noRefresh) { - var numBeds = (+reserv._limits.right.dataset.hcalBedNum)-(+reserv._limits.left.dataset.hcalBedNum); - reserv._beds = []; - for (var i=0; i<=numBeds; reserv._beds.push(+reserv._limits.left.dataset.hcalBedNum+i++)); + var boundsInit = this.loopedOffsetOptimized(reserv._limits.left); + var boundsEnd = this.loopedOffsetOptimized(reserv._limits.right); + var divHeight = (boundsEnd.top+boundsEnd.height)-boundsInit.top-4; + var has_changed = false; - var boundsInit = reserv._limits.left.getBoundingClientRect(); - var boundsEnd = reserv._limits.right.getBoundingClientRect(); + var reservStyles = { + backgroundColor: reserv.color, + color: reserv.colorText, + lineHeight: `${divHeight}px`, + fontSize: '12px', + top: `${boundsInit.top-this._etableOffset.top+2}px`, + left: `${boundsInit.left-this._etableOffset.left+2}px`, + width: `${(boundsEnd.left-boundsInit.left)+boundsEnd.width-4}px`, + height: `${divHeight}px`, + borderLeftWidth: '', + borderLeftStyle: '', + borderRightWidth: '', + borderRightStyle: '', + }; - reserv._html.removeAttribute('style'); + if (reserv._drawModes[0] === 'soft-start') { + has_changed = true; + reservStyles.borderLeftWidth = '3px'; + reservStyles.borderLeftStyle = 'double'; + reservStyles.left = `${boundsInit.left-this._etableOffset.left}px`; + reservStyles.width = `${(boundsEnd.left-boundsInit.left)+boundsEnd.width-2}px`; + } else if (reserv.splitted && reserv.startDate.isSame(reserv.getUserData('realDates')[0], 'day')) { + has_changed = true; + reservStyles.borderLeftWidth = '0'; + reservStyles.width = `${(boundsEnd.left-boundsInit.left)+boundsEnd.width-2}px`; + } + + if (reserv._drawModes[1] === 'soft-end') { + has_changed = true; + reservStyles.borderRightWidth = '3px'; + reservStyles.borderRightStyle = 'double'; + reservStyles.width = `${(boundsEnd.left-boundsInit.left)+boundsEnd.width-2}px`; + } else if (reserv.splitted && reserv.endDate.isSame(reserv.getUserData('realDates')[1], 'day')) { + has_changed = true; + reservStyles.borderRightWidth = '0'; + reservStyles.left = `${boundsInit.left-this._etableOffset.left-1}px`; + reservStyles.width = `${(boundsEnd.left-boundsInit.left)+boundsEnd.width-1}px`; + } if (reserv.splitted) { reserv._html.classList.add('hcal-reservation-splitted'); // 1. Use reservation ID as seed // 2. Use sinusiudal function - // 3. Only use positive values (This decrease longitude, increase frequency) + // 3. Only use positive values (This decrease longitude) // 4. Use the first 5 decimals to make the integer value // 5. Get integer value (Bitwise tilde method) // TODO: Improve pseudo-random number generator var magicNumber = ~~(Math.abs(Math.sin((reserv.getUserData('parent_reservation') || reserv.id))) * 100000); var bbColor = this._intToRgb(magicNumber); - reserv._html.style.borderColor = `rgb(${bbColor[0]},${bbColor[1]},${bbColor[2]})`; + reservStyles.borderColor = `rgb(${bbColor[0]},${bbColor[1]},${bbColor[2]})`; + + if (!has_changed) { + reservStyles.left = `${boundsInit.left-this._etableOffset.left-1}px`; + reservStyles.width = `${(boundsEnd.left-boundsInit.left)+boundsEnd.width+2}px`; + } } else { reserv._html.classList.remove('hcal-reservation-splitted'); } - reserv._html.style.backgroundColor = reserv.color; - reserv._html.style.color = reserv.colorText; - var etableOffset = this.etable.getBoundingClientRect(); - - reserv._html.style.top = `${boundsInit.top-etableOffset.top}px`; - var divHeight = (boundsEnd.bottom-etableOffset.top)-(boundsInit.top-etableOffset.top); - var fontHeight = divHeight/1.2; - if (fontHeight > 16) { fontHeight = 16; } - reserv._html.style.height = `${divHeight}px`; - reserv._html.style.lineHeight = `${divHeight+fontHeight/2.0}px`; - reserv._html.style.fontSize = `${fontHeight}px`; - reserv._html.style.left = `${boundsInit.left-etableOffset.left}px`; - reserv._html.style.width = `${(boundsEnd.left-boundsInit.left)+boundsEnd.width}px`; - if (reserv._drawModes[0] === 'soft-start') { - reserv._html.style.borderLeftWidth = '3px'; - reserv._html.style.borderLeftStyle = 'double'; - } else if (reserv.splitted && reserv.startDate.isSame(reserv.getUserData('realDates')[0], 'day')) { - reserv._html.style.borderLeftWidth = '0'; - } - if (reserv._drawModes[1] === 'soft-end') { - reserv._html.style.borderRightWidth = '3px'; - reserv._html.style.borderRightStyle = 'double'; - } else if (reserv.splitted && reserv.endDate.isSame(reserv.getUserData('realDates')[1], 'day')) { - reserv._html.style.borderRightWidth = '0'; - } + Object.assign(reserv._html.style, reservStyles); } }, @@ -1834,12 +1818,7 @@ HotelCalendar.prototype = { refToRoomNewId = `${refFromReservs.id}@${refToRoomNewId}`; if (refFromRoom.overbooking || refFromRoom.cancelled) { - // Obtain real id - var isf = refFromReservs.room.number.search('EX-'); - var isfb = refFromReservs.room.number.search('/#'); - var cnumber = refFromReservs.room.number; - if (isf != -1 && isfb != -1) { cnumber = cnumber.substr(isf+3, isfb-(isf+3)); } - + var cnumber = this.getExtraRoomRealNumber(refFromRoom); refFromRoom.id = refFromRoomNewId; var newRowId = `${this._sanitizeId(`ROW_${cnumber}_${refToRoom.type}_EXTRA${refToReservs.id}`)}`; var elms = fromRoomRow.querySelectorAll(`td[data-hcal-parent-row='${fromRoomRow.id}']`); @@ -1848,12 +1827,7 @@ HotelCalendar.prototype = { fromRoomRow.dataset.hcalRoomObjId = refFromRoom.id; } if (refToRoom.overbooking || refToRoom.cancelled) { - // Obtain real id - var isf = refToReservs.room.number.search('EX-'); - var isfb = refToReservs.room.number.search('/#'); - var cnumber = refToReservs.room.number; - if (isf != -1 && isfb != -1) { cnumber = cnumber.substr(isf+3, isfb-(isf+3)); } - + var cnumber = this.getExtraRoomRealNumber(refToRoom); refToRoom.id = refToRoomNewId; var newRowId = `${this._sanitizeId(`ROW_${cnumber}_${refFromRoom.type}_EXTRA${refFromReservs.id}`)}`; var elms = toRoomRow.querySelectorAll(`td[data-hcal-parent-row='${toRoomRow.id}']`); @@ -1959,7 +1933,7 @@ HotelCalendar.prototype = { var $this = this; reservDivs = reservDivs || this.e.querySelectorAll('div.hcal-reservation'); for (var rdiv of reservDivs) { - var bounds = rdiv.getBoundingClientRect(); + var bounds = this.loopedOffsetOptimized(rdiv); rdiv.addEventListener('mousemove', function(ev){ var posAction = $this._getRerservationPositionAction(this, ev.layerX, ev.layerY); this.style.cursor = (posAction == HotelCalendar.ACTION.MOVE_LEFT || posAction == HotelCalendar.ACTION.MOVE_RIGHT)?'col-resize':'pointer'; @@ -2060,13 +2034,13 @@ HotelCalendar.prototype = { } $this._lazyModeReservationsSelection = false; - }.bind(this, $this), 100); + }.bind(this, $this), 175); } } } }; - rdiv.addEventListener('mousedown', _funcEvent, false); - rdiv.addEventListener('touchstart', _funcEvent, false); + rdiv.addEventListener('mousedown', _funcEvent, this._supportsPassive ? { passive: true } : false); + rdiv.addEventListener('touchstart', _funcEvent, this._supportsPassive ? { passive: true } : false); rdiv.addEventListener('click', function(ev){ $this._dispatchEvent( 'hcalOnClickReservation', @@ -2109,7 +2083,7 @@ HotelCalendar.prototype = { }, _getRerservationPositionAction: function(/*HTMLObject*/elm, /*Int*/posX, /*Int*/posY) { - var bounds = elm.getBoundingClientRect(); + var bounds = this.loopedOffsetOptimized(elm); if (posX <= 5) { return HotelCalendar.ACTION.MOVE_LEFT; } else if (posX >= bounds.width-10) { return HotelCalendar.ACTION.MOVE_RIGHT; } return HotelCalendar.ACTION.MOVE_ALL; @@ -2161,26 +2135,24 @@ HotelCalendar.prototype = { return; } var cells = [ - this.edtable.querySelectorAll('td.hcal-cell-detail-room-free-type-group-item-day'), - this.edtable.querySelectorAll('td.hcal-cell-detail-room-free-total-group-item-day'), - this.edtable.querySelectorAll('td.hcal-cell-detail-room-perc-occup-group-item-day') + this.edtable.querySelectorAll('.hcal-cell-detail-room-free-type-group-item-day'), + this.edtable.querySelectorAll('.hcal-cell-detail-room-free-total-group-item-day'), + this.edtable.querySelectorAll('.hcal-cell-detail-room-perc-occup-group-item-day') ]; - var cell; - for (var i=0; i<=cells[0].length; ++i) { + for (var cell of cells[0]) { // Occupation by Type - cell = cells[0][i]; if (cell) { - var parentRow = this.$base.querySelector(`#${cell.dataset.hcalParentRow}`); var cell_date = cell.dataset.hcalDate; - var num_rooms = this.getRoomsCapacityByType(parentRow.dataset.hcalRoomType); - var num_free = this.calcDayRoomTypeReservations(cell_date, parentRow.dataset.hcalRoomType); + var num_rooms_type = this._roomCapacities[cell.parentNode.dataset.hcalRoomType]; + var num_free = this.calcDayRoomTypeReservations(cell_date, cell.parentNode.dataset.hcalRoomType); cell.innerText = num_free; - cell.style.backgroundColor = this._generateColor(num_free, num_rooms, 0.35, true, true); + cell.style.backgroundColor = this._generateColor(num_free, num_rooms_type, 0.35, true, true); } } - var num_rooms = this.getRoomsCapacityTotal(); + var cell; + var num_rooms = this._roomCapacityTotal; for (var i=0; i<=this.options.days; ++i) { // Occupation Total cell = cells[1][i]; @@ -2202,6 +2174,34 @@ HotelCalendar.prototype = { }, //==== PRICELIST + getPricelist: function(onlyNew) { + var data = {}; + + var key = this._pricelist_id; + var pricelist = this._pricelist[key]; + for (var listitem of pricelist) { + for (var i=0; i<=this.options.days; ++i) { + var dd = this.options.startDate.clone().local().startOf('day').add(i,'d').utc(); + var dd_local = dd.clone().local(); + + var input = this.edtable.querySelector(`#${this._sanitizeId(`INPUT_PRICE_${key}_${listitem.room}_${dd_local.format(HotelCalendar.DATE_FORMAT_SHORT_)}`)}`); + if (input.value !== input.dataset.orgValue) { + var value = input.value; + var orgValue = input.dataset.orgValue; + var parentCell = this.edtable.querySelector(`#${input.dataset.hcalParentCell}`); + var parentRow = this.edtable.querySelector(`#${parentCell.dataset.hcalParentRow}`); + if (!(parentRow.dataset.hcalRoomTypeId in data)) { data[parentRow.dataset.hcalRoomTypeId] = []; } + data[parentRow.dataset.hcalRoomTypeId].push({ + 'date': HotelCalendar.toMoment(parentCell.dataset.hcalDate).format('YYYY-MM-DD'), + 'price': value + }); + } + } + } + + return data; + }, + setPricelist: function(/*List*/pricelist) { this._pricelist = pricelist; this._updatePriceList(); @@ -2293,9 +2293,7 @@ HotelCalendar.prototype = { }, getDateDiffDays: function(/*MomentObject*/start, /*MomentObject*/end) { - var end_date = end.clone().startOf('day'); - var start_date = start.clone().startOf('day'); - return end_date.diff(start_date, 'days'); + return end.diff(start, 'days'); }, getDateLimits: function(/*List HReservationObject*/reservs, /*Bool?*/noCheckouts) { @@ -2393,6 +2391,10 @@ HotelCalendar.prototype = { return true; }, + getDates: function() { + return [this.options.startDate.clone(), this._endDate.clone()]; + }, + //==== EVENT FUNCTIONS _onInputChange: function(/*EventObject*/ev, /*HTMLObject*/elm) { //var parentCell = this.edtable.querySelector(`#${elm.dataset.hcalParentCell}`); @@ -2602,25 +2604,38 @@ HotelCalendar.prototype = { } if (reservs.length) { this._splitReservation = reservs[0]; - this._divideDivs = [$(this._splitReservation._html).clone().text('').appendTo(this.edivr), $(this._splitReservation._html).clone().text('').appendTo(this.edivr)]; + var defStyle = { + top: this._splitReservation._html.style.top, + left: this._splitReservation._html.style.left, + height: this._splitReservation._html.style.height, + }; + this._divideDivs = [ + $('
', {class: 'hcal-reservation-divide-l', css: defStyle}).appendTo(this.edivr), + $('
', {class: 'hcal-reservation-divide-r', css: defStyle}).appendTo(this.edivr) + ]; var diff = this.getDateDiffDays(this._splitReservation.startDate, date_cell); - this._divideDivs[0].addClass('hcal-reservation-divide-l'); - this._divideDivs[1].addClass('hcal-reservation-divide-r'); - - var etableOffset = this.etable.getBoundingClientRect(); - var boundsCell = ev.target.getBoundingClientRect(); - var beginCell = this._splitReservation._limits.left.getBoundingClientRect(); - var endCell = this._splitReservation._limits.right.getBoundingClientRect(); - var splitCell = boundsCell; - var splitDate = date_cell.clone(); + var boundsCell = false; + var beginCell = this.loopedOffsetOptimized(this._splitReservation._limits.left); + var endCell = this.loopedOffsetOptimized(this._splitReservation._limits.right); this._splitDate = date_cell.clone(); if (date_cell.isSame(this._splitReservation.endDate.clone().subtract(1, 'd'), 'day')) { - splitDate.subtract(1, 'd'); - splitCell = this.getCell(this._splitDate, this._splitReservation.room, 0); + this._splitDate.subtract(1, 'd'); + var tcell = this.getCell(this._splitDate, this._splitReservation.room, 0); + if (tcell) { + boundsCell = this.loopedOffsetOptimized(tcell); + } else { + boundsCell = false; + this._splitReservation = false; + this._splitDate = false; + } + } else { + boundsCell = this.loopedOffsetOptimized(ev.target); + } + if (boundsCell) { + this._divideDivs[0][0].style.width = `${(boundsCell.left-beginCell.left)+boundsCell.width}px`; + this._divideDivs[1][0].style.left = `${(boundsCell.left-this._etableOffset.left)+boundsCell.width}px`; + this._divideDivs[1][0].style.width = `${(endCell.left-boundsCell.left)}px`; } - this._divideDivs[0][0].style.width = `${(splitCell.left-beginCell.left)+splitCell.width}px`; - this._divideDivs[1][0].style.left = `${(splitCell.left-etableOffset.left)+splitCell.width}px`; - this._divideDivs[1][0].style.width = `${(endCell.left-splitCell.left)}px`; } else { this._splitReservation = false; this._splitDate = false; @@ -2688,6 +2703,7 @@ HotelCalendar.prototype = { hasInvalidLink = !hasInvalidLink && r._html.classList.contains('hcal-reservation-invalid'); r._html.classList.remove('hcal-reservation-action'); r._html.classList.remove('hcal-reservation-invalid'); + r._html.style.visibility = ''; } } @@ -2744,10 +2760,41 @@ HotelCalendar.prototype = { onMainResize: function(/*EventObject*/ev) { _.defer(function(){ + this._updateOffsets(); this._updateReservations(); }.bind(this)); }, + //=== OPTIMIZED OFFSET + // Method from https://jsperf.com/offset-vs-getboundingclientrect/7 + loopedOffsetOptimized: function (elem) { + var offsetLeft = elem.offsetLeft + , offsetTop = elem.offsetTop + , offsetWidth = elem.offsetWidth + , offsetHeight = elem.offsetHeight + , lastElem = elem; + + while (elem = elem.offsetParent) { + if (elem === document.body) { //from my observation, document.body always has scrollLeft/scrollTop == 0 + break; + } + offsetLeft += elem.offsetLeft; + offsetTop += elem.offsetTop; + lastElem = elem; + } + // if (lastElem && lastElem.style.position === 'fixed') { //slow - http://jsperf.com/offset-vs-getboundingclientrect/6 + // //if(lastElem !== document.body) { //faster but does gives false positive in Firefox + // offsetLeft += window.pageXOffset || document.documentElement.scrollLeft; + // offsetTop += window.pageYOffset || document.documentElement.scrollTop; + // } + return { + left: offsetLeft, + top: offsetTop, + width: offsetWidth, + height: offsetHeight, + }; + }, + //==== COLOR FUNCTIONS (RANGE: 0.0|1.0) _intToRgb: function(/*Int*/RGBint) { return [(RGBint >> 16) & 255, (RGBint >> 8) & 255, RGBint & 255]; diff --git a/hotel_calendar/static/src/xml/hotel_calendar_view.xml b/hotel_calendar/static/src/xml/hotel_calendar_view.xml index 78b6d4498..227d916f8 100644 --- a/hotel_calendar/static/src/xml/hotel_calendar_view.xml +++ b/hotel_calendar/static/src/xml/hotel_calendar_view.xml @@ -113,24 +113,26 @@