mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[IMP] Channel Connector
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
'views/room_pricelist_cached_views.xml',
|
||||
'views/hotel_reservation_views.xml',
|
||||
'views/hotel_calendar_management_views.xml',
|
||||
'views/hotel_calendar_views.xml',
|
||||
'data/menus.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
|
||||
@@ -25,4 +25,9 @@
|
||||
<menuitem id="hotel_room_pricelist_cached" name="Room Pricelist Cached"
|
||||
sequence="1" action="hotel_room_pricelist_cached_action_form_tree" parent="sale.menu_sale_config"/>
|
||||
|
||||
<menuitem id="hotel_calendar_record_menu" name="Calendars"
|
||||
parent="hotel.hotel_configuration_menu" sequence="10"
|
||||
groups="hotel.group_hotel_manager"
|
||||
action="hotel_calendar_action_form_tree" />
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from . import hotel_calendar
|
||||
from . import bus_hotel_calendar
|
||||
from . import room_pricelist_cached
|
||||
from . import hotel_calendar_management
|
||||
|
||||
15
hotel_calendar/models/hotel_calendar.py
Normal file
15
hotel_calendar/models/hotel_calendar.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, api, _, fields
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HotelCalendar(models.Model):
|
||||
_name = 'hotel.calendar'
|
||||
|
||||
name = fields.Char('Name', required=True)
|
||||
segmentation_ids = fields.Many2many('hotel.room.type.class', string='Segmentation')
|
||||
location_ids = fields.Many2many('hotel.floor', string='Location')
|
||||
amenity_ids = fields.Many2many('hotel.amenity', string='Amenity')
|
||||
room_type_ids = fields.Many2many('hotel.room.type', string='Room Type')
|
||||
@@ -122,13 +122,13 @@ class HotelCalendarManagement(models.TransientModel):
|
||||
def _hcalendar_room_json_data(self, rooms):
|
||||
json_data = []
|
||||
for room in rooms:
|
||||
json_data.append((
|
||||
room.id,
|
||||
room.name,
|
||||
room.get_capacity(),
|
||||
room.list_price,
|
||||
room.total_rooms_count,
|
||||
))
|
||||
json_data.append({
|
||||
'id': room.id,
|
||||
'name': room.name,
|
||||
'capacity': room.get_capacity(),
|
||||
'price': room.list_price,
|
||||
'total_rooms': room.total_rooms_count,
|
||||
})
|
||||
return json_data
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -64,19 +64,20 @@ class HotelReservation(models.Model):
|
||||
pricelist_id = int(pricelist_id)
|
||||
json_rooms = []
|
||||
for room in rooms:
|
||||
json_rooms.append((
|
||||
room.id,
|
||||
room.name,
|
||||
room.capacity,
|
||||
'', # Reserved for type code
|
||||
room.shared_room,
|
||||
room.room_type_id
|
||||
and ['pricelist', room.room_type_id.id, pricelist_id,
|
||||
room.room_type_id.name]
|
||||
or 0,
|
||||
room.room_type_id.name,
|
||||
room.room_type_id.id,
|
||||
room.floor_id.id))
|
||||
json_rooms.append({
|
||||
'id': room.id,
|
||||
'name': room.name,
|
||||
'capacity': room.capacity,
|
||||
'class_id': room.room_type_id.class_id.id,
|
||||
'shared': room.shared_room,
|
||||
'price': room.room_type_id
|
||||
and ['pricelist', room.room_type_id.id, pricelist_id,
|
||||
room.room_type_id.name] or 0,
|
||||
'room_type_name': room.room_type_id.name,
|
||||
'room_type_id': room.room_type_id.id,
|
||||
'floor_id': room.floor_id.id,
|
||||
'amentity_ids': room.room_type_id.room_amenity_ids.ids,
|
||||
})
|
||||
return json_rooms
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -33,17 +33,9 @@ class HotelRoomTypeResrtrictionItem(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
restrictions_default_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'default_restriction_id')
|
||||
if restrictions_default_id:
|
||||
restrictions_default_id = int(restrictions_default_id)
|
||||
ret_vals = super(HotelRoomTypeResrtrictionItem, self).write(vals)
|
||||
|
||||
bus_hotel_calendar_obj = self.env['bus.hotel.calendar']
|
||||
for record in self:
|
||||
if record.restriction_id.id != restrictions_default_id or \
|
||||
record.applied_on != '0_room_type':
|
||||
continue
|
||||
bus_hotel_calendar_obj.send_restriction_notification({
|
||||
'restriction_id': record.restriction_id.id,
|
||||
'date': record.date,
|
||||
|
||||
@@ -3,3 +3,4 @@ access_room_price_cache_user,hotel_calendar.model_room_pricelist_cached_user,hot
|
||||
access_room_price_cache_call,hotel_calendar.model_room_pricelist_cached_call,hotel_calendar.model_room_pricelist_cached,hotel.group_hotel_call,1,1,1,1
|
||||
access_hotel_product_pricelist_item_call,hotel_calendar.pricelist_item_call,hotel_calendar.model_product_pricelist_item,hotel.group_hotel_call,1,1,1,1
|
||||
access_hotel_product_pricelist_item_user,hotel_calendar.pricelist_item_use,hotel_calendar.model_product_pricelist_item,hotel.group_hotel_user,1,1,1,1
|
||||
access_hotel_calendar,access_hotel_calendar,model_hotel_calendar,base.group_user,1,0,0,0
|
||||
|
||||
|
@@ -110,18 +110,19 @@ var PMSCalendarController = AbstractController.extend({
|
||||
var rooms = [];
|
||||
for (var r of results['rooms']) {
|
||||
var nroom = new HRoom(
|
||||
r[0], // Id
|
||||
r[1], // Name
|
||||
r[2], // Capacity
|
||||
r[3], // Category
|
||||
r[4], // Shared Room
|
||||
r[5] // Price
|
||||
r['id'],
|
||||
r['name'],
|
||||
r['capacity'],
|
||||
r['class_id'],
|
||||
r['shared'],
|
||||
r['price']
|
||||
);
|
||||
nroom.addUserData({
|
||||
'room_type_name': r[6],
|
||||
'room_type_id': r[7],
|
||||
'floor_id': r[8],
|
||||
'amenities': r[9]
|
||||
'room_type_name': r['room_type_name'],
|
||||
'room_type_id': r['room_type_id'],
|
||||
'floor_id': r['floor_id'],
|
||||
'amenities': r['amenity_ids'],
|
||||
'class_id': r['class_id']
|
||||
});
|
||||
rooms.push(nroom);
|
||||
}
|
||||
@@ -236,10 +237,10 @@ var PMSCalendarController = AbstractController.extend({
|
||||
_onLoadViewFilters: function (ev) {
|
||||
var self = this;
|
||||
$.when(
|
||||
this.model.get_room_types(),
|
||||
this.model.get_room_type_class(),
|
||||
this.model.get_floors(),
|
||||
this.model.get_amenities(),
|
||||
this.model.get_rooms()
|
||||
this.model.get_room_types()
|
||||
).then(function(a1, a2, a3, a4){
|
||||
self.renderer.loadViewFilters(a1, a2, a3, a4);
|
||||
});
|
||||
|
||||
@@ -79,9 +79,9 @@ return AbstractModel.extend({
|
||||
context: Session.user_context,
|
||||
});
|
||||
},
|
||||
get_rooms: function() {
|
||||
get_room_type_class: function() {
|
||||
return this._rpc({
|
||||
model: 'hotel.room.type',
|
||||
model: 'hotel.room.type.class',
|
||||
method: 'search_read',
|
||||
args: [false, ['id','name']],
|
||||
context: Session.user_context,
|
||||
|
||||
@@ -30,6 +30,7 @@ var HotelCalendarView = AbstractRenderer.extend({
|
||||
_reserv_tooltips: {},
|
||||
_days_tooltips: [],
|
||||
_last_dates: [false, false],
|
||||
_hcalendars: [],
|
||||
|
||||
|
||||
/** VIEW METHODS **/
|
||||
@@ -771,7 +772,7 @@ var HotelCalendarView = AbstractRenderer.extend({
|
||||
var virtual = _.map(this.$el.find('#pms-search #virtual_list').val(), function(item){ return +item; });
|
||||
var domain = [];
|
||||
if (category && category.length > 0) {
|
||||
domain.push(['categ_id', 'in', category]);
|
||||
domain.push(['class_id', 'in', category]);
|
||||
}
|
||||
if (floor && floor.length > 0) {
|
||||
domain.push(['floor_id', 'in', floor]);
|
||||
|
||||
@@ -89,10 +89,10 @@ var MPMSCalendarController = AbstractController.extend({
|
||||
var rooms = [];
|
||||
for (var r of results['rooms']) {
|
||||
var nroom = new HRoomType(
|
||||
r[0], // Id
|
||||
r[1], // Name
|
||||
r[2], // Capacity
|
||||
r[3], // Price
|
||||
r['id'],
|
||||
r['name'],
|
||||
r['capacity'],
|
||||
r['price'],
|
||||
);
|
||||
rooms.push(nroom);
|
||||
}
|
||||
|
||||
@@ -41,23 +41,23 @@ function HotelCalendar(/*String*/querySelector, /*Dictionary*/options, /*List*/p
|
||||
};
|
||||
|
||||
/** Options **/
|
||||
options = options || {};
|
||||
this.options = {
|
||||
startDate: ((options.startDate && HotelCalendar.toMomentUTC(options.startDate)) || moment(new Date()).utc()).subtract('1', 'd'),
|
||||
days: (options.days || ((options.startDate && HotelCalendar.toMomentUTC(options.startDate)) || moment(new Date())).clone().local().daysInMonth()),
|
||||
rooms: options.rooms || [],
|
||||
allowInvalidActions: options.allowInvalidActions || false,
|
||||
assistedMovement: options.assistedMovement || false,
|
||||
endOfWeek: options.endOfWeek || 6,
|
||||
endOfWeekOffset: options.endOfWeekOffset || 0,
|
||||
divideRoomsByCapacity: options.divideRoomsByCapacity || false,
|
||||
currencySymbol: options.currencySymbol || '€',
|
||||
showPricelist: options.showPricelist || false,
|
||||
showAvailability: options.showAvailability || false,
|
||||
showNumRooms: options.showNumRooms || 0,
|
||||
paginatorStepsMin: options.paginatorAdv || 1,
|
||||
paginatorStepsMax: options.paginatorAdv || 15,
|
||||
};
|
||||
this.options = _.extend({
|
||||
startDate: moment(new Date()).utc(),
|
||||
days: moment(new Date()).local().daysInMonth(),
|
||||
rooms: [],
|
||||
allowInvalidActions: false,
|
||||
assistedMovement: false,
|
||||
endOfWeek: 6,
|
||||
endOfWeekOffset: 0,
|
||||
divideRoomsByCapacity: false,
|
||||
currencySymbol: '€',
|
||||
showPricelist: false,
|
||||
showAvailability: false,
|
||||
showNumRooms: 0,
|
||||
paginatorStepsMin: 1,
|
||||
paginatorStepsMax: 15,
|
||||
}, options);
|
||||
this.options.startDate.subtract('1', 'd');
|
||||
|
||||
// Check correct values
|
||||
if (this.options.rooms.length > 0 && !(this.options.rooms[0] instanceof HRoom)) {
|
||||
@@ -104,14 +104,15 @@ HotelCalendar.prototype = {
|
||||
//==== CALENDAR
|
||||
setStartDate: function(/*String,MomentObject*/date, /*Int?*/days, /*Bool*/fullUpdate, /*Functions*/callback) {
|
||||
if (moment.isMoment(date)) {
|
||||
this.options.startDate = date.subtract('1','d');
|
||||
this.options.startDate = date;
|
||||
} else if (typeof date === 'string'){
|
||||
this.options.startDate = HotelCalendar.toMomentUTC(date).subtract('1','d');
|
||||
this.options.startDate = HotelCalendar.toMomentUTC(date);
|
||||
} else {
|
||||
console.warn("[Hotel Calendar][setStartDate] Invalid date format!");
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.startDate.subtract('1','d');
|
||||
if (typeof days !== 'undefined') {
|
||||
this.options.days = days;
|
||||
}
|
||||
|
||||
@@ -24,4 +24,11 @@
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="hotel_calendar_action_form_tree">
|
||||
<field name="name">Hotel Calendar</field>
|
||||
<field name="res_model">hotel.calendar</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
36
hotel_calendar/views/hotel_calendar_views.xml
Normal file
36
hotel_calendar/views/hotel_calendar_views.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Form view of hotel room -->
|
||||
<record model="ir.ui.view" id="hotel_calendar_view_form">
|
||||
<field name="name">hotel.calendar.form</field>
|
||||
<field name="model">hotel.calendar</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Calendar">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="segmentation_ids" />
|
||||
<field name="location_ids" />
|
||||
<field name="amenity_ids" />
|
||||
<field name="room_type_ids" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree view of hotel room -->
|
||||
<record model="ir.ui.view" id="hotel_calendar_view_tree">
|
||||
<field name="name">hotel.calendar.tree</field>
|
||||
<field name="model">hotel.calendar</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Hotel Calendar">
|
||||
<field name="name" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -73,8 +73,7 @@
|
||||
<button name="mark_as_readed" string="Mark as Read"
|
||||
type="object" class="oe_highlight"
|
||||
icon="fa-1x fa-thumb-tack"
|
||||
attrs="{'invisible':[('to_read','=', False)]}"
|
||||
/>
|
||||
attrs="{'invisible':[('to_read','=', False)]}" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -113,8 +113,7 @@ class ChannelHotelReservation(models.Model):
|
||||
def unlink(self):
|
||||
vals = []
|
||||
for record in self:
|
||||
if any(record.channel_bind_ids) and record.channel_bind_ids[0].external_id \
|
||||
and not record.parent_reservation:
|
||||
if record.is_from_ota and self._context.get('ota_limits', True):
|
||||
raise UserError(_("You can't delete OTA's reservations"))
|
||||
vals.append({
|
||||
'checkin': record.checkin,
|
||||
@@ -140,26 +139,28 @@ class HotelReservation(models.Model):
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
record.able_to_modify_channel = user.has_group('base.group_system')
|
||||
|
||||
@api.depends('channel_type', 'channel_bind_ids.ota_id')
|
||||
def _get_origin_sale(self):
|
||||
for record in self:
|
||||
if not record.channel_type:
|
||||
record.channel_type = 'door'
|
||||
|
||||
if record.channel_type == 'web' and any(record.channel_bind_ids) and \
|
||||
record.channel_bind_ids[0].ota_id:
|
||||
record.origin_sale = record.channel_bind_ids[0].ota_id.name
|
||||
else:
|
||||
record.origin_sale = dict(
|
||||
self.fields_get(allfields=['channel_type'])['channel_type']['selection']
|
||||
)[record.channel_type]
|
||||
# TODO: Dario v2
|
||||
# @api.depends('channel_type', 'channel_bind_ids.ota_id')
|
||||
# def _get_origin_sale(self):
|
||||
# for record in self:
|
||||
# if not record.channel_type:
|
||||
# record.channel_type = 'door'
|
||||
#
|
||||
# if record.channel_type == 'web' and any(record.channel_bind_ids) and \
|
||||
# record.channel_bind_ids[0].ota_id:
|
||||
# record.origin_sale = record.channel_bind_ids[0].ota_id.name
|
||||
# else:
|
||||
# record.origin_sale = dict(
|
||||
# self.fields_get(allfields=['channel_type'])['channel_type']['selection']
|
||||
# )[record.channel_type]
|
||||
|
||||
channel_bind_ids = fields.One2many(
|
||||
comodel_name='channel.hotel.reservation',
|
||||
inverse_name='odoo_id',
|
||||
string='Hotel Channel Connector Bindings')
|
||||
origin_sale = fields.Char('Origin', compute=_get_origin_sale,
|
||||
store=True)
|
||||
# TODO: Dario v2
|
||||
# origin_sale = fields.Char('Origin', compute=_get_origin_sale,
|
||||
# store=True)
|
||||
is_from_ota = fields.Boolean('Is From OTA',
|
||||
readonly=True,
|
||||
old_name='wis_from_channel')
|
||||
|
||||
@@ -113,10 +113,9 @@ class BindingHotelRoomTypeListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields or \
|
||||
'room_ids' in fields:
|
||||
# FIXME: Supossed that only exists one channel connector per record
|
||||
record.channel_bind_ids[0].modify_room()
|
||||
if 'name' in fields or 'list_price' in fields or 'room_ids' in fields:
|
||||
for binding in record.channel_bind_ids:
|
||||
binding.modify_room()
|
||||
|
||||
# @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
# def on_record_create(self, record, fields=None):
|
||||
|
||||
@@ -68,13 +68,12 @@ class HotelRoomTypeRestriction(models.Model):
|
||||
names = []
|
||||
for name in org_names:
|
||||
restriction_id = room_type_restriction_obj.browse(name[0])
|
||||
if any(restriction_id.channel_bind_ids) and \
|
||||
restriction_id.channel_bind_ids[0].external_id:
|
||||
names.append((
|
||||
name[0],
|
||||
'%s (%s Backend)' % (name[1],
|
||||
restriction_id.channel_bind_ids[0].backend_id.name),
|
||||
))
|
||||
new_name = name[1]
|
||||
if any(restriction_id.channel_bind_ids):
|
||||
for restriction_bind in restriction_id.channel_bind_ids:
|
||||
if restriction_bind.external_id:
|
||||
new_name += ' (%s Backend)' % restriction_bind.backend_id.name
|
||||
names.append((name[0], new_name))
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
return names
|
||||
@@ -86,8 +85,9 @@ class BindingHotelRoomTypeListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if any(record.channel_bind_ids) and 'name' in fields:
|
||||
record.channel_bind_ids[0].update_plan_name()
|
||||
if 'name' in fields:
|
||||
for binding in record.channel_bind_ids:
|
||||
binding.update_plan_name()
|
||||
|
||||
class ChannelBindingHotelRoomTypeRestrictionListener(Component):
|
||||
_name = 'channel.binding.hotel.room.type.restriction.listener'
|
||||
|
||||
@@ -37,6 +37,7 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model):
|
||||
exporter = work.component(usage='hotel.room.type.restriction.item.exporter')
|
||||
return exporter.push_restriction()
|
||||
|
||||
|
||||
class HotelRoomTypeRestrictionItem(models.Model):
|
||||
_inherit = 'hotel.room.type.restriction.item'
|
||||
|
||||
@@ -45,6 +46,7 @@ class HotelRoomTypeRestrictionItem(models.Model):
|
||||
inverse_name='odoo_id',
|
||||
string='Hotel Channel Connector Bindings')
|
||||
|
||||
|
||||
class BindingHotelRoomTypeRestrictionItemListener(Component):
|
||||
_name = 'binding.hotel.room.type.restriction.item.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
@@ -59,6 +61,24 @@ class BindingHotelRoomTypeRestrictionItemListener(Component):
|
||||
if any(fields_checked):
|
||||
record.channel_bind_ids.write({'channel_pushed': False})
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_create(self, record, fields=None):
|
||||
if not any(record.channel_bind_ids):
|
||||
channel_hotel_room_type_rest_item_obj = self.env[
|
||||
'channel.hotel.room.type.restriction.item']
|
||||
for restriction_bind in record.restriction_id.channel_bind_ids:
|
||||
restriction_item_bind = channel_hotel_room_type_rest_item_obj.search([
|
||||
('odoo_id', '=', record.id),
|
||||
('backend_id', '=', restriction_bind.backend_id.id),
|
||||
])
|
||||
if not restriction_item_bind:
|
||||
channel_hotel_room_type_rest_item_obj.create({
|
||||
'odoo_id': record.id,
|
||||
'channel_pushed': False,
|
||||
'backend_id': restriction_bind.backend_id.id,
|
||||
})
|
||||
|
||||
|
||||
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component):
|
||||
_name = 'channel.binding.hotel.room.type.restriction.item.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.exceptions import ValidationError
|
||||
|
||||
|
||||
class HotelRoomTypeClass(models.Model):
|
||||
_inherit = 'hotel.room.type.class'
|
||||
|
||||
_locked_codes = []
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
for record in self:
|
||||
if record.code_class in self._locked_codes:
|
||||
raise ValidationError(_("Can't modify channel room type class"))
|
||||
return super(HotelRoomTypeClass, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
if record.code_class in self._locked_codes:
|
||||
raise ValidationError(_("Can't delete channel room type class"))
|
||||
return super(HotelRoomTypeClass, self).unlink()
|
||||
@@ -68,14 +68,13 @@ class ProductPricelist(models.Model):
|
||||
org_names = super(ProductPricelist, self).name_get()
|
||||
names = []
|
||||
for name in org_names:
|
||||
priclist_id = pricelist_obj.browse(name[0])
|
||||
if any(priclist_id.channel_bind_ids) and \
|
||||
priclist_id.channel_bind_ids[0].external_id:
|
||||
names.append((name[0], '%s (%s Backend)' % (
|
||||
name[1],
|
||||
priclist_id.channel_bind_ids[0].backend_id.name)))
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
pricelist_id = pricelist_obj.browse(name[0])
|
||||
new_name = name[1]
|
||||
if any(pricelist_id.channel_bind_ids):
|
||||
for pricelist_bind in pricelist_id.channel_bind_ids:
|
||||
if pricelist_bind.external_id:
|
||||
new_name += ' (%s Backend)' % pricelist_bind.backend_id.name
|
||||
names.append((name[0], new_name))
|
||||
return names
|
||||
|
||||
class BindingProductPricelistListener(Component):
|
||||
@@ -85,8 +84,9 @@ class BindingProductPricelistListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if any(record.channel_bind_ids) and 'name' in fields:
|
||||
record.channel_bind_ids[0].update_plan_name()
|
||||
if 'name' in fields:
|
||||
for binding in record.channel_bind_ids:
|
||||
binding.update_plan_name()
|
||||
|
||||
class ChannelBindingProductPricelistListener(Component):
|
||||
_name = 'channel.binding.product.pricelist.listener'
|
||||
|
||||
@@ -61,6 +61,23 @@ class BindingProductPricelistItemListener(Component):
|
||||
if any(fields_checked):
|
||||
record.channel_bind_ids.write({'channel_pushed': False})
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_create(self, record, fields=None):
|
||||
if not any(record.channel_bind_ids):
|
||||
channel_product_pricelist_item_obj = self.env[
|
||||
'channel.product.pricelist.item']
|
||||
for pricelist_bind in record.restriction_id.channel_bind_ids:
|
||||
pricelist_item_bind = channel_product_pricelist_item_obj.search([
|
||||
('odoo_id', '=', record.id),
|
||||
('backend_id', '=', pricelist_bind.backend_id.id),
|
||||
])
|
||||
if not pricelist_item_bind:
|
||||
channel_product_pricelist_item_obj.create({
|
||||
'odoo_id': record.id,
|
||||
'channel_pushed': False,
|
||||
'backend_id': pricelist_bind.backend_id.id,
|
||||
})
|
||||
|
||||
class ChannelBindingProductPricelistItemListener(Component):
|
||||
_name = 'channel.binding.product.pricelist.item.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<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"/>
|
||||
<!-- field name="origin_sale"/-->
|
||||
<field name="is_from_ota" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page/field[@name='room_lines']/tree/field[@name='checkout']" position="attributes">
|
||||
@@ -20,7 +20,7 @@
|
||||
<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="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">
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<field name="inherit_id" ref="hotel.hotel_reservation_view_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='checkout']" position="after">
|
||||
<field name="origin_sale"/>
|
||||
<!-- field name="origin_sale"/-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -78,8 +78,8 @@
|
||||
<!-- field name="wchannel_reservation_code"/-->
|
||||
</xpath>
|
||||
<xpath expr="//group" position="inside">
|
||||
<filter name="origin" string="Origin" domain="[]"
|
||||
context="{'group_by':'origin_sale'}"/>
|
||||
<!-- filter name="origin" string="Origin" domain="[]"
|
||||
context="{'group_by':'origin_sale'}"/-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -90,7 +90,7 @@
|
||||
<field name="model">hotel.reservation</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph type="bar">
|
||||
<field name="origin_sale" string="Origin"/>
|
||||
<!-- field name="origin_sale" string="Origin"/-->
|
||||
<field name="price_total" type="measure" />
|
||||
</graph>
|
||||
</field>
|
||||
@@ -103,7 +103,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Reservations">
|
||||
<field name="checkin" type="row" />
|
||||
<field name="origin_sale" string="Origin" type="col" />
|
||||
<!-- field name="origin_sale" string="Origin" type="col" /-->
|
||||
<field name="price_total" string="Price" type="measure" />
|
||||
</pivot>
|
||||
</field>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
'data': [
|
||||
'data/cron_jobs.xml',
|
||||
'data/sequences.xml',
|
||||
'data/records.xml',
|
||||
'views/inherited_channel_connector_backend_views.xml',
|
||||
'views/inherited_channel_ota_info_views.xml',
|
||||
#'security/wubook_security.xml',
|
||||
|
||||
@@ -102,8 +102,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
rcode_a, results_a = self._server.push_activation(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
urljoin(base_url,
|
||||
"/wubook/push/reservations/%s" % security_token),
|
||||
urljoin(base_url, "wubook/push/reservations/%s" % security_token),
|
||||
1)
|
||||
if rcode_a != 0:
|
||||
raise ChannelConnectorError(_("Can't activate push reservations"), {
|
||||
@@ -113,7 +112,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
rcode_b, results_b = self._server.push_update_activation(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
urljoin(base_url, "/wubook/push/rooms/%s" % security_token))
|
||||
urljoin(base_url, "wubook/push/rooms/%s" % security_token))
|
||||
if rcode_b != 0:
|
||||
raise ChannelConnectorError(_("Can't activate push rooms"), {
|
||||
'message': results_b,
|
||||
@@ -122,7 +121,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
return rcode_a == 0 and results_b == 0
|
||||
|
||||
# === ROOMS
|
||||
def create_room(self, shortcode, name, capacity, price, availability):
|
||||
def create_room(self, shortcode, name, capacity, price, availability, defboard, rtype):
|
||||
rcode, results = self._server.new_room(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
@@ -132,8 +131,8 @@ class WuBookAdapter(AbstractComponent):
|
||||
price,
|
||||
availability,
|
||||
shortcode[:4],
|
||||
'nb' # TODO: Complete this part
|
||||
# rtype=('name' in vals and vals['name'] and 3) or 1
|
||||
defboard,
|
||||
rtype=rtype
|
||||
)
|
||||
if rcode != 0:
|
||||
raise ChannelConnectorError(_("Can't create room in WuBook"), {
|
||||
@@ -141,7 +140,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
})
|
||||
return results
|
||||
|
||||
def modify_room(self, channel_room_id, name, capacity, price, availability, scode):
|
||||
def modify_room(self, channel_room_id, name, capacity, price, availability, scode, defboard, rtype):
|
||||
rcode, results = self._server.mod_room(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
@@ -151,8 +150,8 @@ class WuBookAdapter(AbstractComponent):
|
||||
price,
|
||||
availability,
|
||||
scode,
|
||||
'nb'
|
||||
# rtype=('name' in vals and vals['name'] and 3) or 1
|
||||
defboard,
|
||||
rtype=rtype
|
||||
)
|
||||
if rcode != 0:
|
||||
raise ChannelConnectorError(_("Can't modify room in WuBook"), {
|
||||
@@ -405,6 +404,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
rcode, results = self._server.wired_rplan_get_rplan_values(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
'1.1',
|
||||
fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
int(channel_restriction_plan_id))
|
||||
|
||||
44
hotel_channel_connector_wubook/data/records.xml
Normal file
44
hotel_channel_connector_wubook/data/records.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Room</field>
|
||||
<field name="code_class">1</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Apartment</field>
|
||||
<field name="code_class">2</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Beds Number</field>
|
||||
<field name="code_class">3</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Beds</field>
|
||||
<field name="code_class">4</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Bungalow</field>
|
||||
<field name="code_class">5</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Tent</field>
|
||||
<field name="code_class">6</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Villa</field>
|
||||
<field name="code_class">7</field>
|
||||
</record>
|
||||
|
||||
<record model="hotel.room.type.class">
|
||||
<field name="name">Chalet</field>
|
||||
<field name="code_class">8</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -10,4 +10,5 @@ from . import hotel_room_type_restriction_item
|
||||
from . import hotel_room_type_availability
|
||||
from . import hotel_reservation
|
||||
from . import inherited_hotel_folio
|
||||
from . import inherited_hotel_room_type_class
|
||||
from . import channel_ota_info
|
||||
|
||||
@@ -17,7 +17,7 @@ class ChannelBackend(models.Model):
|
||||
to redefine the ``version`` field in the ``_inherit`` model.
|
||||
"""
|
||||
super(ChannelBackend, self).select_versions()
|
||||
return [('1.2', '1.2+')]
|
||||
return [('1.1', '1.1')]
|
||||
|
||||
def _get_default_server(self):
|
||||
return 'https://wired.wubook.net/xrws/'
|
||||
|
||||
@@ -11,7 +11,8 @@ from odoo.addons.hotel_channel_connector_wubook.components.backend_adapter impor
|
||||
WUBOOK_STATUS_ACCEPTED,
|
||||
WUBOOK_STATUS_CANCELLED,
|
||||
WUBOOK_STATUS_CANCELLED_PENALTY,
|
||||
WUBOOK_STATUS_BAD)
|
||||
WUBOOK_STATUS_BAD,
|
||||
WUBOOK_STATUS_GOOD)
|
||||
|
||||
|
||||
class ChannelHotelReservation(models.Model):
|
||||
@@ -32,37 +33,31 @@ class HotelReservation(models.Model):
|
||||
|
||||
@api.multi
|
||||
def action_cancel(self):
|
||||
no_export = self._context.get('connector_no_export', True)
|
||||
if no_export:
|
||||
for record in self:
|
||||
# Can't cancel in Odoo
|
||||
if record.is_from_ota:
|
||||
raise ValidationError(_("Can't cancel reservations from OTA's"))
|
||||
for record in self:
|
||||
# Can't cancel in Odoo
|
||||
if record.is_from_ota and self._context.get('ota_limits', True):
|
||||
raise ValidationError(_("Can't cancel reservations from OTA's"))
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
if user.has_group('hotel.group_hotel_call'):
|
||||
self.write({'to_read': True, 'to_assign': True})
|
||||
|
||||
res = super(HotelReservation, self).action_cancel()
|
||||
if no_export:
|
||||
for record in self:
|
||||
# Only can cancel reservations created directly in wubook
|
||||
if any(record.channel_bind_ids) and \
|
||||
record.channel_bind_ids[0].external_id and \
|
||||
not record.channel_bind_ids[0].ota_id and \
|
||||
record.channel_bind_ids[0].channel_status in ['1', '2', '4']:
|
||||
self._event('on_record_cancel').notify(record)
|
||||
for record in self:
|
||||
# Only can cancel reservations created directly in wubook
|
||||
for binding in record.channel_bind_ids:
|
||||
if binding.external_id and not binding.ota_id and \
|
||||
binding.channel_status in WUBOOK_STATUS_GOOD:
|
||||
self._event('on_record_cancel').notify(binding)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def confirm(self):
|
||||
can_confirm = True
|
||||
for record in self:
|
||||
if record.is_from_ota and any(record.channel_bind_ids) and \
|
||||
int(record.channel_bind_ids[0].channel_status) in WUBOOK_STATUS_BAD:
|
||||
can_confirm = False
|
||||
break
|
||||
if not can_confirm:
|
||||
raise ValidationError(_("Can't confirm OTA's cancelled reservations"))
|
||||
if record.is_from_ota:
|
||||
for binding in record.channel_bind_ids:
|
||||
if int(binding.channel_status) in WUBOOK_STATUS_BAD \
|
||||
and self._context.get('ota_limits', True):
|
||||
raise ValidationError(_("Can't confirm OTA's cancelled reservations"))
|
||||
return super(HotelReservation, self).confirm()
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo import api
|
||||
from odoo import api, fields
|
||||
|
||||
|
||||
class HotelRoomTypeExporter(Component):
|
||||
@@ -12,13 +12,16 @@ class HotelRoomTypeExporter(Component):
|
||||
@api.model
|
||||
def modify_room(self, binding):
|
||||
try:
|
||||
binding.sync_date = fields.Datetime.now()
|
||||
return self.backend_adapter.modify_room(
|
||||
binding.external_id,
|
||||
binding.name,
|
||||
binding.ota_capacity,
|
||||
binding.list_price,
|
||||
binding.total_rooms_count,
|
||||
binding.channel_short_code)
|
||||
binding.channel_short_code,
|
||||
'nb',
|
||||
binding.class_id and binding.class_id.class_code or False)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
section='room',
|
||||
@@ -35,8 +38,9 @@ class HotelRoomTypeExporter(Component):
|
||||
binding.name,
|
||||
binding.ota_capacity,
|
||||
binding.list_price,
|
||||
binding.total_rooms_count
|
||||
)
|
||||
binding.total_rooms_count,
|
||||
'nb',
|
||||
binding.class_id and binding.class_id.class_code or False)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
section='room',
|
||||
|
||||
@@ -22,9 +22,16 @@ class HotelRoomTypeImporter(Component):
|
||||
channel_message=err.data['message'])
|
||||
else:
|
||||
channel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
hotel_room_type_class_obj = self.env['hotel.room.type.class']
|
||||
room_mapper = self.component(usage='import.mapper',
|
||||
model_name='channel.hotel.room.type')
|
||||
for room in results:
|
||||
room_type_class = hotel_room_type_class_obj.search([
|
||||
('code_class', '=', str(room['rtype'])),
|
||||
])
|
||||
room.update({
|
||||
'class_id': room_type_class and room_type_class.id or False,
|
||||
})
|
||||
map_record = room_mapper.map_record(room)
|
||||
room_bind = channel_room_type_obj.search([
|
||||
('backend_id', '=', self.backend_record.id),
|
||||
@@ -57,3 +64,7 @@ class HotelRoomTypeImportMapper(Component):
|
||||
@mapping
|
||||
def backend_id(self, record):
|
||||
return {'backend_id': self.backend_record.id}
|
||||
|
||||
@mapping
|
||||
def class_id(self, record):
|
||||
return {'class_id': record['class_id']}
|
||||
|
||||
@@ -14,6 +14,7 @@ class HotelRoomTypeAvailabilityExporter(Component):
|
||||
_inherit = 'channel.hotel.room.type.availability.exporter'
|
||||
|
||||
def push_availability(self):
|
||||
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
channel_room_type_avail_ids = self.env['channel.hotel.room.type.availability'].search([
|
||||
('backend_id', '=', self.backend_record.id),
|
||||
('channel_pushed', '=', False),
|
||||
@@ -38,7 +39,11 @@ class HotelRoomTypeAvailabilityExporter(Component):
|
||||
'no_ota': channel_room_type_avail.no_ota and 1 or 0,
|
||||
# 'booked': room_type_avail.booked and 1 or 0,
|
||||
})
|
||||
avails.append({'id': room_type.channel_bind_ids[0].channel_room_id, 'days': days})
|
||||
room_type_bind = channel_hotel_room_type_obj.search([
|
||||
('odoo_id', '=', room_type.id),
|
||||
('backend_id', '=', self.backend_record.id),
|
||||
], limit=1)
|
||||
avails.append({'id': room_type_bind.external_id, 'days': days})
|
||||
_logger.info("==[ODOO->CHANNEL]==== AVAILABILITY ==")
|
||||
_logger.info(avails)
|
||||
if any(avails):
|
||||
|
||||
@@ -13,6 +13,7 @@ class HotelRoomTypeRestrictionItemExporter(Component):
|
||||
|
||||
@api.model
|
||||
def push_restriction(self):
|
||||
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction']
|
||||
channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item']
|
||||
unpushed = channel_rest_item_obj.search([
|
||||
@@ -38,8 +39,11 @@ class HotelRoomTypeRestrictionItemExporter(Component):
|
||||
room_type_ids = unpushed_rp.mapped('room_type_id')
|
||||
for room_type in room_type_ids:
|
||||
if any(room_type.channel_bind_ids):
|
||||
# FIXME: Supossed that only exists one channel connector per record
|
||||
room_type_external_id = room_type.channel_bind_ids[0].external_id
|
||||
room_type_bind = channel_hotel_room_type_obj.search([
|
||||
('odoo_id', '=', room_type.id),
|
||||
('backend_id', '=', self.backend_record.id),
|
||||
], limit=1)
|
||||
room_type_external_id = room_type_bind.external_id
|
||||
restrictions[rp.external_id].update({
|
||||
room_type_external_id: [],
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
model_name='channel.hotel.room.type.restriction.item')
|
||||
_logger.info("==[CHANNEL->ODOO]==== RESTRICTIONS ==")
|
||||
_logger.info(plan_restrictions)
|
||||
count = 0
|
||||
for k_rpid, v_rpid in plan_restrictions.items():
|
||||
channel_restriction_id = channel_reserv_restriction_obj.search([
|
||||
('backend_id', '=', self.backend_record.id),
|
||||
@@ -40,6 +41,7 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
], limit=1)
|
||||
if channel_room_type:
|
||||
for item in v_rid:
|
||||
_logger.info(item)
|
||||
map_record = restriction_item_mapper.map_record(item)
|
||||
date_dt = datetime.strptime(item['date'], DEFAULT_WUBOOK_DATE_FORMAT)
|
||||
date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
@@ -47,7 +49,6 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
('backend_id', '=', self.backend_record.id),
|
||||
('restriction_id', '=', channel_restriction_id.odoo_id.id),
|
||||
('date', '=', date_str),
|
||||
('applied_on', '=', '0_room_type'),
|
||||
('room_type_id', '=', channel_room_type.odoo_id.id)
|
||||
], limit=1)
|
||||
item.update({
|
||||
@@ -64,6 +65,8 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
'connector_no_export': True
|
||||
}).create(map_record.values(for_create=True))
|
||||
channel_restriction_item.channel_pushed = True
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@api.model
|
||||
def import_restriction_values(self, date_from, date_to, channel_restr_id=False):
|
||||
@@ -82,7 +85,8 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
dfrom=date_from, dto=date_to)
|
||||
else:
|
||||
if any(results):
|
||||
self._generate_restriction_items(results)
|
||||
return self._generate_restriction_items(results)
|
||||
return 0
|
||||
|
||||
|
||||
class HotelRoomTypeRestrictionItemImportMapper(Component):
|
||||
@@ -96,7 +100,7 @@ class HotelRoomTypeRestrictionItemImportMapper(Component):
|
||||
('max_stay', 'max_stay'),
|
||||
('max_stay_arrival', 'max_stay_arrival'),
|
||||
('closed', 'closed'),
|
||||
('closed_departure', 'closed_departure'),
|
||||
#('closed_departure', 'closed_departure'),
|
||||
('closed_arrival', 'closed_arrival'),
|
||||
('date', 'date'),
|
||||
]
|
||||
@@ -121,3 +125,7 @@ class HotelRoomTypeRestrictionItemImportMapper(Component):
|
||||
@mapping
|
||||
def sync_date(self, record):
|
||||
return {'sync_date': fields.Datetime.now()}
|
||||
|
||||
@mapping
|
||||
def closed_departure(self, record):
|
||||
return {'closed_departure': int(record['closed_departure'])}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.exceptions import ValidationError
|
||||
|
||||
|
||||
class HotelRoomTypeClass(models.Model):
|
||||
_inherit = 'hotel.room.type.class'
|
||||
|
||||
_locked_codes = ('1', '2', '3', '4', '5', '6', '7', '8')
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
for record in self:
|
||||
if record.code_class in self._locked_codes:
|
||||
raise ValidationError(_("Can't modify channel room type class"))
|
||||
return super(HotelRoomTypeClass, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
if record.code_class in self._locked_codes:
|
||||
raise ValidationError(_("Can't delete channel room type class"))
|
||||
return super(HotelRoomTypeClass, self).unlink()
|
||||
Reference in New Issue
Block a user