[WIP][MIG][11.0] Odoo Connector

This commit is contained in:
QS5ELkMu
2018-08-18 02:46:26 +02:00
parent 67b4d836f3
commit 29eca3c988
31 changed files with 776 additions and 571 deletions

View File

@@ -89,7 +89,7 @@ class HotelReservation(models.Model):
'partner_phone': record.partner_id.mobile
or record.partner_id.phone or _('Undefined'),
'state': record.state,
'fix_days': record.splitted or record.wis_from_channel,
'fix_days': record.splitted or record.is_from_ota,
'overbooking': record.overbooking,
'price': record.folio_id.amount_total,
'wrid': record.wrid,

View File

@@ -7,13 +7,13 @@ class BaseHotelChannelConnectorComponent(AbstractComponent):
_collection = 'hotel.channel.backend'
@api.model
def create_issue(self, section, message, wmessage, wid=False,
def create_issue(self, section, message, channel_message, channel_object_id=False,
dfrom=False, dto=False):
self.env['hotel.channel.connector.issue'].sudo().create({
'section': section,
'message': message,
'wid': wid,
'wmessage': wmessage,
'date_start': dfrom and dfrom.strftime(DEFAULT_SERVER_DATE_FORMAT),
'date_end': dto and dto.strftime(DEFAULT_SERVER_DATE_FORMAT),
'channel_object_id': channel_object_id,
'channel_message': channel_message,
'date_start': dfrom,
'date_end': dto,
})

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -0,0 +1,37 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
class ChannelBackend(models.Model):
_name = 'channel.backend'
_description = 'Hotel Channel Backend'
_inherit = 'connector.backend'
@contextmanager
@api.multi
def work_on(self, model_name, **kwargs):
self.ensure_one()
lang = self.default_lang_id
if lang.code != self.env.context.get('lang'):
self = self.with_context(lang=lang.code)
user = self.env['ir.default'].sudo().get(
'res.config.settings', 'hotel_connector_user')
passwd = self.env['ir.default'].sudo().get(
'res.config.settings', 'hotel_connector_passwd')
lcode = self.env['ir.default'].sudo().get(
'res.config.settings', 'hotel_connector_lcode')
pkey = self.env['ir.default'].sudo().get(
'res.config.settings', 'hotel_connector_pkey')
server_addr = self.env['ir.default'].sudo().get(
'res.config.settings', 'hotel_connector_server')
wubook_login = WuBookLogin(
server_addr,
user,
passwd,
lcode,
pkey
)
with WuBookAdapter(wubook_login) as channel_api:
_super = super(ChannelBackend, self)
# from the components we'll be able to do: self.work.magento_api
with _super.work_on(model_name, **kwargs) as work:
yield work

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -0,0 +1,17 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models, fields
from odoo.addons.queue_job.job import job, related_action
class ChannelBinding(models.AbstractModel):
_name = 'channel.binding'
_inherit = 'external.binding'
_description = 'Hotel Channel Connector Binding (abstract)'
backend_id = fields.Many2one(
comodel_name='channel.backend',
string='Hotel Channel Connector Backend',
required=True,
ondelete='restrict')

View File

@@ -20,8 +20,7 @@ class HotelChannelConnectorIssue(models.Model):
date_start = fields.Date("From", readonly=True)
date_end = fields.Date("To", readonly=True)
channel_object_id = fields.Char("Channel Object ID", old_name='wid', readonly=True)
channel_connector_message = fields.Char("Channel Connector Message",
old_name='wmessage', readonly=True)
channel_message = fields.Char("Channel Message", old_name='wmessage', readonly=True)
@api.multi
def mark_readed(self):

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -1,56 +1,38 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from openerp import models, fields, api, _
from openerp.exceptions import UserError, ValidationError
from ..wubook import (
DEFAULT_WUBOOK_DATE_FORMAT,
WUBOOK_STATUS_CONFIRMED,
WUBOOK_STATUS_WAITING,
WUBOOK_STATUS_REFUSED,
WUBOOK_STATUS_ACCEPTED,
WUBOOK_STATUS_CANCELLED,
WUBOOK_STATUS_CANCELLED_PENALTY,
WUBOOK_STATUS_BAD)
from odoo.addons.hotel import date_utils
_logger = logging.getLogger(__name__)
class ChannelHotelReservation(models.Model):
_name = 'channel.hotel.reservation'
_inherit = 'channel.binding'
_inherits = {'hotel.reservation': 'odoo_id'}
_description = 'Channel Hotel Reservation'
class HotelReservation(models.Model):
_inherit = 'hotel.reservation'
@api.multi
def set_access_for_wubook_fields(self):
for rec in self:
user = self.env['res.users'].browse(self.env.uid)
rec.able_to_modify_wubook = user.has_group('base.group_system')
@api.depends('channel_type','wchannel_id')
def _get_origin_sale(self):
@api.depends('channel_reservation_id', 'ota_id')
def _is_from_ota(self):
for record in self:
if not record.channel_type:
record.channel_type = 'door'
record.origin_sale = (record.channel_type != 'web' or not record.wchannel_id) and \
dict(self.fields_get(allfields=['channel_type'])['channel_type']['selection'])[record.channel_type] \
or record.wchannel_id.name
record.is_from_ota = (record.channel_reservation_id and record.ota_id)
@api.depends('wrid', 'wchannel_id')
def _is_from_channel(self):
for record in self:
record.wis_from_channel = (record.wrid and record.wchannel_id)
wrid = fields.Char("WuBook Reservation ID", readonly=True)
wchannel_id = fields.Many2one('wubook.channel.info',
string='WuBook Channel ID',
readonly=True)
wchannel_reservation_code = fields.Char("WuBook Channel Reservation Code",
readonly=True)
wis_from_channel = fields.Boolean('WuBooK Is From Channel',
compute=_is_from_channel, store=False,
readonly=True)
odoo_id = fields.Many2one(comodel_names='hotel.reservation',
string='Reservation',
required=True,
ondelete='cascade')
channel_reservation_id = fields.Char("Channel Reservation ID", readonly=True, old_name='wrid')
ota_id = fields.Many2one('channel.ota.info',
string='Channel OTA ID',
readonly=True,
old_name='wchannel_id')
ota_reservation_id = fields.Char("Channel OTA Reservation Code",
readonly=True,
old_name='channel_reservation_code')
is_from_ota = fields.Boolean('Is From OTA',
compute=_is_from_ota, store=False,
readonly=True,
old_name='wis_from_channel')
to_read = fields.Boolean('To Read', default=False)
able_to_modify_wubook = fields.Boolean(compute=set_access_for_wubook_fields, string='Is user able to modify wubook fields?')
wbook_json = fields.Text(readonly=True)
able_to_modify_channel = fields.Boolean(compute=set_access_for_wubook_fields,
string='Is user able to modify wubook fields?',
old_name='able_to_modify_wubook')
channel_raw_data = fields.Text(readonly=True, old_name='wbook_json')
wstatus = fields.Selection([
('0', 'No WuBook'),
@@ -63,33 +45,75 @@ class HotelReservation(models.Model):
string='WuBook Status', default='0',
readonly=True)
wstatus_reason = fields.Char("WuBook Status Reason", readonly=True)
wcustomer_notes = fields.Text(related='folio_id.wcustomer_notes')
customer_notes = fields.Text(related='folio_id.customer_notes',
old_name='wcustomer_notes')
wmodified = fields.Boolean("WuBook Modified", readonly=True, default=False)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def push_availability(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='channel.exporter')
exporter.push_availability()
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def cancel_reservation(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
wres = adapter.cancel_reservation(
self.channel_reservation_id,
_('Cancelled by %s') % partner_id.name)
if not wres:
raise ValidationError(_("Can't cancel reservation on WuBook"))
class HotelReservation(models.Model):
_inherit = 'hotel.reservation'
@api.multi
def set_access_for_wubook_fields(self):
for rec in self:
user = self.env['res.users'].browse(self.env.uid)
rec.able_to_modify_channel = user.has_group('base.group_system')
@api.depends('channel_type', 'ota_id')
def _get_origin_sale(self):
for record in self:
if not record.channel_type:
record.channel_type = 'door'
record.origin_sale = dict(
self.fields_get(
allfields=['channel_type'])['channel_type']['selection'])[record.channel_type] \
if record.channel_type != 'web' or not record.ota_id \
else record.ota_id.name
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)
@api.model
def create(self, vals):
if vals.get('wrid') != None:
if vals.get('channel_reservation_id') != None:
vals.update({'preconfirm': False})
user = self.env['res.users'].browse(self.env.uid)
if user.has_group('hotel.group_hotel_call'):
vals.update({'to_read': True})
res = super(HotelReservation, self).create(vals)
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
self.env['hotel.virtual.room.availability'].refresh_availability(
vals['checkin'],
vals['checkout'],
vals['product_id'])
self.env['wubook'].push_availability()
self.env['hotel.virtual.room.availability'].refresh_availability(
vals['checkin'],
vals['checkout'],
vals['product_id'])
return res
# @api.multi
# def read(self, fields=None, load='_classic_read'):
# self.to_read = False
# return super(HotelReservation, self).read(fields=fields, load=load)
@api.multi
def write(self, vals):
if self._context.get('wubook_action', True) and \
@@ -125,7 +149,6 @@ class HotelReservation(models.Model):
new_vals[i]['checkin'],
new_vals[i]['checkout'],
new_vals[i]['product_id'])
self.env['wubook'].push_availability()
else:
res = super(HotelReservation, self).write(vals)
return res
@@ -135,7 +158,7 @@ class HotelReservation(models.Model):
vals = []
for record in self:
if record.wrid and not record.parent_reservation:
raise UserError(_("You can't delete wubook reservations"))
raise UserError(_("You can't delete OTA's reservations"))
vals.append({
'checkin': record.checkin,
'checkout': record.checkout,
@@ -150,7 +173,6 @@ class HotelReservation(models.Model):
record['checkin'],
record['checkout'],
record['product_id'])
self.env['wubook'].push_availability()
return res
@api.multi
@@ -159,9 +181,8 @@ class HotelReservation(models.Model):
if waction:
for record in self:
# Can't cancel in Odoo
if record.wis_from_channel:
raise ValidationError(_("Can't cancel reservations \
from OTA's"))
if record.is_from_ota:
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})
@@ -169,44 +190,37 @@ class HotelReservation(models.Model):
res = super(HotelReservation, self).action_cancel()
if waction and self.env['wubook'].is_valid_account():
partner_id = self.env['res.users'].browse(self.env.uid).partner_id
wubook_obj = self.env['wubook']
for record in self:
# Only can cancel reservations created directly in wubook
if record.wrid and record.wrid != '' and \
not record.wchannel_id and \
if record.channel_reservation_id and not record.ota_id and \
record.wstatus in ['1', '2', '4']:
wres = wubook_obj.cancel_reservation(
record.wrid,
'Cancelled by %s' % partner_id.name)
if not wres:
raise ValidationError(_("Can't cancel reservation \
on WuBook"))
self._event('on_record_cancel').notify(record)
return res
@api.multi
def confirm(self):
can_confirm = True
for record in self:
if record.wis_from_channel and int(record.wstatus) in WUBOOK_STATUS_BAD:
if record.is_from_ota and int(record.wstatus) in WUBOOK_STATUS_BAD:
can_confirm = False
break
if not can_confirm:
raise ValidationError(_("Can't confirm cancelled reservations"))
raise ValidationError(_("Can't confirm OTA's cancelled reservations"))
return super(HotelReservation, self).confirm()
@api.multi
def generate_copy_values(self, checkin=False, checkout=False):
self.ensure_one()
res = super(HotelReservation, self).generate_copy_values(
checkin=checkin, checkout=checkout)
res = super().generate_copy_values(checkin=checkin, checkout=checkout)
res.update({
'wrid': self.wrid,
'wchannel_id': self.wchannel_id and self.wchannel_id.id or False,
'wchannel_reservation_code': self.wchannel_reservation_code,
'wis_from_channel': self.wis_from_channel,
'channel_reservation_id': self.channel_reservation_id,
'ota_id': self.ota_id and self.ota_id.id or False,
'ota_reservation_code': self.ota_reservation_code,
'is_from_ota': self.is_from_ota,
'to_read': self.to_read,
'wstatus': self.wstatus,
'wstatus_reason': self.wstatus_reason,
'wcustomer_notes': self.wcustomer_notes,
'customer_notes': self.customer_notes,
})
return res
@@ -221,24 +235,37 @@ class HotelReservation(models.Model):
@api.model
def _hcalendar_reservation_data(self, reservations):
json_reservs, json_tooltips = super(
HotelReservation,
self)._hcalendar_reservation_data(reservations)
json_reservs, json_tooltips = super()._hcalendar_reservation_data(reservations)
reserv_obj = self.env['hotel.reservation']
for reserv in json_reservs:
reservation = reserv_obj.browse(reserv[1])
reserv[13] = reservation.splitted or reservation.wis_from_channel
reserv[13] = reservation.splitted or reservation.is_from_ota
return (json_reservs, json_tooltips)
@api.multi
def mark_as_readed(self):
for record in self:
record.write({'to_read': False, 'to_assign': False})
self.write({'to_read': False, 'to_assign': False})
@api.onchange('checkin', 'checkout', 'product_id')
def on_change_checkin_checkout_product_id(self):
if not self.wis_from_channel:
return super(HotelReservation, self).\
on_change_checkin_checkout_product_id()
if not self.is_from_ota:
return super().on_change_checkin_checkout_product_id()
class ChannelBindingProductListener(Component):
_name = 'channel.binding.hotel.reservation.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.reservation']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).push_availability()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).push_availability()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_cancel(self, record, fields=None):
record.with_delay(priority=20).cancel_reservation()

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -0,0 +1,139 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
class ChannelHotelVirtualRoom(models.Model):
_name = 'channel.hotel.virtual.room'
_inherit = 'channel.binding'
_inherits = {'hotel.virtual.room': 'odoo_id'}
_description = 'Channel Hotel Virtual Room'
@api.depends('ota_capacity')
@api.onchange('room_ids', 'room_type_ids')
def _get_capacity(self):
for rec in self:
rec.ota_capacity = rec.get_capacity()
odoo_id = fields.Many2one(comodel_names='hotel.virtual.room',
string='Reservation',
required=True,
ondelete='cascade')
channel_room_id = fields.Char("Channel Room ID", readonly=True, old_name='wrid')
channel_short_code = fields.Char("Channel Short Code", readonly=True, old_name='wscode')
ota_capacity = fields.Integer("OTA's Capacity", default=1, old_name='wcapacity')
@api.constrains('ota_capacity')
def _check_ota_capacity(self):
for record in self:
if record.ota_capacity < 1:
raise ValidationError(_("OTA's capacity can't be less than one"))
@api.multi
@api.constrains('channel_short_code')
def _check_channel_short_code(self):
for record in self:
if len(record.channel_short_code) > 4: # Wubook scode max. length
raise ValidationError(_("Chanel short code can't be longer than 4 characters"))
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def create_room(self):
self.ensure_one()
if self._context.get('channel_action', True):
seq_obj = self.env['ir.sequence']
shortcode = seq_obj.next_by_code('hotel.room.type')[:4]
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
channel_room_id = adapter.create_room(
shortcode,
self.name,
self.ota_capacity,
self.list_price,
self.max_real_rooms)
if channel_room_id:
self.write({
'channel_room_id': channel_room_id,
'channel_short_code': shortcode,
})
except ValidationError as e:
self.create_issue('room', "Can't create room on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def modify_room(self):
self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id:
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.modify_room(
self.channel_room_id,
self.name,
self.ota_capacity,
self.list_price,
self.max_real_rooms,
self.channel_short_code)
except ValidationError as e:
self.create_issue('room', "Can't modify room on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_room(self):
self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id:
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.delete_room(self.channel_room_id)
except ValidationError as e:
self.create_issue('room', "Can't delete room on channel", "sss")
@job(default_channel='root.channel')
@api.multi
def import_rooms(self):
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
importer = work.component(usage='channel.importer')
return importer.import_rooms()
class HotelVirtualRoom(models.Model):
_inherit = 'hotel.virtual.room'
channel_bind_ids = fields.One2many(
comodel_name='channel.hotel.virtual.room',
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
@api.multi
def get_restrictions(self, date):
restriction_plan_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id'))
self.ensure_one()
restriction = self.env['hotel.virtual.room.restriction.item'].search([
('date_start', '=', date),
('date_end', '=', date),
('virtual_room_id', '=', self.id),
('restriction_id', '=', restriction_plan_id)
], limit=1)
return restriction
class ChannelBindingVirtualRoomListener(Component):
_name = 'channel.binding.virtual.room.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.virtual.room']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).create_room()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).delete_room()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).modidy_room()

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -0,0 +1,116 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
class ChannelHotelVirtualRoomAvailability(models.Model):
_name = 'channel.hotel.virtual.room.availability'
_inherit = 'channel.binding'
_inherits = {'hotel.virtual.room.availability': 'odoo_id'}
_description = 'Channel Product Pricelist'
@api.model
def _default_channel_max_avail(self):
if self.virtual_room_id:
return self.virtual_room_id.max_real_rooms
return -1
odoo_id = fields.Many2one(comodel_names='product.pricelist',
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')
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def create_plan(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
channel_plan_id = adapter.create_plan(self.name,
self.is_daily_plan and 1 or 0)
if channel_plan_id:
self.channel_plan_id = channel_plan_id
except ValidationError as e:
self.create_issue('room', "Can't create plan on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def update_plan_name(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.update_plan_name(
self.channel_plan_id,
self.name)
except ValidationError as e:
self.create_issue('room', "Can't update plan name on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_plan(self):
self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id:
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.delete_plan(self.channel_plan_id)
except ValidationError as e:
self.create_issue('room', "Can't delete plan on channel", "sss")
@job(default_channel='root.channel')
@api.multi
def import_price_plans(self):
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
importer = work.component(usage='channel.importer')
return importer.import_pricing_plans()
class ProductPricelist(models.Model):
_inherit = 'product.pricelist'
channel_bind_ids = fields.One2many(
comodel_name='channel.product.pricelist',
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
@api.multi
@api.depends('name')
def name_get(self):
pricelist_obj = self.env['product.pricelist']
org_names = super(ProductPricelist, self).name_get()
names = []
for name in org_names:
priclist_id = pricelist_obj.browse(name[0])
if priclist_id.wpid:
names.append((name[0], '%s (WuBook)' % name[1]))
else:
names.append((name[0], name[1]))
return names
class ChannelBindingProductPricelistListener(Component):
_name = 'channel.binding.product.pricelist.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.product.pricelist']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).create_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).delete_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'name' in fields:
record.with_delay(priority=20).update_plan_name()

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -0,0 +1,106 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
class ChannelHotelVirtualRoomRestriction(models.Model):
_name = 'channel.hotel.virtual.room.restriction'
_inherit = 'channel.binding'
_inherits = {'hotel.virtual.room.restriction': 'odoo_id'}
_description = 'Channel Hotel Virtual Room Restriction'
odoo_id = fields.Many2one(comodel_names='hotel.virtual.room.restriction',
string='Hotel Virtual Room Restriction',
required=True,
ondelete='cascade')
channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid')
is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan')
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def create_plan(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
channel_plan_id = adapter.create_rplan(self.name)
if channel_plan_id:
self.channel_plan_id = channel_plan_id
except ValidationError as e:
self.create_issue('room', "Can't create restriction plan on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def update_plan_name(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.rename_rplan(
self.channel_plan_id,
self.name)
except ValidationError as e:
self.create_issue('room', "Can't update restriction plan name on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_plan(self):
self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id:
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.delete_rplan(self.channel_plan_id)
except ValidationError as e:
self.create_issue('room', "Can't delete restriction plan on channel", "sss")
@job(default_channel='root.channel')
@api.multi
def import_restriction_plans(self):
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
importer = work.component(usage='channel.importer')
return importer.import_restriction_plans()
class HotelVirtualRoomRestriction(models.Model):
_inherit = 'hotel.virtual.room.restriction'
channel_bind_ids = fields.One2many(
comodel_name='channel.hotel.virtual.room.restriction',
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
@api.multi
@api.depends('name')
def name_get(self):
vroom_restriction_obj = self.env['hotel.virtual.room.restriction']
org_names = super(HotelVirtualRoomRestriction, self).name_get()
names = []
for name in org_names:
restriction_id = vroom_restriction_obj.browse(name[0])
if restriction_id.wpid:
names.append((name[0], '%s (WuBook)' % name[1]))
else:
names.append((name[0], name[1]))
return names
class ChannelBindingHotelVirtualRoomRestrictionListener(Component):
_name = 'channel.binding.hotel.virtual.room.restriction.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.virtual.room.restriction']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).create_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).delete_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'name' in fields:
record.with_delay(priority=20).update_plan_name()

View File

@@ -8,35 +8,37 @@ class HotelFolio(models.Model):
@api.depends('room_lines')
def _has_channel_reservations(self):
if any(self.room_lines):
for room in self.room_lines:
if room.channel_room_id and room.channel_room_id != '':
self.has_channel_reservations = True
return
self.has_channel_reservations = False
for record in self:
channel_reservations = record.room_lines.filtered(lambda x: x.channel_room_id)
record.has_channel_reservations = any(channel_reservations)
seed = fields.Char("WuBook Session Seed", old_name='wseed', readonly=True)
customer_notes = fields.Text("WuBook Customer Notes",
old_name='wcustomer_notes', readonly=True)
has_channel_reservations = fields.Boolean(old_name='whas_wubook_reservations',
compute=_has_channel_reservations,
store=False)
wseed = fields.Char("Wubook Session Seed", readonly=True)
customer_notes = fields.Text("Channel Customer Notes",
readonly=True, old_name='wcustomer_notes')
has_channel_reservations = fields.Boolean(compute=_has_channel_reservations,
store=False,
old_name='whas_wubook_reservations')
@job(default_channel='root.channel')
@api.multi
def import_reservations(self):
return self.env['hotel.channel.connector'].fetch_new_bookings()
self.ensure_one()
with self.backend_id.work_on(self._name) as work:
importer = work.component(usage='channel.importer')
importer.fetch_new_bookings()
@api.multi
def action_confirm(self):
for rec in self:
for room in rec.room_lines:
room.to_read = False
room.to_assign = False
return super(HotelFolio, self).action_confirm()
rec.room_lines.write({
'to_read': False,
'to_assign': False,
})
return super().action_confirm()
@api.multi
def get_grouped_reservations_json(self, state, import_all=False):
super(HotelFolio, self).get_grouped_reservations_json(state, import_all=import_all)
super().get_grouped_reservations_json(state, import_all=import_all)
self.ensure_one()
info_grouped = []
for rline in self.room_lines:
@@ -68,12 +70,14 @@ class HotelFolio(models.Model):
@api.depends('room_lines')
def _compute_has_cancelled_reservations_to_send(self):
super(HotelFolio, self)._compute_has_cancelled_reservations_to_send()
has_to_send = False
for rline in self.room_lines:
if rline.splitted:
super()._compute_has_cancelled_reservations_to_send()
hotel_reserv_obj = self.env['hotel.reservation']
for record in self:
splitted_reservation_ids = record.room_lines.filtered(lambda x: x.splitted)
has_to_send = False
for rline in splitted_reservation_ids:
master_reservation = rline.parent_reservation or rline
has_to_send = self.env['hotel.reservation'].search_count([
has_to_send = hotel_reserv_obj.search_count([
('splitted', '=', True),
('folio_id', '=', self.id),
('to_send', '=', True),
@@ -83,7 +87,6 @@ class HotelFolio(models.Model):
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
]) > 0
elif rline.to_send and rline.state == 'cancelled' and not rline.wmodified:
has_to_send = True
break
self.has_cancelled_reservations_to_send = has_to_send
if has_to_send:
break
record.has_cancelled_reservations_to_send = has_to_send

View File

@@ -1,111 +0,0 @@
# 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 HotelVirtualRoom(models.Model):
_inherit = 'hotel.room.type'
@api.depends('wcapacity')
@api.onchange('room_ids', 'room_type_ids')
def _get_capacity(self):
hotel_room_obj = self.env['hotel.room']
for rec in self:
rec.wcapacity = rec.get_capacity()
wscode = fields.Char("WuBook Short Code", readonly=True)
wrid = fields.Char("WuBook Room ID", readonly=True)
wcapacity = fields.Integer("WuBook Capacity", default=1)
@api.constrains('wcapacity')
def _check_wcapacity(self):
for record in self:
if record.wcapacity < 1:
raise ValidationError(_("wcapacity can't be less than one"))
@api.multi
@api.constrains('wscode')
def _check_wscode(self):
for record in self:
if len(record.wscode) > 4: # Wubook scode max. length
raise ValidationError(_("SCODE Can't be longer than 4 characters"))
@api.multi
def get_restrictions(self, date):
restriction_plan_id = int(self.env['ir.default'].sudo().get(
'hotel.config.settings', 'parity_restrictions_id'))
self.ensure_one()
restriction = self.env['hotel.virtual.room.restriction.item'].search([
('date_start', '=', date),
('date_end', '=', date),
('virtual_room_id', '=', self.id),
('restriction_id', '=', restriction_plan_id)
], limit=1)
return restriction
# if restriction:
# return restriction
# else:
# vroom_rest_it_obj = self.env['hotel.virtual.room.restriction.item']
# global_restr = vroom_rest_it_obj.search([
# ('applied_on', '=', '1_global'),
# ('restriction_id', '=', restriction_plan_id)
# ], limit=1)
# if global_restr:
# return global_restr
# return False
@api.model
def create(self, vals):
vroom = super(HotelVirtualRoom, self).create(vals)
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
seq_obj = self.env['ir.sequence']
shortcode = seq_obj.next_by_code('hotel.room.type')[:4]
wrid = self.env['wubook'].create_room(
shortcode,
vroom.name,
vroom.wcapacity,
vroom.list_price,
vroom.max_real_rooms
)
if not wrid:
raise ValidationError(_("Can't create room on WuBook"))
vroom.with_context(wubook_action=False).write({
'wrid': wrid,
'wscode': shortcode,
})
return vroom
@api.multi
def write(self, vals):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
wubook_obj = self.env['wubook']
for record in self:
if record.wrid and record.wrid != '':
wres = wubook_obj.modify_room(
vals.get('wrid', record.wrid),
vals.get('name', record.name),
vals.get('wcapacity', record.wcapacity),
vals.get('list_price', record.list_price),
vals.get('max_real_rooms', record.max_real_rooms),
vals.get('wscode', record.wscode))
if not wres:
raise ValidationError(_("Can't modify room on WuBook"))
return super(HotelVirtualRoom, self).write(vals)
@api.multi
def unlink(self):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
for record in self:
if record.wrid and record.wrid != '':
wres = self.env['wubook'].delete_room(record.wrid)
if not wres:
raise ValidationError(_("Can't delete room on WuBook"))
return super(HotelVirtualRoom, self).unlink()
@api.multi
def import_rooms(self):
return self.env['wubook'].import_rooms()

View File

@@ -1,134 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime, timedelta
from openerp import models, fields, api, _
from openerp.exceptions import ValidationError
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
class ProductPricelist(models.Model):
_inherit = 'product.pricelist'
wpid = fields.Char("WuBook Plan ID", readonly=True)
wdaily = fields.Boolean("WuBook Daily Plan", default=True)
@api.multi
def get_wubook_prices(self):
self.ensure_one()
prices = {}
if self.wdaily:
min_date = False
max_date = False
for item in self.item_ids:
if not item.date_start or not item.date_end:
continue
date_start_dt = fields.Datetime.from_string(item.date_start)
date_end_dt = fields.Datetime.from_string(item.date_end)
if not min_date or date_start_dt < min_date:
min_date = date_start_dt
if not max_date or date_end_dt > max_date:
max_date = date_end_dt
if not min_date or not max_date:
return prices
days_diff = abs((max_date - min_date).days)
vrooms = self.env['hotel.room.type'].search([
('wrid', '!=', ''),
('wrid', '!=', False)
])
for vroom in vrooms:
prices.update({vroom.wrid: []})
for i in range(0, days_diff or 1):
ndate_dt = min_date + timedelta(days=i)
product_id = vroom.product_id.with_context(
quantity=1,
date=ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
pricelist=self.id,
uom=vroom.product_id.product_tmpl_id.uom_id.id)
prices[vroom.wrid].append(product_id.price)
else:
vrooms = self.env['hotel.room.type'].search([
('wrid', '!=', ''),
('wrid', '!=', False)
])
for item in self.item_ids:
if not item.date_start or not item.date_end:
continue
date_start_dt = fields.Datetime.from_string(item.date_start)
date_end_dt = fields.Datetime.from_string(item.date_end)
days_diff = abs((date_end_dt - date_start_dt).days)
vals = {}
for vroom in vrooms:
wdays = [False, False, False, False, False, False, False]
for i in range(0, 7):
ndate_dt = date_start_dt + timedelta(days=i)
product_id = vroom.product_id.with_context(
quantity=1,
date=ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
pricelist=self.id,
uom=vroom.product_id.product_tmpl_id.uom_id.id)
wdays[ndate_dt.weekday()] = product_id.price
vals.update({vroom.wrid: wdays})
prices.update({
'dfrom': item.date_start,
'dto': item.date_end,
'values': vals,
})
return prices
@api.model
def create(self, vals):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
wpid = self.env['wubook'].create_plan(vals['name'],
vals.get('wdaily') and
1 or 0)
if not wpid:
raise ValidationError(_("Can't create plan on WuBook"))
vals.update({'wpid': wpid})
pricelist = super(ProductPricelist, self).create(vals)
return pricelist
@api.multi
def write(self, vals):
nname = vals.get('name')
if self._context.get('wubook_action', True) and nname and \
self.env['wubook'].is_valid_account():
for record in self:
if record.wpid and record.wpid != '':
wres = self.env['wubook'].update_plan_name(
vals.get('wpid', record.wpid),
nname)
if not wres:
raise ValidationError(_("Can't update plan name \
on WuBook"))
updated = super(ProductPricelist, self).write(vals)
return updated
@api.multi
def unlink(self):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
for record in self:
if record.wpid and record.wpid != '':
wres = self.env['wubook'].delete_plan(record.wpid)
if not wres:
raise ValidationError(_("Can't delete plan on WuBook"))
return super(ProductPricelist, self).unlink()
@api.multi
def import_price_plans(self):
return self.env['wubook'].import_pricing_plans()
@api.multi
@api.depends('name')
def name_get(self):
pricelistObj = self.env['product.pricelist']
org_names = super(ProductPricelist, self).name_get()
names = []
for name in org_names:
priclist_id = pricelistObj.browse(name[0])
if priclist_id.wpid:
names.append((name[0], '%s (WuBook)' % name[1]))
else:
names.append((name[0], name[1]))
return names

View File

@@ -7,50 +7,49 @@ from openerp.exceptions import ValidationError
class ProductPricelistItem(models.Model):
_inherit = 'product.pricelist.item'
wpushed = fields.Boolean("WuBook Pushed", default=True, readonly=True)
wdaily = fields.Boolean(related='pricelist_id.wdaily', readonly=True)
is_channel_pushed = fields.Boolean("WuBook Pushed", default=True, readonly=True,
old_name='wpushed')
is_daily_plan = fields.Boolean(related='pricelist_id.wdaily', readonly=True,
old_name='wdaily')
@api.constrains('fixed_price')
def _check_fixed_price(self):
vroom_obj = self.env['hotel.room.type']
vroom_obj = self.env['hotel.virtual.room']
for record in self:
vroom = vroom_obj.search([
('product_id.product_tmpl_id', '=', record.product_tmpl_id.id)
], limit=1)
if vroom and vroom.wrid and record.compute_price == 'fixed' \
if vroom and vroom.channel_room_id and record.compute_price == 'fixed' \
and record.fixed_price <= 0.0:
raise ValidationError(_("Price need be greater than zero"))
@api.model
def create(self, vals):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
if self._context.get('channel_action', True):
pricelist_id = self.env['product.pricelist'].browse(
vals.get('pricelist_id'))
vroom = self.env['hotel.room.type'].search([
vroom = self.env['hotel.virtual.room'].search([
('product_id.product_tmpl_id', '=',
vals.get('product_tmpl_id')),
('wrid', '!=', False)
('channel_room_id', '!=', False)
])
if vroom and pricelist_id.wpid:
vals.update({'wpushed': False})
if vroom and pricelist_id.channel_plan_id:
vals.update({'is_channel_pushed': False})
return super(ProductPricelistItem, self).create(vals)
@api.multi
def write(self, vals):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
if self._context.get('channel_action', True):
prices_obj = self.env['product.pricelist']
for record in self:
pricelist_id = vals.get('pricelist_id') and \
prices_obj.browse(vals.get('pricelist_id')) or \
record.pricelist_id
pricelist_id = prices_obj.browse(vals.get('pricelist_id')) if \
vals.get('pricelist_id') else record.pricelist_id
product_tmpl_id = vals.get('product_tmpl_id') or \
record.product_tmpl_id.id
vroom = self.env['hotel.room.type'].search([
record.product_tmpl_id.id
vroom = self.env['hotel.virtual.room'].search([
('product_id.product_tmpl_id', '=', product_tmpl_id),
('wrid', '!=', False)
('channel_room_id', '!=', False),
])
if vroom and pricelist_id.wpid:
vals.update({'wpushed': False})
if vroom and pricelist_id.channel_plan_id:
vals.update({'is_channel_pushed': False})
return super(ProductPricelistItem, self).write(vals)

View File

@@ -30,18 +30,15 @@ class ResPartner(models.Model):
if folio_ids:
folio_ids.write({
'partner_id': org_partner_id.id,
})
folio_ids = self.env['hotel.folio'].search([
('partner_invoice_id', '=', record.id)
])
if folio_ids:
folio_ids.write({
'partner_invoice_id': org_partner_id.id,
})
# DANGER: self-delete... perhaps best invisible?
# record.unlink() This cause mistakes
record.write({'active': False})
# return {
# 'type': 'ir.actions.act_window',
# 'res_model': 'res.partner',
# 'views': [[False, "form"]],
# 'target': 'current',
# 'res_id': org_partner_id.id,
# }
else:
# If not found, this is the 'confirmed'
vals.update({'unconfirmed': False})

View File

@@ -1,125 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime, timedelta
from openerp import models, fields, api, _
from openerp.exceptions import ValidationError
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
class ReservationRestriction(models.Model):
_inherit = 'hotel.virtual.room.restriction'
wpid = fields.Char("WuBook Restriction Plan ID", readonly=True)
wdaily = fields.Boolean("Plan Daily", default=True, readonly=True)
@api.multi
def get_wubook_restrictions(self):
self.ensure_one()
prices = {}
min_date = False
max_date = False
for item in self.item_ids:
if not item.date_start or not item.date_end:
continue
date_start_dt = fields.Datetime.from_string(item.date_start)
date_end_dt = fields.Datetime.from_string(item.date_end)
if not min_date or date_start_dt < min_date:
min_date = date_start_dt
if not max_date or date_end_dt > max_date:
max_date = date_end_dt
if not min_date or not max_date:
return prices
days_diff = abs((max_date - min_date).days)
vrooms = self.env['hotel.room.type'].search([
('wrid', '!=', ''),
('wrid', '!=', False)
])
for vroom in vrooms:
prices.update({vroom.wrid: []})
for i in range(0, days_diff or 1):
ndate_dt = min_date + timedelta(days=i)
product_id = vroom.product_id.with_context(
quantity=1,
date=ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
pricelist=self.id,
uom=vroom.product_id.product_tmpl_id.uom_id.id)
prices[vroom.wrid].append(product_id.price)
return prices
@api.model
def create(self, vals):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
wpid = self.env['wubook'].create_rplan(vals['name'])
if not wpid:
raise ValidationError(_("Can't create restriction plan on \
WuBook"))
vals.update({'wpid': wpid})
rules = self._context.get('rules')
if rules:
vals.update({'wdaily': False})
restriction = super(ReservationRestriction, self).create(vals)
if rules:
# Basic Rules
vroom_rest_it_obj = self.env['hotel.virtual.room.restriction.item']
vroom_rest_it_obj.with_context({'wubook_action': False}).create({
'closed_arrival': rules['closed_arrival'],
'closed': rules['closed'],
'min_stay': rules['min_stay'],
'closed_departure': rules['closed_departure'],
'max_stay': rules['max_stay'],
'min_stay_arrival': rules['min_stay_arrival'],
'restriction_id': restriction.id,
'applied_on': '1_global',
})
return restriction
@api.multi
def write(self, vals):
nname = vals.get('name')
if self._context.get('wubook_action', True) and nname and \
self.env['wubook'].is_valid_account():
for record in self:
if record.wpid and record.wpid != '':
wres = self.env['wubook'].rename_rplan(
vals.get('wpid', record.wpid),
nname)
if not wres:
raise ValidationError(_("Can't rename restriction plan \
on WuBook"))
updated = super(ReservationRestriction, self).write(vals)
return updated
@api.multi
def unlink(self):
if self._context.get('wubook_action', True) and \
self.env['wubook'].is_valid_account():
for record in self:
if record.wpid and record.wpid != '':
wres = self.env['wubook'].delete_rplan(record.wpid)
if not wres:
raise ValidationError(_("Can't delete restriction plan \
on WuBook"))
return super(ReservationRestriction, self).unlink()
@api.multi
def import_restriction_plans(self):
return self.env['wubook'].import_restriction_plans()
@api.multi
@api.depends('name')
def name_get(self):
roomRestrictionObj = self.env['hotel.virtual.room.restriction']
org_names = super(ReservationRestriction, self).name_get()
names = []
for name in org_names:
restriction_id = roomRestrictionObj.browse(name[0])
if restriction_id.wpid:
names.append((name[0], '%s (WuBook)' % name[1]))
else:
names.append((name[0], name[1]))
return names

View File

@@ -6,7 +6,8 @@ from openerp import models, fields, api
class ReservationRestrictionItem(models.Model):
_inherit = 'hotel.virtual.room.restriction.item'
wpushed = fields.Boolean("WuBook Pushed", default=False, readonly=True)
channel_pushed = fields.Boolean("WuBook Pushed", default=False, readonly=True,
old_name='wpushed')
@api.onchange('date_start')
def _onchange_date_start(self):
@@ -22,6 +23,6 @@ class ReservationRestrictionItem(models.Model):
def write(self, vals):
if vals.get('date_start'):
vals['date_end'] = vals.get('date_start')
if self._context.get('wubook_action', True):
vals.update({'wpushed': False})
if self._context.get('channel_action', True):
vals.update({'channel_pushed': False})
return super(ReservationRestrictionItem, self).write(vals)

View File

@@ -12,19 +12,21 @@ class VirtualRoomAvailability(models.Model):
_inherit = 'hotel.virtual.room.availability'
@api.model
def _default_wmax_avail(self):
def _default_channel_max_avail(self):
if self.virtual_room_id:
return self.virtual_room_id.max_real_rooms
return -1
wmax_avail = fields.Integer("Max. Wubook Avail",
default=_default_wmax_avail)
wpushed = fields.Boolean("WuBook Pushed", readonly=True, default=False)
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('avail')
def _check_avail(self):
vroom_obj = self.env['hotel.room.type']
issue_obj = self.env['wubook.issue']
vroom_obj = self.env['hotel.virtual.room']
issue_obj = self.env['hotel.channel.connector.issue']
wubook_obj = self.env['wubook']
for record in self:
cavail = len(vroom_obj.check_availability_virtual_room(
@@ -35,7 +37,7 @@ class VirtualRoomAvailability(models.Model):
if record.avail > max_avail:
issue_obj.sudo().create({
'section': 'avail',
'message': _("The new availability can't be greater than \
'message': _(r"The new availability can't be greater than \
the actual availability \
\n[%s]\nInput: %d\Limit: %d") % (record.virtual_room_id.name,
record.avail,

View File

@@ -0,0 +1,3 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -0,0 +1,107 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
class ChannelProductPricelist(models.Model):
_name = 'channel.product.pricelist'
_inherit = 'channel.binding'
_inherits = {'product.pricelist': 'odoo_id'}
_description = 'Channel Product Pricelist'
odoo_id = fields.Many2one(comodel_names='product.pricelist',
string='Pricelist',
required=True,
ondelete='cascade')
channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid')
is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan')
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def create_plan(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
channel_plan_id = adapter.create_plan(self.name,
self.is_daily_plan and 1 or 0)
if channel_plan_id:
self.channel_plan_id = channel_plan_id
except ValidationError as e:
self.create_issue('room', "Can't create plan on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def update_plan_name(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.update_plan_name(
self.channel_plan_id,
self.name)
except ValidationError as e:
self.create_issue('room', "Can't update plan name on channel", "sss")
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_plan(self):
self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id:
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
try:
adapter.delete_plan(self.channel_plan_id)
except ValidationError as e:
self.create_issue('room', "Can't delete plan on channel", "sss")
@job(default_channel='root.channel')
@api.multi
def import_price_plans(self):
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
importer = work.component(usage='channel.importer')
return importer.import_pricing_plans()
class ProductPricelist(models.Model):
_inherit = 'product.pricelist'
channel_bind_ids = fields.One2many(
comodel_name='channel.product.pricelist',
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
@api.multi
@api.depends('name')
def name_get(self):
pricelist_obj = self.env['product.pricelist']
org_names = super(ProductPricelist, self).name_get()
names = []
for name in org_names:
priclist_id = pricelist_obj.browse(name[0])
if priclist_id.wpid:
names.append((name[0], '%s (WuBook)' % name[1]))
else:
names.append((name[0], name[1]))
return names
class ChannelBindingProductPricelistListener(Component):
_name = 'channel.binding.product.pricelist.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.product.pricelist']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).create_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).delete_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'name' in fields:
record.with_delay(priority=20).update_plan_name()

View File

@@ -26,33 +26,33 @@ class WubookConfiguration(models.TransientModel):
@api.multi
def set_wubook_user(self):
return self.env['ir.default'].sudo().set(
'wubook.config.settings', 'wubook_user', self.wubook_user)
'wubook.config.settings', 'wubook_user', self.wubook_user)
@api.multi
def set_wubook_passwd(self):
return self.env['ir.default'].sudo().set(
'wubook.config.settings', 'wubook_passwd', self.wubook_passwd)
'wubook.config.settings', 'wubook_passwd', self.wubook_passwd)
@api.multi
def set_wubook_lcode(self):
return self.env['ir.default'].sudo().set(
'wubook.config.settings', 'wubook_lcode', self.wubook_lcode)
'wubook.config.settings', 'wubook_lcode', self.wubook_lcode)
@api.multi
def set_wubook_server(self):
return self.env['ir.default'].sudo().set(
'wubook.config.settings', 'wubook_server', self.wubook_server)
'wubook.config.settings', 'wubook_server', self.wubook_server)
@api.multi
def set_wubook_pkey(self):
return self.env['ir.default'].sudo().set(
'wubook.config.settings', 'wubook_pkey', self.wubook_pkey)
'wubook.config.settings', 'wubook_pkey', self.wubook_pkey)
@api.multi
def set_wubook_push_security_token(self):
return self.env['ir.default'].sudo().set(
'wubook.config.settings',
'wubook_push_security_token', self.wubook_push_security_token)
'wubook.config.settings',
'wubook_push_security_token', self.wubook_push_security_token)
# Dangerus method: Usefull for cloned instances with new wubook account
@api.multi
@@ -77,21 +77,21 @@ class WubookConfiguration(models.TransientModel):
vrooms = self.env['hotel.room.type'].search([])
for vroom in vrooms:
shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4]
wrid = wubook_obj.create_room(
channel_room_id = wubook_obj.create_room(
shortcode,
vroom.name,
vroom.wcapacity,
vroom.list_price,
vroom.max_real_rooms
)
if wrid:
if channel_room_id:
vroom.with_context(wubook_action=False).write({
'wrid': wrid,
'channel_room_id': channel_room_id,
'wscode': shortcode,
})
else:
vroom.with_context(wubook_action=False).write({
'wrid': '',
'channel_room_id': '',
'wscode': '',
})
# Create Restrictions
@@ -99,16 +99,16 @@ class WubookConfiguration(models.TransientModel):
restriction_ids = vroom_rest_obj.search([])
for restriction in restriction_ids:
if restriction.wpid != '0':
wpid = wubook_obj.create_rplan(restriction.name)
channel_plan_id = wubook_obj.create_rplan(restriction.name)
restriction.write({
'wpid': wpid or ''
'channel_plan_id': channel_plan_id or ''
})
# Create Pricelist
pricelist_ids = self.env['product.pricelist'].search([])
for pricelist in pricelist_ids:
wpid = wubook_obj.create_plan(pricelist.name, pricelist.wdaily)
channel_plan_id = wubook_obj.create_plan(pricelist.name, pricelist.wdaily)
pricelist.write({
'wpid': wpid or ''
'channel_plan_id': channel_plan_id or ''
})
wubook_obj.close_connection()
@@ -120,14 +120,14 @@ class WubookConfiguration(models.TransientModel):
# Reset Reservations
reservation_ids = self.env['hotel.reservation'].search([
('wrid', '!=', ''),
('wrid', '!=', False)
('channel_reservation_id', '!=', ''),
('channel_reservation_id', '!=', False)
])
reservation_ids.with_context(wubook_action=False).write({
'wrid': '',
'wchannel_id': False,
'wchannel_reservation_code': '',
'wis_from_channel': False,
'channel_reservation_id': '',
'ota_id': False,
'ota_reservation_id': '',
'is_from_ota': False,
'wstatus': 0
})

View File

@@ -3,13 +3,17 @@
from openerp import models, fields, api
class WuBookChannelInfo(models.Model):
_name = 'wubook.channel.info'
class HotelChannelConnectorOTAInfo(models.Model):
_name = 'hote.channel.connector.ota.info'
wid = fields.Char("WuBook Channel ID", required=True)
name = fields.Char("Channel Name", required=True)
ota_id = fields.Char("Channel OTA ID", required=True)
name = fields.Char("OTA Name", required=True)
ical = fields.Boolean("ical", default=False)
@job(default_channel='root.channel')
@api.multi
def import_channels_info(self):
return self.env['wubook'].import_channels_info()
self.ensure_one()
with self.backend_id.work_on(self._name) as work:
importer = work.component(usage='channel.importer')
return importer.import_channels_info()

View File

@@ -58,8 +58,8 @@ class TestHotelFolio(TestHotelWubook):
('wrid', '=', processed_rids[0])
], order='id ASC', limit=1)
self.assertTrue(nreserv, "Can't found reservation")
self.assertTrue(nreserv.folio_id.whas_wubook_reservations,
"Can't found reservations from wubook")
self.assertTrue(nreserv.folio_id.has_channel_reservations,
"Can't found reservations from channel")
def test_import_reservations(self):
now_utc_dt = date_utils.now()

View File

@@ -31,7 +31,7 @@ from .common import TestHotelWubook
class TestHotelReservation(TestHotelWubook):
def test_is_from_channel(self):
def test_is_from_ota(self):
now_utc_dt = date_utils.now()
checkin_utc_dt = now_utc_dt + timedelta(days=3)
checkin_dt = date_utils.dt_as_timezone(checkin_utc_dt,
@@ -64,9 +64,9 @@ class TestHotelReservation(TestHotelWubook):
('wrid', 'in', processed_rids)
])
self.assertTrue(nreserv, "Reservation not found")
self.assertTrue(nreserv.wis_from_channel)
self.assertTrue(nreserv.is_from_ota)
nreserv.wrid = ''
self.assertFalse(nreserv.wis_from_channel)
self.assertFalse(nreserv.is_from_ota)
def test_write(self):
now_utc_dt = date_utils.now()

View File

@@ -29,7 +29,7 @@ class MassiveChangesWizard(models.TransientModel):
@api.multi
def duplicate_reservation(self):
reservation_id = self.env['hotel.reservation'].browse(
self.env.context.get('active_id'))
if reservation_id and reservation_id.wis_from_channel:
self.env.context.get('active_id'))
if reservation_id and reservation_id.is_from_ota:
raise ValidationError(_("Can't duplicate a reservation from channel"))
return super(MassiveChangesWizard, self).duplicate_reservation()

View File

@@ -34,7 +34,7 @@ class MassivePriceChangeWizard(models.TransientModel):
if not reservation_id:
return False
if reservation_id.wis_from_channel:
if reservation_id.is_from_ota:
raise ValidationError(
_("Can't change prices of reservations from OTA's"))