Merge branch 'pr_multicalendar' of https://github.com/hootel/hootel into 11.0

This commit is contained in:
Dario Lodeiros
2019-01-16 10:45:46 +01:00
23 changed files with 742 additions and 754 deletions

View File

@@ -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)

View File

@@ -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,
})

View File

@@ -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',

View File

@@ -22,9 +22,6 @@
web_icon="hotel_calendar,static/description/icon_calendar_configurator.png"
action="action_hotel_calendar_management" groups="hotel.group_hotel_manager" />
<menuitem id="hotel_room_pricelist_cached" name="Room Pricelist Cached"
sequence="1" action="hotel_room_pricelist_cached_action_form_tree" parent="sale.menu_sale_config"/>
<menuitem id="hotel_calendar_record_menu" name="Calendars"
parent="hotel.hotel_configuration_menu" sequence="10"
groups="hotel.group_hotel_manager"

View File

@@ -2,17 +2,15 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import hotel_calendar
from . import bus_hotel_calendar
from . import room_pricelist_cached
from . import hotel_calendar_management
from . import inherited_hotel_reservation
from . import inherited_product_pricelist_item
from . import inherited_res_users
from . import inherited_hotel_room
from . import inherited_hotel_room_type
from . import inherited_hotel_room_type_restriction_item
from . import inherited_hotel_room_type_availability
from . import inherited_product_pricelist
from . import inherited_product_pricelist_item
from . import inherited_hotel_folio
from . import ir_default
from . import ir_actions_act_window_view
from . import ir_ui_view

View File

@@ -1,12 +1,10 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# 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')

View File

@@ -1,21 +1,9 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# 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()

View File

@@ -1,6 +1,6 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# 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

View File

@@ -1,34 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# 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,
})

View File

@@ -1,17 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# 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')

View File

@@ -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
1 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
2 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
3 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
4 access_hotel_calendar access_hotel_calendar model_hotel_calendar base.group_user 1 0 0 0

View File

@@ -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 */
/* TODO: Use Odoo Colours based on http://www.odoo.com/openerp_website/static/src/less/variables.less */

View File

@@ -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));

View File

@@ -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: '<div class="text-center"><i class="fa fa-spin fa-spinner"></i> Getting Calendar Data From Server...</div>',
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,
})
}
});
});

View File

@@ -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');

View File

@@ -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,
});
},

View File

@@ -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');
}
});

View File

