mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
Calendar Improvements (#89)
* [IMP] Allow change adults resizing reservation on calendar * [IMP] Board Services * [WIP] Board Service * [IMP] Calendar Buttons & Default Options * [WIP] Availability * [WIP] Availability * [WIP] Availability * [IMP] Calendar buttons
This commit is contained in:
@@ -2,9 +2,7 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from datetime import timedelta
|
||||
from openerp import models, fields, api
|
||||
from openerp.tools import (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
class MassiveChangesWizard(models.TransientModel):
|
||||
@@ -12,10 +10,9 @@ class MassiveChangesWizard(models.TransientModel):
|
||||
|
||||
# Common fields
|
||||
section = fields.Selection([
|
||||
('0', 'Availability'),
|
||||
('1', 'Restrictions'),
|
||||
('2', 'Pricelist'),
|
||||
], string='Section', default='0')
|
||||
('restrictions', 'Restrictions'),
|
||||
('prices', 'Pricelist'),
|
||||
], string='Section', default='prices')
|
||||
date_start = fields.Date('Start Date', required=True)
|
||||
date_end = fields.Date('End Date', required=True)
|
||||
dmo = fields.Boolean('Monday', default=True)
|
||||
@@ -33,10 +30,6 @@ class MassiveChangesWizard(models.TransientModel):
|
||||
# string="Virtual Rooms")
|
||||
room_type_ids = fields.Many2many('hotel.room.type', string="Room Types")
|
||||
|
||||
# Availability fields
|
||||
change_avail = fields.Boolean(default=False)
|
||||
avail = fields.Integer('Avail', default=0)
|
||||
|
||||
# Restriction fields
|
||||
restriction_id = fields.Many2one('hotel.room.type.restriction',
|
||||
'Restriction Plan')
|
||||
@@ -187,46 +180,6 @@ class MassiveChangesWizard(models.TransientModel):
|
||||
})
|
||||
hotel_room_type_re_it_obj.create(vals)
|
||||
|
||||
@api.model
|
||||
def _get_availability_values(self, ndate, room_type, record):
|
||||
hotel_room_type_obj = self.env['hotel.room.type']
|
||||
vals = {}
|
||||
if record.change_avail:
|
||||
cavail = len(hotel_room_type_obj.check_availability_room_type(
|
||||
ndate.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
ndate.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
room_type_id=room_type.id))
|
||||
vals.update({
|
||||
'avail': min(cavail, room_type.total_rooms_count, record.avail),
|
||||
})
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def _save_availability(self, ndate, room_types, record):
|
||||
hotel_room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
domain = [('date', '=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT))]
|
||||
|
||||
for room_type in room_types:
|
||||
vals = self._get_availability_values(ndate, room_type, record)
|
||||
if not any(vals):
|
||||
continue
|
||||
|
||||
room_types_avail = hotel_room_type_avail_obj.search(
|
||||
domain+[('room_type_id', '=', room_type.id)]
|
||||
)
|
||||
if any(room_types_avail):
|
||||
# Mail module want a singleton
|
||||
for vr_avail in room_types_avail:
|
||||
vr_avail.write(vals)
|
||||
else:
|
||||
vals.update({
|
||||
'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'room_type_id': room_type.id
|
||||
})
|
||||
hotel_room_type_avail_obj.with_context({
|
||||
'mail_create_nosubscribe': True,
|
||||
}).create(vals)
|
||||
|
||||
@api.multi
|
||||
def massive_change_close(self):
|
||||
self._do_massive_change()
|
||||
@@ -239,6 +192,13 @@ class MassiveChangesWizard(models.TransientModel):
|
||||
"type": "ir.actions.do_nothing",
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _save(self, ndate, room_types, record):
|
||||
if record.section == 'restrictions':
|
||||
self._save_restrictions(ndate, room_types, record)
|
||||
elif record.section == 'prices':
|
||||
self._save_prices(ndate, room_types, record)
|
||||
|
||||
@api.multi
|
||||
def _do_massive_change(self):
|
||||
hotel_room_type_obj = self.env['hotel.room.type']
|
||||
@@ -256,11 +216,5 @@ class MassiveChangesWizard(models.TransientModel):
|
||||
ndate = date_start_dt + timedelta(days=i)
|
||||
if not wedays[ndate.timetuple()[6]]:
|
||||
continue
|
||||
|
||||
if record.section == '0':
|
||||
self._save_availability(ndate, room_types, record)
|
||||
elif record.section == '1':
|
||||
self._save_restrictions(ndate, room_types, record)
|
||||
elif record.section == '2':
|
||||
self._save_prices(ndate, room_types, record)
|
||||
self._save(ndate, room_types, record)
|
||||
return True
|
||||
|
||||
@@ -24,24 +24,9 @@
|
||||
<field name="dsa" colspan="1" />
|
||||
<field name="dsu" colspan="1" />
|
||||
</group>
|
||||
<!-- Availability Fields -->
|
||||
<group col="3" colspan="3" attrs="{'invisible':[('section', '!=', '0')]}">
|
||||
<table class="oe_form_group">
|
||||
<thead>
|
||||
<th width="12%"></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_avail" /> <strong> Avail</strong></td>
|
||||
<td class="oe_form_group_cell" colspan="3"><field name="avail" attrs="{'readonly':[('change_avail', '=', False)]}" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</group>
|
||||
<!-- Restricion Fields -->
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('section', '!=', '1')]}">
|
||||
<field name="restriction_id" colspan="4" attrs="{'required':[('section', '=', '1')]}" />
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('section', '!=', 'restrictions')]}">
|
||||
<field name="restriction_id" colspan="4" attrs="{'required':[('section', '=', 'restrictions')]}" />
|
||||
<table class="oe_form_group" colspan="4">
|
||||
<thead>
|
||||
<th width="20%"></th>
|
||||
@@ -80,9 +65,9 @@
|
||||
</table>
|
||||
</group>
|
||||
<!-- Priclist Fields -->
|
||||
<group attrs="{'invisible':[('section', '!=', '2')]}">
|
||||
<field name="pricelist_id" attrs="{'required':[('section', '=', '2')]}"/>
|
||||
<field name="price" attrs="{'required':[('section', '=', '2')]}"/>
|
||||
<group attrs="{'invisible':[('section', '!=', 'prices')]}">
|
||||
<field name="pricelist_id" attrs="{'required':[('section', '=', 'prices')]}"/>
|
||||
<field name="price" attrs="{'required':[('section', '=', 'prices')]}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="massive_change" string="Massive Change" type="object"
|
||||
|
||||
@@ -8,7 +8,6 @@ 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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from datetime import datetime
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from odoo import models, api, _
|
||||
from odoo import models, api
|
||||
from odoo.addons.hotel_calendar.controllers.bus import HOTEL_BUS_CHANNEL_ID
|
||||
|
||||
|
||||
@@ -114,21 +114,6 @@ class BusHotelCalendar(models.TransientModel):
|
||||
},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _generate_availability_notification(self, vals):
|
||||
date_dt = datetime.strptime(vals['date'], DEFAULT_SERVER_DATE_FORMAT)
|
||||
return {
|
||||
'type': 'availability',
|
||||
'availability': {
|
||||
vals['room_type_id']: {
|
||||
date_dt.strftime("%d/%m/%Y"): [
|
||||
vals['avail'],
|
||||
vals['id'],
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def send_reservation_notification(self, vals):
|
||||
notif = self._generate_reservation_notif(vals)
|
||||
@@ -146,9 +131,3 @@ class BusHotelCalendar(models.TransientModel):
|
||||
notif = self._generate_restriction_notification(vals)
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'hotel.reservation',
|
||||
HOTEL_BUS_CHANNEL_ID), notif)
|
||||
|
||||
@api.model
|
||||
def send_availability_notification(self, vals):
|
||||
notif = self._generate_availability_notification(vals)
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'hotel.reservation',
|
||||
HOTEL_BUS_CHANNEL_ID), notif)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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.tools import (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
@@ -33,24 +33,12 @@ class HotelCalendarManagement(models.TransientModel):
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def _get_availability_values(self, avail, room_type):
|
||||
room_type_obj = self.env['hotel.room.type']
|
||||
cavail = len(room_type_obj.check_availability_room_type(
|
||||
avail['date'], avail['date'], room_type_id=room_type.id))
|
||||
ravail = min(cavail, room_type.total_rooms_count, int(avail['avail']))
|
||||
vals = {
|
||||
'avail': ravail,
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def save_changes(self, pricelist_id, restriction_id, pricelist,
|
||||
restrictions, availability):
|
||||
restrictions, availability=False):
|
||||
room_type_obj = self.env['hotel.room.type']
|
||||
product_pricelist_item_obj = self.env['product.pricelist.item']
|
||||
room_type_rest_item_obj = self.env['hotel.room.type.restriction.item']
|
||||
room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
|
||||
# Save Pricelist
|
||||
for k_price in pricelist.keys():
|
||||
@@ -98,26 +86,6 @@ class HotelCalendarManagement(models.TransientModel):
|
||||
else:
|
||||
res_id.write(vals)
|
||||
|
||||
# Save Availability
|
||||
for k_avail in availability.keys():
|
||||
room_type_id = room_type_obj.browse(int(k_avail))
|
||||
for avail in availability[k_avail]:
|
||||
vals = self._get_availability_values(avail, room_type_id)
|
||||
avail_id = room_type_avail_obj.search([
|
||||
('date', '=', avail['date']),
|
||||
('room_type_id', '=', room_type_id.id),
|
||||
], limit=1)
|
||||
if not avail_id:
|
||||
vals.update({
|
||||
'date': avail['date'],
|
||||
'room_type_id': room_type_id.id,
|
||||
})
|
||||
avail_id = room_type_avail_obj.with_context({
|
||||
'mail_create_nosubscribe': True,
|
||||
}).create(vals)
|
||||
else:
|
||||
avail_id.write(vals)
|
||||
|
||||
@api.model
|
||||
def _hcalendar_room_json_data(self, rooms):
|
||||
json_data = []
|
||||
@@ -167,36 +135,6 @@ class HotelCalendarManagement(models.TransientModel):
|
||||
})
|
||||
return json_data
|
||||
|
||||
@api.model
|
||||
def _generate_avalaibility_data(self, room_type, date, avail):
|
||||
return {
|
||||
'id': avail and avail.id or False,
|
||||
'date': avail and avail.date or date,
|
||||
'avail': avail and avail.avail or room_type.total_rooms_count,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _hcalendar_availability_json_data(self, dfrom, dto):
|
||||
date_start = fields.Date.from_string(dfrom)
|
||||
date_end = fields.Date.from_string(dto)
|
||||
date_diff = abs((date_end - date_start).days) + 1
|
||||
hotel_room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
room_types = self.env['hotel.room.type'].search([])
|
||||
json_data = {}
|
||||
|
||||
for room_type in room_types:
|
||||
json_data[room_type.id] = []
|
||||
for i in range(0, date_diff):
|
||||
cur_date = date_start + timedelta(days=i)
|
||||
cur_date_str = cur_date.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
avail = hotel_room_type_avail_obj.search([
|
||||
('date', '=', cur_date_str),
|
||||
('room_type_id', '=', room_type.id)
|
||||
])
|
||||
json_data[room_type.id].append(
|
||||
self._generate_avalaibility_data(room_type, cur_date_str, avail))
|
||||
return json_data
|
||||
|
||||
@api.model
|
||||
def _hcalendar_events_json_data(self, dfrom, dto):
|
||||
date_start = fields.Date.from_string(dfrom) - timedelta(days=1)
|
||||
@@ -286,13 +224,11 @@ class HotelCalendarManagement(models.TransientModel):
|
||||
|
||||
json_prices = self._hcalendar_pricelist_json_data(pricelist_item_ids)
|
||||
json_rest = self._hcalendar_restriction_json_data(restriction_item_ids)
|
||||
json_avails = self._hcalendar_availability_json_data(dfrom, dto)
|
||||
json_rc = self._hcalendar_get_count_reservations_json_data(dfrom, dto)
|
||||
json_events = self._hcalendar_events_json_data(dfrom, dto)
|
||||
vals.update({
|
||||
'prices': json_prices or [],
|
||||
'restrictions': json_rest or [],
|
||||
'availability': json_avails or [],
|
||||
'count_reservations': json_rc or [],
|
||||
'events': json_events or [],
|
||||
})
|
||||
|
||||
@@ -6,7 +6,8 @@ from odoo import models, fields
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
pms_divide_rooms_by_capacity = fields.Boolean('Divide rooms by capacity')
|
||||
pms_divide_rooms_by_capacity = fields.Boolean('Divide rooms by capacity',
|
||||
default=True)
|
||||
pms_end_day_week = fields.Selection([
|
||||
('1', 'Monday'),
|
||||
('2', 'Tuesday'),
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pms-menu {
|
||||
overflow: auto;
|
||||
background-color: #f8f8f8;
|
||||
@@ -65,41 +67,57 @@
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
/* BUTTON STATES */
|
||||
.navbar-default {
|
||||
border-color: #f8f8f8;
|
||||
}
|
||||
|
||||
button .led {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: 5px;
|
||||
top: -1px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 1px solid lightgray;
|
||||
border-top-width: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
button .led-disabled {
|
||||
background-color: dimgrey;
|
||||
}
|
||||
button .led-enabled {
|
||||
background-color: khaki;
|
||||
box-shadow: 0 0 8px 0px khaki;
|
||||
|
||||
.unify-enabled {
|
||||
background-color: #43A200;
|
||||
box-shadow: 0 0 8px 0px #43A200;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button .led-green {
|
||||
background-color: chartreuse;
|
||||
box-shadow: 0 0 8px 0px chartreuse;
|
||||
.divide-enabled {
|
||||
background-color: #43A200;
|
||||
box-shadow: 0 0 8px 0px #43A200;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button .led-blue {
|
||||
background-color: cornflowerblue;
|
||||
box-shadow: 0 0 8px 0px cornflowerblue;
|
||||
.overbooking-enabled {
|
||||
background-color: #43A200;
|
||||
box-shadow: 0 0 8px 0px #43A200;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancelled-enabled {
|
||||
background-color: #43A200;
|
||||
box-shadow: 0 0 8px 0px #43A200;
|
||||
}
|
||||
|
||||
.swap-from {
|
||||
background-color: #E7CF1D;
|
||||
box-shadow: 0 0 8px 0px #E7CF1D;
|
||||
color: #7C7BAD;
|
||||
}
|
||||
|
||||
.swap-to {
|
||||
background-color: #43A200;
|
||||
box-shadow: 0 0 8px 0px #43A200;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.unify-enabled i, .divide-enabled i, .cancelled-enabled i, .overbooking-enabled i, .swap-to i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.swap-from i {
|
||||
color: #7C7BAD;
|
||||
}
|
||||
|
||||
/* END: BUTTON STATES */
|
||||
|
||||
input#bookings_search {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
@@ -130,14 +148,21 @@ input#bookings_search {
|
||||
min-height: 3.5em;
|
||||
padding: 3px;
|
||||
transition: all 0.5s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button-highlight {
|
||||
background-color: orange;
|
||||
.overbooking-highlight {
|
||||
color: white;
|
||||
background-color: #FFA500;
|
||||
}
|
||||
.button-highlight i {
|
||||
|
||||
.cancelled-highlight {
|
||||
color: white;
|
||||
background-color: #FFA500;
|
||||
}
|
||||
|
||||
.overbooking-highlight i, .cancelled-highlight i {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.multi-calendar-tab-plus {
|
||||
@@ -194,6 +219,11 @@ input#bookings_search {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-hidden-xs {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/** TOOLTIPS **/
|
||||
.popover-content {
|
||||
font-family: Garuda, sans-serif;
|
||||
@@ -257,6 +287,12 @@ input#bookings_search {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
@media (min-width:0px) and (max-width: 1600px) {
|
||||
/* .text-hidden-xs {
|
||||
visibility: hidden;
|
||||
} */
|
||||
}
|
||||
|
||||
/* WARNING: The .row-eq-height class uses CSS3's flexbox layout mode,
|
||||
which is not supported in Internet Explorer 9 and below. */
|
||||
.row-eq-height {
|
||||
|
||||
@@ -17,7 +17,6 @@ var AbstractController = require('web.AbstractController'),
|
||||
var PMSCalendarController = AbstractController.extend({
|
||||
custom_events: _.extend({}, AbstractController.prototype.custom_events, {
|
||||
onLoadViewFilters: '_onLoadViewFilters',
|
||||
onUpdateButtonsCounter: '_onUpdateButtonsCounter',
|
||||
onViewAttached: '_onViewAttached',
|
||||
onApplyFilters: '_onApplyFilters',
|
||||
}),
|
||||
@@ -163,6 +162,7 @@ var PMSCalendarController = AbstractController.extend({
|
||||
}
|
||||
|
||||
self._multi_calendar.set_active_calendar(self._multi_calendar._calendars.length-1);
|
||||
self._update_buttons_counter();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -246,6 +246,7 @@ var PMSCalendarController = AbstractController.extend({
|
||||
self._multi_calendar.merge_reservations(reservs, active_calendar);
|
||||
|
||||
self._multi_calendar._assign_extra_info(active_calendar);
|
||||
self._update_buttons_counter();
|
||||
});
|
||||
}.bind(this)).then(function(){
|
||||
self._last_dates = filterDates;
|
||||
@@ -264,62 +265,56 @@ var PMSCalendarController = AbstractController.extend({
|
||||
self._on_change_filter_date();
|
||||
});
|
||||
|
||||
this.renderer.$el.find("#btn_swap button").on('click', function(ev){
|
||||
this.renderer.$el.find("#btn_swap > button").on('click', function(ev){
|
||||
var active_calendar = self._multi_calendar.get_active_calendar();
|
||||
var hcalSwapMode = active_calendar.getSwapMode();
|
||||
var $led = $(this).find('.led');
|
||||
var $btn = $(this);
|
||||
if (hcalSwapMode === HotelCalendar.MODE.NONE) {
|
||||
active_calendar.setSwapMode(HotelCalendar.MODE.SWAP_FROM);
|
||||
$("#btn_swap span.ntext").html(_t("Continue"));
|
||||
$led.removeClass('led-disabled');
|
||||
$led.addClass('led-green');
|
||||
$btn.removeClass('swap-to');
|
||||
$btn.addClass('swap-from');
|
||||
} else if (active_calendar.getReservationAction().inReservations.length > 0 && hcalSwapMode === HotelCalendar.MODE.SWAP_FROM) {
|
||||
active_calendar.setSwapMode(HotelCalendar.MODE.SWAP_TO);
|
||||
$("#btn_swap span.ntext").html(_t("End"));
|
||||
$led.removeClass('led-green');
|
||||
$led.addClass('led-blue');
|
||||
$btn.removeClass('swap-from');
|
||||
$btn.addClass('swap-to');
|
||||
} 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');
|
||||
$btn.removeClass('swap-from swap-to');
|
||||
}
|
||||
});
|
||||
|
||||
this.renderer.$el.find('#pms-menu #btn_action_overbooking button').on('click', function(ev){
|
||||
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();
|
||||
if (active_calendar.options.showOverbookings) {
|
||||
$(this).find('.led').removeClass('led-disabled');
|
||||
$(this).find('.led').addClass('led-enabled');
|
||||
$(this).addClass('overbooking-enabled');
|
||||
} else {
|
||||
$(this).find('.led').addClass('led-disabled');
|
||||
$(this).find('.led').removeClass('led-enabled');
|
||||
$(this).removeClass('overbooking-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){
|
||||
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();
|
||||
if (active_calendar.options.showCancelled) {
|
||||
$(this).find('.led').removeClass('led-disabled');
|
||||
$(this).find('.led').addClass('led-enabled');
|
||||
$(this).addClass('cancelled-enabled');
|
||||
} else {
|
||||
$(this).find('.led').addClass('led-disabled');
|
||||
$(this).find('.led').removeClass('led-enabled');
|
||||
$(this).removeClass('cancelled-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){
|
||||
this.renderer.$el.find('#pms-menu #btn_action_divide > button').on('click', function(ev){
|
||||
var active_calendar = self._multi_calendar.get_active_calendar();
|
||||
var cur_mode = active_calendar.getSelectionMode();
|
||||
active_calendar.setSelectionMode(cur_mode===HotelCalendar.ACTION.DIVIDE?HotelCalendar.MODE.NONE:HotelCalendar.ACTION.DIVIDE);
|
||||
});
|
||||
|
||||
this.renderer.$el.find('#pms-menu #btn_action_unify button').on('click', function(ev){
|
||||
this.renderer.$el.find('#pms-menu #btn_action_unify > button').on('click', function(ev){
|
||||
var active_calendar = self._multi_calendar.get_active_calendar();
|
||||
var cur_mode = active_calendar.getSelectionMode();
|
||||
active_calendar.setSelectionMode(cur_mode===HotelCalendar.ACTION.UNIFY?HotelCalendar.MODE.NONE:HotelCalendar.ACTION.UNIFY);
|
||||
@@ -545,8 +540,8 @@ var PMSCalendarController = AbstractController.extend({
|
||||
});
|
||||
this._multi_calendar.on_calendar('hcalOnCancelSwapReservations', function(ev){
|
||||
$("#btn_swap span.ntext").html(_t("Start Swap"));
|
||||
var $led = $("#btn_swap").find('.led');
|
||||
$led.removeClass('led-blue').removeClass('led-green').addClass('led-disabled');
|
||||
var $btn = $("#btn_swap > button");
|
||||
$btn.removeClass('swap-from swap-to');
|
||||
});
|
||||
this._multi_calendar.on_calendar('hcalOnChangeReservation', function(ev){
|
||||
var newReservation = ev.detail.newReserv;
|
||||
@@ -555,64 +550,71 @@ var PMSCalendarController = AbstractController.extend({
|
||||
var newPrice = ev.detail.newPrice;
|
||||
var folio_id = newReservation.getUserData('folio_id');
|
||||
|
||||
var linkedReservs = _.find(ev.detail.calendar_obj._reservations, function(item){
|
||||
return item.id !== newReservation.id && !item.unusedZone && item.getUserData('folio_id') === folio_id;
|
||||
});
|
||||
|
||||
var hasChanged = false;
|
||||
|
||||
var qdict = {
|
||||
ncheckin: newReservation.startDate.clone().local().format(HotelConstants.L10N_DATETIME_MOMENT_FORMAT),
|
||||
ncheckout: newReservation.endDate.clone().local().format(HotelConstants.L10N_DATETIME_MOMENT_FORMAT),
|
||||
ncheckin: newReservation.startDate.clone().local().format(HotelConstants.L10N_DATE_MOMENT_FORMAT),
|
||||
ncheckout: newReservation.endDate.clone().local().format(HotelConstants.L10N_DATE_MOMENT_FORMAT),
|
||||
nroom: newReservation.room.number,
|
||||
nprice: newPrice,
|
||||
ocheckin: oldReservation.startDate.clone().local().format(HotelConstants.L10N_DATETIME_MOMENT_FORMAT),
|
||||
ocheckout: oldReservation.endDate.clone().local().format(HotelConstants.L10N_DATETIME_MOMENT_FORMAT),
|
||||
nadults: newReservation.adults,
|
||||
ocheckin: oldReservation.startDate.clone().local().format(HotelConstants.L10N_DATE_MOMENT_FORMAT),
|
||||
ocheckout: oldReservation.endDate.clone().local().format(HotelConstants.L10N_DATE_MOMENT_FORMAT),
|
||||
oroom: oldReservation.room.number,
|
||||
oprice: oldPrice,
|
||||
hasReservsLinked: (linkedReservs && linkedReservs.length !== 0)?true:false
|
||||
oadults: oldReservation.adults
|
||||
};
|
||||
var dialog = new Dialog(self, {
|
||||
title: _t("Confirm Reservation Changes"),
|
||||
buttons: [
|
||||
{
|
||||
text: _t("Yes, change it"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
disabled: !newReservation.id,
|
||||
click: function () {
|
||||
var roomId = newReservation.room.id;
|
||||
if (newReservation.room.overbooking || newReservation.room.cancelled) {
|
||||
roomId = +newReservation.room.id.substr(newReservation.room.id.indexOf('@')+1);
|
||||
|
||||
if (qdict['ncheckin'] !== qdict['ocheckin'] || qdict['ncheckout'] !== qdict['ocheckout']
|
||||
|| qdict['nroom'] !== qdict['oroom'] || qdict['nadults'] !== qdict['oadults']) {
|
||||
var linkedReservs = _.find(ev.detail.calendar_obj._reservations, function(item){
|
||||
return item.id !== newReservation.id && !item.unusedZone && item.getUserData('folio_id') === folio_id;
|
||||
});
|
||||
qdict['hasReservsLinked'] = (linkedReservs && linkedReservs.length !== 0)?true:false;
|
||||
|
||||
var hasChanged = false;
|
||||
|
||||
var dialog = new Dialog(self, {
|
||||
title: _t("Confirm Reservation Changes"),
|
||||
buttons: [
|
||||
{
|
||||
text: _t("Yes, change it"),
|
||||
classes: 'btn-primary',
|
||||
close: true,
|
||||
disabled: !newReservation.id,
|
||||
click: function () {
|
||||
var roomId = newReservation.room.id;
|
||||
if (newReservation.room.overbooking || newReservation.room.cancelled) {
|
||||
roomId = +newReservation.room.id.substr(newReservation.room.id.indexOf('@')+1);
|
||||
}
|
||||
var write_values = {
|
||||
'checkin': newReservation.startDate.format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT),
|
||||
'checkout': newReservation.endDate.format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT),
|
||||
'room_id': roomId,
|
||||
'adults': newReservation.adults,
|
||||
'overbooking': newReservation.room.overbooking
|
||||
};
|
||||
if (newReservation.room.cancelled) {
|
||||
write_values['state'] = 'cancelled';
|
||||
} else if (!newReservation.room.cancelled && oldReservation.cancelled) {
|
||||
write_values['state'] = 'draft';
|
||||
}
|
||||
self.updateReservations(ev.detail.calendar_obj, [newReservation.id],
|
||||
write_values, oldReservation, newReservation);
|
||||
hasChanged = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
text: _t("No"),
|
||||
close: true,
|
||||
}
|
||||
var write_values = {
|
||||
'checkin': newReservation.startDate.format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT),
|
||||
'checkout': newReservation.endDate.format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT),
|
||||
'room_id': roomId,
|
||||
'overbooking': newReservation.room.overbooking
|
||||
};
|
||||
if (newReservation.room.cancelled) {
|
||||
write_values['state'] = 'cancelled';
|
||||
} else if (!newReservation.room.cancelled && oldReservation.cancelled) {
|
||||
write_values['state'] = 'draft';
|
||||
}
|
||||
self.updateReservations(ev.detail.calendar_obj, [newReservation.id],
|
||||
write_values, oldReservation, newReservation);
|
||||
hasChanged = true;
|
||||
],
|
||||
$content: QWeb.render('HotelCalendar.ConfirmReservationChanges', qdict)
|
||||
}).open();
|
||||
dialog.on('closed', this, function(e){
|
||||
if (!hasChanged) {
|
||||
ev.detail.calendar_obj.replaceReservation(newReservation, oldReservation);
|
||||
}
|
||||
},
|
||||
{
|
||||
text: _t("No"),
|
||||
close: true,
|
||||
}
|
||||
],
|
||||
$content: QWeb.render('HotelCalendar.ConfirmReservationChanges', qdict)
|
||||
}).open();
|
||||
dialog.on('closed', this, function(e){
|
||||
if (!hasChanged) {
|
||||
ev.detail.calendar_obj.replaceReservation(newReservation, oldReservation);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
this._multi_calendar.on_calendar('hcalOnUpdateSelection', function(ev){
|
||||
for (var td of ev.detail.old_cells) {
|
||||
@@ -641,17 +643,17 @@ var PMSCalendarController = AbstractController.extend({
|
||||
}
|
||||
});
|
||||
this._multi_calendar.on_calendar('hcalOnChangeSelectionMode', function(ev){
|
||||
var $ledDivide = this.renderer.$el.find('#pms-menu #btn_action_divide button .led');
|
||||
var $ledUnify = this.renderer.$el.find('#pms-menu #btn_action_unify button .led');
|
||||
var $btnDivide = this.renderer.$el.find('#pms-menu #btn_action_divide > button');
|
||||
var $btnUnify = this.renderer.$el.find('#pms-menu #btn_action_unify > button');
|
||||
if (ev.detail.newMode === HotelCalendar.ACTION.DIVIDE) {
|
||||
$ledDivide.removeClass('led-disabled').addClass('led-enabled');
|
||||
$btnDivide.addClass('divide-enabled');
|
||||
} else {
|
||||
$ledDivide.removeClass('led-enabled').addClass('led-disabled');
|
||||
$btnDivide.removeClass('divide-enabled');
|
||||
}
|
||||
if (ev.detail.newMode === HotelCalendar.ACTION.UNIFY) {
|
||||
$ledUnify.removeClass('led-disabled').addClass('led-enabled');
|
||||
$btnUnify.addClass('unify-enabled');
|
||||
} else {
|
||||
$ledUnify.removeClass('led-enabled').addClass('led-disabled');
|
||||
$btnUnify.removeClass('unify-enabled');
|
||||
}
|
||||
}.bind(this));
|
||||
this._multi_calendar.on_calendar('hcalOnChangeSelection', function(ev){
|
||||
@@ -765,19 +767,36 @@ var PMSCalendarController = AbstractController.extend({
|
||||
};
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
_onViewAttached: function (ev) {
|
||||
this._multi_calendar.recalculate_reservation_positions();
|
||||
},
|
||||
|
||||
_onUpdateButtonsCounter: function (ev) {
|
||||
_update_buttons_counter: function (ev) {
|
||||
var self = this;
|
||||
var domain_checkouts = [['checkout', '=', moment().format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT)]];
|
||||
var domain_checkins = [['checkin', '=', moment().format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT)]];
|
||||
var domain_overbookings = [['overbooking', '=', true], ['state', 'not in', ['cancelled']]];
|
||||
var domain_cancelled = [['state', '=', 'cancelled']];
|
||||
var active_calendar = this._multi_calendar.get_active_calendar();
|
||||
|
||||
var filterDates = active_calendar.getDates();
|
||||
var dfrom_fmt = filterDates[0].format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT),
|
||||
dto_fmt = filterDates[1].format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT),
|
||||
now_fmt = moment().format(HotelConstants.ODOO_DATETIME_MOMENT_FORMAT);
|
||||
|
||||
var domain_checkouts = [
|
||||
['checkout', '=', now_fmt],
|
||||
['state', 'not in', ['cancelled']]
|
||||
];
|
||||
var domain_checkins = [
|
||||
['checkin', '=', now_fmt],
|
||||
['state', 'not in', ['cancelled']]
|
||||
];
|
||||
var domain_overbookings = [
|
||||
['checkin', '>=', dfrom_fmt],
|
||||
['overbooking', '=', true], ['state', 'not in', ['cancelled']]
|
||||
];
|
||||
var domain_cancelled = [
|
||||
'|', '&',
|
||||
['checkout', '>', dfrom_fmt],
|
||||
['checkout', '<', dto_fmt],
|
||||
['checkin', '>=', dfrom_fmt],
|
||||
['checkin', '<=', dto_fmt],
|
||||
['state', '=', 'cancelled']
|
||||
];
|
||||
|
||||
$.when(
|
||||
this.model.search_count(domain_checkouts),
|
||||
this.model.search_count(domain_checkins),
|
||||
@@ -788,6 +807,13 @@ var PMSCalendarController = AbstractController.extend({
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
_onViewAttached: function (ev) {
|
||||
this._multi_calendar.recalculate_reservation_positions();
|
||||
},
|
||||
|
||||
_onLoadViewFilters: function (ev) {
|
||||
var self = this;
|
||||
$.when(
|
||||
@@ -854,7 +880,7 @@ var PMSCalendarController = AbstractController.extend({
|
||||
this._multi_calendar.merge_reservations(nreservs);
|
||||
}
|
||||
if (need_update_counters) {
|
||||
this._onUpdateButtonsCounter();
|
||||
this._update_buttons_counter();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -923,36 +949,36 @@ var PMSCalendarController = AbstractController.extend({
|
||||
$dateEndDays.val(active_calendar.getOptions('orig_days'));
|
||||
$dateEndDays.trigger('change');
|
||||
|
||||
/* Overbooking Led */
|
||||
var $led = this.renderer.$el.find('#pms-menu #btn_action_overbooking button .led');
|
||||
/* Overbooking */
|
||||
var $led = this.renderer.$el.find('#pms-menu #btn_action_overbooking > button');
|
||||
if (active_calendar.options.showOverbookings) {
|
||||
$led.removeClass('led-disabled').addClass('led-enabled');
|
||||
$led.addClass('overbooking-enabled');
|
||||
} else {
|
||||
$led.removeClass('led-enabled').addClass('led-disabled');
|
||||
$led.removeClass('overbooking-enabled');
|
||||
}
|
||||
|
||||
/* Cancelled Led */
|
||||
$led = this.renderer.$el.find('#pms-menu #btn_action_cancelled button .led');
|
||||
/* Cancelled */
|
||||
$led = this.renderer.$el.find('#pms-menu #btn_action_cancelled > button');
|
||||
if (active_calendar.options.showCancelled) {
|
||||
$led.removeClass('led-disabled').addClass('led-enabled');
|
||||
$led.addClass('cancelled-enabled');
|
||||
} else {
|
||||
$led.removeClass('led-enabled').addClass('led-disabled');
|
||||
$led.removeClass('cancelled-enabled');
|
||||
}
|
||||
|
||||
/* Divide Led */
|
||||
$led = this.renderer.$el.find('#pms-menu #btn_action_divide button .led');
|
||||
/* Divide */
|
||||
$led = this.renderer.$el.find('#pms-menu #btn_action_divide > button');
|
||||
if (active_calendar.getSelectionMode() === HotelCalendar.ACTION.DIVIDE) {
|
||||
$led.removeClass('led-disabled').addClass('led-enabled');
|
||||
$led.addClass('divide-enabled');
|
||||
} else {
|
||||
$led.removeClass('led-enabled').addClass('led-disabled');
|
||||
$led.removeClass('divide-enabled');
|
||||
}
|
||||
|
||||
/* Unify Led */
|
||||
$led = this.renderer.$el.find('#pms-menu #btn_action_unify button .led');
|
||||
$led = this.renderer.$el.find('#pms-menu #btn_action_unify > button');
|
||||
if (active_calendar.getSelectionMode() === HotelCalendar.ACTION.UNIFY) {
|
||||
$led.removeClass('led-disabled').addClass('led-enabled');
|
||||
$led.addClass('unify-enabled');
|
||||
} else {
|
||||
$led.removeClass('led-enabled').addClass('led-disabled');
|
||||
$led.removeClass('unify-enabled');
|
||||
}
|
||||
|
||||
/* Calendar Record */
|
||||
@@ -962,6 +988,7 @@ var PMSCalendarController = AbstractController.extend({
|
||||
|
||||
/* Calendar Filters */
|
||||
this._refresh_filters(this._multi_calendar.get_active_filters());
|
||||
this._update_buttons_counter();
|
||||
},
|
||||
|
||||
_refresh_filters: function(calendar_filters) {
|
||||
|
||||
@@ -59,41 +59,24 @@ var HotelCalendarView = AbstractRenderer.extend({
|
||||
|
||||
update_buttons_counter: function(ncheckouts, ncheckins, noverbookings, ncancelled) {
|
||||
var self = this;
|
||||
// Checkouts Button
|
||||
var $ninfo = self.$el.find('#pms-menu #btn_action_checkout span.ninfo');
|
||||
$ninfo.text(ncheckouts);
|
||||
if (ncheckouts) {
|
||||
$ninfo.parent().parent().addClass('button-highlight');
|
||||
} else {
|
||||
$ninfo.parent().parent().removeClass('button-highlight');
|
||||
}
|
||||
|
||||
// Checkins Button
|
||||
$ninfo = self.$el.find('#pms-menu #btn_action_checkin span.ninfo');
|
||||
$ninfo.text(ncheckins);
|
||||
if (ncheckins) {
|
||||
$ninfo.parent().parent().addClass('button-highlight');
|
||||
} else {
|
||||
$ninfo.parent().parent().removeClass('button-highlight');
|
||||
}
|
||||
|
||||
// OverBookings
|
||||
$ninfo = self.$el.find('#pms-menu #btn_action_overbooking span.ninfo');
|
||||
var $ninfo = self.$el.find('#pms-menu #btn_action_overbooking span.ninfo');
|
||||
$ninfo.text(noverbookings);
|
||||
if (noverbookings) {
|
||||
$ninfo.parent().parent().addClass('button-highlight');
|
||||
$ninfo.parent().parent().addClass('overbooking-highlight');
|
||||
} else {
|
||||
$ninfo.parent().parent().removeClass('button-highlight');
|
||||
$ninfo.parent().parent().removeClass('overbooking-highlight');
|
||||
}
|
||||
|
||||
// Cancelled
|
||||
$ninfo = self.$el.find('#pms-menu #btn_action_cancelled span.ninfo');
|
||||
$ninfo.text(ncancelled);
|
||||
if (ncancelled) {
|
||||
$ninfo.parent().parent().addClass('button-highlight');
|
||||
} else {
|
||||
$ninfo.parent().parent().removeClass('button-highlight');
|
||||
}
|
||||
// // Cancelled
|
||||
// $ninfo = self.$el.find('#pms-menu #btn_action_cancelled span.ninfo');
|
||||
// $ninfo.text(ncancelled);
|
||||
// if (ncancelled) {
|
||||
// $ninfo.parent().parent().addClass('cancelled-highlight');
|
||||
// } else {
|
||||
// $ninfo.parent().parent().removeClass('cancelled-highlight');
|
||||
// }
|
||||
},
|
||||
|
||||
init_calendar_view: function(){
|
||||
@@ -102,20 +85,20 @@ var HotelCalendarView = AbstractRenderer.extend({
|
||||
/** VIEW CONTROLS INITIALIZATION **/
|
||||
// DATE TIME PICKERS
|
||||
var DTPickerOptions = {
|
||||
viewMode: 'months',
|
||||
icons : {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down'
|
||||
},
|
||||
//language : moment.locale(),
|
||||
locale : moment.locale(),
|
||||
format : HotelConstants.L10N_DATE_MOMENT_FORMAT,
|
||||
widgetPositioning:{
|
||||
horizontal: 'auto',
|
||||
vertical: 'bottom'
|
||||
}
|
||||
viewMode: 'months',
|
||||
icons : {
|
||||
time: 'fa fa-clock-o',
|
||||
date: 'fa fa-calendar',
|
||||
up: 'fa fa-chevron-up',
|
||||
down: 'fa fa-chevron-down'
|
||||
},
|
||||
//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');
|
||||
@@ -179,7 +162,6 @@ var HotelCalendarView = AbstractRenderer.extend({
|
||||
});
|
||||
|
||||
return $.when(
|
||||
this.trigger_up('onUpdateButtonsCounter'),
|
||||
this.trigger_up('onLoadViewFilters'),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -126,20 +126,6 @@ var MPMSCalendarController = AbstractController.extend({
|
||||
for (var notif of notifications) {
|
||||
if (notif[0][1] === 'hotel.reservation') {
|
||||
switch (notif[1]['type']) {
|
||||
case 'availability':
|
||||
var avail = notif[1]['availability'];
|
||||
var room_type = Object.keys(avail)[0];
|
||||
var day = Object.keys(avail[room_type])[0];
|
||||
var dt = HotelCalendarManagement.toMoment(day);
|
||||
var availability = {};
|
||||
availability[room_type] = [{
|
||||
'date': dt.format(HotelConstants.ODOO_DATE_MOMENT_FORMAT),
|
||||
'avail': avail[room_type][day][0],
|
||||
'no_ota': avail[room_type][day][1],
|
||||
'id': avail[room_type][day][2]
|
||||
}];
|
||||
this.renderer._hcalendar.addAvailability(availability);
|
||||
break;
|
||||
case 'pricelist':
|
||||
var prices = notif[1]['price'];
|
||||
var pricelist_id = Object.keys(prices)[0];
|
||||
|
||||
@@ -21,7 +21,7 @@ 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',
|
||||
|
||||
@@ -69,19 +69,21 @@ var HotelCalendarManagementView = AbstractRenderer.extend({
|
||||
},
|
||||
|
||||
/** CUSTOM METHODS **/
|
||||
save_changes: function() {
|
||||
var self = this;
|
||||
get_values_to_save: function() {
|
||||
var btn_save = this.$el.find('#btn_save_changes');
|
||||
if (!btn_save.hasClass('need-save')) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var pricelist = this._hcalendar.getPricelist(true);
|
||||
var restrictions = this._hcalendar.getRestrictions(true);
|
||||
var availability = this._hcalendar.getAvailability(true);
|
||||
|
||||
var params = this.generate_params();
|
||||
var oparams = [params['prices'], params['restrictions'], pricelist, restrictions, availability];
|
||||
return [params['prices'], params['restrictions'], pricelist, restrictions];
|
||||
},
|
||||
|
||||
save_changes: function() {
|
||||
var oparams = this.get_values_to_save();
|
||||
this.trigger_up('onSaveChanges', oparams);
|
||||
},
|
||||
|
||||
@@ -135,7 +137,7 @@ var HotelCalendarManagementView = AbstractRenderer.extend({
|
||||
this.$CalendarHeaderDays = this.$el.find("div.table-room_type-data-header");
|
||||
|
||||
// Sticky Header Days
|
||||
this.$ehcal.scroll(this._on_scroll.bind(this));
|
||||
$('.o_content').scroll(this._on_scroll.bind(this));
|
||||
},
|
||||
|
||||
setCalendarData: function (prices, restrictions, availability, count_reservations) {
|
||||
@@ -169,10 +171,10 @@ var HotelCalendarManagementView = AbstractRenderer.extend({
|
||||
},
|
||||
|
||||
_on_scroll: function() {
|
||||
var curScrollPos = this.$ehcal.scrollTop();
|
||||
var curScrollPos = $('.o_content').scrollTop();
|
||||
if (curScrollPos > 0) {
|
||||
this.$CalendarHeaderDays.css({
|
||||
top: `${curScrollPos}px`,
|
||||
top: `${curScrollPos-this.$ehcal.position().top}px`,
|
||||
position: 'sticky'
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
* Alexandre Díaz <alex@aloxa.eu>
|
||||
*/
|
||||
|
||||
#pms-search-cal-pag {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#mpms-search {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.hcal-management-table, .hcal-management-table-day {
|
||||
border-collapse: collapse !important;
|
||||
table-layout: fixed;
|
||||
@@ -29,7 +37,7 @@
|
||||
width: 110px !important;
|
||||
vertical-align: center;
|
||||
min-width: 110px;
|
||||
height: 115px;
|
||||
height: 130px;
|
||||
min-height: 100px;
|
||||
white-space: nowrap;
|
||||
/*overflow: hidden;*/
|
||||
@@ -77,11 +85,11 @@
|
||||
}
|
||||
|
||||
.hcal-management-low > tbody > tr > td, .hcal-management-low > table > tbody > tr > td {
|
||||
height: 35px !important;
|
||||
height: 58px !important;
|
||||
}
|
||||
|
||||
.hcal-management-medium > tbody > tr > td, .hcal-management-medium > table > tbody > tr > td {
|
||||
height: 60px !important;
|
||||
height: 80px !important;
|
||||
}
|
||||
|
||||
.hcal-management-medium tr[name='rest_b'], .hcal-management-medium tr[name='rest_c'],
|
||||
@@ -92,12 +100,20 @@
|
||||
|
||||
.table-room_types {
|
||||
flex: 1 1 auto;
|
||||
margin-top: 1.6em;
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
|
||||
.table-room_type-data-header {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.table-room_type-data-header .hcal-management-table tr td {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#hcal_management_widget {
|
||||
overflow: auto;
|
||||
display:flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#hcal-management-container-dd {
|
||||
@@ -139,6 +155,11 @@ table.hcal-management-table input {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.filter-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
button.hcal-management-input {
|
||||
font-size: 9px;
|
||||
border: 1px solid gray;
|
||||
|
||||
@@ -1936,7 +1936,13 @@ HotelCalendar.prototype = {
|
||||
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';
|
||||
if (posAction == HotelCalendar.ACTION.MOVE_LEFT || posAction == HotelCalendar.ACTION.MOVE_RIGHT) {
|
||||
this.style.cursor = 'col-resize';
|
||||
} else if (posAction == HotelCalendar.ACTION.MOVE_DOWN) {
|
||||
this.style.cursor = 'ns-resize';
|
||||
} else {
|
||||
this.style.cursor = 'pointer';
|
||||
}
|
||||
}, false);
|
||||
var _funcEvent = function(ev){
|
||||
if ($this._isLeftButtonPressed(ev)) {
|
||||
@@ -2014,8 +2020,8 @@ HotelCalendar.prototype = {
|
||||
$this._lazyModeReservationsSelection = setTimeout(function($this){
|
||||
var reserv = $this.getReservation(this.dataset.hcalReservationObjId);
|
||||
$this._updateHighlightInvalidZones(reserv);
|
||||
if (reserv.readOnly || (reserv.fixDays && ($this.reservationAction.action == HotelCalendar.ACTION.MOVE_LEFT ||
|
||||
$this.reservationAction.action == HotelCalendar.ACTION.MOVE_RIGHT))) {
|
||||
if (reserv.readOnly || (reserv.fixDays && ($this.reservationAction.action == HotelCalendar.ACTION.MOVE_LEFT
|
||||
|| $this.reservationAction.action == HotelCalendar.ACTION.MOVE_RIGHT))) {
|
||||
$this.reservationAction.action = HotelCalendar.ACTION.NONE;
|
||||
return false;
|
||||
}
|
||||
@@ -2084,8 +2090,9 @@ HotelCalendar.prototype = {
|
||||
|
||||
_getRerservationPositionAction: function(/*HTMLObject*/elm, /*Int*/posX, /*Int*/posY) {
|
||||
var bounds = this.loopedOffsetOptimized(elm);
|
||||
if (posX <= 5) { return HotelCalendar.ACTION.MOVE_LEFT; }
|
||||
else if (posX >= bounds.width-10) { return HotelCalendar.ACTION.MOVE_RIGHT; }
|
||||
if (posX <= 4) { return HotelCalendar.ACTION.MOVE_LEFT; }
|
||||
else if (posX >= bounds.width-8) { return HotelCalendar.ACTION.MOVE_RIGHT; }
|
||||
else if (posY >= bounds.height-4) { return HotelCalendar.ACTION.MOVE_DOWN; }
|
||||
return HotelCalendar.ACTION.MOVE_ALL;
|
||||
},
|
||||
|
||||
@@ -2542,6 +2549,18 @@ HotelCalendar.prototype = {
|
||||
reserv.startDate.set({'date': date_cell.date(), 'month': date_cell.month(), 'year': date_cell.year()});
|
||||
this.reservationAction.newReservationObj = reserv;
|
||||
needUpdate = true;
|
||||
} else if (this.reservationAction.action == HotelCalendar.ACTION.MOVE_DOWN) {
|
||||
var parentRow = ev.target.parentNode.parentNode.parentNode.parentNode;
|
||||
var room = this.getRoom(parentRow.dataset.hcalRoomObjId);
|
||||
|
||||
if (room.id === reserv.room.id) {
|
||||
if (!this.reservationAction.oldReservationObj) {
|
||||
this.reservationAction.oldReservationObj = reserv.clone();
|
||||
}
|
||||
reserv.adults = +ev.target.dataset.hcalBedNum + 1;
|
||||
this.reservationAction.newReservationObj = reserv;
|
||||
needUpdate = true;
|
||||
}
|
||||
} else if (this.reservationAction.action == HotelCalendar.ACTION.MOVE_ALL) {
|
||||
// Relative Movement
|
||||
date_cell.subtract(this.reservationAction.daysOffset, 'd');
|
||||
@@ -2856,7 +2875,7 @@ HotelCalendar.prototype = {
|
||||
|
||||
/** CONSTANTS **/
|
||||
HotelCalendar.DOMAIN = { NONE: -1, RESERVATIONS: 0, ROOMS: 1 };
|
||||
HotelCalendar.ACTION = { NONE: -1, MOVE_ALL: 0, MOVE_LEFT: 1, MOVE_RIGHT: 2, SWAP: 3, DIVIDE: 4, UNIFY: 5 };
|
||||
HotelCalendar.ACTION = { NONE: -1, MOVE_ALL: 0, MOVE_LEFT: 1, MOVE_RIGHT: 2, MOVE_DOWN: 3, SWAP: 4, DIVIDE: 5, UNIFY: 6 };
|
||||
HotelCalendar.MODE = { NONE: -1, SWAP_FROM: 0, SWAP_TO: 1 };
|
||||
HotelCalendar.DATE_FORMAT_SHORT_ = 'DD/MM/YYYY';
|
||||
HotelCalendar.DATE_FORMAT_LONG_ = HotelCalendar.DATE_FORMAT_SHORT_ + ' HH:mm:ss';
|
||||
|
||||
@@ -201,24 +201,42 @@ HotelCalendarManagement.prototype = {
|
||||
row = table.insertRow();
|
||||
row.setAttribute('name', 'price');
|
||||
cell = row.insertCell();
|
||||
cell.setAttribute('colspan', '3');
|
||||
cell.setAttribute('colspan', '4');
|
||||
telm = document.createElement("input");
|
||||
telm.setAttribute('id', this._sanitizeId(`PRICE_${roomId}_${dateShortStr}`));
|
||||
telm.setAttribute('name', 'price');
|
||||
telm.setAttribute('type', 'edit');
|
||||
telm.setAttribute('title', this._t('Price'));
|
||||
telm.value = room.price;
|
||||
telm.dataset.orgValue = room.price;
|
||||
telm.dataset.hcalParentCell = parentCell.getAttribute('id');
|
||||
telm.classList.add('hcal-management-input');
|
||||
telm.addEventListener('change', function(ev){ $this.onInputChange(ev, this); }, false);
|
||||
cell.appendChild(telm);
|
||||
|
||||
row = table.insertRow();
|
||||
row.setAttribute('name', 'avail');
|
||||
row.style.display = 'none';
|
||||
cell = row.insertCell();
|
||||
cell.setAttribute('colspan', '2');
|
||||
telm = document.createElement("input");
|
||||
telm.setAttribute('id', this._sanitizeId(`QUOTA_${roomId}_${dateShortStr}`));
|
||||
telm.setAttribute('name', 'quota');
|
||||
telm.setAttribute('type', 'edit');
|
||||
telm.setAttribute('title', this._t('Availability Quota'));
|
||||
telm.value = telm.dataset.orgValue = 0;
|
||||
telm.dataset.hcalParentCell = parentCell.getAttribute('id');
|
||||
telm.classList.add('hcal-management-input');
|
||||
telm.addEventListener('change', function(ev){ $this.onInputChange(ev, this); }, false);
|
||||
cell.appendChild(telm);
|
||||
cell = row.insertCell();
|
||||
cell.setAttribute('colspan', '2');
|
||||
telm = document.createElement("input");
|
||||
telm.setAttribute('id', this._sanitizeId(`AVAIL_${roomId}_${dateShortStr}`));
|
||||
telm.setAttribute('name', 'avail');
|
||||
telm.setAttribute('id', this._sanitizeId(`MAX_AVAIL_${roomId}_${dateShortStr}`));
|
||||
telm.setAttribute('name', 'max_avail');
|
||||
telm.setAttribute('type', 'edit');
|
||||
telm.setAttribute('title', this._t('Availability'));
|
||||
telm.value = 0;
|
||||
telm.setAttribute('title', this._t('Max. Availability'));
|
||||
telm.value = telm.dataset.orgValue = 0;
|
||||
telm.dataset.hcalParentCell = parentCell.getAttribute('id');
|
||||
telm.classList.add('hcal-management-input');
|
||||
telm.addEventListener('change', function(ev){ $this.onInputChange(ev, this); }, false);
|
||||
@@ -305,6 +323,7 @@ HotelCalendarManagement.prototype = {
|
||||
telm.appendChild(selectOpt);
|
||||
cell.appendChild(telm);
|
||||
cell = row.insertCell();
|
||||
cell.setAttribute('colspan', '1');
|
||||
telm = document.createElement("input");
|
||||
telm.setAttribute('id', this._sanitizeId(`FREE_ROOMS_${roomId}_${dateShortStr}`));
|
||||
telm.setAttribute('name', 'free_rooms');
|
||||
@@ -331,7 +350,6 @@ HotelCalendarManagement.prototype = {
|
||||
telm.classList.add('hcal-management-input', 'pull-left');
|
||||
telm.addEventListener('click', function(ev){ $this.onInputChange(ev, this); }, false);
|
||||
cell.appendChild(telm);
|
||||
|
||||
telm = document.createElement("span");
|
||||
telm.setAttribute('id', this._sanitizeId(`OPTIONS_${roomId}_${dateShortStr}`));
|
||||
telm.setAttribute('name', 'options');
|
||||
@@ -376,8 +394,7 @@ HotelCalendarManagement.prototype = {
|
||||
this._updateRestrictions();
|
||||
}
|
||||
if (typeof avail !== 'undefined' && avail) {
|
||||
this._availability = avail;
|
||||
this._updateAvailability();
|
||||
this.setAvailability(avail);
|
||||
}
|
||||
if (typeof count_free_rooms !== 'undefined' && count_free_rooms) {
|
||||
this._free_rooms = count_free_rooms;
|
||||
@@ -385,6 +402,16 @@ HotelCalendarManagement.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
setAvailability: function(avails) {
|
||||
this._availability = avails;
|
||||
if (this._availability) {
|
||||
for (var elm of this.etable.querySelectorAll("tr[name=avail]")) {
|
||||
elm.style.display = "";
|
||||
}
|
||||
}
|
||||
this._updateAvailability();
|
||||
},
|
||||
|
||||
//==== ROOMS
|
||||
getRoom: function(/*String*/id) {
|
||||
return _.find(this.options.rooms, function(item){ return item.id == id; });
|
||||
@@ -696,7 +723,8 @@ HotelCalendarManagement.prototype = {
|
||||
for (var avail of this._availability[room_typeId]) {
|
||||
var dd = HotelCalendarManagement.toMoment(avail.date, this.options.dateFormatShort);
|
||||
var inputIds = [
|
||||
`AVAIL_${room_typeId}_${dd.format(HotelCalendarManagement._DATE_FORMAT_SHORT)}`, avail.avail,
|
||||
`QUOTA_${room_typeId}_${dd.format(HotelCalendarManagement._DATE_FORMAT_SHORT)}`, avail.quota,
|
||||
`MAX_AVAIL_${room_typeId}_${dd.format(HotelCalendarManagement._DATE_FORMAT_SHORT)}`, avail.max_avail,
|
||||
`NO_OTA_${room_typeId}_${dd.format(HotelCalendarManagement._DATE_FORMAT_SHORT)}`, avail.no_ota
|
||||
];
|
||||
|
||||
@@ -731,17 +759,21 @@ HotelCalendarManagement.prototype = {
|
||||
for (var i=0; i<=this.options.days; i++) {
|
||||
var ndate = this.options.startDate.clone().add(i, 'd');
|
||||
var ndateStr = ndate.format(HotelCalendarManagement._DATE_FORMAT_SHORT);
|
||||
var inputAvailId = this._sanitizeId(`AVAIL_${room.id}_${ndateStr}`);
|
||||
var inputAvail = this.etable.querySelector(`#${inputAvailId}`);
|
||||
var inputQuotaId = this._sanitizeId(`QUOTA_${room.id}_${ndateStr}`);
|
||||
var inputQuota = this.etable.querySelector(`#${inputQuotaId}`);
|
||||
var inputMaxAvailId = this._sanitizeId(`MAX_AVAIL_${room.id}_${ndateStr}`);
|
||||
var inputMaxAvail = this.etable.querySelector(`#${inputMaxAvailId}`);
|
||||
var inputNoOTAId = this._sanitizeId(`NO_OTA_${room.id}_${ndateStr}`);
|
||||
var inputNoOTA = this.etable.querySelector(`#${inputNoOTAId}`);
|
||||
|
||||
if (!onlyNew || (onlyNew && (inputAvail.value !== inputAvail.dataset.orgValue ||
|
||||
(inputNoOTA.dataset.state && inputNoOTA.dataset.state !== inputNoOTA.dataset.orgValue)))) {
|
||||
if (!onlyNew || (onlyNew && (inputQuota.value !== inputQuota.dataset.orgValue
|
||||
|| inputMaxAvail.value !== inputMaxAvail.dataset.orgValue
|
||||
|| (inputNoOTA.dataset.state && inputNoOTA.dataset.state !== inputNoOTA.dataset.orgValue)))) {
|
||||
if (!(room.id in data)) { data[room.id] = []; }
|
||||
data[room.id].push({
|
||||
'date': ndate.format('YYYY-MM-DD'),
|
||||
'avail': inputAvail.value,
|
||||
'quota': inputQuota.value,
|
||||
'max_avail': inputMaxAvail.value,
|
||||
'no_ota': Boolean(inputNoOTA.dataset.state === 'true') || false
|
||||
});
|
||||
}
|
||||
@@ -790,7 +822,7 @@ HotelCalendarManagement.prototype = {
|
||||
value = elm.checked;
|
||||
}
|
||||
else if (name === 'min_stay' || name === 'min_stay_arrival' || name === 'max_stay' ||
|
||||
name === 'price' || name === 'avail' || name === 'max_stay_arrival') {
|
||||
name === 'price' || name === 'quota' || name === 'max_avail' || name === 'max_stay_arrival') {
|
||||
if (!this._isNumeric(value)) {
|
||||
elm.style.backgroundColor = 'red';
|
||||
} else if (orgValue !== value) {
|
||||
@@ -973,9 +1005,9 @@ HotelCalendarManagement.prototype = {
|
||||
},
|
||||
minDate: $this.options.startDate,
|
||||
maxDate: $this.options.startDate.clone().add($this.options.days, 'd'),
|
||||
language : moment.locale(),
|
||||
//language : moment.locale(),
|
||||
format : HotelCalendarManagement._DATE_FORMAT_SHORT,
|
||||
disabledHours: true // TODO: Odoo uses old datetimepicker version
|
||||
disabledHours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 18, 19, 20, 21, 22, 23]
|
||||
};
|
||||
|
||||
$('table#hcal-management-clone-dates #date_begin').datetimepicker(DTPickerOptions);
|
||||
|
||||
@@ -4,81 +4,63 @@
|
||||
<t t-name="hotel_calendar.HotelCalendarManagementView">
|
||||
<div class="col-xs-12 col-lg-12 nopadding">
|
||||
<div class="col-xs-12 col-lg-12 nopadding">
|
||||
<div class="col-xs-12 col-lg-12 nopadding" id="mpms-search">
|
||||
<div class="col-xs-12 col-lg-12" id="mpms-search">
|
||||
<table class="col-xs-12 col-lg-12 nopadding" id="pms-search-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-xs-1 col-lg-1">
|
||||
<button class="btn col-xs-12 col-lg-12" id="btn_save_changes" title="Save Changes">
|
||||
<i class="fa fa-save fa-stack-2x"> </i> &nbsp;
|
||||
<i class="fa fa-save fa-stack-2x"> </i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="col-xs-4 col-lg-3">
|
||||
<div class="col-xs-12 col-lg-12 nopadding">
|
||||
<div class="col-xs-3 col-lg-3 nopadding text-right filter-title">
|
||||
<strong style="line-height:2em">PRICELIST:</strong>
|
||||
</div>
|
||||
<div class="col-xs-9 col-lg-9">
|
||||
<select class="list form-control" id="price_list"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-lg-12 nopadding">
|
||||
<div class="col-xs-3 col-lg-3 nopadding text-right filter-title">
|
||||
<strong style="line-height:2em">RESTRICTIONS:</strong>
|
||||
</div>
|
||||
<div class="col-xs-9 col-lg-9">
|
||||
<select class="list form-control" id="restriction_list"/>
|
||||
</div>
|
||||
</div>
|
||||
<td class="col-xs-2 col-lg-2">
|
||||
<select class="list form-control" id="price_list"/>
|
||||
<select class="list form-control" id="restriction_list"/>
|
||||
</td>
|
||||
<td class="col-xs-4 col-lg-4">
|
||||
<div class="col-xs-12 col-lg-12">
|
||||
<table style="margin: 0 auto">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
FROM<br/>
|
||||
<div class="input-group date" id="date_begin">
|
||||
<input type="text" class="o_datepicker_input form-control" name="date_begin" required="required" />
|
||||
<span class="input-group-addon">
|
||||
<span class="fa fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
TO<br/>
|
||||
<div class="input-group date" id="date_end">
|
||||
<input type="text" class="o_datepicker_input form-control" name="date_end" required="required" />
|
||||
<span class="input-group-addon">
|
||||
<span class="fa fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<table style="margin: 0 auto">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="filter-title">FROM</span><br/>
|
||||
<div class="input-group date" id="date_begin">
|
||||
<input type="text" class="o_datepicker_input form-control" name="date_begin" required="required" />
|
||||
<span class="input-group-addon">
|
||||
<span class="fa fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="filter-title">TO</span><br/>
|
||||
<div class="input-group date" id="date_end">
|
||||
<input type="text" class="o_datepicker_input form-control" name="date_end" required="required" />
|
||||
<span class="input-group-addon">
|
||||
<span class="fa fa-calendar"></span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td class="col-xs-1 col-lg-1">
|
||||
<strong style="line-height:2em">View Mode:</strong>
|
||||
<strong class="filter-title">View Mode:</strong>
|
||||
<select class="list form-control" id="mode_list">
|
||||
<option value="all">All</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="low">Low</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="col-xs-2 col-lg-2">
|
||||
<div class="col-xs-12 col-lg-12" id="pms-search-cal-pag">
|
||||
<button id="cal-pag-prev-plus" class="btn"><i class="fa fa-3x fa-angle-double-left"></i></button>
|
||||
<button id="cal-pag-prev" class="btn"><i class="fa fa-3x fa-angle-left"></i></button>
|
||||
<button id="cal-pag-selector" class="btn"><i class="fa fa-3x fa-calendar"></i></button>
|
||||
<button id="cal-pag-next" class="btn"><i class="fa fa-3x fa-angle-right"></i></button>
|
||||
<button id="cal-pag-next-plus" class="btn"><i class="fa fa-3x fa-angle-double-right"></i></button>
|
||||
</div>
|
||||
<td class="col-xs-2 col-lg-2" id="pms-search-cal-pag">
|
||||
<button id="cal-pag-prev-plus" class="btn btn-sm"><i class="fa fa-2x fa-angle-double-left"></i></button>
|
||||
<button id="cal-pag-prev" class="btn btn-sm"><i class="fa fa-2x fa-angle-left"></i></button>
|
||||
<button id="cal-pag-selector" class="btn btn-sm"><i class="fa fa-2x fa-calendar"></i></button>
|
||||
<button id="cal-pag-next" class="btn btn-sm"><i class="fa fa-2x fa-angle-right"></i></button>
|
||||
<button id="cal-pag-next-plus" class="btn btn-sm"><i class="fa fa-2x fa-angle-double-right"></i></button>
|
||||
</td>
|
||||
<td class="col-xs-1 col-lg-1">
|
||||
<button class="btn btn-default col-xs-12 col-lg-12" id="btn_massive_changes" title="Launch Massive Changes">
|
||||
<i class="fa fa-bolt fa-stack-2x"> </i> &nbsp;
|
||||
<i class="fa fa-bolt fa-stack-2x"> </i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
<t t-if="oroom != nroom">
|
||||
<strong>Room:</strong> <t t-esc="oroom"/><br/>
|
||||
</t>
|
||||
<t t-if="oadults != nadults">
|
||||
<strong>Adults:</strong> <t t-esc="oadults"/><br/>
|
||||
</t>
|
||||
<!--
|
||||
<t t-if="oprice != nprice">
|
||||
<strong>Price:</strong> <t t-esc="oprice" widget="monetary"/><br/>
|
||||
@@ -36,6 +39,9 @@
|
||||
<t t-if="oroom != nroom">
|
||||
<strong>Room:</strong> <t t-esc="nroom"/><br/>
|
||||
</t>
|
||||
<t t-if="oadults != nadults">
|
||||
<strong>Adults:</strong> <t t-esc="nadults"/><br/>
|
||||
</t>
|
||||
<!--
|
||||
<t t-if="oprice != nprice">
|
||||
<strong>Price:</strong> <t t-esc="nprice" widget="monetary"/><br/>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<i class="fa fa-fw o_button_icon fa-sign-out"> </i>
|
||||
<div class="o_button_text">
|
||||
<span class="ninfo">0</span><br/>
|
||||
Checkouts
|
||||
<span class="text-hidden-xs">Checkouts</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -32,7 +32,7 @@
|
||||
<i class="fa fa-fw o_button_icon fa-sign-in"> </i>
|
||||
<div class="o_button_text">
|
||||
<span class="ninfo">0</span><br/>
|
||||
Checkins
|
||||
<span class="text-hidden-xs">Checkins</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -41,7 +41,7 @@
|
||||
<div class='led led-disabled'></div>
|
||||
<i class="fa fa-fw o_button_icon fa-retweet"> </i>
|
||||
<div class="o_button_text">
|
||||
<span class="ntext">Start Swap</span>
|
||||
<span class="text-hidden-xs ntext">Start Swap</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
<button class="btn btn-default col-xs-12 col-md-12 button-box" data-action="hotel.open_wizard_reservations">
|
||||
<i class="fa fa-fw o_button_icon fa-magic"> </i>
|
||||
<div class="o_button_text">
|
||||
Wizard
|
||||
<span class="text-hidden-xs">Wizard</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -59,7 +59,7 @@
|
||||
<i class="fa fa-fw o_button_icon fa-clock-o"> </i>
|
||||
<div class="o_button_text">
|
||||
<span class="ninfo">0</span><br/>
|
||||
Overbooking
|
||||
<span class="text-hidden-xs">Overbook.</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@
|
||||
<i class="fa fa-fw o_button_icon fa-calendar-times-o"> </i>
|
||||
<div class="o_button_text">
|
||||
<span class="ninfo">0</span><br/>
|
||||
Cancelled
|
||||
<span class="text-hidden-xs">Cancelled</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -78,7 +78,7 @@
|
||||
<div class='led led-disabled'></div>
|
||||
<i class="fa fa-fw o_button_icon fa-scissors"> </i>
|
||||
<div class="o_button_text">
|
||||
Divide
|
||||
<span class="text-hidden-xs">Divide</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
<div class='led led-disabled'></div>
|
||||
<i class="fa fa-fw o_button_icon fa-compress"> </i>
|
||||
<div class="o_button_text">
|
||||
Unify
|
||||
<span class="text-hidden-xs">Unify</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# Copyright 2018-2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from . import inherited_hotel_reservation
|
||||
from . import inherited_bus_hotel_calendar
|
||||
from . import inherited_hotel_calendar_management
|
||||
from . import inherited_hotel_room_type_availability
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# Copyright 2018-2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from datetime import datetime
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
@@ -9,6 +9,29 @@ from odoo.addons.hotel_calendar.controllers.bus import HOTEL_BUS_CHANNEL_ID
|
||||
class BusHotelCalendar(models.TransientModel):
|
||||
_inherit = 'bus.hotel.calendar'
|
||||
|
||||
@api.model
|
||||
def _generate_availability_notification(self, vals):
|
||||
date_dt = datetime.strptime(vals['date'], DEFAULT_SERVER_DATE_FORMAT)
|
||||
return {
|
||||
'type': 'availability',
|
||||
'availability': {
|
||||
vals['room_type_id']: {
|
||||
date_dt.strftime("%d/%m/%Y"): {
|
||||
'quota': vals['quota'],
|
||||
'max_avail': vals['max_avail'],
|
||||
'id': vals['id'],
|
||||
'no_ota': vals['no_ota'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def send_availability_notification(self, vals):
|
||||
notif = self._generate_availability_notification(vals)
|
||||
self.env['bus.bus'].sendone((self._cr.dbname, 'hotel.reservation',
|
||||
HOTEL_BUS_CHANNEL_ID), notif)
|
||||
|
||||
@api.model
|
||||
def _generate_issue_notification(self, ntype, title, issue_id, section,
|
||||
message):
|
||||
@@ -26,14 +49,6 @@ class BusHotelCalendar(models.TransientModel):
|
||||
},
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _generate_availability_notification(self, vals):
|
||||
date_dt = datetime.strptime(vals['date'], DEFAULT_SERVER_DATE_FORMAT)
|
||||
json = super(BusHotelCalendar, self)._generate_availability_notification(vals)
|
||||
json['availability'][vals['room_type_id']][date_dt.strftime("%d/%m/%Y")].append(
|
||||
vals['no_ota'])
|
||||
return json
|
||||
|
||||
@api.model
|
||||
def send_issue_notification(self, ntype, title, issue_id, section, message):
|
||||
notif = self._generate_issue_notification(ntype, title, issue_id, section, message)
|
||||
|
||||
@@ -1,38 +1,96 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# Copyright 2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo import models, api
|
||||
from datetime import timedelta
|
||||
from odoo import models, api, fields
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
class HotelCalendarManagement(models.TransientModel):
|
||||
_inherit = 'hotel.calendar.management'
|
||||
|
||||
@api.model
|
||||
def _get_availability_values(self, avail, room_type):
|
||||
vals = super(HotelCalendarManagement, self)._get_availability_values(
|
||||
avail, room_type)
|
||||
vals.update({
|
||||
'channel_max_avail': vals['avail'],
|
||||
'no_ota': avail['no_ota'],
|
||||
})
|
||||
return vals
|
||||
def _hcalendar_availability_json_data(self, dfrom, dto):
|
||||
date_start = fields.Date.from_string(dfrom)
|
||||
date_end = fields.Date.from_string(dto)
|
||||
date_diff = abs((date_end - date_start).days) + 1
|
||||
hotel_room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
room_types = self.env['hotel.room.type'].search([])
|
||||
json_data = {}
|
||||
|
||||
for room_type in room_types:
|
||||
json_data[room_type.id] = []
|
||||
for i in range(0, date_diff):
|
||||
cur_date = date_start + timedelta(days=i)
|
||||
cur_date_str = cur_date.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
avail = hotel_room_type_avail_obj.search([
|
||||
('date', '=', cur_date_str),
|
||||
('room_type_id', '=', room_type.id)
|
||||
])
|
||||
json_data[room_type.id].append(
|
||||
self._generate_avalaibility_data(room_type, cur_date_str, avail))
|
||||
return json_data
|
||||
|
||||
@api.model
|
||||
def _generate_avalaibility_data(self, room_type, date, avail):
|
||||
vals = super(HotelCalendarManagement, self)._generate_avalaibility_data(
|
||||
room_type, date, avail)
|
||||
vals.update({
|
||||
return {
|
||||
'id': avail and avail.id or False,
|
||||
'date': avail and avail.date or date,
|
||||
'no_ota': avail and avail.no_ota or False,
|
||||
})
|
||||
'quota': avail and avail.quota or -1,
|
||||
'max_avail': avail and avail.max_avail or -1,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_availability_values(self, vals):
|
||||
vals = {
|
||||
'quota': vals['quota'],
|
||||
'max_avail': vals['max_avail'],
|
||||
'no_ota': vals['no_ota'],
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.multi
|
||||
def save_changes(self, pricelist_id, restriction_id, pricelist,
|
||||
restrictions, availability):
|
||||
restrictions, availability=False):
|
||||
res = super(HotelCalendarManagement, self).save_changes(
|
||||
pricelist_id,
|
||||
restriction_id,
|
||||
pricelist,
|
||||
restrictions,
|
||||
availability)
|
||||
availability=availability)
|
||||
|
||||
room_type_obj = self.env['hotel.room.type']
|
||||
room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
# Save Availability
|
||||
for k_avail in availability.keys():
|
||||
room_type_id = room_type_obj.browse(int(k_avail))
|
||||
for avail in availability[k_avail]:
|
||||
vals = self._get_availability_values(avail)
|
||||
avail_id = room_type_avail_obj.search([
|
||||
('date', '=', avail['date']),
|
||||
('room_type_id', '=', room_type_id.id),
|
||||
], limit=1)
|
||||
if not avail_id:
|
||||
vals.update({
|
||||
'date': avail['date'],
|
||||
'room_type_id': room_type_id.id,
|
||||
})
|
||||
avail_id = room_type_avail_obj.with_context({
|
||||
'mail_create_nosubscribe': True,
|
||||
}).create(vals)
|
||||
else:
|
||||
avail_id.write(vals)
|
||||
|
||||
self.env['channel.backend'].cron_push_changes()
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def get_hcalendar_all_data(self, dfrom, dto, pricelist_id, restriction_id,
|
||||
withRooms):
|
||||
res = super(HotelCalendarManagement, self).get_hcalendar_all_data(
|
||||
dfrom, dto, pricelist_id, restriction_id, withRooms)
|
||||
json_avails = self._hcalendar_availability_json_data(dfrom, dto)
|
||||
res.update({
|
||||
'availability': json_avails or [],
|
||||
})
|
||||
return res
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# Copyright 2018-2019 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 HotelRoomTypeAvailability(models.Model):
|
||||
@@ -9,7 +9,9 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
def _prepare_notif_values(self, record):
|
||||
return {
|
||||
'date': record.date,
|
||||
'avail': record.avail,
|
||||
'quota': record.quota,
|
||||
'no_ota': record.no_ota,
|
||||
'max_avail': record.max_avail,
|
||||
'room_type_id': record.room_type_id.id,
|
||||
'id': record.id,
|
||||
}
|
||||
@@ -35,7 +35,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pms-menu #btn_channel_manager_request.incoming i {
|
||||
animation-name: channel-manager-changes;
|
||||
animation-duration: 2s;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define('hotel_calendar.PMSHotelCalendarControllerChannelConnector', function (require) {
|
||||
odoo.define('hotel_calendar_channel_connector.PMSHotelCalendarController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var PMSCalendarController = require('hotel_calendar.PMSCalendarController');
|
||||
@@ -8,7 +8,7 @@ var Core = require('web.core');
|
||||
|
||||
var QWeb = Core.qweb;
|
||||
|
||||
var PMSHotelCalendarControllerChannelConnector = PMSCalendarController.include({
|
||||
var PMSHotelCalendarController = PMSCalendarController.include({
|
||||
_sounds: [],
|
||||
SOUNDS: { NONE: 0, BOOK_NEW:1, BOOK_CANCELLED:2 },
|
||||
|
||||
@@ -88,6 +88,6 @@ var PMSHotelCalendarControllerChannelConnector = PMSCalendarController.include({
|
||||
|
||||
});
|
||||
|
||||
return PMSHotelCalendarControllerChannelConnector;
|
||||
return PMSHotelCalendarController;
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 Alexandre Díaz <dev@redneboa.es>
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define('hotel_calendar_channel_connector.MPMSCalendarController', function (require) {
|
||||
"use strict";
|
||||
|
||||
var MPMSCalendarController = require('hotel_calendar.MPMSCalendarController');
|
||||
var HotelConstants = require('hotel_calendar.Constants');
|
||||
var Core = require('web.core');
|
||||
|
||||
var QWeb = Core.qweb;
|
||||
|
||||
|
||||
var MPMSCalendarController = MPMSCalendarController.include({
|
||||
|
||||
_onBusNotification: function (notifications) {
|
||||
this._super.apply(this, arguments);
|
||||
if (!this.renderer._hcalendar) { return; }
|
||||
|
||||
for (var notif of notifications) {
|
||||
if (notif[0][1] === 'hotel.reservation') {
|
||||
switch (notif[1]['type']) {
|
||||
case 'availability':
|
||||
var avail = notif[1]['availability'];
|
||||
var room_type = Object.keys(avail)[0];
|
||||
var day = Object.keys(avail[room_type])[0];
|
||||
var dt = HotelCalendarManagement.toMoment(day);
|
||||
var availability = {};
|
||||
availability[room_type] = [{
|
||||
'date': dt.format(HotelConstants.ODOO_DATE_MOMENT_FORMAT),
|
||||
'quota': avail[room_type][day]['quota'],
|
||||
'max_avail': avail[room_type][day]['max_avail'],
|
||||
'no_ota': avail[room_type][day]['no_ota'],
|
||||
'id': avail[room_type][day]['id']
|
||||
}];
|
||||
this.renderer._hcalendar.addAvailability(availability);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return MPMSCalendarController;
|
||||
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2019 Alexandre Díaz <dev@redneboa.es>
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define('hotel_calendar_channel_connector.MPMSCalendarRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
var MPMSCalendarRenderer = require('hotel_calendar.MPMSCalendarRenderer');
|
||||
var Core = require('web.core');
|
||||
|
||||
var QWeb = Core.qweb;
|
||||
|
||||
|
||||
var MPMSCalendarRenderer = MPMSCalendarRenderer.include({
|
||||
|
||||
/** CUSTOM METHODS **/
|
||||
get_values_to_save: function() {
|
||||
var values = this._super.apply(this, arguments);
|
||||
if (values) {
|
||||
var availability = this._hcalendar.getAvailability(true);
|
||||
values.push(availability);
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
return MPMSCalendarRenderer;
|
||||
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
/* global odoo, $ */
|
||||
// Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define('hotel_calendar_channel_connector.PMSHotelCalendarRendererChannelConnector', function (require) {
|
||||
odoo.define('hotel_calendar_channel_connector.PMSHotelCalendarRenderer', function (require) {
|
||||
'use strict';
|
||||
|
||||
var PMSCalendarRenderer = require('hotel_calendar.PMSCalendarRenderer');
|
||||
|
||||
var PMSHotelCalendarRendererChannelConnector = PMSCalendarRenderer.include({
|
||||
var PMSHotelCalendarRenderer = PMSCalendarRenderer.include({
|
||||
|
||||
update_buttons_counter_channel_connector: function (nreservations, nissues) {
|
||||
// Cloud Reservations
|
||||
@@ -46,5 +46,5 @@ odoo.define('hotel_calendar_channel_connector.PMSHotelCalendarRendererChannelCon
|
||||
}
|
||||
});
|
||||
|
||||
return PMSHotelCalendarRendererChannelConnector;
|
||||
return PMSHotelCalendarRenderer;
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<i class="fa fa-fw o_button_icon fa-exclamation-circle"> </i>
|
||||
<div class="o_button_text">
|
||||
<span class="ninfo">0</span><br/>
|
||||
Issues
|
||||
<span class="text-hidden-xs">Issues</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
@@ -17,7 +17,7 @@
|
||||
<i class="fa fa-fw o_button_icon fa-cloud"></i>
|
||||
<div class="o_button_text">
|
||||
<span class="cloud-text">0</span><br/>
|
||||
To Assign
|
||||
<span class="text-hidden-xs">To Assign</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<link rel="stylesheet" href="/hotel_calendar_channel_connector/static/src/css/view.css" />
|
||||
<script type="text/javascript" src="/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_controller.js"></script>
|
||||
<script type="text/javascript" src="/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_renderer.js"></script>
|
||||
<script type="text/javascript" src="/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_management_controller.js"></script>
|
||||
<script type="text/javascript" src="/hotel_calendar_channel_connector/static/src/js/views/hotel_calendar_management_renderer.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
},
|
||||
'data': [
|
||||
'views/hotel_channel_connector_issue_views.xml',
|
||||
'views/hotel_room_type_availability_views.xml',
|
||||
'views/inherited_hotel_reservation_views.xml',
|
||||
'views/inherited_hotel_room_type_views.xml',
|
||||
'views/inherited_hotel_room_type_availability_views.xml',
|
||||
'views/inherited_hotel_folio_views.xml',
|
||||
'views/inherited_product_pricelist_views.xml',
|
||||
'views/inherited_product_pricelist_item_views.xml',
|
||||
|
||||
@@ -14,3 +14,4 @@ from . import inherited_hotel_folio
|
||||
from . import inherited_res_partner
|
||||
from . import channel_ota_info
|
||||
from . import hotel_channel_connector_issue
|
||||
from . import inherited_hotel_board_service_room_type
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# Copyright 2018-2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from datetime import timedelta
|
||||
@@ -10,108 +10,71 @@ from odoo.addons.component.core import Component
|
||||
from odoo.addons.component_event import skip_if
|
||||
|
||||
|
||||
class HotelRoomTypeAvailability(models.Model):
|
||||
_name = 'hotel.room.type.availability'
|
||||
_inherit = 'mail.thread'
|
||||
|
||||
@api.model
|
||||
def _default_max_avail(self):
|
||||
if self.room_type_id:
|
||||
return self.room_type_id.total_rooms_count
|
||||
return -1
|
||||
|
||||
@api.model
|
||||
def _default_quota(self):
|
||||
room_type_id = self._context.get('room_type_id')
|
||||
if room_type_id:
|
||||
room_type_id = self.env['hotel.room_type'].browse(room_type_id)
|
||||
return room_type_id.default_quota if room_type_id else -1
|
||||
return -1
|
||||
|
||||
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
|
||||
required=True, track_visibility='always',
|
||||
ondelete='cascade')
|
||||
date = fields.Date('Date', required=True, track_visibility='always')
|
||||
max_avail = fields.Integer("Max. Avail", default=-1, readonly=True)
|
||||
quota = fields.Integer("Quota", default=_default_quota)
|
||||
channel_bind_ids = fields.One2many(
|
||||
comodel_name='channel.hotel.room.type.availability',
|
||||
inverse_name='odoo_id',
|
||||
string='Hotel Room Type Availability Connector Bindings')
|
||||
no_ota = fields.Boolean('No OTA', default=False)
|
||||
booked = fields.Boolean('Booked', default=False, readonly=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('room_type_registry_unique',
|
||||
'unique(room_type_id, date)',
|
||||
'Only can exists one availability in the same day for the same room \
|
||||
type!')
|
||||
]
|
||||
|
||||
@api.constrains('max_avail', 'quota')
|
||||
def _check_max_avail_quota(self):
|
||||
for record in self:
|
||||
if record.max_avail != -1 and record.max_avail > record.quota:
|
||||
raise ValidationError(_("Invalid Max Avail!"))
|
||||
if record.quota != -1 and record.quota > record.room_type_id.total_rooms_count:
|
||||
raise ValidationError(_("Invalid Quota!"))
|
||||
|
||||
@api.onchange('room_type_id')
|
||||
def onchange_room_type_id(self):
|
||||
if self.room_type_id:
|
||||
self.quota = self.room_type_id.default_quota
|
||||
|
||||
|
||||
class ChannelHotelRoomTypeAvailability(models.Model):
|
||||
_name = 'channel.hotel.room.type.availability'
|
||||
_inherit = 'channel.binding'
|
||||
_inherits = {'hotel.room.type.availability': 'odoo_id'}
|
||||
_description = 'Channel Availability'
|
||||
|
||||
@api.model
|
||||
def _default_channel_max_avail(self):
|
||||
if self.odoo_id.room_type_id:
|
||||
return self.odoo_id.room_type_id.total_rooms_count
|
||||
return -1
|
||||
|
||||
avail = fields.Integer("Avail", default=0, readonly=True)
|
||||
odoo_id = fields.Many2one(comodel_name='hotel.room.type.availability',
|
||||
string='Pricelist',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
channel_max_avail = fields.Integer("Max. Channel Avail",
|
||||
default=_default_channel_max_avail,
|
||||
old_name='wmax_avail')
|
||||
channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False,
|
||||
old_name='wpushed')
|
||||
|
||||
@api.constrains('channel_max_avail')
|
||||
def _check_channel_max_avail(self):
|
||||
for record in self:
|
||||
if record.channel_max_avail > record.odoo_id.room_type_id.total_rooms_count:
|
||||
raise ValidationError(_("max avail for channel can't be high \
|
||||
than total rooms \
|
||||
count: %d") % record.odoo_id.room_type_id.total_rooms_count)
|
||||
|
||||
@api.model
|
||||
def refresh_availability(self, checkin, checkout, room_id):
|
||||
date_start = fields.Date.from_string(checkin)
|
||||
date_end = fields.Date.from_string(checkout)
|
||||
# Not count end day of the reservation
|
||||
date_diff = (date_end - date_start).days
|
||||
|
||||
channel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
channel_room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
|
||||
room_type_binds = channel_room_type_obj.search([
|
||||
('backend_id', '=', self.backend_id.id),
|
||||
('room_ids', '=', room_id),
|
||||
])
|
||||
for room_type_bind in room_type_binds:
|
||||
if room_type_bind.external_id:
|
||||
for i in range(0, date_diff):
|
||||
ndate_dt = date_start + timedelta(days=i)
|
||||
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
avail = len(channel_room_type_obj.odoo_id.check_availability_room_type(
|
||||
ndate_str,
|
||||
ndate_str,
|
||||
room_type_id=room_type_bind.odoo.id))
|
||||
max_avail = room_type_bind.total_rooms_count
|
||||
room_type_avail_id = channel_room_type_avail_obj.search([
|
||||
('room_type_id', '=', room_type_bind.odoo.id),
|
||||
('date', '=', ndate_str)], limit=1)
|
||||
if room_type_avail_id and room_type_avail_id.channel_max_avail >= 0:
|
||||
max_avail = room_type_avail_id.channel_max_avail
|
||||
avail = max(
|
||||
min(avail, room_type_bind.total_rooms_count, max_avail), 0)
|
||||
|
||||
if room_type_avail_id:
|
||||
room_type_avail_id.write({'avail': avail})
|
||||
else:
|
||||
channel_room_type_avail_obj.create({
|
||||
'room_type_id': room_type_bind.odoo.id,
|
||||
'date': ndate_str,
|
||||
'avail': avail,
|
||||
})
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@api.model
|
||||
def import_availability(self, backend, dfrom, dto):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(usage='hotel.room.type.availability.importer')
|
||||
return importer.import_availability_values(dfrom, dto)
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@api.model
|
||||
def push_availability(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(usage='hotel.room.type.availability.exporter')
|
||||
return exporter.push_availability()
|
||||
|
||||
class HotelRoomTypeAvailability(models.Model):
|
||||
_inherit = 'hotel.room.type.availability'
|
||||
|
||||
channel_bind_ids = fields.One2many(
|
||||
comodel_name='channel.hotel.room.type.availability',
|
||||
inverse_name='odoo_id',
|
||||
string='Hotel Room Type Availability Connector Bindings')
|
||||
|
||||
no_ota = fields.Boolean('No OTA', default=False)
|
||||
booked = fields.Boolean('Booked', default=False, readonly=True)
|
||||
|
||||
def _prepare_notif_values(self, record):
|
||||
vals = super(HotelRoomTypeAvailability, self)._prepare_notif_values(record)
|
||||
vals.update({
|
||||
'no_ota': record.no_ota,
|
||||
})
|
||||
return vals
|
||||
channel_pushed = fields.Boolean("Channel Pushed", readonly=True,
|
||||
default=False)
|
||||
|
||||
@api.constrains('avail')
|
||||
def _check_avail(self):
|
||||
@@ -126,8 +89,8 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
if record.avail > max_avail:
|
||||
issue_obj.sudo().create({
|
||||
'section': 'avail',
|
||||
'internal_message': _(r"The new availability can't be greater than \
|
||||
the max. availability \
|
||||
'internal_message': _(r"The new availability can't be \
|
||||
greater than the max. availability \
|
||||
(%s) [Input: %d\Max: %d]") % (record.room_type_id.name,
|
||||
record.avail,
|
||||
max_avail),
|
||||
@@ -136,12 +99,74 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
})
|
||||
# Auto-Fix channel availability
|
||||
self._event('on_fix_channel_availability').notify(record)
|
||||
return super(HotelRoomTypeAvailability, self)._check_avail()
|
||||
|
||||
@api.onchange('room_type_id')
|
||||
def onchange_room_type_id(self):
|
||||
if self.room_type_id:
|
||||
self.channel_max_avail = self.room_type_id.total_rooms_count
|
||||
@api.model
|
||||
def refresh_availability(self, checkin, checkout, backend_id, room_id=False, room_type_id=False):
|
||||
date_start = fields.Date.from_string(checkin)
|
||||
date_end = fields.Date.from_string(checkout)
|
||||
# Not count end day of the reservation
|
||||
date_diff = (date_end - date_start).days
|
||||
|
||||
channel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
channel_room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
|
||||
if room_type_id:
|
||||
room_type_bind = channel_room_type_obj.browse(room_type_id)
|
||||
else:
|
||||
domain = [('backend_id', '=', backend_id)]
|
||||
if room_id:
|
||||
domain.append(('room_ids', 'in', [room_id]))
|
||||
room_type_bind = channel_room_type_obj.search(domain, limit=1)
|
||||
if room_type_bind and room_type_bind.external_id:
|
||||
for i in range(0, date_diff):
|
||||
ndate_dt = date_start + timedelta(days=i)
|
||||
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
to_eval = []
|
||||
cavail = len(channel_room_type_obj.odoo_id.check_availability_room_type(
|
||||
ndate_str,
|
||||
ndate_str,
|
||||
room_type_id=room_type_bind.odoo.id))
|
||||
to_eval.append(cavail)
|
||||
to_eval.append(room_type_bind.total_rooms_count)
|
||||
room_type_avail_id = channel_room_type_avail_obj.search([
|
||||
('room_type_id', '=', room_type_bind.odoo.id),
|
||||
('date', '=', ndate_str)], limit=1)
|
||||
if room_type_avail_id:
|
||||
if room_type_avail_id.channel_max_avail >= 0:
|
||||
to_eval.append(
|
||||
room_type_avail_id.channel_max_avail)
|
||||
if room_type_avail_id.quota >= 0:
|
||||
to_eval.append(room_type_avail_id.quota)
|
||||
if room_type_avail_id.max_avail >= 0:
|
||||
to_eval.append(room_type_avail_id.max_avail)
|
||||
avail = max(min(to_eval), 0)
|
||||
|
||||
if room_type_avail_id \
|
||||
and avail != room_type_avail_id.avail:
|
||||
room_type_avail_id.write({'avail': avail})
|
||||
else:
|
||||
channel_room_type_avail_obj.create({
|
||||
'room_type_id': room_type_bind.odoo.id,
|
||||
'date': ndate_str,
|
||||
'avail': avail,
|
||||
})
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@api.model
|
||||
def import_availability(self, backend, dfrom, dto):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(
|
||||
usage='hotel.room.type.availability.importer')
|
||||
return importer.import_availability_values(dfrom, dto)
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@api.model
|
||||
def push_availability(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(
|
||||
usage='hotel.room.type.availability.exporter')
|
||||
return exporter.push_availability()
|
||||
|
||||
|
||||
class BindingHotelRoomTypeAvailabilityListener(Component):
|
||||
_name = 'binding.hotel.room.type.listener'
|
||||
@@ -150,8 +175,15 @@ class BindingHotelRoomTypeAvailabilityListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if 'avail' in fields:
|
||||
record.channel_bind_ids.write({'channel_pushed': False})
|
||||
fields_to_check = ('quota', 'max_avail')
|
||||
fields_checked = [elm for elm in fields_to_check if elm in fields]
|
||||
if any(fields_checked) and any(record.channel_bind_ids):
|
||||
for binding in record.channel_bind_ids:
|
||||
binding.refresh_availability(
|
||||
record.checkin,
|
||||
record.checkout,
|
||||
binding.backend_id.id,
|
||||
room_type_id=record.room_type_id.id)
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_create(self, record, fields=None):
|
||||
@@ -165,11 +197,17 @@ class BindingHotelRoomTypeAvailabilityListener(Component):
|
||||
('backend_id', '=', backend.id),
|
||||
])
|
||||
if not avail_bind:
|
||||
channel_room_type_avail_obj.create({
|
||||
avail_bind = channel_room_type_avail_obj.create({
|
||||
'odoo_id': record.id,
|
||||
'channel_pushed': False,
|
||||
'backend_id': backend.id,
|
||||
})
|
||||
avail_bind.refresh_availability(
|
||||
record.checkin,
|
||||
record.checkout,
|
||||
backend.id,
|
||||
room_type_id=record.room_type_id.id)
|
||||
|
||||
|
||||
class ChannelBindingHotelRoomTypeAvailabilityListener(Component):
|
||||
_name = 'channel.binding.hotel.room.type.availability.listener'
|
||||
@@ -185,4 +223,10 @@ class ChannelBindingHotelRoomTypeAvailabilityListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_fix_channel_availability(self, record, fields=None):
|
||||
record.update_availability()
|
||||
if any(record.channel_bind_ids):
|
||||
for binding in record.channel_bind_ids:
|
||||
record.refresh_availability(
|
||||
record.checkin,
|
||||
record.checkout,
|
||||
binding.backend_id.id,
|
||||
room_type_id=record.room_type_id.id)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Copyright 2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class HotelBoardServiceRoomType(models.Model):
|
||||
|
||||
_inherit = 'hotel.board.service.room.type'
|
||||
|
||||
channel_service = fields.Selection([], string='Channel Board Service')
|
||||
@@ -11,11 +11,10 @@
|
||||
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="no_ota" />
|
||||
<field name="avail" />
|
||||
<field name="booked" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="channel_max_avail" />
|
||||
<field name="channel_pushed" />
|
||||
</group>
|
||||
</form>
|
||||
@@ -27,7 +26,8 @@
|
||||
<field name="model">channel.hotel.room.type.availability</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Hotel Channel Room Availability">
|
||||
<field name="backend_id"/>
|
||||
<field name="backend_id" />
|
||||
<field name="avail" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<!-- FORM availability -->
|
||||
<record id="hotel_room_type_availability_view_form" model="ir.ui.view">
|
||||
<field name="name">hotel.room.type.availability.form</field>
|
||||
<field name="model">hotel.room.type.availability</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Availability">
|
||||
<sheet>
|
||||
<group>
|
||||
<!-- <h1><field name="room_type_id" required="1"/></h1> -->
|
||||
<h1><field name="room_type_id" required="1"/></h1>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date" required="1"/>
|
||||
<field name="quota"/>
|
||||
<field name="max_avail"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="connector" string="Channel Connector">
|
||||
<group string="Hotel Channel Bindings">
|
||||
<field name="channel_bind_ids" nolabel="1">
|
||||
<tree>
|
||||
<field name="backend_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TREE restriction -->
|
||||
<record id="hotel_room_type_availability_view_tree" model="ir.ui.view">
|
||||
<field name="name">hotel.room.type.availability.tree</field>
|
||||
<field name="model">hotel.room.type.availability</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Restrictions">
|
||||
<!-- <field name="room_type_id" required="1"/> -->
|
||||
<field name="room_type_id" required="1"/>
|
||||
<field name="date" required="1"/>
|
||||
<field name="quota"/>
|
||||
<field name="max_avail"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action of reservation restriction -->
|
||||
<record model="ir.actions.act_window" id="hotel_room_type_availability_action">
|
||||
<field name="name">Virtual Room Availability</field>
|
||||
<field name="res_model">hotel.room.type.availability</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -9,8 +9,7 @@
|
||||
<field name="has_channel_reservations" invisible="True" />
|
||||
<field name="customer_notes" readonly="True" attrs="{'invisible':[('has_channel_reservations', '=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page/field[@name='room_lines']/tree/field[@name='checkout']" position="after">
|
||||
<!-- field name="origin_sale"/-->
|
||||
<!--xpath expr="//notebook/page/field[@name='room_lines']/tree/field[@name='checkout']" position="after">
|
||||
<field name="is_from_ota" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page/field[@name='room_lines']/tree/field[@name='checkout']" position="attributes">
|
||||
@@ -20,7 +19,6 @@
|
||||
<attribute name="attrs">{'readonly': [('is_from_ota', '!=', False)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page/field[@name='room_lines']/form/sheet/header/field[@name='folio_id']" position="after">
|
||||
<!-- field name="origin_sale" invisible="1"/-->
|
||||
<field name="is_from_ota" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page/field[@name='room_lines']/form/sheet/h3/field[@name='checkout']" position="attributes">
|
||||
@@ -28,7 +26,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page/field[@name='room_lines']/form/sheet/h3/field[@name='checkin']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('is_from_ota', '!=', False)]}</attribute>
|
||||
</xpath>
|
||||
</xpath-->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<!-- FORM availability -->
|
||||
<record id="room_type_availability_view_form" model="ir.ui.view">
|
||||
<field name="model">hotel.room.type.availability</field>
|
||||
<field name="inherit_id" ref="hotel.room_type_availability_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<notebook>
|
||||
<page name="connector" string="Channel Connector">
|
||||
<group string="Hotel Channel Bindings">
|
||||
<field name="channel_bind_ids" nolabel="1">
|
||||
<tree>
|
||||
<field name="backend_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TREE restriction -->
|
||||
<!--record id="room_type_availability_view_tree" model="ir.ui.view">
|
||||
<field name="model">hotel.room.type.availability</field>
|
||||
<field name="inherit_id" ref="hotel.room_type_availability_view_tree" />
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='avail']" position="after">
|
||||
<field name="channel_max_avail" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record-->
|
||||
|
||||
</odoo>
|
||||
@@ -5,6 +5,10 @@
|
||||
<field name="model">hotel.room.type</field>
|
||||
<field name="inherit_id" ref="hotel.hotel_room_type_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='board_service_room_type_ids']/tree" position="inside">
|
||||
<field name="channel_service" />
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<notebook>
|
||||
<page name="connector" string="Channel Connector">
|
||||
|
||||
@@ -1,28 +1,76 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# Copyright 2018-2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from datetime import datetime, timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo import fields, models, api
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
class MassiveChangesWizard(models.TransientModel):
|
||||
_inherit = 'hotel.wizard.massive.changes'
|
||||
|
||||
section = fields.Selection(selection_add=[
|
||||
('avail', 'Availability'),
|
||||
])
|
||||
|
||||
# Availability fields
|
||||
change_quota = fields.Boolean(default=False)
|
||||
quota = fields.Integer('Quota', default=-1)
|
||||
change_max_avail = fields.Boolean(default=False)
|
||||
max_avail = fields.Integer('Max. Avail.', default=-1)
|
||||
change_no_ota = fields.Boolean(default=False)
|
||||
no_ota = fields.Boolean('No OTA', default=False)
|
||||
|
||||
@api.model
|
||||
def _get_availability_values(self, ndate, room_type, record):
|
||||
vals = super(MassiveChangesWizard, self)._get_availability_values(
|
||||
ndate, room_type, record)
|
||||
vals.update({
|
||||
'channel_max_avail': vals['avail'],
|
||||
'no_ota': record.no_ota,
|
||||
})
|
||||
vals = {}
|
||||
if record.change_quota:
|
||||
vals.update({
|
||||
'quota': record.quota,
|
||||
})
|
||||
if record.change_no_ota:
|
||||
vals.update({
|
||||
'no_ota': record.no_ota,
|
||||
})
|
||||
if record.change_max_avail:
|
||||
vals.update({
|
||||
'max_avail': record.max_avail,
|
||||
})
|
||||
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def _save_availability(self, ndate, room_types, record):
|
||||
hotel_room_type_avail_obj = self.env['hotel.room.type.availability']
|
||||
domain = [('date', '=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT))]
|
||||
|
||||
for room_type in room_types:
|
||||
vals = self._get_availability_values(ndate, room_type, record)
|
||||
if not any(vals):
|
||||
continue
|
||||
|
||||
room_types_avail = hotel_room_type_avail_obj.search(
|
||||
domain+[('room_type_id', '=', room_type.id)]
|
||||
)
|
||||
if any(room_types_avail):
|
||||
# Mail module want a singleton
|
||||
for vr_avail in room_types_avail:
|
||||
vr_avail.write(vals)
|
||||
else:
|
||||
vals.update({
|
||||
'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'room_type_id': room_type.id
|
||||
})
|
||||
hotel_room_type_avail_obj.with_context({
|
||||
'mail_create_nosubscribe': True,
|
||||
}).create(vals)
|
||||
|
||||
@api.multi
|
||||
def massive_change(self):
|
||||
res = super(MassiveChangesWizard, self).massive_change()
|
||||
self.env['channel.backend'].push_changes()
|
||||
self.env['channel.backend'].cron_push_changes()
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _save(self, ndate, room_types, record):
|
||||
super(MassiveChangesWizard, self)._save(ndate, room_types, record)
|
||||
if record.section == 'avail':
|
||||
self._save_availability(ndate, room_types, record)
|
||||
|
||||
@@ -5,11 +5,30 @@
|
||||
<field name="model">hotel.wizard.massive.changes</field>
|
||||
<field name="inherit_id" ref="hotel.view_hotel_massive_changes_wizard" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//table[hasclass('oe_form_group')][1]/tbody" position="inside">
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_no_ota" /> <strong> No OTA</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="no_ota" attrs="{'readonly':[('change_no_ota', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<xpath expr="//form/group[last()]" position="after">
|
||||
<!-- Availability Fields -->
|
||||
<group col="3" colspan="3" attrs="{'invisible':[('section', '!=', 'avail')]}">
|
||||
<table class="oe_form_group">
|
||||
<thead>
|
||||
<th width="12%"></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_max_avail" /> <strong> Max. Avail.</strong></td>
|
||||
<td class="oe_form_group_cell" colspan="3"><field name="max_avail" attrs="{'readonly':[('change_max_avail', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_quota" /> <strong> Quota</strong></td>
|
||||
<td class="oe_form_group_cell" colspan="3"><field name="quota" attrs="{'readonly':[('change_quota', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_no_ota" /> <strong> No OTA</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="no_ota" attrs="{'readonly':[('change_no_ota', '=', False)]}" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -12,3 +12,4 @@ from . import hotel_reservation
|
||||
from . import inherited_hotel_folio
|
||||
from . import inherited_hotel_room_type_class
|
||||
from . import channel_ota_info
|
||||
from . import inherited_hotel_board_service_room_type
|
||||
|
||||
@@ -28,14 +28,10 @@ class HotelRoomTypeAvailabilityExporter(Component):
|
||||
lambda x: x.room_type_id.id == room_type.id)
|
||||
days = []
|
||||
for channel_room_type_avail in channel_room_type_avails:
|
||||
cavail = channel_room_type_avail.avail
|
||||
if channel_room_type_avail.channel_max_avail >= 0 and \
|
||||
cavail > channel_room_type_avail.channel_max_avail:
|
||||
cavail = channel_room_type_avail.channel_max_avail
|
||||
date_dt = fields.Date.from_string(channel_room_type_avail.date)
|
||||
days.append({
|
||||
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
'avail': cavail,
|
||||
'avail': channel_room_type_avail.avail,
|
||||
'no_ota': channel_room_type_avail.no_ota and 1 or 0,
|
||||
# 'booked': room_type_avail.booked and 1 or 0,
|
||||
})
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
import logging
|
||||
from datetime import date, timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping, only_create
|
||||
from odoo import api, _, fields
|
||||
from odoo import api, fields, _
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -64,9 +63,19 @@ class HotelRoomTypeAvailabilityImporter(Component):
|
||||
('date', '=', room['date'])
|
||||
], limit=1)
|
||||
if room_type_avail_bind:
|
||||
room_type_avail_bind.with_context({
|
||||
'connector_no_export': True,
|
||||
}).write(map_record.values())
|
||||
if room_type_avail_bind.avail != room['avail']:
|
||||
room_type_avail_bind.with_context({
|
||||
'connector_no_export': True,
|
||||
}).write({'channel_pushed': False})
|
||||
self.create_issue(
|
||||
section='avail',
|
||||
dfrom=room['date'], dto=room['date'],
|
||||
internal_message=_(
|
||||
"Channel try to change availiability! \
|
||||
Updating channel values... \
|
||||
(Odoo: %d -- Channel: %d" % (
|
||||
room['avail'],
|
||||
room_type_avail_bind)))
|
||||
else:
|
||||
room_type_avail_bind = channel_room_type_avail_obj.with_context({
|
||||
'connector_no_export': True,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright 2019 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class HotelBoardServiceRoomType(models.Model):
|
||||
|
||||
_inherit = 'hotel.board.service.room.type'
|
||||
|
||||
channel_service = fields.Selection(selection_add=[
|
||||
('nb', 'No Board'),
|
||||
('fb', 'Full Board'),
|
||||
('hb', 'Half Board'),
|
||||
('bb', 'Breakfast Only'),
|
||||
('ai', 'All Inclusive')
|
||||
])
|
||||
Reference in New Issue
Block a user