diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 24d359851..078113490 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -960,7 +960,9 @@ class HotelReservation(models.Model): master_reservation = self.parent_reservation or self splitted_reservs = self.env['hotel.reservation'].search([ + '|', ('splitted', '=', True), + ('id', '=', master_reservation.id), ('folio_id', '=', self.folio_id.id), '|', ('parent_reservation', '=', master_reservation.id), @@ -1006,14 +1008,7 @@ class HotelReservation(models.Model): else: tprice[0] += rline.price - record.write({ - 'checkout': new_start_date_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), - 'price_total': tprice[0], - 'splitted': True, - 'reservation_line_ids': reservation_lines[0], - }) - parent_res = record.parent_reservation or \ - record + parent_res = record.parent_reservation or record vals.update({ 'splitted': True, 'price_total': tprice[1], @@ -1022,83 +1017,85 @@ class HotelReservation(models.Model): 'discount': parent_res.discount, 'reservation_line_ids': reservation_lines[1], }) - reservation_copy = self.env['hotel.reservation'].create(vals) + reservation_copy = self.env['hotel.reservation'].with_context({ + 'ignore_avail_restrictions': True}).create(vals) if not reservation_copy: raise ValidationError(_("Unexpected error copying record. \ Can't split reservation!")) + record.write({ + 'checkout': new_start_date_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT), + 'price_total': tprice[0], + 'splitted': True, + 'reservation_line_ids': reservation_lines[0], + }) return True @api.multi - def unify(self): - # FIXME Remove product inheritance - pass - # self.ensure_one() - # if not self.splitted: - # raise ValidationError(_("This reservation can't be unified")) - # - # master_reservation = self.parent_reservation or self - # self_is_master = (master_reservation == self) - # - # splitted_reservs = self.env['hotel.reservation'].search([ - # ('splitted', '=', True), - # ('folio_id', '=', self.folio_id.id), - # '|', - # ('parent_reservation', '=', master_reservation.id), - # ('id', '=', master_reservation.id) - # ]) - # - # rooms_products = splitted_reservs.mapped('product_id.id') - # if len(rooms_products) > 1 or \ - # (len(rooms_products) == 1 - # and master_reservation.product_id.id != rooms_products[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 - # for reserv in splitted_reservs: - # if last_checkout < reserv.checkout: - # last_checkout = reserv.checkout - # - # # Agrupate reservation lines - # reservation_line_ids = splitted_reservs.mapped('reservation_line_ids') - # reservation_line_ids.sorted(key=lambda r: r.date) - # rlines = [(5, False, False)] - # tprice = 0.0 - # for rline in reservation_line_ids: - # rlines.append((0, False, { - # 'date': rline.date, - # 'price': rline.price, - # })) - # tprice += rline.price - # - # # Unify - # folio = self.folio_id # FIX: To Allow Unify confirm reservations - # state = folio.state # FIX - # folio.state = 'draft' # FIX - # osplitted_reservs = splitted_reservs - master_reservation - # osplitted_reservs.sudo().unlink() - # folio.state = state # FIX - # - # # FIXME: Two writes because checkout regenerate reservation lines - # master_reservation.write({ - # 'checkout': last_checkout, - # 'splitted': False, - # }) - # master_reservation.write({ - # 'reservation_line_ids': rlines, - # 'price_unit': tprice, - # }) - # if not self_is_master: - # return {'type': 'ir.actions.act_window_close'} - # return True - # - # ''' - # Created this because "copy()" function create a new record - # and collide with date restrictions. - # This function generate a usable dictionary with reservation values - # for copy purposes. - # ''' + def unify(self, reserv_ids=None): + self.ensure_one() + if not self.splitted: + raise ValidationError(_("This reservation can't be unified")) + + master_reservation = self.parent_reservation or self + + splitted_reservs = self.env['hotel.reservation'].search([ + ('splitted', '=', True), + ('folio_id', '=', self.folio_id.id), + '|', + ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id) + ]) + self.unify_books(splitted_reservs) + + self_is_master = (master_reservation == self) + if not self_is_master: + return {'type': 'ir.actions.act_window_close'} + + @api.model + def unify_ids(self, reserv_ids): + splitted_reservs = self.env[self._name].browse(reserv_ids) + self.unify_books(splitted_reservs) + + @api.model + def unify_books(self, splitted_reservs): + master_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]): + 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 + for reserv in splitted_reservs: + if last_checkout < reserv.checkout: + last_checkout = reserv.checkout + + # Agrupate reservation lines + reservation_line_ids = splitted_reservs.mapped('reservation_line_ids') + reservation_line_ids.sorted(key=lambda r: r.date) + rlines = [(5, False, False)] + tprice = 0.0 + for rline in reservation_line_ids: + rlines.append((0, False, { + 'date': rline.date, + 'price': rline.price, + })) + tprice += rline.price + + # Unify + osplitted_reservs = splitted_reservs - master_reservation + osplitted_reservs.sudo().unlink() + + master_reservation.write({ + 'checkout': last_checkout, + 'splitted': False, + 'reservation_line_ids': rlines, + 'price_total': tprice, + }) + return True + @api.multi def open_master(self): diff --git a/hotel_calendar/models/inherited_hotel_reservation.py b/hotel_calendar/models/inherited_hotel_reservation.py index ea554a076..7c0bdb194 100644 --- a/hotel_calendar/models/inherited_hotel_reservation.py +++ b/hotel_calendar/models/inherited_hotel_reservation.py @@ -434,8 +434,9 @@ class HotelReservation(models.Model): '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 'product_id' in vals or \ - 'parent_reservation' in vals or 'overbooking' in vals: + 'reserve_color_text' 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', 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 9b56c6d32..97478fb5c 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 @@ -18,7 +18,6 @@ var PMSCalendarController = AbstractController.extend({ custom_events: _.extend({}, AbstractController.prototype.custom_events, { onLoadViewFilters: '_onLoadViewFilters', onUpdateButtonsCounter: '_onUpdateButtonsCounter', - onSwapReservations: '_onSwapReservations', onViewAttached: '_onViewAttached', onApplyFilters: '_onApplyFilters', }), @@ -72,6 +71,25 @@ var PMSCalendarController = AbstractController.extend({ }); }, + swapReservations: function (fromIds, toIds, detail, refFromReservDiv, refToReservDiv) { + var self = this; + return this.model.swap_reservations(fromIds, toIds).then(function(results){ + var allReservs = detail.inReservs.concat(detail.outReservs); + for (var nreserv of allReservs) { + self.renderer.$el.find(nreserv._html).stop(true); + } + }).fail(function(err, errev){ + for (var nreserv of detail.inReservs) { + self.renderer.$el.find(nreserv._html).animate({'top': refFromReservDiv.style.top}, 'fast'); + } + for (var nreserv of detail.outReservs) { + self.renderer.$el.find(nreserv._html).animate({'top': refToReservDiv.style.top}, 'fast'); + } + + self._multi_calendar.swap_reservations(detail.outReservs, detail.inReservs); + }); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -114,11 +132,7 @@ var PMSCalendarController = AbstractController.extend({ var reservs = []; for (var r of results['reservations']) { - var nreserv = new HReservation(self._generate_reservation_obj_values(r)); - nreserv.addUserData({ - 'folio_id': r['folio_id'], - 'parent_reservation': r['parent_reservation'] - }); + var nreserv = self._create_reservation_obj(r); reservs.push(nreserv); } @@ -219,11 +233,7 @@ var PMSCalendarController = AbstractController.extend({ this.model.get_calendar_data(oparams).then(function(results){ var reservs = []; for (var r of results['reservations']) { - var nreserv = new HReservation(self._generate_reservation_obj_values(r)); - nreserv.addUserData({ - 'folio_id': r['folio_id'], - 'parent_reservation': r['parent_reservation'] - }); + var nreserv = self._create_reservation_obj(r); reservs.push(nreserv); } @@ -378,8 +388,7 @@ var PMSCalendarController = AbstractController.extend({ }); }); this._multi_calendar.on_calendar('hcalOnUnifyReservations', function(ev){ - console.log("TO UNIFY"); - console.log(ev.detail.toUnify); + self.model.unify_reservations(_.map(ev.detail.toUnify, 'id')); }); this._multi_calendar.on_calendar('hcalOnSwapReservations', function(ev){ var qdict = {}; @@ -404,13 +413,7 @@ var PMSCalendarController = AbstractController.extend({ for (var nreserv of ev.detail.outReservs) { $(nreserv._html).animate({'top': refFromReservDiv.style.top}); } - self.trigger_up('onSwapReservations', { - 'fromIds': fromIds, - 'toIds': toIds, - 'detail': ev.detail, - 'refFromReservDiv': refFromReservDiv, - 'refToReservDiv': refToReservDiv - }); + self.swapReservations(fromIds, toIds, ev.detail, refFromReservDiv, refToReservDiv); } else { var qdict = {}; var dialog = new Dialog(self, { @@ -597,28 +600,53 @@ var PMSCalendarController = AbstractController.extend({ }.bind(this)); }, + _create_reservation_obj: function(json_reserv) { + var nreserv = new HReservation({ + 'id': json_reserv['id'], + 'room_id': json_reserv['room_id'], + 'title': json_reserv['name'], + 'adults': json_reserv['adults'], + 'childrens': json_reserv['childrens'], + 'startDate': HotelCalendar.toMoment(json_reserv['checkin'], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), + 'endDate': HotelCalendar.toMoment(json_reserv['checkout'], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), + 'color': json_reserv['bgcolor'], + 'colorText': json_reserv['color'], + 'splitted': json_reserv['splitted'] || false, + 'readOnly': json_reserv['read_only'] || false, + 'fixDays': json_reserv['fix_days'] || false, + 'fixRooms': json_reserv['fix_room'], + 'unusedZone': false, + 'linkedId': false, + 'overbooking': json_reserv['overbooking'], + 'cancelled': json_reserv['state'] === 'cancelled' + }); + nreserv.addUserData({ + 'folio_id': json_reserv['folio_id'], + 'parent_reservation': json_reserv['parent_reservation'], + 'realDates': [ + HotelCalendar.toMoment(json_reserv['real_dates'][0], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), + HotelCalendar.toMoment(json_reserv['real_dates'][1], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT) + ] + }); + + return nreserv; + }, + + _generate_reservation_tooltip_dict: function(tp) { + return { + 'name': tp['name'], + 'phone': tp['phone'], + 'arrival_hour': HotelCalendar.toMomentUTC(tp['checkin'], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT).local().format('HH:mm'), + 'num_split': tp['num_split'], + 'amount_total': Number(tp['amount_total']).toLocaleString(), + 'reservation_type': tp['type'], + 'out_service_description': tp['out_service_description'] + }; + }, + //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- - _onSwapReservations: function (ev) { - var self = this; - return this.model.swap_reservations(ev.data.fromIds, ev.data.toIds).then(function(results){ - var allReservs = ev.data.detail.inReservs.concat(ev.data.detail.outReservs); - for (var nreserv of allReservs) { - self.renderer.$el.find(nreserv._html).stop(true); - } - }).fail(function(err, errev){ - for (var nreserv of ev.data.detail.inReservs) { - self.renderer.$el.find(nreserv._html).animate({'top': refFromReservDiv.style.top}, 'fast'); - } - for (var nreserv of ev.detail.outReservs) { - self.renderer.$el.find(nreserv._html).animate({'top': refToReservDiv.style.top}, 'fast'); - } - - self._multi_calendar.swap_reservations(ev.data.detail.outReservs, ev.data.detail.inReservs); - }); - }, - _onViewAttached: function (ev) { this._multi_calendar.recalculate_reservation_positions(); }, @@ -651,29 +679,6 @@ var PMSCalendarController = AbstractController.extend({ }); }, - _generate_reservation_obj_values: function(reserv) { - return { - 'id': reserv['id'], - 'room_id': reserv['room_id'], - 'title': reserv['name'], - 'adults': reserv['adults'], - 'childrens': reserv['childrens'], - 'startDate': HotelCalendar.toMomentUTC(reserv['checkin'], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), - 'endDate': HotelCalendar.toMomentUTC(reserv['checkout'], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT), - 'color': reserv['bgcolor'], - 'colorText': reserv['color'], - 'splitted': reserv['splitted'] || false, - 'readOnly': reserv['read_only'] || false, - 'fixDays': reserv['fix_days'] || false, - 'fixRooms': reserv['fix_room'], - 'unusedZone': false, - 'linkedId': false, - 'overbooking': reserv['overbooking'], - 'cancelled': reserv['state'] === 'cancelled', - 'realDates': reserv['real_dates'] - } - }, - _onBusNotification: function(notifications) { var need_reload_pricelists = false; var need_update_counters = false; @@ -707,11 +712,7 @@ var PMSCalendarController = AbstractController.extend({ nreservs = _.reject(nreservs, function(item){ return item.id == reserv['id']; }); } else { nreservs = _.reject(nreservs, {'id': reserv['id']}); // Only like last changes - var nreserv = new HReservation(this._generate_reservation_obj_values(reserv)); - nreserv.addUserData({ - 'folio_id': reserv['folio_id'], - 'parent_reservation': reserv['parent_reservation'], - }); + var nreserv = this._create_reservation_obj(reserv); this._multi_calendar._reserv_tooltips[reserv['id']] = notif[1]['tooltip']; nreservs.push(nreserv); } @@ -757,18 +758,6 @@ var PMSCalendarController = AbstractController.extend({ this._multi_calendar.get_active_calendar().setDomain(HotelCalendar.DOMAIN.ROOMS, domain); }, - _generate_reservation_tooltip_dict: function(tp) { - return { - 'name': tp['name'], - 'phone': tp['phone'], - 'arrival_hour': HotelCalendar.toMomentUTC(tp['checkin'], HotelConstants.ODOO_DATETIME_MOMENT_FORMAT).local().format('HH:mm'), - 'num_split': tp['num_split'], - 'amount_total': Number(tp['amount_total']).toLocaleString(), - 'reservation_type': tp['type'], - 'out_service_description': tp['out_service_description'] - }; - }, - _on_change_filter_date: function(isStartDate) { isStartDate = isStartDate || false; var $dateTimePickerBegin = this.renderer.$el.find('#pms-menu #date_begin'); @@ -867,6 +856,9 @@ var PMSCalendarController = AbstractController.extend({ $types.trigger('change'); }, + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- _find_bootstrap_environment: function() { var envs = ['xs', 'sm', 'md', 'lg']; 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 2559a87bf..a236e01ff 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 @@ -134,13 +134,22 @@ return AbstractModel.extend({ split_reservation: function(id, nights) { return this._rpc({ - model: 'hotel.reservation', + model: this.modelName, method: 'split', args: [[id], nights], context: Session.user_context, }) }, + unify_reservations: function(reserv_ids) { + return this._rpc({ + model: this.modelName, + method: 'unify_ids', + args: [reserv_ids], + context: Session.user_context, + }) + }, + save_changes: function(params) { params.splice(0, 0, false); // FIXME: ID=False because first parameter its an integer return this._rpc({ 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 4353ab9e2..94f7cb35c 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 @@ -24,11 +24,6 @@ var HotelCalendarView = AbstractRenderer.extend({ searchable: false, searchview_hidden: true, - // Custom Options - _reserv_tooltips: {}, - _days_tooltips: [], - _last_dates: [false, false], - /** VIEW METHODS **/ init: function(parent, state, params) { @@ -230,24 +225,6 @@ var HotelCalendarView = AbstractRenderer.extend({ }.bind(this)); }, - toggle_pms_search: function() { - var $pms_search = this.$el.find('#pms-menu'); - if ($pms_search.position().top < 0) - { - var $navbar = $('.navbar'); - var toPos = $navbar.height() + parseInt($navbar.css('border-top-width'), 10) + parseInt($navbar.css('border-bottom-width'), 10); - $pms_search.animate({ - 'top': `${toPos}px`, - 'opacity': 1.0, - }, 'fast'); - } else { - $pms_search.animate({ - 'top': `-${$pms_search.height()}px`, - 'opacity': 0.0, - }, 'slow'); - } - }, - _generate_search_domain: function(tsearch) { var domain = []; domain.push('|', '|', '|', '|', diff --git a/hotel_calendar/static/src/js/widgets/MultiCalendar.js b/hotel_calendar/static/src/js/widgets/MultiCalendar.js index 02bd885c5..58c701eff 100644 --- a/hotel_calendar/static/src/js/widgets/MultiCalendar.js +++ b/hotel_calendar/static/src/js/widgets/MultiCalendar.js @@ -110,7 +110,10 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) { remove_reservation: function(reserv_id) { this._dataset['reservations'] = _.reject(this._dataset['reservations'], {id: reserv_id}); for (var calendar of this._calendars) { - calendar.removeReservation(reserv_id); + var reserv = calendar.getReservation(reserv_id); + if (reserv) { + calendar.removeReservation(reserv); + } } }, diff --git a/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js b/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js index 9dadf4338..55bd5423e 100644 --- a/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js +++ b/hotel_calendar/static/src/lib/hcalendar/js/hcalendar.js @@ -136,8 +136,7 @@ HotelCalendar.prototype = { this._updateView(!fullUpdate, callback); }, - getOptions: function(/*String?*/key) {+ - console.log(this.options); + getOptions: function(/*String?*/key) { if (typeof key !== 'undefined') { return this.options[key]; } @@ -1769,7 +1768,13 @@ HotelCalendar.prototype = { if (reserv.splitted) { reserv._html.classList.add('hcal-reservation-splitted'); - var magicNumber = Math.floor(Math.abs(Math.sin((reserv.getUserData('parent_reservation') || reserv.id))) * 100000); + // 1. Use reservation ID as seed + // 2. Use sinusiudal function + // 3. Only use positive values (This decrease longitude, increase frequency) + // 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]})`; } else { @@ -1787,23 +1792,19 @@ HotelCalendar.prototype = { reserv._html.style.height = `${divHeight}px`; reserv._html.style.lineHeight = `${divHeight+fontHeight/2.0}px`; reserv._html.style.fontSize = `${fontHeight}px`; - - var clearBorderLeft = function(/*HTMLObject*/elm) { - elm.style.borderLeftWidth = '3px'; - elm.style.borderLeftStyle = 'double'; - }; - var clearBorderRight = function(/*HTMLObject*/elm) { - elm.style.borderRightWidth = '3px'; - elm.style.borderRightStyle = 'double'; - }; - 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') { - clearBorderLeft(reserv._html); + 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') { - clearBorderRight(reserv._html); + 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'; } } },