Merge branch '11.0' into hotel_node_master

This commit is contained in:
Pablo
2018-10-13 19:14:02 +02:00
56 changed files with 1133 additions and 883 deletions

View File

@@ -47,12 +47,12 @@
'views/inherit_product_product.xml',
'views/hotel_room_amenities_type.xml',
'views/hotel_room_amenities.xml',
'views/reservation_restriction_views.xml',
'views/reservation_restriction_item_views.xml',
'views/hotel_room_type_restriction_views.xml',
'views/hotel_room_type_restriction_item_views.xml',
'views/hotel_reservation.xml',
# 'views/room_type_views.xml',
'views/cardex.xml',
'views/room_type_availability.xml',
'views/hotel_room_type_availability.xml',
# 'views/hotel_dashboard.xml',
'data/cron_jobs.xml',
'data/records.xml',

View File

@@ -81,7 +81,7 @@ class HotelRoomType(models.Model):
('id', '=', room_type_id)
])
# QUESTION What linked represent? Rooms in this type ?
rooms_linked = self.room_ids
rooms_linked = room_type_id.room_ids
free_rooms = free_rooms & rooms_linked
return free_rooms.sorted(key=lambda r: r.sequence)

View File

@@ -16,8 +16,7 @@ class HotelRoomTypeRestrictionItem(models.Model):
# required=True, ondelete='cascade')
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
required=True, ondelete='cascade')
date_start = fields.Date('From')
date_end = fields.Date("To")
date = fields.Date('Date')
applied_on = fields.Selection([
('1_global', 'Global'),
# ('0_room_type', 'Virtual Room')], string="Apply On", required=True,
@@ -35,7 +34,7 @@ class HotelRoomTypeRestrictionItem(models.Model):
closed_arrival = fields.Boolean('Closed Arrival')
_sql_constraints = [('room_type_registry_unique',
'unique(restriction_id, room_type_id, date_start, date_end)',
'unique(restriction_id, room_type_id, date)',
'Only can exists one restriction in the same day for the same room type!')]
@api.constrains('min_stay', 'min_stay_arrival', 'max_stay',
@@ -52,17 +51,6 @@ class HotelRoomTypeRestrictionItem(models.Model):
raise ValidationError(
("Max. Stay Arrival can't be less than zero"))
@api.constrains('date_start', 'date_end')
def _check_date_start_date_end(self):
if self.applied_on == '1_global':
self.date_start = False
self.date_end = False
elif self.date_start and self.date_end:
date_start_dt = fields.Date.from_string(self.date_start)
date_end_dt = fields.Date.from_string(self.date_end)
if date_end_dt < date_start_dt:
raise ValidationError(_("Invalid Dates"))
@api.constrains('applied_on')
def _check_applied_on(self):
count = self.search_count([('applied_on', '=', '1_global')])

View File

@@ -2,7 +2,7 @@
<odoo>
<!-- FORM restriction -->
<record id="reservation_restriction_item_view_form" model="ir.ui.view">
<record id="room_type_restriction_item_view_form" model="ir.ui.view">
<field name="name">hotel.room.type.restriction.item.form</field>
<field name="model">hotel.room.type.restriction.item</field>
<field name="arch" type="xml">
@@ -13,12 +13,7 @@
<field name="room_type_id" attrs="{'invisible':[['applied_on', '=', '1_global']]}" required="True"/>
</group>
<group>
<group>
<field name="date_start"/>
</group>
<group>
<field name="date_end"/>
</group>
<field name="date"/>
</group>
<group>
<group>
@@ -37,7 +32,7 @@
</record>
<!-- TREE restriction -->
<record id="reservation_restriction_item_view_tree" model="ir.ui.view">
<record id="room_type_restriction_item_view_tree" model="ir.ui.view">
<field name="name">hotel.room.type.restriction.item.tree</field>
<field name="model">hotel.room.type.restriction.item</field>
<field name="arch" type="xml">
@@ -45,8 +40,7 @@
<field name="applied_on"/>
<!-- <field name="room_type_id" attrs="{'invisible':[['applied_on', '=', '1_room_type']]}"/> -->
<field name="room_type_id" attrs="{'invisible':[['applied_on', '=', '0_room_type']]}"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="date"/>
<field name="min_stay"/>
<field name="closed"/>
</tree>

View File

@@ -2,7 +2,7 @@
<odoo>
<!-- FORM restriction -->
<record id="reservation_restriction_view_form" model="ir.ui.view">
<record id="room_type_restriction_view_form" model="ir.ui.view">
<field name="name">hotel.room.type.restriction.form</field>
<field name="model">hotel.room.type.restriction</field>
<field name="arch" type="xml">
@@ -21,8 +21,7 @@
<field name="applied_on"/>
<!-- <field name="room_type_id" attr="{'invisible':[['applied_on', '=', '1_room_type']]}"/> -->
<field name="room_type_id" attr="{'invisible':[['applied_on', '=', '0_room_type']]}"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="date"/>
<field name="min_stay"/>
<field name="closed"/>
</tree>
@@ -34,7 +33,7 @@
</record>
<!-- TREE restriction -->
<record id="reservation_restriction_view_tree" model="ir.ui.view">
<record id="room_type_restriction_view_tree" model="ir.ui.view">
<field name="name">hotel.room.type.restriction.tree</field>
<field name="model">hotel.room.type.restriction</field>
<field name="arch" type="xml">
@@ -46,7 +45,7 @@
</record>
<!-- Action of reservation restriction -->
<record model="ir.actions.act_window" id="reservation_restriction_action">
<record model="ir.actions.act_window" id="room_type_restriction_action">
<field name="name">Reservation restrictions</field>
<field name="res_model">hotel.room.type.restriction</field>
<field name="view_type">form</field>
@@ -55,7 +54,7 @@
<!-- MENUS -->
<menuitem name="Restrictions" id="reservation_restriction_menu"
action="reservation_restriction_action" sequence="22"
action="room_type_restriction_action" sequence="22"
parent="hotel.configuration_others"/>
</odoo>

View File

@@ -84,8 +84,7 @@ class HotelCalendarManagement(models.TransientModel):
for k_res in restrictions.keys():
for restriction in restrictions[k_res]:
res_id = room_type_rest_item_obj.search([
('date_start', '>=', restriction['date']),
('date_end', '<=', restriction['date']),
('date', '=', restriction['date']),
('restriction_id', '=', int(restriction_id)),
('applied_on', '=', '0_room_type'),
('room_type_id', '=', int(k_res)),
@@ -93,8 +92,7 @@ class HotelCalendarManagement(models.TransientModel):
vals = self._get_restrictions_values(restriction)
if not res_id:
vals.update({
'date_start': restriction['date'],
'date_end': restriction['date'],
'date': restriction['date'],
'restriction_id': int(restriction_id),
'applied_on': '0_room_type',
'room_type_id': int(k_res),
@@ -159,10 +157,9 @@ class HotelCalendarManagement(models.TransientModel):
def _hcalendar_restriction_json_data(self, restrictions):
json_data = {}
for rec in restrictions:
# TODO: date_end - date_start loop
json_data.setdefault(rec.room_type_id.id, []).append({
'id': rec.id,
'date': rec.date_start,
'date': rec.date,
'min_stay': rec.min_stay,
'min_stay_arrival': rec.min_stay_arrival,
'max_stay': rec.max_stay,
@@ -282,7 +279,7 @@ class HotelCalendarManagement(models.TransientModel):
room_type_rest_it_obj = self.env['hotel.room.type.restriction.item']
restriction_item_ids = room_type_rest_it_obj.search([
('date_start', '>=', dfrom), ('date_end', '<=', dto),
('date', '>=', dfrom), ('date', '<=', dto),
('restriction_id', '=', restriction_id),
('applied_on', '=', '0_room_type'),
])

View File

@@ -162,7 +162,7 @@ class HotelReservation(models.Model):
restriction_id = int(restriction_id)
date_start = fields.Date.from_string(dfrom) - timedelta(days=1)
date_end = fields.Date.from_string(dto)
date_diff = abs((date_end - date_sart).days) + 1
date_diff = abs((date_end - date_start).days) + 1
# Get Prices
json_rooms_rests = {}
room_types = self.env['hotel.room.type'].search(
@@ -176,8 +176,7 @@ class HotelReservation(models.Model):
ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
rest_id = room_type_rest_obj.search([
('room_type_id', '=', room_type.id),
('date_start', '>=', ndate_str),
('date_end', '<=', ndate_str),
('date', '>=', ndate_str),
('applied_on', '=', '0_room_type'),
('restriction_id', '=', restriction_id)
], limit=1)

View File

@@ -10,7 +10,7 @@ class HotelRoomType(models.Model):
@api.multi
def unlink(self):
room_type_pr_cached_obj = self.env['hotel.room.pricelist.cached']
room_type_pr_cached_obj = self.env['room.pricelist.cached']
for record in self:
pr_chached = room_type_pr_cached_obj.search([
('room_id', '=', record.id)

View File

@@ -10,7 +10,7 @@ class HotelRoomTypeResrtrictionItem(models.Model):
@api.model
def create(self, vals):
res = super(HotelVirtualRoomResrtrictionItem, self).create(vals)
res = super(HotelRoomTypeResrtrictionItem, self).create(vals)
restrictions_parity_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id')
if restrictions_parity_id:
@@ -20,7 +20,7 @@ class HotelRoomTypeResrtrictionItem(models.Model):
self.applied_on == '0_room_type':
self.env['bus.hotel.calendar'].send_restriction_notification({
'restriction_id': self.restriction_id.id,
'date': self.date_start,
'date': self.date,
'min_stay': self.min_stay,
'min_stay_arrival': self.min_stay_arrival,
'max_stay': self.max_stay,
@@ -39,7 +39,7 @@ class HotelRoomTypeResrtrictionItem(models.Model):
'res.config.settings', 'parity_restrictions_id')
if restrictions_parity_id:
restrictions_parity_id = int(restrictions_parity_id)
ret_vals = super(HotelVirtualRoomResrtrictionItem, self).write(vals)
ret_vals = super(HotelRoomTypeResrtrictionItem, self).write(vals)
bus_hotel_calendar_obj = self.env['bus.hotel.calendar']
for record in self:
@@ -48,7 +48,7 @@ class HotelRoomTypeResrtrictionItem(models.Model):
continue
bus_hotel_calendar_obj.send_restriction_notification({
'restriction_id': record.restriction_id.id,
'date': record.date_start,
'date': record.date,
'min_stay': record.min_stay,
'min_stay_arrival': record.min_stay_arrival,
'max_stay': record.max_stay,
@@ -86,7 +86,7 @@ class HotelRoomTypeResrtrictionItem(models.Model):
'room_type_id': record.room_type_id.id,
'id': record.id,
})
res = super(HotelVirtualRoomResrtrictionItem, self).unlink()
res = super(HotelRoomTypeResrtrictionItem, self).unlink()
bus_hotel_calendar_obj = self.env['bus.hotel.calendar']
for uval in unlink_vals:
bus_hotel_calendar_obj.send_restriction_notification(uval)

View File

@@ -18,7 +18,6 @@
},
'data': [
'data/cron_jobs.xml',
'wizard/wubook_installer.xml',
'wizard/wubook_import_plan_prices.xml',
'wizard/wubook_import_plan_restrictions.xml',
'wizard/wubook_import_availability.xml',
@@ -29,10 +28,9 @@
'views/inherited_hotel_folio_views.xml',
'views/inherited_product_pricelist_views.xml',
'views/inherited_product_pricelist_item_views.xml',
'views/inherited_reservation_restriction_views.xml',
'views/inherited_reservation_restriction_item_views.xml',
'views/inherited_hotel_room_type_restriction_views.xml',
'views/inherited_res_partner_views.xml',
'views/hotel_channel_connector_ota_info_views.xml',
'views/channel_ota_info_views.xml',
'views/hotel_channel_connector_issue_views.xml',
'views/channel_hotel_reservation_views.xml',
'views/channel_hotel_room_type_views.xml',
@@ -40,7 +38,6 @@
'views/channel_hotel_room_type_restriction_views.xml',
'views/channel_product_pricelist_views.xml',
'views/channel_connector_backend_views.xml',
'views/channel_connector_menu.xml',
'data/menus.xml',
'data/sequences.xml',
#'security/ir.model.access.csv',

View File

@@ -4,5 +4,6 @@
from . import core
from . import backend_adapter
from . import binder
from . import exporter
from . import importer
from . import exporter
from . import mapper

View File

@@ -1,8 +1,8 @@
# 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
import xmlrpc.client
import logging
from odoo.addons.component.core import AbstractComponent
from odoo.addons.queue_job.exception import RetryableJobError
from odoo.tools import (
@@ -10,7 +10,9 @@ from odoo.tools import (
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
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, _
_logger = logging.getLogger(__name__)
# GLOBAL VARS
DEFAULT_WUBOOK_DATE_FORMAT = "%d/%m/%Y"
@@ -62,9 +64,11 @@ class WuBookServer(object):
@property
def server(self):
if self._server is None and self._login_data.is_valid():
if not self._login_data.is_valid():
raise ChannelConnectorError("Invalid Channel Parameters!")
if self._server is None:
try:
self._server = xmlrpclib.Server(self._login_data.address)
self._server = xmlrpc.client.ServerProxy(self._login_data.address)
res, tok = self._server.acquire_token(
self._login_data.user,
self._login_data.passwd,
@@ -179,10 +183,10 @@ class HotelChannelInterfaceAdapter(AbstractComponent):
@property
def _server(self):
try:
channel_server = getattr(self.work, 'hotel_channel_server')
channel_server = getattr(self.work, 'channel_api')
except AttributeError:
raise AttributeError(
'You must provide a hotel_channel_server attribute with a '
'You must provide a channel_api attribute with a '
'WuBookServer instance to be able to use the '
'Backend Adapter.'
)
@@ -191,14 +195,14 @@ class HotelChannelInterfaceAdapter(AbstractComponent):
@property
def _session_info(self):
try:
channel_server = getattr(self.work, 'hotel_channel_server')
channel_server = getattr(self.work, 'channel_api')
except AttributeError:
raise AttributeError(
'You must provide a hotel_channel_server attribute with a '
'You must provide a channel_api attribute with a '
'WuBookServer instance to be able to use the '
'Backend Adapter.'
)
return (channel_server.session_token, channel_server.lcode)
return (channel_server.session_token, channel_server._login_data.lcode)
class WuBookAdapter(AbstractComponent):
_name = 'wubook.adapter'
@@ -219,7 +223,7 @@ class WuBookAdapter(AbstractComponent):
# rtype=('name' in vals and vals['name'] and 3) or 1
)
if rcode != 0:
raise ValidationError(_("Can't create room in WuBook"), {
raise ChannelConnectorError("Can't create room in WuBook", {
'message': results,
})
return results
@@ -238,7 +242,7 @@ class WuBookAdapter(AbstractComponent):
# rtype=('name' in vals and vals['name'] and 3) or 1
)
if rcode != 0:
raise ValidationError(_("Can't modify room in WuBook"), {
raise ChannelConnectorError("Can't modify room in WuBook", {
'message': results,
'channel_id': channel_room_id,
})
@@ -250,7 +254,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
channel_room_id)
if rcode != 0:
raise ValidationError(_("Can't delete room in WuBook"), {
raise ChannelConnectorError("Can't delete room in WuBook", {
'message': results,
'channel_id': channel_room_id,
})
@@ -262,7 +266,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
channel_room_id)
if rcode != 0:
raise ValidationError(_("Can't fetch room values from WuBook"), {
raise ChannelConnectorError("Can't fetch room values from WuBook", {
'message': results,
'channel_id': channel_room_id,
})
@@ -276,7 +280,7 @@ class WuBookAdapter(AbstractComponent):
date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rooms)
if rcode != 0:
raise ValidationError(_("Can't fetch rooms values from WuBook"), {
raise ChannelConnectorError("Can't fetch rooms values from WuBook", {
'message': results,
})
return results
@@ -287,7 +291,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
rooms_avail)
if rcode != 0:
raise ValidationError(_("Can't update rooms availability in WuBook"), {
raise ChannelConnectorError("Can't update rooms availability in WuBook", {
'message': results,
})
return results
@@ -295,7 +299,7 @@ class WuBookAdapter(AbstractComponent):
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"), {
raise ChannelConnectorError("Can't call 'corporate_fetch' from WuBook", {
'message': results,
})
return results
@@ -325,7 +329,7 @@ class WuBookAdapter(AbstractComponent):
customer,
adults+children)
if rcode != 0:
raise ValidationError(_("Can't create reservations in wubook"), {
raise ChannelConnectorError("Can't create reservations in wubook", {
'message': results,
'date_from': checkin,
'date_to': checkout,
@@ -339,7 +343,7 @@ class WuBookAdapter(AbstractComponent):
channel_reservation_id,
reason)
if rcode != 0:
raise ValidationError(_("Can't cancel reservation in WuBook"), {
raise ChannelConnectorError("Can't cancel reservation in WuBook", {
'message': results,
'channel_reservation_id': channel_reservation_id,
})
@@ -352,7 +356,7 @@ class WuBookAdapter(AbstractComponent):
1,
0)
if rcode != 0:
raise ValidationError(_("Can't process reservations from wubook"), {
raise ChannelConnectorError("Can't process reservations from wubook", {
'message': results,
})
return results
@@ -363,7 +367,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
channel_reservation_id)
if rcode != 0:
raise ValidationError(_("Can't process reservation from wubook"), {
raise ChannelConnectorError("Can't process reservation from wubook", {
'message': results,
})
return results
@@ -378,7 +382,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
channel_reservation_ids)
if rcode != 0:
raise ValidationError(_("Can't mark as readed a reservation in wubook"), {
raise ChannelConnectorError("Can't mark as readed a reservation in wubook", {
'message': results,
'channel_reservation_ids': str(channel_reservation_ids),
})
@@ -392,7 +396,7 @@ class WuBookAdapter(AbstractComponent):
name,
daily)
if rcode != 0:
raise ValidationError(_("Can't add pricing plan to wubook"), {
raise ChannelConnectorError("Can't add pricing plan to wubook", {
'message': results,
})
return results
@@ -403,7 +407,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
channel_plan_id)
if rcode != 0:
raise ValidationError(_("Can't delete pricing plan from wubook"), {
raise ChannelConnectorError("Can't delete pricing plan from wubook", {
'message': results,
'channel_plan_id': channel_plan_id,
})
@@ -416,7 +420,7 @@ class WuBookAdapter(AbstractComponent):
channel_plan_id,
new_name)
if rcode != 0:
raise ValidationError(_("Can't update pricing plan name in wubook"), {
raise ChannelConnectorError("Can't update pricing plan name in wubook", {
'message': results,
'channel_plan_id': channel_plan_id,
})
@@ -430,7 +434,7 @@ class WuBookAdapter(AbstractComponent):
date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
prices)
if rcode != 0:
raise ValidationError(_("Can't update pricing plan in wubook"), {
raise ChannelConnectorError("Can't update pricing plan in wubook", {
'message': results,
'channel_plan_id': channel_plan_id,
'date_from': date_from,
@@ -444,7 +448,7 @@ class WuBookAdapter(AbstractComponent):
channel_plan_id,
periods)
if rcode != 0:
raise ValidationError(_("Can't update pricing plan period in wubook"), {
raise ChannelConnectorError("Can't update pricing plan period in wubook", {
'message': results,
'channel_plan_id': channel_plan_id,
})
@@ -455,7 +459,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[0],
self._session_info[1])
if rcode != 0:
raise ValidationError(_("Can't get pricing plans from wubook"), {
raise ChannelConnectorError("Can't get pricing plans from wubook", {
'message': results,
})
return results
@@ -469,7 +473,7 @@ class WuBookAdapter(AbstractComponent):
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rooms or [])
if rcode != 0:
raise ValidationError(_("Can't get pricing plans from wubook"), {
raise ChannelConnectorError("Can't get pricing plans from wubook", {
'message': results,
'channel_plan_id': channel_plan_id,
'date_from': date_from,
@@ -483,7 +487,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[0],
self._session_info[1])
if rcode != 0:
raise ValidationError(_("Can't fetch restriction plans from wubook"), {
raise ChannelConnectorError("Can't fetch restriction plans from wubook", {
'message': results,
})
return results
@@ -496,7 +500,7 @@ class WuBookAdapter(AbstractComponent):
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"), {
raise ChannelConnectorError("Can't fetch restriction plans from wubook", {
'message': results,
'channel_restriction_plan_id': channel_restriction_plan_id,
'date_from': date_from,
@@ -512,7 +516,7 @@ class WuBookAdapter(AbstractComponent):
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
values)
if rcode != 0:
raise ValidationError(_("Can't update plan restrictions on wubook"), {
raise ChannelConnectorError("Can't update plan restrictions on wubook", {
'message': results,
'channel_restriction_plan_id': channel_restriction_plan_id,
'date_from': date_from,
@@ -526,7 +530,7 @@ class WuBookAdapter(AbstractComponent):
name,
compact and 1 or 0)
if rcode != 0:
raise ValidationError(_("Can't create plan restriction in wubook"), {
raise ChannelConnectorError("Can't create plan restriction in wubook", {
'message': results,
})
return results
@@ -538,7 +542,7 @@ class WuBookAdapter(AbstractComponent):
channel_restriction_plan_id,
new_name)
if rcode != 0:
raise ValidationError(_("Can't rename plan restriction in wubook"), {
raise ChannelConnectorError("Can't rename plan restriction in wubook", {
'message': results,
'channel_restriction_plan_id': channel_restriction_plan_id,
})
@@ -550,7 +554,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[1],
channel_restriction_plan_id)
if rcode != 0:
raise ValidationError(_("Can't delete plan restriction on wubook"), {
raise ChannelConnectorError("Can't delete plan restriction on wubook", {
'message': results,
'channel_restriction_plan_id': channel_restriction_plan_id,
})
@@ -559,7 +563,7 @@ class WuBookAdapter(AbstractComponent):
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"), {
raise ChannelConnectorError("Can't import channels info from wubook", {
'message': results,
})
return results

View File

@@ -12,4 +12,5 @@ class HotelConnectorModelBinder(Component):
'channel.hotel.room.type.availability',
'channel.hotel.room.type.restriction',
'channel.product.pricelist',
'channel.ota.info',
]

View File

@@ -7,16 +7,21 @@ from odoo import api
class BaseHotelChannelConnectorComponent(AbstractComponent):
_name = 'base.hotel.channel.connector'
_inherit = 'base.connector'
_collection = 'hotel.channel.backend'
_collection = 'channel.backend'
@api.model
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,
'internal_message': message,
'channel_object_id': channel_object_id,
'channel_message': channel_message,
'date_start': dfrom,
'date_end': dto,
})
class ChannelConnectorError(Exception):
def __init__(self, message, data):
super().__init__(message)
self.data = data

View File

@@ -2,6 +2,10 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import psycopg2
from contextlib import contextmanager
from odoo.addons.connector.exception import (IDMissingInBackend,
RetryableJobError)
from odoo.addons.component.core import AbstractComponent
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,

View File

@@ -7,6 +7,7 @@ import json
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import AbstractComponent, Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.hotel import date_utils
from odoo import _
from odoo.tools import (
@@ -130,7 +131,7 @@ class HotelChannelConnectorImporter(AbstractComponent):
@api.model
def _generate_booking_vals(self, broom, checkin_str, checkout_str,
is_cancellation, wchannel_info, wstatus, crcode,
is_cancellation, channel_info, wstatus, crcode,
rcode, room_type, split_booking, dates_checkin,
dates_checkout, book):
# Generate Reservation Day Lines
@@ -154,24 +155,27 @@ class HotelChannelConnectorImporter(AbstractComponent):
persons = room_type.wcapacity
if 'ancillary' in broom and 'guests' in broom['ancillary']:
persons = broom['ancillary']['guests']
vals = {
'checkin': checkin_str,
'checkout': checkout_str,
'adults': persons,
'children': book['children'],
'reservation_line_ids': reservation_line_ids,
'price_unit': tprice,
'to_assign': True,
'wrid': rcode,
'wchannel_id': wchannel_info and wchannel_info.id,
'wchannel_reservation_code': crcode,
'channel_reservation_id': rcode,
'ota_id': channel_info and channel_info.id,
'ota_reservation_id': crcode,
'channel_raw_data': json.dumps(book),
'wstatus': wstatus,
'to_read': True,
'state': is_cancellation and 'cancelled' or 'draft',
'room_type_id': room_type.id,
'splitted': split_booking,
'wbook_json': json.dumps(book),
'wmodified': book['was_modified']
'wmodified': book['was_modified'],
'odoo_id': [0, False, {
'checkin': checkin_str,
'checkout': checkout_str,
'adults': persons,
'children': book['children'],
'reservation_line_ids': reservation_line_ids,
'price_unit': tprice,
'to_assign': True,
'to_read': True,
'state': is_cancellation and 'cancelled' or 'draft',
'room_type_id': room_type.id,
'splitted': split_booking,
}],
}
_logger.info("===== CONTRUCT RESERV")
_logger.info(vals)
@@ -210,8 +214,10 @@ class HotelChannelConnectorImporter(AbstractComponent):
tz_hotel = self.env['ir.default'].sudo().get(
'res.config.settings', 'tz_hotel')
res_partner_obj = self.env['res.partner']
channel_reserv_obj = self.env['channel.hotel.reservation']
hotel_reserv_obj = self.env['hotel.reservation']
hotel_folio_obj = self.env['hotel.folio']
channel_room_type_obj = self.env['channel.hotel.room.type']
hotel_room_type_obj = self.env['hotel.room.type']
# Space for store some data for construct folios
processed_rids = []
@@ -231,10 +237,10 @@ class HotelChannelConnectorImporter(AbstractComponent):
# (for example set a invalid new reservation and receive in
# the same transaction an cancellation)
if crcode in failed_reservations:
self.create_channel_connector_issue(
self.create_issue(
'reservation',
"Can't process a reservation that previusly failed!",
'', wid=book['reservation_code'])
'', channel_object_id=book['reservation_code'])
continue
# Get dates for the reservation (GMT->UTC)
@@ -260,34 +266,37 @@ class HotelChannelConnectorImporter(AbstractComponent):
# Search Folio. If exists.
folio_id = False
if crcode != 'undefined':
reserv_folio = hotel_reserv_obj.search([
('wchannel_reservation_code', '=', crcode)
reserv_folio = channel_reserv_obj.search([
('ota_reservation_id', '=', crcode)
], limit=1)
if reserv_folio:
folio_id = reserv_folio.folio_id
folio_id = reserv_folio.odoo_id.folio_id
else:
reserv_folio = hotel_reserv_obj.search([
('wrid', '=', rcode)
reserv_folio = channel_reserv_obj.search([
('channel_reservation_id', '=', rcode)
], limit=1)
if reserv_folio:
folio_id = reserv_folio.folio_id
folio_id = reserv_folio.odoo_id.folio_id
# Need update reservations?
sreservs = hotel_reserv_obj.search([('wrid', '=', rcode)])
reservs = folio_id.room_lines if folio_id else sreservs
sreservs = channel_reserv_obj.search([('channel_reservation_id', '=', rcode)])
reservs = folio_id.room_lines if folio_id else sreservs.mapped(lambda x: x.odoo_id)
reservs_processed = False
if any(reservs):
folio_id = reservs[0].folio_id
for reserv in reservs:
if reserv.wrid == rcode:
reserv.with_context({'wubook_action': False}).write({
if reserv.channel_reservation_id == rcode:
binding_id = reserv.channel_bind_ids[0]
binding_id.write({
'channel_raw_data': json.dumps(book),
'wstatus': str(book['status']),
'wstatus_reason': book.get('status_reason', ''),
})
reserv.with_context({'wubook_action': False}).write({
'to_read': True,
'to_assign': True,
'price_unit': book['amount'],
'wcustomer_notes': book['customer_notes'],
'wbook_json': json.dumps(book),
'customer_notes': book['customer_notes'],
})
if reserv.partner_id.unconfirmed:
reserv.partner_id.write(
@@ -321,22 +330,22 @@ class HotelChannelConnectorImporter(AbstractComponent):
partner_id = res_partner_obj.create(self._generate_partner_vals(book))
# Search Wubook Channel Info
wchannel_info = self.env['wubook.channel.info'].search(
[('wid', '=', str(book['id_channel']))], limit=1)
channel_info = self.env['hotel.channel.connector.ota.info'].search(
[('ota_id', '=', str(book['id_channel']))], limit=1)
reservations = []
used_rooms = []
# Iterate booked rooms
for broom in book['booked_rooms']:
room_type = hotel_room_type_obj.search([
('wrid', '=', broom['room_id'])
room_type = channel_room_type_obj.search([
('channel_room_id', '=', broom['room_id'])
], limit=1)
if not room_type:
self.create_channel_connector_issue(
self.create_issue(
'reservation',
"Can't found any room type associated to '%s' \
in this hotel" % book['rooms'],
'', wid=book['reservation_code'])
'', channel_object_id=book['reservation_code'])
failed_reservations.append(crcode)
continue
@@ -355,26 +364,26 @@ class HotelChannelConnectorImporter(AbstractComponent):
checkin_str,
checkout_str,
is_cancellation,
wchannel_info,
channel_info,
bstatus,
crcode,
rcode,
room_type,
room_type.odoo_id,
split_booking,
dates_checkin,
dates_checkout,
book,
)
if vals['price_unit'] != book['amount']:
self.create_channel_connector_issue(
self.create_issue(
'reservation',
"Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']),
'', wid=book['reservation_code'])
'', channel_object_id=book['reservation_code'])
free_rooms = hotel_room_type_obj.check_availability_room(
free_rooms = room_type.odoo_id.check_availability_room(
checkin_str,
checkout_str,
room_type_id=room_type.id,
room_type_id=room_type.odoo_id.id,
notthis=used_rooms)
if any(free_rooms):
vals.update({
@@ -412,11 +421,11 @@ class HotelChannelConnectorImporter(AbstractComponent):
checkin_utc_dt,
checkout_utc_dt,
is_cancellation,
wchannel_info,
channel_info,
bstatus,
crcode,
rcode,
room_type,
room_type.odoo_id,
False,
(checkin_utc_dt, False),
(checkout_utc_dt, False),
@@ -424,15 +433,15 @@ class HotelChannelConnectorImporter(AbstractComponent):
)
vals.update({
'product_id':
room_type.room_ids[0].product_id.id,
'name': room_type.name,
room_type.odoo_id.room_ids[0].product_id.id,
'name': room_type.odoo_id.name,
'overbooking': True,
})
reservations.append((0, False, vals))
self.create_channel_connector_issue(
self.create_issue(
'reservation',
"Reservation imported with overbooking state",
'', wid=rcode)
'', channel_object_id=rcode)
dates_checkin = [False, False]
dates_checkout = [False, False]
split_booking = False
@@ -448,10 +457,10 @@ class HotelChannelConnectorImporter(AbstractComponent):
]
if split_booking:
self.create_channel_connector_issue(
self.create_issue(
'reservation',
"Reservation Splitted",
'', wid=rcode)
'', channel_object_id=rcode)
# Create Folio
if not any(failed_reservations) and any(reservations):
@@ -487,11 +496,11 @@ class HotelChannelConnectorImporter(AbstractComponent):
creserv.parent_reservation = preserv.id
processed_rids.append(rcode)
except Exception as e_msg:
self.create_channel_connector_issue(
except ChannelConnectorError as err:
self.create_issue(
'reservation',
e_msg[0],
'', wid=rcode)
err.data['message'],
'', channel_object_id=rcode)
failed_reservations.append(crcode)
return (processed_rids, any(failed_reservations),
checkin_utc_dt, checkout_utc_dt)
@@ -654,84 +663,6 @@ class HotelChannelConnectorImporter(AbstractComponent):
return True
@api.model
def _generate_wubook_channel_info(self, channels):
channel_info_obj = self.env['wubook.channel.info']
count = 0
for k_cid, v_cid in channels.iteritems():
vals = {
'name': v_cid['name'],
'ical': v_cid['ical'] == 1,
}
channel_info = channel_info_obj.search([
('wid', '=', k_cid)
], limit=1)
if channel_info:
channel_info.write(vals)
else:
vals.update({
'wid': k_cid
})
channel_info_obj.create(vals)
count = count + 1
return count
@api.model
def get_rooms(self):
count = 0
try:
results = self.backend_adapter.fetch_rooms()
room_type_obj = self.env['hotel.room.type']
count = len(results)
for room in results:
vals = {
'name': room['name'],
'wrid': room['id'],
'wscode': room['shortname'],
'list_price': room['price'],
'wcapacity': room['occupancy'],
# 'max_real_rooms': room['availability'],
}
room_type = room_type_obj.search([('wrid', '=', room['id'])], limit=1)
if room_type:
room_type.with_context({'wubook_action': False}).write(vals)
else:
room_type_obj.with_context({'wubook_action': False}).create(vals)
except ValidationError:
self.create_issue('room', _("Can't import rooms from WuBook"), results)
return count
@api.model
def fetch_rooms_values(self, dfrom, dto, rooms=False,
set_max_avail=False):
# Sanitize Dates
now_dt = date_utils.now()
dfrom_dt = date_utils.get_datetime(dfrom)
dto_dt = date_utils.get_datetime(dto)
if dto_dt < now_dt:
return True
if dfrom_dt < now_dt:
dfrom_dt = now_dt
if dfrom_dt > dto_dt:
dtemp_dt = dfrom_dt
dfrom_dt = dto_dt
dto_dt = dtemp_dt
try:
results = self.backend_adapter.fetch_rooms_values(
dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rooms)
self._generate_room_values(dfrom, dto, results,
set_max_avail=set_max_avail)
except ValidationError:
self.create_issue('room', _("Can't fetch rooms values from WuBook"),
results, dfrom=dfrom, dto=dto)
return False
return True
@api.model
def fetch_booking(self, channel_reservation_id):
try:
@@ -746,11 +677,11 @@ class HotelChannelConnectorImporter(AbstractComponent):
self.backend_adapter.fetch_rooms_values(
checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
except ValidationError:
self.create_channel_connector_issue(
except ChannelConnectorError as err:
self.create_issue(
'reservation',
_("Can't process reservations from wubook"),
results, channel_object_id=channel_reservation_id)
err.data['message'], channel_object_id=channel_reservation_id)
return False
return True
@@ -759,11 +690,11 @@ class HotelChannelConnectorImporter(AbstractComponent):
try:
results = self.backend_adapter.get_pricing_plans()
count = self._generate_pricelists(results)
except ValidationError:
except ChannelConnectorError as err:
self.create_issue(
'plan',
_("Can't get pricing plans from wubook"),
results)
err.data['message'])
return 0
return count
@@ -776,11 +707,11 @@ class HotelChannelConnectorImporter(AbstractComponent):
date_to,
rooms)
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
except ValidationError:
except ChannelConnectorError as err:
self.create_issue(
'plan',
_("Can't fetch plan prices from wubook"),
results)
err.data['message'])
return False
return True
@@ -799,11 +730,12 @@ class HotelChannelConnectorImporter(AbstractComponent):
date_to,
rooms)
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
except ValidationError:
except ChannelConnectorError as err:
self.create_issue(
'plan',
"Can't fetch all plan prices from wubook!",
results, wid=channel_plan_id, dfrom=date_from, dto=date_to)
err.data['message'],
channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to)
return False
return no_errors
@@ -812,11 +744,11 @@ class HotelChannelConnectorImporter(AbstractComponent):
try:
results = self.backend_adapter.rplan_rplans()
count = self._generate_restrictions(results)
except ValidationError:
except ChannelConnectorError as err:
self.create_issue(
'rplan',
_("Can't fetch restriction plans from wubook"),
results)
err.data['message'])
return 0
return count
@@ -829,25 +761,39 @@ class HotelChannelConnectorImporter(AbstractComponent):
int(channel_restriction_plan_id))
if any(results):
self._generate_restriction_items(results)
except ValidationError:
except ChannelConnectorError as err:
self.create_issue(
'rplan',
_("Can't fetch plan restrictions from wubook"),
results,
wid=channel_restriction_plan_id,
err.data['message'],
channel_object_id=channel_restriction_plan_id,
dfrom=date_from, dto=date_to)
return False
return True
@api.model
def import_channels_info(self):
try:
results = self.backend_adapter.get_channels_info()
count = self._generate_wubook_channel_info(results)
except ValidationError:
self.create_issue(
'channel',
_("Can't import channels info from wubook"),
results)
return 0
return count
class BatchImporter(AbstractComponent):
""" The role of a BatchImporter is to search for a list of
items to import, then it can either import them directly or delay
the import of each item separately.
"""
_name = 'channel.batch.importer'
_inherit = ['base.importer', 'base.hotel.channel.connector']
_usage = 'batch.importer'
def _import_record(self, external_id):
""" Import a record directly or delay the import of the record.
Method to implement in sub-classes.
"""
raise NotImplementedError
class DirectBatchImporter(AbstractComponent):
""" Import the records directly, without delaying the jobs. """
_name = 'channel.direct.batch.importer'
_inherit = 'channel.batch.importer'
def _import_record(self, external_id):
""" Import the record directly """
self.model.import_record(self.backend_record, external_id)

View File

@@ -0,0 +1,16 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.component.core import AbstractComponent
class ChannelImportMapper(AbstractComponent):
_name = 'channel.import.mapper'
_inherit = ['base.hotel.channel.connector', 'base.import.mapper']
_usage = 'import.mapper'
class ChannelExportMapper(AbstractComponent):
_name = 'channel.export.mapper'
_inherit = ['base.hotel.channel.connector', 'base.export.mapper']
_usage = 'export.mapper'

View File

@@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.actions.act_window" id="open_hotel_channel_connector_ota_info_tree_all">
<field name="name">Hotel Channel Connector OTA's Info</field>
<field name="res_model">hotel.channel.connector.ota.info</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_channel_connector_root"
parent="connector.menu_connector_root"
name="Channel"
sequence="10"
groups="connector.group_connector_manager"/>
<record model="ir.actions.act_window" id="open_hotel_channel_connector_issue_tree_all">
<field name="name">Hotel Channel Connector Issues</field>
<field name="res_model">hotel.channel.connector.issue</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_hotel_channel_connector_issue_form_search"/>
<field name="context">{"search_default_to_read":True}</field>
</record>
<menuitem id="menu_cannel_backend"
name="Backends"
sequence="1"
parent="menu_channel_connector_root"
action="action_channel_backend"/>
<menuitem id="hotel_channel_connector_menu" name="Hotel Channel Connector"
sequence="25" parent="base.menu_administration" />
<menuitem id="channel_ota_info_menu"
name="OTA's"
sequence="2"
action="open_channel_ota_info_tree_all"
parent="menu_channel_connector_root"/>
<menuitem id="hotel_channel_connector_ota_info_menu" name="Channels Info"
sequence="1" action="open_hotel_channel_connector_ota_info_tree_all" parent="hotel_channel_connector_menu"/>
<menuitem id="hotel_channel_connector_issue_menu" name="Issues"
sequence="2" action="open_hotel_channel_connector_issue_tree_all" parent="hotel_channel_connector_menu"/>
<menuitem id="hotel_channel_connector_issue_menu"
name="Issues"
sequence="3"
action="open_hotel_channel_connector_issue_tree_all"
parent="menu_channel_connector_root"/>
</odoo>

View File

@@ -1,17 +1,17 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import channel_backend
from . import channel_binding
from . import res_config
from . import channel_backend
from . import hotel_room_type
from . import product_pricelist
from . import inherited_product_pricelist_item
from . import hotel_room_type_restriction
from . import inherited_reservation_restriction_item
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_res_partner
from . import hotel_channel_connector_ota_info
from . import channel_ota_info
from . import hotel_channel_connector_issue
from . import res_config

View File

@@ -1,6 +1,8 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import os
import binascii
from contextlib import contextmanager
from odoo import models, api, fields
from ...components.backend_adapter import WuBookLogin, WuBookServer
@@ -27,12 +29,32 @@ class ChannelBackend(models.Model):
server = fields.Char('Channel Service Server',
default='https://wired.wubook.net/xrws/')
pkey = fields.Char('Channel Service PKey')
security_token = fields.Char('Channel Service Security Token')
@api.multi
def generate_key(self):
for record in self:
record.security_token = binascii.hexlify(os.urandom(32)).decode()
@api.multi
def import_reservations(self):
channel_hotel_reservation = self.env['channel.hotel.reservation']
channel_hotel_reservation_obj = self.env['channel.hotel.reservation']
for backend in self:
channel_hotel_reservation.import_reservations(backend)
channel_hotel_reservation_obj.import_reservations(backend)
return True
@api.multi
def import_rooms(self):
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
for backend in self:
channel_hotel_room_type_obj.import_rooms(backend)
return True
@api.multi
def import_otas_info(self):
channel_ota_info_obj = self.env['channel.ota.info']
for backend in self:
channel_ota_info_obj.import_otas_info(backend)
return True
@contextmanager
@@ -47,6 +69,165 @@ class ChannelBackend(models.Model):
self.pkey)
with WuBookServer(wubook_login) as channel_api:
_super = super(ChannelBackend, self)
# from the components we'll be able to do: self.work.channel_api
with _super.work_on(model_name, channel_api=channel_api, **kwargs) as work:
yield work
# Dangerus method: Usefull for cloned instances with new wubook account
@api.multi
def resync(self):
self.ensure_one()
now_utc_dt = fields.Date.now()
now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
# Reset Issues
issue_ids = self.env['wubook.issue'].search([])
issue_ids.write({
'to_read': False
})
# Push Virtual Rooms
wubook_obj = self.env['wubook'].with_context({
'init_connection': False
})
if wubook_obj.init_connection():
ir_seq_obj = self.env['ir.sequence']
room_types = self.env['hotel.room.type'].search([])
for room_type in room_types:
shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4]
channel_room_id = wubook_obj.create_room(
shortcode,
room_type.name,
room_type.wcapacity,
room_type.list_price,
room_type.total_rooms_count
)
if channel_room_id:
room_type.with_context(wubook_action=False).write({
'channel_room_id': channel_room_id,
'wscode': shortcode,
})
else:
room_type.with_context(wubook_action=False).write({
'channel_room_id': '',
'wscode': '',
})
# Create Restrictions
room_type_rest_obj = self.env['hotel.room.type.restriction']
restriction_ids = room_type_rest_obj.search([])
for restriction in restriction_ids:
if restriction.wpid != '0':
channel_plan_id = wubook_obj.create_rplan(restriction.name)
restriction.write({
'channel_plan_id': channel_plan_id or ''
})
# Create Pricelist
pricelist_ids = self.env['product.pricelist'].search([])
for pricelist in pricelist_ids:
channel_plan_id = wubook_obj.create_plan(pricelist.name, pricelist.is_daily_plan)
pricelist.write({
'channel_plan_id': channel_plan_id or ''
})
wubook_obj.close_connection()
# Reset Folios
folio_ids = self.env['hotel.folio'].search([])
folio_ids.with_context(wubook_action=False).write({
'wseed': '',
})
# Reset Reservations
reservation_ids = self.env['hotel.reservation'].search([
('channel_reservation_id', '!=', ''),
('channel_reservation_id', '!=', False)
])
reservation_ids.with_context(wubook_action=False).write({
'channel_reservation_id': '',
'ota_id': False,
'ota_reservation_id': '',
'is_from_ota': False,
'wstatus': 0
})
# Get Parity Models
pricelist_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_pricelist_id'))
restriction_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id'))
room_type_restr_it_obj = self.env['hotel.room.type.restriction.item']
# Secure Wubook Input
restriction_item_ids = room_type_restr_it_obj.search([
('applied_on', '=', '0_room_type'),
('date_start', '<', now_utc_str),
])
if any(restriction_item_ids):
restriction_item_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push restrictions
restriction_item_ids = room_type_restr_it_obj.search([
('restriction_id', '=', restriction_id),
('applied_on', '=', '0_room_type'),
('wpushed', '=', True),
('date_start', '>=', now_utc_str),
])
if any(restriction_item_ids):
restriction_item_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Secure Wubook Input
pricelist_item_ids = self.env['product.pricelist.item'].search([
('applied_on', '=', '1_product'),
('compute_price', '=', 'fixed'),
('date_start', '<', now_utc_str),
])
if any(pricelist_item_ids):
pricelist_item_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push pricelists
pricelist_item_ids = self.env['product.pricelist.item'].search([
('pricelist_id', '=', pricelist_id),
('applied_on', '=', '1_product'),
('compute_price', '=', 'fixed'),
('wpushed', '=', True),
('date_start', '>=', now_utc_str),
])
if any(pricelist_item_ids):
pricelist_item_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Secure Wubook Input
availabity_ids = self.env['hotel.room.type.availability'].search([
('date', '<', now_utc_str),
])
if any(availabity_ids):
availabity_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push availability
availabity_ids = self.env['hotel.room.type.availability'].search([
('wpushed', '=', True),
('date', '>=', now_utc_str),
])
if any(availabity_ids):
availabity_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Generate Security Token
self.env['ir.default'].sudo().set(
'wubook.config.settings',
'wubook_push_security_token',
binascii.hexlify(os.urandom(16)).decode())
self.env.cr.commit() # FIXME: Need do this
# Push Changes
if wubook_obj.init_connection():
wubook_obj.push_activation()
wubook_obj.import_channels_info()
wubook_obj.push_changes()
wubook_obj.close_connection()

View File

@@ -15,3 +15,8 @@ class ChannelBinding(models.AbstractModel):
string='Hotel Channel Connector Backend',
required=True,
ondelete='restrict')
_sql_constraints = [
('channel_uniq', 'unique(backend_id, external_id)',
'A binding already exists with the same Channel ID.'),
]

View File

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

View File

@@ -0,0 +1,30 @@
# 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
from odoo.addons.component.core import Component
class ChannelOtaInfo(models.Model):
_name = 'channel.ota.info'
_inherit = 'channel.binding'
_description = 'Channel OTA Info'
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.model
def import_otas_info(self, backend):
with backend.work_on(self._name) as work:
importer = work.component(usage='ota.info.importer')
return importer.import_otas_info()
class HotelRoomTypeAdapter(Component):
_name = 'channel.ota.info.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.ota.info'
def fetch_rooms(self):
return super(HotelRoomTypeAdapter, self).fetch_rooms()

View File

@@ -0,0 +1,62 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping
from odoo import fields, api, _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
class ChannelOtaInfoImporter(Component):
_name = 'channel.ota.info.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.ota.info']
_usage = 'ota.info.importer'
@api.model
def import_otas_info(self):
count = 0
try:
results = self.backend_adapter.get_channels_info()
channel_ota_info_obj = self.env['channel.ota.info']
ota_info_mapper = self.component(usage='import.mapper',
model_name='channel.ota.info')
for ota_id in results.keys():
vals = {
'id': ota_id,
'name': results[ota_id]['name'],
'ical': results[ota_id]['ical'] == 1,
}
map_record = ota_info_mapper.map_record(vals)
ota_info_bind = channel_ota_info_obj.search([
('ota_id', '=', ota_id)
], limit=1)
if ota_info_bind:
ota_info_bind.write(map_record.values())
else:
ota_info_bind.create(map_record.values(for_create=True))
count = count + 1
except ChannelConnectorError as err:
self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message'])
return count
class ChannelOtaInfoImportMapper(Component):
_name = 'channel.ota.info.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.ota.info'
direct = [
('id', 'ota_id'),
('name', 'name'),
('ical', 'ical'),
]
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -1,20 +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 odoo.addons.queue_job.job import job
class HotelChannelConnectorOTAInfo(models.Model):
_name = 'hotel.channel.connector.ota.info'
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):
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

@@ -53,7 +53,7 @@ class ChannelHotelReservation(models.Model):
@api.model
def import_reservations(self, backend):
with backend.work_on(self._name) as work:
importer = work.component(usage='channel.hotel.reservation.importer')
importer = work.component(usage='hotel.reservation.importer')
return importer.fetch_new_bookings()
@api.depends('channel_reservation_id', 'ota_id')
@@ -271,7 +271,15 @@ class HotelReservation(models.Model):
if not self.is_from_ota:
return super().on_change_checkin_checkout_product_id()
class ChannelBindingProductListener(Component):
class HotelReservationAdapter(Component):
_name = 'channel.hotel.reservation.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.hotel.reservation'
def fetch_new_bookings(self):
return super(HotelReservationAdapter, self).fetch_new_bookings()
class ChannelBindingHotelReservationListener(Component):
_name = 'channel.binding.hotel.reservation.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.reservation']

View File

@@ -3,6 +3,7 @@
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, api, _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
@@ -14,3 +15,30 @@ class HotelReservationImporter(Component):
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.hotel.reservation']
_usage = 'hotel.reservation.importer'
def fetch_new_bookings(self):
try:
results = self.backend_adapter.fetch_new_bookings()
processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \
self._generate_reservations(results)
if any(processed_rids):
uniq_rids = list(set(processed_rids))
rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids)
if rcodeb != 0:
self.create_issue(
'wubook',
_("Problem trying mark bookings (%s)") % str(processed_rids),
'')
# Update Odoo availability (don't wait for wubook)
# This cause abuse service in first import!!
if checkin_utc_dt and checkout_utc_dt:
self.backend_adapter.fetch_rooms_values(
checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
except ChannelConnectorError as err:
self.create_issue(
'reservation',
_("Can't process reservations from wubook"),
err.data['message'])
return False
return True

View File

@@ -2,3 +2,5 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common
from . import importer
from . import exporter

View File

@@ -1,11 +1,13 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import api, models, fields, _
from odoo.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if
_logger = logging.getLogger(__name__)
class ChannelHotelRoomType(models.Model):
_name = 'channel.hotel.room.type'
@@ -21,6 +23,22 @@ class ChannelHotelRoomType(models.Model):
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.onchange('room_ids')
def _get_capacity(self):
for rec in self:
rec.ota_capacity = rec.odoo_id.get_capacity()
def _check_self_unlink(self):
if not self.odoo_id:
self.sudo().unlink()
@job(default_channel='root.channel')
@api.model
def import_rooms(self, backend):
with backend.work_on(self._name) as work:
importer = work.component(usage='hotel.room.type.importer')
return importer.get_rooms()
@api.constrains('ota_capacity')
def _check_ota_capacity(self):
for record in self:
@@ -39,65 +57,30 @@ class ChannelHotelRoomType(models.Model):
@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]
if not self.channel_room_id:
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.total_rooms_count)
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")
exporter = work.component(usage='hotel.room.type.exporter')
exporter.create_room(self)
@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:
if 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.total_rooms_count,
self.channel_short_code)
except ValidationError as e:
self.create_issue('room', "Can't modify room on channel", "sss")
exporter = work.component(usage='hotel.room.type.exporter')
exporter.modify_room(self)
@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:
if 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()
exporter = work.component(usage='hotel.room.type.exporter')
exporter.delete_room(self)
class HotelRoomType(models.Model):
_inherit = 'hotel.room.type'
@@ -107,10 +90,16 @@ class HotelRoomType(models.Model):
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
capacity = fields.Integer("Capacity", compute="_compute_capacity")
@api.multi
def _compute_capacity(self):
for record in self:
record.capacity = record.get_capacity()
@api.onchange('room_ids')
def _get_capacity(self):
for rec in self:
rec.channel_bind_ids.ota_capacity = rec.get_capacity()
def _onchange_room_ids(self):
self._compute_capacity()
@api.multi
def get_restrictions(self, date):
@@ -118,26 +107,61 @@ class HotelRoomType(models.Model):
'res.config.settings', 'parity_restrictions_id'))
self.ensure_one()
restriction = self.env['hotel.room.type.restriction.item'].search([
('date_start', '=', date),
('date_end', '=', date),
('date', '=', date),
('room_type_id', '=', self.id),
('restriction_id', '=', restriction_plan_id)
], limit=1)
return restriction
@api.multi
def create_bindings(self):
backends = self.env['channel.backend'].search([])
binding_obj = self.env['channel.hotel.room.type']
for backend in backends:
binding = binding_obj.search([
('odoo_id', '=', self.id),
('backend_id', '=', backend.id)], limit=1)
if not binding:
binding_obj.sudo().create({
'odoo_id': self.id,
'backend_id': backend.id,
})
class HotelRoomTypeAdapter(Component):
_name = 'channel.hotel.room.type.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.hotel.room.type'
def fetch_rooms(self):
return super(HotelRoomTypeAdapter, self).fetch_rooms()
class BindingHotelRoomTypeListener(Component):
_name = 'binding.hotel.room.type.listener'
_inherit = 'base.connector.listener'
_apply_on = ['hotel.room.type']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'name' in fields or 'list_price' in fields:
record.channel_bind_ids[0].modify_room()
# @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
# def on_record_create(self, record, fields=None):
# record.create_bindings()
class ChannelBindingRoomTypeListener(Component):
_name = 'channel.binding.room.type.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.room.type']
_apply_on = ['channel.hotel.room.type']
@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()
def on_record_create(self, record, fields=None):
record.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()
record.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()
record.modify_room()

View File

@@ -0,0 +1,55 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeExporter(Component):
_name = 'channel.hotel.room.type.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.hotel.room.type']
_usage = 'hotel.room.type.exporter'
@api.model
def modify_room(self, binding):
try:
return self.backend_adapter.modify_room(
binding.channel_room_id,
binding.name,
binding.ota_capacity,
binding.list_price,
binding.total_rooms_count,
binding.channel_short_code)
except ChannelConnectorError as err:
self.create_issue('room', _("Can't modify rooms in WuBook"), err.data['message'])
@api.model
def delete_room(self, binding):
try:
return self.backend_adapter.delete_room(binding.channel_room_id)
except ChannelConnectorError as err:
self.create_issue('room', _("Can't delete room in WuBook"), err.data['message'])
@api.model
def create_room(self, binding):
try:
seq_obj = self.env['ir.sequence']
short_code = seq_obj.next_by_code('hotel.room.type')[:4]
external_id = self.backend_adapter.create_room(
short_code,
binding.name,
binding.ota_capacity,
binding.list_price,
binding.total_rooms_count
)
binding.write({
'channel_room_id': external_id,
'channel_short_code': short_code,
})
except ChannelConnectorError as err:
self.create_issue('room', _("Can't delete room in WuBook"), err.data['message'])
else:
self.binder.bind(external_id, binding)

View File

@@ -0,0 +1,170 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.hotel import date_utils
from odoo import fields, api, _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo.addons.hotel_channel_connector.components.backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT
_logger = logging.getLogger(__name__)
class HotelRoomTypeImporter(Component):
_name = 'channel.hotel.room.type.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.hotel.room.type']
_usage = 'hotel.room.type.importer'
@api.model
def get_rooms(self):
count = 0
try:
results = self.backend_adapter.fetch_rooms()
channel_room_type_obj = self.env['channel.hotel.room.type']
room_mapper = self.component(usage='import.mapper',
model_name='channel.hotel.room.type')
count = len(results)
for room in results:
map_record = room_mapper.map_record(room)
room_bind = channel_room_type_obj.search([
('channel_room_id', '=', room['id'])
], limit=1)
if room_bind:
room_bind.with_context({'wubook_action': False}).write(map_record.values())
else:
room_bind = channel_room_type_obj.with_context({'wubook_action': False}).create(
map_record.values(for_create=True))
except ChannelConnectorError as err:
self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message'])
return count
@api.model
def fetch_rooms_values(self, dfrom, dto, rooms=False,
set_max_avail=False):
# Sanitize Dates
now_dt = date_utils.now()
dfrom_dt = date_utils.get_datetime(dfrom)
dto_dt = date_utils.get_datetime(dto)
if dto_dt < now_dt:
return True
if dfrom_dt < now_dt:
dfrom_dt = now_dt
if dfrom_dt > dto_dt:
dfrom_dt, dto_dt = dto_dt, dfrom_dt
try:
results = self.backend_adapter.fetch_rooms_values(
dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rooms)
self._generate_room_values(dfrom, dto, results,
set_max_avail=set_max_avail)
except ChannelConnectorError as err:
self.create_issue('room', _("Can't fetch rooms values from WuBook"),
err.data['message'], dfrom=dfrom, dto=dto)
return False
return True
@api.model
def _map_room_values_availability(self, day_vals, set_max_avail):
channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
room_avail_mapper = self.component(usage='import.mapper',
model_name='channel.hotel.room.type.availability')
map_record = room_avail_mapper.map_record(day_vals)
map_record.update(channel_pushed=True)
if set_max_avail:
map_record.update(max_avail=day_vals.get('avail', 0))
channel_room_type_avail = channel_room_type_avail_obj.search([
('room_type_id', '=', day_vals['room_type_id']),
('date', '=', day_vals['date'])
], limit=1)
if channel_room_type_avail:
channel_room_type_avail.with_context({
'wubook_action': False,
}).write(map_record.values())
else:
channel_room_type_avail_obj.with_context({
'wubook_action': False,
'mail_create_nosubscribe': True,
}).create(map_record.values(for_create=True))
@api.model
def _map_room_values_restrictions(self, day_vals):
channel_room_type_restr_item_obj = self.env['channel.hotel.room.type.restriction.item']
room_restriction_mapper = self.component(
usage='import.mapper',
model_name='channel.hotel.room.type.restriction.item')
map_record = room_restriction_mapper.map_record(day_vals)
map_record.update(channel_pushed=True)
room_type_restr = channel_room_type_restr_item_obj.search([
('room_type_id', '=', day_vals['room_type_id']),
('applied_on', '=', '0_room_type'),
('date', '=', day_vals['date']),
('restriction_id', '=', day_vals['restriction_plan_id']),
])
if room_type_restr:
room_type_restr.with_context({
'wubook_action': False,
}).write(map_record.values())
else:
channel_room_type_restr_item_obj.with_context({
'wubook_action': False,
}).create(map_record.values(for_create=True))
@api.model
def _generate_room_values(self, dfrom, dto, values, set_max_avail=False):
channel_room_type_restr_obj = self.env['channel.hotel.room.type.restriction']
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
def_restr_plan = channel_room_type_restr_obj.search([('channel_plan_id', '=', '0')])
_logger.info("==== ROOM VALUES (%s -- %s)", dfrom, dto)
_logger.info(values)
for k_rid, v_rid in values.iteritems():
room_type = channel_hotel_room_type_obj.search([
('channel_plan_id', '=', k_rid)
], limit=1)
if room_type:
date_dt = date_utils.get_datetime(
dfrom,
dtformat=DEFAULT_WUBOOK_DATE_FORMAT)
for day_vals in v_rid:
date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
day_vals.update({
'room_type_id': room_type.odoo_id.id,
'date': date_str,
})
self._map_room_values_availability(day_vals, set_max_avail)
if def_restr_plan:
day_vals.update({
'restriction_plan_id': def_restr_plan.odoo_id.id
})
self._map_room_values_restrictions(day_vals)
date_dt = date_dt + timedelta(days=1)
return True
class HotelRoomTypeImportMapper(Component):
_name = 'channel.hotel.room.type.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.hotel.room.type'
direct = [
('id', 'channel_room_id'),
('shortname', 'channel_short_code'),
('occupancy', 'ota_capacity'),
('price', 'list_price'),
('name', 'name'),
]
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -2,3 +2,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common
from . import importer

View File

@@ -38,7 +38,7 @@ class ChannelHotelRoomTypeAvailability(models.Model):
for record in self:
if record.channel_max_avail > record.odoo_id.room_type_id.total_rooms_count:
raise ValidationError(_("max avail for channel can't be high \
than toal rooms \
than total rooms \
count: %d") % record.odoo_id.room_type_id.total_rooms_count)
@job(default_channel='root.channel')

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).
import logging
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.hotel import date_utils
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeAvailabilityImporter(Component):
_name = 'channel.hotel.room.type.availability.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.hotel.room.type.availability']
_usage = 'hotel.room.type.availability.importer'
class HotelRoomTypeAvailabilityImportMapper(Component):
_name = 'channel.hotel.room.type.availability.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.hotel.room.type.availability'
direct = [
('no_ota', 'no_ota'),
('booked', 'booked'),
('avail', 'avail'),
('room_type_id', 'room_type_id'),
('date', 'date'),
]
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -2,3 +2,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common
from . import importer

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).
import logging
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.hotel import date_utils
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeRestrictionImporter(Component):
_name = 'channel.hotel.room.type.restriction.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.hotel.room.type.restriction']
_usage = 'hotel.room.type.restriction.importer'
class HotelRoomTypeRestrictionImportMapper(Component):
_name = 'channel.hotel.room.type.restriction.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.hotel.room.type.restriction'
direct = [
('no_ota', 'no_ota'),
('booked', 'booked'),
('avail', 'avail'),
('room_type_id', 'room_type_id'),
('date', 'date')
]
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

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

View File

@@ -0,0 +1,48 @@
# 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.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if
class ChannelHotelRoomTypeRestrictionItem(models.Model):
_name = 'channel.hotel.room.type.restriction.item'
_inherit = 'channel.binding'
_inherits = {'hotel.room.type.restriction.item': 'odoo_id'}
_description = 'Channel Hotel Room Type Restriction Item'
odoo_id = fields.Many2one(comodel_name='hotel.room.type.restriction.item',
string='Hotel Virtual Room Restriction',
required=True,
ondelete='cascade')
channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False,
old_name='wpushed')
@job(default_channel='root.channel')
@api.multi
def update_channel_pushed(self, status):
self.ensure_one()
self.channel_pushed = status
class HotelRoomTypeRestrictionItem(models.Model):
_inherit = 'hotel.room.type.restriction.item'
channel_bind_ids = fields.One2many(
comodel_name='channel.hotel.room.type.restriction.item',
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component):
_name = 'channel.binding.hotel.room.type.restriction.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.room.type.restriction']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None):
record.update_channel_pushed(False)
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.update_channel_pushed(False)

View File

@@ -0,0 +1,44 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from datetime import timedelta
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping, only_create
from odoo.addons.hotel import date_utils
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeRestrictionImporter(Component):
_name = 'channel.hotel.room.type.restriction.item.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.hotel.room.type.restriction.item']
_usage = 'hotel.room.type.restriction.item.importer'
class HotelRoomTypeRestrictionItemImportMapper(Component):
_name = 'channel.hotel.room.type.restriction.item.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.hotel.room.type.restriction.item'
direct = [
('min_stay', 'min_stay'),
('min_stay_arrival', 'min_stay_arrival'),
('max_stay', 'max_stay'),
('max_stay_arrival', 'max_stay_arrival'),
('closed', 'closed'),
('closed_departure', 'closed_departure'),
('closed_arrival', 'closed_arrival'),
('room_type_id', 'room_type_id'),
('date', 'date'),
]
@only_create
@mapping
def applied_on(self, record):
return {'applied_on': '0_room_type'}
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -1,7 +1,7 @@
# 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 odoo import models, fields, api
class ResPartner(models.Model):

View File

@@ -1,29 +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
class ReservationRestrictionItem(models.Model):
_inherit = 'hotel.room.type.restriction.item'
channel_pushed = fields.Boolean("Channel Pushed", default=False, readonly=True,
old_name='wpushed')
@api.onchange('date_start')
def _onchange_date_start(self):
self.date_end = self.date_start
@api.model
def create(self, vals):
if vals.get('date_start'):
vals['date_end'] = vals.get('date_start')
return super(ReservationRestrictionItem, self).create(vals)
@api.multi
def write(self, vals):
if vals.get('date_start'):
vals['date_end'] = vals.get('date_start')
if self._context.get('channel_action', True):
vals.update({'channel_pushed': False})
return super(ReservationRestrictionItem, self).write(vals)

View File

@@ -1,196 +1,32 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import os
import binascii
import logging
from datetime import datetime, timedelta
from odoo import models, fields, api, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
_logger = logging.getLogger(__name__)
from openerp import models, fields, api
class HotelChannelConnectorConfiguration(models.TransientModel):
class HotelConfiguration(models.TransientModel):
_inherit = 'res.config.settings'
channel_push_security_token = fields.Char('WuBook Push Notification Security Token')
default_channel_connector = fields.Many2one(
'channel.backend',
'Default Channel Connector Backend')
@api.multi
def set_values(self):
super(HotelChannelConnectorConfiguration, self).set_values()
super(HotelConfiguration, self).set_values()
self.env['ir.default'].sudo().set(
'res.config.settings', 'channel_push_security_token',
self.channel_push_security_token)
'res.config.settings', 'default_channel_connector',
self.default_channel_connector.id)
@api.model
def get_values(self):
res = super(HotelChannelConnectorConfiguration, self).get_values()
res = super(HotelConfiguration, self).get_values()
# ONLY FOR v11. DO NOT FORWARD-PORT
channel_push_security_token = self.env['ir.default'].sudo().get(
'res.config.settings', 'channel_push_security_token')
default_channel_connector = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_channel_connector')
res.update(
channel_push_security_token=channel_push_security_token,
default_channel_connector=default_channel_connector,
)
return res
# Dangerus method: Usefull for cloned instances with new wubook account
@api.multi
def resync(self):
self.ensure_one()
now_utc_dt = fields.Date.now()
now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
# Reset Issues
issue_ids = self.env['wubook.issue'].search([])
issue_ids.write({
'to_read': False
})
# Push Virtual Rooms
wubook_obj = self.env['wubook'].with_context({
'init_connection': False
})
if wubook_obj.init_connection():
ir_seq_obj = self.env['ir.sequence']
room_types = self.env['hotel.room.type'].search([])
for room_type in room_types:
shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4]
channel_room_id = wubook_obj.create_room(
shortcode,
room_type.name,
room_type.wcapacity,
room_type.list_price,
room_type.total_rooms_count
)
if channel_room_id:
room_type.with_context(wubook_action=False).write({
'channel_room_id': channel_room_id,
'wscode': shortcode,
})
else:
room_type.with_context(wubook_action=False).write({
'channel_room_id': '',
'wscode': '',
})
# Create Restrictions
room_type_rest_obj = self.env['hotel.room.type.restriction']
restriction_ids = room_type_rest_obj.search([])
for restriction in restriction_ids:
if restriction.wpid != '0':
channel_plan_id = wubook_obj.create_rplan(restriction.name)
restriction.write({
'channel_plan_id': channel_plan_id or ''
})
# Create Pricelist
pricelist_ids = self.env['product.pricelist'].search([])
for pricelist in pricelist_ids:
channel_plan_id = wubook_obj.create_plan(pricelist.name, pricelist.is_daily_plan)
pricelist.write({
'channel_plan_id': channel_plan_id or ''
})
wubook_obj.close_connection()
# Reset Folios
folio_ids = self.env['hotel.folio'].search([])
folio_ids.with_context(wubook_action=False).write({
'wseed': '',
})
# Reset Reservations
reservation_ids = self.env['hotel.reservation'].search([
('channel_reservation_id', '!=', ''),
('channel_reservation_id', '!=', False)
])
reservation_ids.with_context(wubook_action=False).write({
'channel_reservation_id': '',
'ota_id': False,
'ota_reservation_id': '',
'is_from_ota': False,
'wstatus': 0
})
# Get Parity Models
pricelist_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_pricelist_id'))
restriction_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id'))
room_type_restr_it_obj = self.env['hotel.room.type.restriction.item']
# Secure Wubook Input
restriction_item_ids = room_type_restr_it_obj.search([
('applied_on', '=', '0_room_type'),
('date_start', '<', now_utc_str),
])
if any(restriction_item_ids):
restriction_item_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push restrictions
restriction_item_ids = room_type_restr_it_obj.search([
('restriction_id', '=', restriction_id),
('applied_on', '=', '0_room_type'),
('wpushed', '=', True),
('date_start', '>=', now_utc_str),
])
if any(restriction_item_ids):
restriction_item_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Secure Wubook Input
pricelist_item_ids = self.env['product.pricelist.item'].search([
('applied_on', '=', '1_product'),
('compute_price', '=', 'fixed'),
('date_start', '<', now_utc_str),
])
if any(pricelist_item_ids):
pricelist_item_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push pricelists
pricelist_item_ids = self.env['product.pricelist.item'].search([
('pricelist_id', '=', pricelist_id),
('applied_on', '=', '1_product'),
('compute_price', '=', 'fixed'),
('wpushed', '=', True),
('date_start', '>=', now_utc_str),
])
if any(pricelist_item_ids):
pricelist_item_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Secure Wubook Input
availabity_ids = self.env['hotel.room.type.availability'].search([
('date', '<', now_utc_str),
])
if any(availabity_ids):
availabity_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push availability
availabity_ids = self.env['hotel.room.type.availability'].search([
('wpushed', '=', True),
('date', '>=', now_utc_str),
])
if any(availabity_ids):
availabity_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Generate Security Token
self.env['ir.default'].sudo().set(
'wubook.config.settings',
'wubook_push_security_token',
binascii.hexlify(os.urandom(16)).decode())
self.env.cr.commit() # FIXME: Need do this
# Push Changes
if wubook_obj.init_connection():
wubook_obj.push_activation()
wubook_obj.import_channels_info()
wubook_obj.push_changes()
wubook_obj.close_connection()

View File

@@ -5,12 +5,12 @@
<field name="name">channel.backend.form</field>
<field name="model">channel.backend</field>
<field name="arch" type="xml">
<form string="Channel Backend">
<form string="Hotel Channel Backend">
<header>
<button name="synchronize_metadata"
<button name="synchronize_push_urls"
type="object"
class="oe_highlight"
string="Synchronize Metadata"/>
string="Synchronize Push URL's"/>
</header>
<sheet>
<label for="name" class="oe_edit_only"/>
@@ -21,12 +21,19 @@
<notebook>
<page string="API" name="api">
<group colspan="4" col="4">
<field name="version" colspan="4"/>
<field name="version" colspan="2"/>
<field name="server" colspan="2"/>
<field name="lcode" colspan="2"/>
<field name="pkey" colspan="2"/>
<field name="username" colspan="2"/>
<field name="passwd" password="1" colspan="2"/>
</group>
<group colspan="4" col="4">
<field name="security_token" colspan="4"/>
<button colspan="1" name="generate_key"
string="Generate New Token" type="object"
class="oe_edit_only" />
</group>
</page>
</notebook>
</group>
@@ -59,6 +66,24 @@
string="Import in background"/>
</div>
</group>
<group>
<label string="Import Rooms" class="oe_inline"/>
<div>
<button name="import_rooms"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
<group>
<label string="Import OTA's Info" class="oe_inline"/>
<div>
<button name="import_otas_info"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
</page>
</notebook>
</sheet>
@@ -78,7 +103,7 @@
</record>
<record id="action_channel_backend" model="ir.actions.act_window">
<field name="name">Channel Backends</field>
<field name="name">Hotel Channel Backends</field>
<field name="res_model">channel.backend</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_channel_connector_root"
parent="connector.menu_connector_root"
name="Channel"
sequence="10"
groups="connector.group_connector_manager"/>
<menuitem id="menu_cannel_backend"
name="Backends"
parent="menu_channel_connector_root"
action="action_channel_backend"/>
</odoo>

View File

@@ -6,6 +6,9 @@
<field name="model">channel.hotel.room.type</field>
<field name="arch" type="xml">
<form string="Hotel Channel Virtual Room">
<group>
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
<group>
<field name="channel_room_id" />
<field name="channel_short_code" />

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form view -->
<record model="ir.ui.view" id="view_hotel_channel_connector_ota_info_form">
<field name="name">channel.ota.info.form</field>
<field name="model">channel.ota.info</field>
<field name="arch" type="xml">
<form string="Channel OTA's Info" >
<sheet>
<group>
<field name="ota_id" />
<field name="name" />
<field name="ical" readonly="True" widget="checkbox" />
</group>
</sheet>
</form>
</field>
</record>
<!-- Tree view -->
<record model="ir.ui.view" id="view_hotel_channel_connector_ota_info_tree">
<field name="name">channel.ota.info.tree</field>
<field name="model">channel.ota.info</field>
<field name="arch" type="xml">
<tree string="Channel OTA's Info">
<field name="ota_id" />
<field name="name" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="open_channel_ota_info_tree_all">
<field name="name">Hotel Channel Connector OTA's Info</field>
<field name="res_model">channel.ota.info</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

View File

@@ -69,4 +69,13 @@
</field>
</record>
<record model="ir.actions.act_window" id="open_hotel_channel_connector_issue_tree_all">
<field name="name">Hotel Channel Connector Issues</field>
<field name="res_model">hotel.channel.connector.issue</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="search_view_id" ref="view_hotel_channel_connector_issue_form_search"/>
<field name="context">{"search_default_to_read":True}</field>
</record>
</odoo>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Form view -->
<record model="ir.ui.view" id="view_hotel_channel_connector_ota_info_form">
<field name="name">hotel.channel.connector.ota.info.form</field>
<field name="model">hotel.channel.connector.ota.info</field>
<field name="arch" type="xml">
<form string="Channel OTA's Info" >
<sheet>
<group>
<field name="ota_id" />
<field name="name" />
<field name="ical" readonly="True" widget="checkbox" />
</group>
</sheet>
</form>
</field>
</record>
<!-- Tree view -->
<record model="ir.ui.view" id="view_hotel_channel_connector_ota_info_tree">
<field name="name">hotel.channel.connector.ota.info.tree</field>
<field name="model">hotel.channel.connector.ota.info</field>
<field name="arch" type="xml">
<tree string="Channel OTA's Info">
<field name="ota_id" />
<field name="name" />
</tree>
</field>
</record>
</odoo>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<odoo>
<record id="reservation_restriction_view" model="ir.ui.view">
<record id="room_type_restriction_view" model="ir.ui.view">
<field name="model">hotel.room.type.restriction</field>
<field name="inherit_id" ref="hotel.reservation_restriction_view_form" />
<field name="inherit_id" ref="hotel.room_type_restriction_view_form" />
<field name="arch" type="xml">
<xpath expr="//form[1]//sheet" position="before">
<header>

View File

@@ -9,9 +9,10 @@
<notebook>
<page name="connector">
<group string="Hotel Channel Bindings">
<field name="channel_bind_ids" nolabel="1">
<field name="capacity" invisible="1" />
<field name="channel_bind_ids" context="{'default_ota_capacity': capacity}" nolabel="1">
<tree>
<field name="backend_id"/>
<field name="backend_id" />
</tree>
</field>
</group>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<odoo>
<record id="reservation_restriction_item_form_view" model="ir.ui.view">
<field name="model">hotel.room.type.restriction.item</field>
<field name="inherit_id" ref="hotel.reservation_restriction_item_view_form" />
<field name="arch" type="xml">
<xpath expr="//field[@name='date_end']" position="attributes">
<attribute name="readonly">True</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -19,7 +19,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import wubook_installer
from . import wubook_import_plan_prices
from . import wubook_import_plan_restrictions
from . import wubook_import_availability

View File

@@ -1,156 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import os
import binascii
from openerp import models, fields, api, _
from openerp.exceptions import ValidationError
from ..components.backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT
from odoo.addons.hotel import date_utils
class WuBookInstaller(models.TransientModel):
_name = 'wubook.installer'
_inherit = 'res.config.installer'
wubook_user = fields.Char('User', required=True)
wubook_passwd = fields.Char('Password', required=True)
wubook_lcode = fields.Char('LCode', required=True)
wubook_server = fields.Char(string='Server',
default='https://wired.wubook.net/xrws/',
required=True)
wubook_pkey = fields.Char('PKey', required=True)
activate_push = fields.Boolean('Active Push Notifications', default=True)
@api.multi
def execute(self):
super(WuBookInstaller, self).execute()
return self.execute_simple()
@api.multi
def execute_simple(self):
activate_push = True
for rec in self:
self.env['ir.default'].sudo().set('wubook.config.settings',
'wubook_user',
rec.wubook_user)
self.env['ir.default'].sudo().set('wubook.config.settings',
'wubook_passwd',
rec.wubook_passwd)
self.env['ir.default'].sudo().set('wubook.config.settings',
'wubook_lcode',
rec.wubook_lcode)
self.env['ir.default'].sudo().set('wubook.config.settings',
'wubook_server',
rec.wubook_server)
self.env['ir.default'].sudo().set('wubook.config.settings',
'wubook_pkey',
rec.wubook_pkey)
activate_push = rec.activate_push
self.env['ir.default'].sudo().set(
'wubook.config.settings',
'wubook_push_security_token',
binascii.hexlify(os.urandom(16)).decode())
self.env.cr.commit() # FIXME: Need do this
# Create Wubook Base Restrictions
restr_obj = self.env['hotel.room.type.restriction'].with_context({
'wubook_action': False
})
base_rest = restr_obj.search([('wpid', '=', '0')], limit=1)
if not base_rest:
nrest = restr_obj.create({
'name': 'Base WuBook Restrictions',
'wpid': '0',
})
if not nrest:
raise ValidationError(_("Can't create base wubook restrictions"))
# Initialize WuBook
wres = self.env['wubook'].initialize(activate_push)
if not wres:
raise ValidationError("Can't finish installation!")
# Open Next Step
v_id = 'hotel_wubook_proto.view_wubook_configuration_installer_parity'
return {
'name': _("Configure Hotel Parity"),
'type': 'ir.actions.act_window',
'res_model': 'wubook.installer.parity',
'view_id': self.env.ref(v_id).id,
'view_type': 'form',
'view_mode': 'form',
'target': 'new'
}
class WuBookInstallerParity(models.TransientModel):
_name = 'wubook.installer.parity'
_inherit = 'res.config.installer'
parity_pricelist_id = fields.Many2one('product.pricelist',
'Product Pricelist')
parity_restrictions_id = fields.Many2one('hotel.room.type.restriction',
'Restrictions')
import_data = fields.Boolean('Import Data From WuBook', default=False)
date_start = fields.Date('Date Start')
date_end = fields.Date('Date End')
@api.multi
def execute(self):
self.execute_simple()
return super(WuBookInstallerParity, self).execute()
@api.multi
def execute_simple(self):
wubookObj = self.env['wubook']
irValuesObj = self.env['ir.values']
for rec in self:
irValuesObj.sudo().set_default('res.config.settings',
'parity_pricelist_id',
rec.parity_pricelist_id.id)
irValuesObj.sudo().set_default('res.config.settings',
'parity_restrictions_id',
rec.parity_restrictions_id.id)
import_data = rec.import_data
if rec.import_data:
date_start_dt = date_utils.get_datetime(rec.date_start)
date_end_dt = date_utils.get_datetime(rec.date_end)
# Availability
wresAvail = wubookObj.fetch_rooms_values(
date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT))
# Pricelist
wresPrices = wubookObj.fetch_plan_prices(
rec.parity_pricelist_id.wpid,
date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT))
# Restrictions
wresRestr = wubookObj.fetch_rplan_restrictions(
date_start_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_end_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rec.parity_restrictions_id.wpid)
if not wresAvail or not wresPrices or not wresRestr:
raise ValidationError(_("Errors importing data from WuBook"))
# Reservations
wubookObj.fetch_new_bookings()

View File

@@ -1,90 +0,0 @@
<odoo>
<record id="view_wubook_configuration_installer" model="ir.ui.view">
<field name="name">wubook.installer.form</field>
<field name="model">wubook.installer</field>
<field name="inherit_id" ref="base.res_config_installer"/>
<field name="arch" type="xml">
<xpath expr="//form[1]" position="attributes">
<attribute name="string">WuBook Configuration</attribute>
</xpath>
<xpath expr="//footer[1]" position="replace">
<footer>
<button name="action_next" type="object" string="Continue" class="oe_highlight"/>
</footer>
</xpath>
<xpath expr="//form/separator[1]" position="replace">
<p class="oe_grey">
WuBook API Configuration. This wizard will activate push request and synchronize rooms &amp; reservations with Odoo.
</p>
<group>
<field name="wubook_server" />
</group>
<group>
<field name="wubook_user" />
<field name="wubook_passwd" password="True" />
</group>
<group>
<field name="wubook_lcode" />
<field name="wubook_pkey" />
</group>
<group>
<field name="activate_push" />
</group>
</xpath>
</field>
</record>
<record id="view_wubook_configuration_installer_parity" model="ir.ui.view">
<field name="name">wubook.installer.parity.form</field>
<field name="model">wubook.installer.parity</field>
<field name="inherit_id" ref="base.res_config_installer"/>
<field name="arch" type="xml">
<xpath expr="//form[1]" position="attributes">
<attribute name="string">WuBook Configuration Parity</attribute>
</xpath>
<xpath expr="//footer[1]" position="replace">
<footer>
<button name="action_next" type="object" string="Finish Installation" class="oe_highlight"/>
</footer>
</xpath>
<xpath expr="//form/separator[1]" position="replace">
<p class="oe_grey">
These models are used as masters
</p>
<group>
<field name="parity_pricelist_id" domain="[('wpid', '!=', False),('wpid', '!=', '')]" required="True" />
<field name="parity_restrictions_id" domain="[('wpid', '!=', False),('wpid', '!=', '')]" required="True" />
</group>
<group>
<field name="import_data" />
</group>
<group attrs="{'invisible':[('import_data', '=', False)]}">
<group>
<field name="date_start" attrs="{'required':[('import_data', '=', True)]}" />
</group>
<group>
<field name="date_end" attrs="{'required':[('import_data', '=', True)]}" />
</group>
</group>
</xpath>
</field>
</record>
<record id="action_wubook_configuration_installer" model="ir.actions.act_window">
<field name="name">Configure WuBook Data</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">wubook.installer</field>
<field name="view_id" ref="view_wubook_configuration_installer"/>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!--record id="wubook_configuration_installer_todo" model="ir.actions.todo">
<field name="action_id" ref="action_wubook_configuration_installer"/>
<field name="sequence">3</field>
<field name="type">automatic</field>
</record-->
</odoo>