mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
566 lines
19 KiB
Python
566 lines
19 KiB
Python
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import _
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.addons.component.core import AbstractComponent
|
|
from odoo.addons.queue_job.exception import RetryableJobError
|
|
from odoo.tools import (
|
|
DEFAULT_SERVER_DATE_FORMAT,
|
|
DEFAULT_SERVER_DATETIME_FORMAT)
|
|
from odoo.addons.payment.models.payment_acquirer import _partner_split_name
|
|
from odoo.addons.hotel import date_utils
|
|
from odoo import fields
|
|
|
|
# GLOBAL VARS
|
|
DEFAULT_WUBOOK_DATE_FORMAT = "%d/%m/%Y"
|
|
DEFAULT_WUBOOK_TIME_FORMAT = "%H:%M"
|
|
DEFAULT_WUBOOK_DATETIME_FORMAT = "%s %s" % (DEFAULT_WUBOOK_DATE_FORMAT,
|
|
DEFAULT_WUBOOK_TIME_FORMAT)
|
|
WUBOOK_STATUS_CONFIRMED = 1
|
|
WUBOOK_STATUS_WAITING = 2
|
|
WUBOOK_STATUS_REFUSED = 3
|
|
WUBOOK_STATUS_ACCEPTED = 4
|
|
WUBOOK_STATUS_CANCELLED = 5
|
|
WUBOOK_STATUS_CANCELLED_PENALTY = 6
|
|
|
|
WUBOOK_STATUS_GOOD = (
|
|
WUBOOK_STATUS_CONFIRMED,
|
|
WUBOOK_STATUS_WAITING,
|
|
WUBOOK_STATUS_ACCEPTED,
|
|
)
|
|
WUBOOK_STATUS_BAD = (
|
|
WUBOOK_STATUS_REFUSED,
|
|
WUBOOK_STATUS_CANCELLED,
|
|
WUBOOK_STATUS_CANCELLED_PENALTY,
|
|
)
|
|
|
|
class WuBookLogin(object):
|
|
def __init__(self, address, user, passwd, lcode, pkey):
|
|
self.address = address
|
|
self.user = user
|
|
self.passwd = passwd
|
|
self.lcode = lcode
|
|
self.pkey = pkey
|
|
|
|
def is_valid(self):
|
|
return self.address and self.user and self.passwd and self.lcode and self.pkey
|
|
|
|
class WuBookServer(object):
|
|
def __init__(self, login_data):
|
|
self._server = None
|
|
self._token = None
|
|
self._login_data = login_data
|
|
|
|
def __enter__(self):
|
|
# we do nothing, api is lazy
|
|
return self
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
if self._server is not None:
|
|
self.close()
|
|
|
|
@property
|
|
def server(self):
|
|
if self._server is None and self._login_data.is_valid():
|
|
try:
|
|
self._server = xmlrpclib.Server(self._login_data.address)
|
|
res, tok = self._server.acquire_token(
|
|
self._login_data.user,
|
|
self._login_data.passwd,
|
|
self._login_data.pkey)
|
|
if res == 0:
|
|
self._token = tok
|
|
else:
|
|
self._server = None
|
|
except Exception:
|
|
self._server = None
|
|
raise RetryableJobError("Can't connect with channel!")
|
|
return self._server
|
|
|
|
@property
|
|
def session_token(self):
|
|
return self._token
|
|
|
|
@property
|
|
def lcode(self):
|
|
return self._login_data.lcode
|
|
|
|
def close(self):
|
|
self._server.release_token(self._token)
|
|
self._token = None
|
|
self._server = None
|
|
|
|
class HotelChannelInterfaceAdapter(AbstractComponent):
|
|
_name = 'hotel.channel.adapter'
|
|
_inherit = ['base.backend.adapter', 'base.hotel.channel.connector']
|
|
_usage = 'backend.adapter'
|
|
|
|
def create_room(self, shortcode, name, capacity, price, availability):
|
|
raise NotImplementedError
|
|
|
|
def modify_room(self, channel_room_id, name, capacity, price, availability, scode):
|
|
raise NotImplementedError
|
|
|
|
def delete_room(self, channel_room_id):
|
|
raise NotImplementedError
|
|
|
|
def fetch_rooms(self, channel_room_id=0):
|
|
raise NotImplementedError
|
|
|
|
def fetch_rooms_values(self, date_from, date_to, rooms=False):
|
|
raise NotImplementedError
|
|
|
|
def update_availability(self, rooms_avail):
|
|
raise NotImplementedError
|
|
|
|
def corporate_fetch(self):
|
|
raise NotImplementedError
|
|
|
|
def create_reservation(self, channel_room_id, customer_name, email, city,
|
|
phone, address, country_code, checkin, checkout,
|
|
adults, children, notes=''):
|
|
raise NotImplementedError
|
|
|
|
def cancel_reservation(self, channel_reservation_id, reason=""):
|
|
raise NotImplementedError
|
|
|
|
def fetch_new_bookings(self):
|
|
raise NotImplementedError
|
|
|
|
def fetch_booking(self, channel_reservation_id):
|
|
raise NotImplementedError
|
|
|
|
def mark_bookings(self, channel_reservation_ids):
|
|
raise NotImplementedError
|
|
|
|
def create_plan(self, name, daily=1):
|
|
raise NotImplementedError
|
|
|
|
def delete_plan(self, channel_plan_id):
|
|
raise NotImplementedError
|
|
|
|
def update_plan_name(self, channel_plan_id, new_name):
|
|
raise NotImplementedError
|
|
|
|
def update_plan_prices(self, channel_plan_id, date_from, prices):
|
|
raise NotImplementedError
|
|
|
|
def update_plan_periods(self, channel_plan_id, periods):
|
|
raise NotImplementedError
|
|
|
|
def get_pricing_plans(self):
|
|
raise NotImplementedError
|
|
|
|
def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms):
|
|
raise NotImplementedError
|
|
|
|
def rplan_rplans(self):
|
|
raise NotImplementedError
|
|
|
|
def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id):
|
|
raise NotImplementedError
|
|
|
|
def update_rplan_values(self, channel_restriction_plan_id, date_from, values):
|
|
raise NotImplementedError
|
|
|
|
def create_rplan(self, name, compact=False):
|
|
raise NotImplementedError
|
|
|
|
def rename_rplan(self, channel_restriction_plan_id, new_name):
|
|
raise NotImplementedError
|
|
|
|
def delete_rplan(self, channel_restriction_plan_id):
|
|
raise NotImplementedError
|
|
|
|
def get_channels_info(self):
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def _server(self):
|
|
try:
|
|
channel_server = getattr(self.work, 'hotel_channel_server')
|
|
except AttributeError:
|
|
raise AttributeError(
|
|
'You must provide a hotel_channel_server attribute with a '
|
|
'WuBookServer instance to be able to use the '
|
|
'Backend Adapter.'
|
|
)
|
|
return channel_server.server
|
|
|
|
@property
|
|
def _session_info(self):
|
|
try:
|
|
channel_server = getattr(self.work, 'hotel_channel_server')
|
|
except AttributeError:
|
|
raise AttributeError(
|
|
'You must provide a hotel_channel_server attribute with a '
|
|
'WuBookServer instance to be able to use the '
|
|
'Backend Adapter.'
|
|
)
|
|
return (channel_server.session_token, channel_server.lcode)
|
|
|
|
class WuBookAdapter(AbstractComponent):
|
|
_name = 'wubook.adapter'
|
|
_inherit = 'hotel.channel.adapter'
|
|
|
|
# === ROOMS
|
|
def create_room(self, shortcode, name, capacity, price, availability):
|
|
rcode, results = self._server.new_room(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
0,
|
|
name,
|
|
capacity,
|
|
price,
|
|
availability,
|
|
shortcode[:4],
|
|
'nb' # TODO: Complete this part
|
|
# rtype=('name' in vals and vals['name'] and 3) or 1
|
|
)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't create room in WuBook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def modify_room(self, channel_room_id, name, capacity, price, availability, scode):
|
|
rcode, results = self._server.mod_room(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_room_id,
|
|
name,
|
|
capacity,
|
|
price,
|
|
availability,
|
|
scode,
|
|
'nb'
|
|
# rtype=('name' in vals and vals['name'] and 3) or 1
|
|
)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't modify room in WuBook"), {
|
|
'message': results,
|
|
'channel_id': channel_room_id,
|
|
})
|
|
return results
|
|
|
|
def delete_room(self, channel_room_id):
|
|
rcode, results = self._server.del_room(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_room_id)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't delete room in WuBook"), {
|
|
'message': results,
|
|
'channel_id': channel_room_id,
|
|
})
|
|
return results
|
|
|
|
def fetch_rooms(self, channel_room_id=0):
|
|
rcode, results = self._server.fetch_rooms(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_room_id)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't fetch room values from WuBook"), {
|
|
'message': results,
|
|
'channel_id': channel_room_id,
|
|
})
|
|
return results
|
|
|
|
def fetch_rooms_values(self, date_from, date_to, rooms=False):
|
|
rcode, results = self._server.fetch_rooms_values(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
rooms)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't fetch rooms values from WuBook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def update_availability(self, rooms_avail):
|
|
rcode, results = self._server.update_sparse_avail(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
rooms_avail)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't update rooms availability in WuBook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def corporate_fetch(self):
|
|
rcode, results = self._server.corporate_fetchable_properties(self.TOKEN)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't call 'corporate_fetch' from WuBook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
# === RESERVATIONS
|
|
def create_reservation(self, channel_room_id, customer_name, email, city,
|
|
phone, address, country_code, checkin, checkout,
|
|
adults, children, notes=''):
|
|
customer_name = _partner_split_name(customer_name)
|
|
customer = {
|
|
'lname': customer_name[0],
|
|
'fname': customer_name[1],
|
|
'email': email,
|
|
'city': city,
|
|
'phone': phone,
|
|
'street': address,
|
|
'country': country_code,
|
|
'arrival_hour': date_utils.get_datetime(checkin).strftime("%H:%M"),
|
|
'notes': notes
|
|
}
|
|
rcode, results = self._server.new_reservation(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
date_utils.get_datetime(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
date_utils.get_datetime(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
{channel_room_id: [adults+children, 'nb']},
|
|
customer,
|
|
adults+children)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't create reservations in wubook"), {
|
|
'message': results,
|
|
'date_from': checkin,
|
|
'date_to': checkout,
|
|
})
|
|
return results
|
|
|
|
def cancel_reservation(self, channel_reservation_id, reason=""):
|
|
rcode, results = self._server.cancel_reservation(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_reservation_id,
|
|
reason)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't cancel reservation in WuBook"), {
|
|
'message': results,
|
|
'channel_reservation_id': channel_reservation_id,
|
|
})
|
|
return results
|
|
|
|
def fetch_new_bookings(self):
|
|
rcode, results = self._server.fetch_new_bookings(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
1,
|
|
0)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't process reservations from wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def fetch_booking(self, channel_reservation_id):
|
|
rcode, results = self.backend_adapter.fetch_booking(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_reservation_id)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't process reservation from wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def mark_bookings(self, channel_reservation_ids):
|
|
init_connection = self._context.get('init_connection', True)
|
|
if init_connection:
|
|
if not self.init_connection():
|
|
return False
|
|
rcode, results = self._server.mark_bookings(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_reservation_ids)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't mark as readed a reservation in wubook"), {
|
|
'message': results,
|
|
'channel_reservation_ids': str(channel_reservation_ids),
|
|
})
|
|
return results
|
|
|
|
# === PRICE PLANS
|
|
def create_plan(self, name, daily=1):
|
|
rcode, results = self._server.add_pricing_plan(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
name,
|
|
daily)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't add pricing plan to wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def delete_plan(self, channel_plan_id):
|
|
rcode, results = self._server.del_plan(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_plan_id)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't delete pricing plan from wubook"), {
|
|
'message': results,
|
|
'channel_plan_id': channel_plan_id,
|
|
})
|
|
return results
|
|
|
|
def update_plan_name(self, channel_plan_id, new_name):
|
|
rcode, results = self._server.update_plan_name(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_plan_id,
|
|
new_name)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't update pricing plan name in wubook"), {
|
|
'message': results,
|
|
'channel_plan_id': channel_plan_id,
|
|
})
|
|
return results
|
|
|
|
def update_plan_prices(self, channel_plan_id, date_from, prices):
|
|
rcode, results = self._server.update_plan_prices(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_plan_id,
|
|
date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
prices)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't update pricing plan in wubook"), {
|
|
'message': results,
|
|
'channel_plan_id': channel_plan_id,
|
|
'date_from': date_from,
|
|
})
|
|
return results
|
|
|
|
def update_plan_periods(self, channel_plan_id, periods):
|
|
rcode, results = self.SERVER.update_plan_periods(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_plan_id,
|
|
periods)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't update pricing plan period in wubook"), {
|
|
'message': results,
|
|
'channel_plan_id': channel_plan_id,
|
|
})
|
|
return results
|
|
|
|
def get_pricing_plans(self):
|
|
rcode, results = self.SERVER.get_pricing_plans(
|
|
self._session_info[0],
|
|
self._session_info[1])
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't get pricing plans from wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms):
|
|
rcode, results = self._server.fetch_plan_prices(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_plan_id,
|
|
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
rooms or [])
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't get pricing plans from wubook"), {
|
|
'message': results,
|
|
'channel_plan_id': channel_plan_id,
|
|
'date_from': date_from,
|
|
'date_to': date_to
|
|
})
|
|
return results
|
|
|
|
# === RESTRICTIONS
|
|
def rplan_rplans(self):
|
|
rcode, results = self._server.rplan_rplans(
|
|
self._session_info[0],
|
|
self._session_info[1])
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't fetch restriction plans from wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id):
|
|
rcode, results = self._server.wired_rplan_get_rplan_values(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
channel_restriction_plan_id)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't fetch restriction plans from wubook"), {
|
|
'message': results,
|
|
'channel_restriction_plan_id': channel_restriction_plan_id,
|
|
'date_from': date_from,
|
|
'date_to': date_to,
|
|
})
|
|
return results
|
|
|
|
def update_rplan_values(self, channel_restriction_plan_id, date_from, values):
|
|
rcode, results = self._server.rplan_update_rplan_values(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_restriction_plan_id,
|
|
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
|
values)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't update plan restrictions on wubook"), {
|
|
'message': results,
|
|
'channel_restriction_plan_id': channel_restriction_plan_id,
|
|
'date_from': date_from,
|
|
})
|
|
return results
|
|
|
|
def create_rplan(self, name, compact=False):
|
|
rcode, results = self._server.rplan_add_rplan(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
name,
|
|
compact and 1 or 0)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't create plan restriction in wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|
|
|
|
def rename_rplan(self, channel_restriction_plan_id, new_name):
|
|
rcode, results = self._server.rplan_rename_rplan(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_restriction_plan_id,
|
|
new_name)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't rename plan restriction in wubook"), {
|
|
'message': results,
|
|
'channel_restriction_plan_id': channel_restriction_plan_id,
|
|
})
|
|
return results
|
|
|
|
def delete_rplan(self, channel_restriction_plan_id):
|
|
rcode, results = self._server.rplan_del_rplan(
|
|
self._session_info[0],
|
|
self._session_info[1],
|
|
channel_restriction_plan_id)
|
|
if rcode != 0:
|
|
raise ValidationError(_("Can't delete plan restriction on wubook"), {
|
|
'message': results,
|
|
'channel_restriction_plan_id': channel_restriction_plan_id,
|
|
})
|
|
return results
|
|
|
|
def get_channels_info(self):
|
|
results = self._server.get_channels_info(self._session_info[0])
|
|
if not any(results):
|
|
raise ValidationError(_("Can't import channels info from wubook"), {
|
|
'message': results,
|
|
})
|
|
return results
|