@@ -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 {

View File

@@ -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<diff_date; ++i) {
for (var b=0; b<=numBeds; ++b) {
var reservs = this.getReservationsByDay(ndate, true, true, reservation.room.id, +limits.left.dataset.hcalBedNum+b, reservation);
if (reservs.length) {
notFound = true;
nbed = nbed?nbed+1:+limits.left.dataset.hcalBedNum+b+1;
break;
}
var reservs = this.getReservationsByDay(ndate, true, true, reservation.room.id, +limits.left.dataset.hcalBedNum, reservation);
if (reservs.length) {
notFound = true;
nbed = nbed?nbed+1:+limits.left.dataset.hcalBedNum+1;
break;
}
if (notFound) { break; }
ndate.add(1, 'd');
}
}
} while (notFound && nbed <= reservation.room.capacity);
} while (notFound && nbed < reservation.room.capacity);
reservation._limits = limits;
// Update Beds
if (limits.isValid()) {
var numBeds = (+limits.right.dataset.hcalBedNum)-(+limits.left.dataset.hcalBedNum);
reservation._beds = [];
for (var i=0; i<=numBeds; reservation._beds.push(+limits.left.dataset.hcalBedNum+i++));
}
},
//==== CELLS
@@ -576,34 +617,11 @@ HotelCalendar.prototype = {
},
getDayRoomTypeReservations: function(/*String,MomentObject*/day, /*String*/room_type) {
var reservs = this.getReservationsByDay(day, true);
return _.filter(reservs, function(item){ return item.room && item.room.type === room_type && !item.unusedZone; });
},
getRoomsByType: function(/*String*/type) {
return _.filter(this.options.rooms, function(item){ return item.type === type && !item.overbooking && !item.cancelled; });
},
getRoomsCapacityByType: function(/*String*/type) {
var trooms = this.getRoomsByType(type);
var num_rooms = 0;
for (var tr of trooms) {
num_rooms += tr.shared?tr.capacity:1;
}
return num_rooms;
},
getRoomsCapacityTotal: function() {
var num_rooms = 0;
var rooms = _.filter(this.options.rooms, function(item){ return !item.overbooking && !item.cancelled; });
for (var tr of rooms) {
num_rooms += tr.shared?tr.capacity:1;
}
return num_rooms;
return _.filter(this.getReservationsByDay(day, true), function(item){ return item.room && item.room.type === room_type && !item.unusedZone; });
},
getRoomTypes: function() {
return _.uniq(_.pluck(this.options.rooms, 'type'));
return _.keys(this._roomsMap);
},
getRoom: function(/*String,Int*/id, /*Boolean?*/isExtra, /*Int?*/reservId) {
@@ -656,32 +674,26 @@ HotelCalendar.prototype = {
var exRoomRow = this.getExtraRoomRow(ex_reserv);
if (exRoomRow) {
// Update Reservations Position
var bounds = exRoomRow.getBoundingClientRect();
var cheight = bounds.bottom-bounds.top;
var bounds = this.loopedOffsetOptimized(exRoomRow);
var start_index = _.indexOf(this.options.rooms, ex_reserv.room) + 1;
for (var i=start_index; i<this.options.rooms.length; i++) {
var reservs = this.getReservationsByRoom(this.options.rooms[i], true);
for (var reserv of reservs) {
if (reserv && reserv._html) {
var top = parseInt(reserv._html.style.top, 10);
reserv._html.style.top = `${top - cheight}px`;
reserv._html.style.top = `${top - bounds.height}px`;
}
}
}
exRoomRow.parentNode.removeChild(exRoomRow);
this.options.rooms = _.reject(this.options.rooms, function(item){ return item.id === ex_reserv.room.id; });
this.options.rooms = _.reject(this.options.rooms, {id: ex_reserv.room.id});
}
},
getRealExtraRoomInfo: function(/*HRoomObject*/room) {
// Obtain real id
var isf = room.number.search('EX-');
var isfb = room.number.search('/#');
var cnumber = room.number;
if (isf != -1 && isfb != -1) { cnumber = cnumber.substr(isf+3, isfb-(isf+3)); }
// Obtain the original room row
var cnumber = this.getExtraRoomRealNumber(room);
var mainRoomRowId = this._sanitizeId(`ROW_${cnumber}_${room.type}`);
var mainRoomRow = this.e.querySelector('#'+mainRoomRowId);
if (!mainRoomRow) {
@@ -691,12 +703,16 @@ HotelCalendar.prototype = {
return [this.getRoom(mainRoomRow.dataset.hcalRoomObjId), mainRoomRow];
},
getExtraRoomRow: function(/*HReservationObject*/ex_reserv) {
// Obtain real id
var isf = ex_reserv.room.number.search('EX-');
var isfb = ex_reserv.room.number.search('/#');
var cnumber = ex_reserv.room.number;
getExtraRoomRealNumber: function(/*HRoomObject*/room) {
var isf = room.number.search(this._extraRowIndicators[0]);
var isfb = room.number.search(this._extraRowIndicators[1]);
var cnumber = room.number;
if (isf != -1 && isfb != -1) { cnumber = cnumber.substr(isf+3, isfb-(isf+3)); }
return cnumber;
},
getExtraRoomRow: function(/*HReservationObject*/ex_reserv) {
var cnumber = this.getExtraRoomRealNumber(ex_reserv.room);
return this.e.querySelector(`#${this._sanitizeId(`ROW_${cnumber}_${ex_reserv.room.type}_EXTRA${ex_reserv.id}`)}`);
},
@@ -711,7 +727,7 @@ HotelCalendar.prototype = {
var exr = this.getExtraRooms(mainRoom.id);
var ex_room = mainRoom.clone();
ex_room.id = `${reservId}@${mainRoom.id}`;
ex_room.number = `EX-${mainRoom.number}/#${exr.length}`;
ex_room.number = `${this._extraRowIndicators[0]}${mainRoom.number}${this._extraRowIndicators[1]}${exr.length}`;
for (var key in extraData) {
ex_room[key] = extraData[key];
}
@@ -777,8 +793,8 @@ HotelCalendar.prototype = {
}
// Update Reservations Position
var bounds = row.getBoundingClientRect();
var cheight = bounds.bottom-bounds.top;
var bounds = this.loopedOffsetOptimized(row);
var cheight = bounds.height;
var start_index = _.indexOf(this.options.rooms, ex_room) + 1;
for (var i=start_index; i<this.options.rooms.length; ++i) {
var reservs = this.getReservationsByRoom(this.options.rooms[i], true);
@@ -810,39 +826,37 @@ HotelCalendar.prototype = {
if (this._restrictions) {
// Rooms Restrictions
for (var room of this.options.rooms) {
if (room.price[0] == 'pricelist') {
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 = '';
}
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<this.options.showNumRooms && i<rows.length; ++i)
{
var bounds = rows[i].getBoundingClientRect();
cheight += bounds.bottom-bounds.top;
cheight += rows[i].offsetHeight;
}
this.edivr.style.height = `${cheight}px`;
this.edivr.style.maxHeight = 'initial';
@@ -1418,35 +1406,6 @@ HotelCalendar.prototype = {
}
},
//==== PRICELISTS
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;
},
//==== UPDATE FUNCTIONS
_updateView: function(/*Bool*/notData, /*function*/callback) {
this._createTableReservationDays();
@@ -1456,25 +1415,34 @@ HotelCalendar.prototype = {
this._updateCellSelection();
this._createTableDetailDays();
_.defer(function(){
this._updateReservations(true);
}.bind(this));
if (!notData) {
_.defer(function(){
this._updateRestrictions();
this._updatePriceList();
this._updateReservationOccupation();
}.bind(this));
}
_.defer(function(self){
self._updateOffsets();
self._updateReservations(true);
if (!notData) {
_.defer(function(self){
self._updateRestrictions();
self._updatePriceList();
self._updateReservationOccupation();
}, self);
}
}, this);
// if (!notData) {
// _.defer(function(self){
// self._createTableDetailDays();
// self._updateRestrictions();
// self._updatePriceList();
// self._updateReservationOccupation();
// }, this);
// }
},
_updateOBIndicators: function() {
var mainBounds = this.edivr.getBoundingClientRect();
var mainBounds = this._edivrOffset;
for (var reserv of this._reservations) {
if (reserv.overbooking && reserv._html) {
var eOffset = this.e.getBoundingClientRect();
var bounds = reserv._html.getBoundingClientRect();
if (bounds.top > 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 = [
$('<div/>', {class: 'hcal-reservation-divide-l', css: defStyle}).appendTo(this.edivr),
$('<div/>', {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];

View File

@@ -113,24 +113,26 @@
</button>
</div>
<div class="col-xs-12 col-md-12 nopadding menu-filter-box">
<h4>Filters</h4>
<select class="form-control" id="type_list" placeholder="Select Segmentation..." multiple="multiple"/>
<select class="list form-control" id="floor_list" placeholder="Select Location..." multiple="multiple"/>
<select class="list form-control" id="amenities_list" placeholder="Select Amenities..." multiple="multiple"/>
<select class="list form-control" id="virtual_list" placeholder="Select Type..." multiple="multiple"/>
<div class="filter-record col-xs-12 col-md-12" style="padding:4px">
<div class="col-xs-8 col-md-8 nopadding">
<input type="edit" id="calendar_name" class="form-control" />
</div>
<div class="col-xs-2 col-md-2 nopadding">
<button class="btn btn-primary col-xs-12 col-md-12" id="btn_save_calendar_record">
<i class="fa fa-fw fa-save"> </i>
</button>
</div>
<div class="col-xs-2 col-md-2 nopadding">
<button class="btn btn-primary col-xs-12 col-md-12" id="btn_reload_calendar_filters">
<i class="fa fa-fw fa-refresh"> </i>
</button>
<h4 data-toggle="collapse" data-target="#filters"><i class="fa fa-chevron-circle-right"></i> Filters</h4>
<div id="filters" class="collapse">
<select class="form-control" id="type_list" placeholder="Select Segmentation..." multiple="multiple"/>
<select class="list form-control" id="floor_list" placeholder="Select Location..." multiple="multiple"/>
<select class="list form-control" id="amenities_list" placeholder="Select Amenities..." multiple="multiple"/>
<select class="list form-control" id="virtual_list" placeholder="Select Type..." multiple="multiple"/>
<div class="filter-record col-xs-12 col-md-12" style="padding:4px">
<div class="col-xs-8 col-md-8 nopadding">
<input type="edit" id="calendar_name" class="form-control" />
</div>
<div class="col-xs-2 col-md-2 nopadding">
<button class="btn btn-primary col-xs-12 col-md-12" id="btn_save_calendar_record">
<i class="fa fa-fw fa-save"> </i>
</button>
</div>
<div class="col-xs-2 col-md-2 nopadding">
<button class="btn btn-primary col-xs-12 col-md-12" id="btn_reload_calendar_filters">
<i class="fa fa-fw fa-refresh"> </i>
</button>
</div>
</div>
</div>
</div>

View File

@@ -17,13 +17,6 @@
<field name="domain">[('checkout','=', datetime.datetime.now().strftime('%Y-%m-%d'))]</field>
</record>
<record model="ir.actions.act_window" id="hotel_room_pricelist_cached_action_form_tree">
<field name="name">Room Pricelist Cached</field>
<field name="res_model">room.pricelist.cached</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record model="ir.actions.act_window" id="hotel_calendar_action_form_tree">
<field name="name">Hotel Calendar</field>
<field name="res_model">hotel.calendar</field>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form view of hotel room -->
<record model="ir.ui.view" id="hotel_room_pricelist_cached_view_form">
<field name="name">hotel.room.pricelist.cached.form</field>
<field name="model">room.pricelist.cached</field>
<field name="arch" type="xml">
<form string="Room Pricelist Cached">
<sheet>
<group>
<field name="room_id" />
<field name="date" />
<field name="price" />
</group>
</sheet>
</form>
</field>
</record>
<!-- Tree view of hotel room -->
<record model="ir.ui.view" id="hotel_room_pricelist_cached_view_tree">
<field name="name">hotel.room.pricelist.cached.tree</field>
<field name="model">room.pricelist.cached</field>
<field name="arch" type="xml">
<tree string="Room Pricelist Cached">
<field name="room_id" />
<field name="date" />
<field name="price" />
</tree>
</field>
</record>
</odoo>

View File

@@ -43,10 +43,10 @@ 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 and ((rline.state == 'cancelled' and not rline.channel_modified) or rline.state != 'cancelled'):
dates = rline.get_real_checkin_checkout()
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 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 and ((r.state == 'cancelled' and not r.channel_modified) or r.state != 'cancelled'))
self.room_lines.filtered(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 and ((r.state == 'cancelled' and not r.channel_modified) or r.state != 'cancelled'))
),
'room_type': {
'id': rline.room_type_id.id,