Merge branch 'hotel_calendar' of https://github.com/hootel/hootel into 11.0

This commit is contained in:
Dario Lodeiros
2018-11-07 18:53:02 +01:00
61 changed files with 1590 additions and 488 deletions

View File

@@ -110,7 +110,7 @@ class BusHotelCalendar(models.TransientModel):
return { return {
'type': 'availability', 'type': 'availability',
'availability': { 'availability': {
vals['room_id']: { vals['room_type_id']: {
date_dt.strftime("%d/%m/%Y"): [ date_dt.strftime("%d/%m/%Y"): [
vals['avail'], vals['avail'],
vals['no_ota'], vals['no_ota'],

View File

@@ -8,7 +8,7 @@ class HotelRoomTypeAvailability(models.Model):
@api.model @api.model
def create(self, vals): def create(self, vals):
res = super(HotelVirtualRoomAvailability, self).create(vals) res = super(HotelRoomTypeAvailability, self).create(vals)
self.env['bus.hotel.calendar'].send_availability_notification({ self.env['bus.hotel.calendar'].send_availability_notification({
'date': res.date, 'date': res.date,
'avail': res.avail, 'avail': res.avail,
@@ -20,7 +20,7 @@ class HotelRoomTypeAvailability(models.Model):
@api.multi @api.multi
def write(self, vals): def write(self, vals):
ret_vals = super(HotelVirtualRoomAvailability, self).write(vals) ret_vals = super(HotelRoomTypeAvailability, self).write(vals)
bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] bus_hotel_calendar_obj = self.env['bus.hotel.calendar']
for record in self: for record in self:
bus_hotel_calendar_obj.send_availability_notification({ bus_hotel_calendar_obj.send_availability_notification({
@@ -44,7 +44,7 @@ class HotelRoomTypeAvailability(models.Model):
'no_ota': False, 'no_ota': False,
'id': record.id, 'id': record.id,
}) })
res = super(HotelVirtualRoomAvailability, self).unlink() res = super(HotelRoomTypeAvailability, self).unlink()
bus_hotel_calendar_obj = self.env['bus.hotel.calendar'] bus_hotel_calendar_obj = self.env['bus.hotel.calendar']
for uval in unlink_vals: for uval in unlink_vals:
bus_hotel_calendar_obj.send_availability_notification(uval) bus_hotel_calendar_obj.send_availability_notification(uval)

View File

@@ -25,10 +25,10 @@ class IrDefault(models.Model):
fixed_price = pitem.fixed_price fixed_price = pitem.fixed_price
room_type = room_type_obj.search([ room_type = room_type_obj.search([
('product_id.product_tmpl_id', '=', product_tmpl_id), ('product_id.product_tmpl_id', '=', product_tmpl_id),
('date_start', '>=', fields.Date.today())
], limit=1) ], limit=1)
room_pr_cached_obj.create({ if room_type:
'room_type_id': room_type.id, room_pr_cached_obj.create({
'date': date_start, 'room_id': room_type.id,
'price': fixed_price, 'date': date_start,
}) 'price': fixed_price,
})

View File

@@ -8,24 +8,22 @@
<field name="priority" eval="80"/> <field name="priority" eval="80"/>
<field name="inherit_id" ref="base.res_config_settings_view_form"/> <field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside"> <xpath expr="//div[@data-key='hotel']" position="inside">
<div class="app_settings_block o_not_app" data-string="Hotel" string="Hotel" data-key="hotel" groups="hotel.group_hotel_manager"> <h2>Calendar colors</h2>
<h2>Calendar colors</h2> <div class="row mt16 o_settings_container">
<div class="row mt16 o_settings_container"> <div class="col-xs-12 col-md-6 o_setting_box">
<div class="col-xs-12 col-md-6 o_setting_box"> <field name="color_pre_reservation" required="True" widget="color"/><field name="color_letter_pre_reservation" required="True" widget="color" />
<field name="color_pre_reservation" required="True" widget="color"/><field name="color_letter_pre_reservation" required="True" widget="color" /> <field name="color_reservation" required="True" widget="color"/><field name="color_letter_reservation" required="True" widget="color" />
<field name="color_reservation" required="True" widget="color"/><field name="color_letter_reservation" required="True" widget="color" /> <field name="color_reservation_pay" required="True" widget="color"/><field name="color_letter_reservation_pay" required="True" widget="color" />
<field name="color_reservation_pay" required="True" widget="color"/><field name="color_letter_reservation_pay" required="True" widget="color" /> <field name="color_stay" required="True" widget="color"/><field name="color_letter_stay" required="True" widget="color" />
<field name="color_stay" required="True" widget="color"/><field name="color_letter_stay" required="True" widget="color" /> <field name="color_stay_pay" required="True" widget="color"/><field name="color_letter_stay_pay" required="True" widget="color" />
<field name="color_stay_pay" required="True" widget="color"/><field name="color_letter_stay_pay" required="True" widget="color" /> </div>
</div> <div class="col-xs-12 col-md-6 o_setting_box">
<div class="col-xs-12 col-md-6 o_setting_box"> <field name="color_checkout" required="True" widget="color"/><field name="color_letter_checkout" required="True" widget="color" />
<field name="color_checkout" required="True" widget="color"/><field name="color_letter_checkout" required="True" widget="color" /> <field name="color_dontsell" required="True" widget="color"/><field name="color_letter_dontsell" required="True" widget="color" />
<field name="color_dontsell" required="True" widget="color"/><field name="color_letter_dontsell" required="True" widget="color" /> <field name="color_staff" required="True" widget="color"/><field name="color_letter_staff" required="True" widget="color" />
<field name="color_staff" required="True" widget="color"/><field name="color_letter_staff" required="True" widget="color" /> <field name="color_to_assign" required="True" widget="color"/><field name="color_letter_to_assign" required="True" widget="color" />
<field name="color_to_assign" required="True" widget="color"/><field name="color_letter_to_assign" required="True" widget="color" /> <field name="color_payment_pending" required="True" widget="color"/><field name="color_letter_payment_pending" required="True" widget="color" />
<field name="color_payment_pending" required="True" widget="color"/><field name="color_letter_payment_pending" required="True" widget="color" />
</div>
</div> </div>
</div> </div>
</xpath> </xpath>

View File

@@ -22,6 +22,7 @@
'wizard/wubook_import_plan_restrictions.xml', 'wizard/wubook_import_plan_restrictions.xml',
'wizard/wubook_import_availability.xml', 'wizard/wubook_import_availability.xml',
'views/general.xml', 'views/general.xml',
'views/hotel_channel_connector_issue_views.xml',
'views/inherited_hotel_reservation_views.xml', 'views/inherited_hotel_reservation_views.xml',
'views/inherited_hotel_room_type_views.xml', 'views/inherited_hotel_room_type_views.xml',
'views/inherited_hotel_room_type_availability_views.xml', 'views/inherited_hotel_room_type_availability_views.xml',
@@ -29,14 +30,16 @@
'views/inherited_product_pricelist_views.xml', 'views/inherited_product_pricelist_views.xml',
'views/inherited_product_pricelist_item_views.xml', 'views/inherited_product_pricelist_item_views.xml',
'views/inherited_hotel_room_type_restriction_views.xml', 'views/inherited_hotel_room_type_restriction_views.xml',
'views/inherited_hotel_room_type_restriction_item_views.xml',
'views/inherited_res_partner_views.xml', 'views/inherited_res_partner_views.xml',
'views/channel_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_reservation_views.xml',
'views/channel_hotel_room_type_views.xml', 'views/channel_hotel_room_type_views.xml',
'views/channel_hotel_room_type_availability_views.xml', 'views/channel_hotel_room_type_availability_views.xml',
'views/channel_hotel_room_type_restriction_views.xml', 'views/channel_hotel_room_type_restriction_views.xml',
'views/channel_hotel_room_type_restriction_item_views.xml',
'views/channel_product_pricelist_views.xml', 'views/channel_product_pricelist_views.xml',
'views/channel_product_pricelist_item_views.xml',
'views/channel_connector_backend_views.xml', 'views/channel_connector_backend_views.xml',
'data/menus.xml', 'data/menus.xml',
'data/sequences.xml', 'data/sequences.xml',

View File

@@ -9,7 +9,6 @@ from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT) DEFAULT_SERVER_DATETIME_FORMAT)
from odoo.addons.payment.models.payment_acquirer import _partner_split_name from odoo.addons.payment.models.payment_acquirer import _partner_split_name
from odoo.addons.hotel import date_utils
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, _ from odoo import fields, _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -276,8 +275,8 @@ class WuBookAdapter(AbstractComponent):
rcode, results = self._server.fetch_rooms_values( rcode, results = self._server.fetch_rooms_values(
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rooms) rooms)
if rcode != 0: if rcode != 0:
raise ChannelConnectorError("Can't fetch rooms values from WuBook", { raise ChannelConnectorError("Can't fetch rooms values from WuBook", {
@@ -317,14 +316,14 @@ class WuBookAdapter(AbstractComponent):
'phone': phone, 'phone': phone,
'street': address, 'street': address,
'country': country_code, 'country': country_code,
'arrival_hour': date_utils.get_datetime(checkin).strftime("%H:%M"), 'arrival_hour': fields.Datetime.from_string(checkin).strftime("%H:%M"),
'notes': notes 'notes': notes
} }
rcode, results = self._server.new_reservation( rcode, results = self._server.new_reservation(
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
date_utils.get_datetime(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_utils.get_datetime(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
{channel_room_id: [adults+children, 'nb']}, {channel_room_id: [adults+children, 'nb']},
customer, customer,
adults+children) adults+children)
@@ -431,7 +430,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
channel_plan_id, channel_plan_id,
date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
prices) prices)
if rcode != 0: if rcode != 0:
raise ChannelConnectorError("Can't update pricing plan in wubook", { raise ChannelConnectorError("Can't update pricing plan in wubook", {
@@ -442,7 +441,7 @@ class WuBookAdapter(AbstractComponent):
return results return results
def update_plan_periods(self, channel_plan_id, periods): def update_plan_periods(self, channel_plan_id, periods):
rcode, results = self.SERVER.update_plan_periods( rcode, results = self._server.update_plan_periods(
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
channel_plan_id, channel_plan_id,
@@ -455,7 +454,7 @@ class WuBookAdapter(AbstractComponent):
return results return results
def get_pricing_plans(self): def get_pricing_plans(self):
rcode, results = self.SERVER.get_pricing_plans( rcode, results = self._server.get_pricing_plans(
self._session_info[0], self._session_info[0],
self._session_info[1]) self._session_info[1])
if rcode != 0: if rcode != 0:
@@ -469,8 +468,8 @@ class WuBookAdapter(AbstractComponent):
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
channel_plan_id, channel_plan_id,
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
rooms or []) rooms or [])
if rcode != 0: if rcode != 0:
raise ChannelConnectorError("Can't get pricing plans from wubook", { raise ChannelConnectorError("Can't get pricing plans from wubook", {
@@ -496,8 +495,8 @@ class WuBookAdapter(AbstractComponent):
rcode, results = self._server.wired_rplan_get_rplan_values( rcode, results = self._server.wired_rplan_get_rplan_values(
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
channel_restriction_plan_id) channel_restriction_plan_id)
if rcode != 0: if rcode != 0:
raise ChannelConnectorError("Can't fetch restriction plans from wubook", { raise ChannelConnectorError("Can't fetch restriction plans from wubook", {
@@ -513,7 +512,7 @@ class WuBookAdapter(AbstractComponent):
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
channel_restriction_plan_id, channel_restriction_plan_id,
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT), fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
values) values)
if rcode != 0: if rcode != 0:
raise ChannelConnectorError("Can't update plan restrictions on wubook", { raise ChannelConnectorError("Can't update plan restrictions on wubook", {

View File

@@ -11,6 +11,8 @@ class HotelConnectorModelBinder(Component):
'channel.hotel.room.type', 'channel.hotel.room.type',
'channel.hotel.room.type.availability', 'channel.hotel.room.type.availability',
'channel.hotel.room.type.restriction', 'channel.hotel.room.type.restriction',
'channel.hotel.room.type.restriction.item',
'channel.product.pricelist', 'channel.product.pricelist',
'channel.product.pricelist.item',
'channel.ota.info', 'channel.ota.info',
] ]

View File

@@ -9,18 +9,6 @@ class BaseHotelChannelConnectorComponent(AbstractComponent):
_inherit = 'base.connector' _inherit = 'base.connector'
_collection = '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,
'internal_message': message,
'channel_object_id': channel_object_id,
'channel_message': channel_message,
'date_start': dfrom,
'date_end': dto,
})
class ChannelConnectorError(Exception): class ChannelConnectorError(Exception):
def __init__(self, message, data): def __init__(self, message, data):
super().__init__(message) super().__init__(message)

View File

@@ -12,7 +12,7 @@ from odoo.tools import (
DEFAULT_SERVER_DATETIME_FORMAT) DEFAULT_SERVER_DATETIME_FORMAT)
from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT
from odoo.addons.hotel import date_utils from odoo.addons.hotel import date_utils
from odoo import api from odoo import api, fields
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class HotelChannelConnectorExporter(AbstractComponent): class HotelChannelConnectorExporter(AbstractComponent):
@@ -25,42 +25,6 @@ class HotelChannelConnectorExporter(AbstractComponent):
return self.push_availability() and self.push_priceplans() and \ return self.push_availability() and self.push_priceplans() and \
self.push_restrictions() self.push_restrictions()
@api.model
def push_availability(self):
room_type_avail_ids = self.env['hotel.room.type.availability'].search([
('wpushed', '=', False),
('date', '>=', date_utils.now(hours=False).strftime(
DEFAULT_SERVER_DATE_FORMAT))
])
room_types = room_type_avail_ids.mapped('room_type_id')
avails = []
for room_type in room_types:
room_type_avails = room_type_avail_ids.filtered(
lambda x: x.room_type_id.id == room_type.id)
days = []
for room_type_avail in room_type_avails:
room_type_avail.with_context({
'wubook_action': False}).write({'wpushed': True})
wavail = room_type_avail.avail
if wavail > room_type_avail.wmax_avail:
wavail = room_type_avail.wmax_avail
date_dt = date_utils.get_datetime(
room_type_avail.date,
dtformat=DEFAULT_SERVER_DATE_FORMAT)
days.append({
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
'avail': wavail,
'no_ota': room_type_avail.no_ota and 1 or 0,
# 'booked': room_type_avail.booked and 1 or 0,
})
avails.append({'id': room_type.wrid, 'days': days})
_logger.info("UPDATING AVAILABILITY IN WUBOOK...")
_logger.info(avails)
if any(avails):
self.backend_adapter.update_availability(avails)
return True
@api.model @api.model
def push_priceplans(self): def push_priceplans(self):
unpushed = self.env['product.pricelist.item'].search([ unpushed = self.env['product.pricelist.item'].search([

View File

@@ -238,9 +238,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
# the same transaction an cancellation) # the same transaction an cancellation)
if crcode in failed_reservations: if crcode in failed_reservations:
self.create_issue( self.create_issue(
'reservation', section='reservation',
"Can't process a reservation that previusly failed!", internal_emssage="Can't process a reservation that previusly failed!",
'', channel_object_id=book['reservation_code']) channel_object_id=book['reservation_code'])
continue continue
# Get dates for the reservation (GMT->UTC) # Get dates for the reservation (GMT->UTC)
@@ -342,10 +342,10 @@ class HotelChannelConnectorImporter(AbstractComponent):
], limit=1) ], limit=1)
if not room_type: if not room_type:
self.create_issue( self.create_issue(
'reservation', section='reservation',
"Can't found any room type associated to '%s' \ internal_message="Can't found any room type associated to '%s' \
in this hotel" % book['rooms'], in this hotel" % book['rooms'],
'', channel_object_id=book['reservation_code']) channel_object_id=book['reservation_code'])
failed_reservations.append(crcode) failed_reservations.append(crcode)
continue continue
@@ -376,9 +376,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
) )
if vals['price_unit'] != book['amount']: if vals['price_unit'] != book['amount']:
self.create_issue( self.create_issue(
'reservation', section='reservation',
"Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']), internal_message="Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']),
'', channel_object_id=book['reservation_code']) channel_object_id=book['reservation_code'])
free_rooms = room_type.odoo_id.check_availability_room( free_rooms = room_type.odoo_id.check_availability_room(
checkin_str, checkin_str,
@@ -439,9 +439,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
}) })
reservations.append((0, False, vals)) reservations.append((0, False, vals))
self.create_issue( self.create_issue(
'reservation', section='reservation',
"Reservation imported with overbooking state", internal_message="Reservation imported with overbooking state",
'', channel_object_id=rcode) channel_object_id=rcode)
dates_checkin = [False, False] dates_checkin = [False, False]
dates_checkout = [False, False] dates_checkout = [False, False]
split_booking = False split_booking = False
@@ -458,9 +458,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
if split_booking: if split_booking:
self.create_issue( self.create_issue(
'reservation', section='reservation',
"Reservation Splitted", internal_message="Reservation Splitted",
'', channel_object_id=rcode) channel_object_id=rcode)
# Create Folio # Create Folio
if not any(failed_reservations) and any(reservations): if not any(failed_reservations) and any(reservations):
@@ -498,9 +498,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
processed_rids.append(rcode) processed_rids.append(rcode)
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'reservation', section='reservation',
err.data['message'], internal_message=err.data['message'],
'', channel_object_id=rcode) channel_object_id=rcode)
failed_reservations.append(crcode) failed_reservations.append(crcode)
return (processed_rids, any(failed_reservations), return (processed_rids, any(failed_reservations),
checkin_utc_dt, checkout_utc_dt) checkin_utc_dt, checkout_utc_dt)
@@ -692,9 +692,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
count = self._generate_pricelists(results) count = self._generate_pricelists(results)
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'plan', section='plan',
_("Can't get pricing plans from wubook"), internal_message=_("Can't get pricing plans from wubook"),
err.data['message']) channel_message=err.data['message'])
return 0 return 0
return count return count
@@ -709,9 +709,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'plan', section='plan',
_("Can't fetch plan prices from wubook"), internal_message=_("Can't fetch plan prices from wubook"),
err.data['message']) channel_message=err.data['message'])
return False return False
return True return True
@@ -732,9 +732,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results) self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'plan', section='plan',
"Can't fetch all plan prices from wubook!", internal_message="Can't fetch all plan prices from wubook!",
err.data['message'], channel_message=err.data['message'],
channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to) channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to)
return False return False
return no_errors return no_errors
@@ -746,9 +746,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
count = self._generate_restrictions(results) count = self._generate_restrictions(results)
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'rplan', section='rplan',
_("Can't fetch restriction plans from wubook"), internal_message=_("Can't fetch restriction plans from wubook"),
err.data['message']) channel_message=err.data['message'])
return 0 return 0
return count return count
@@ -763,9 +763,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
self._generate_restriction_items(results) self._generate_restriction_items(results)
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'rplan', section='rplan',
_("Can't fetch plan restrictions from wubook"), internal_message=_("Can't fetch plan restrictions from wubook"),
err.data['message'], channel_message=err.data['message'],
channel_object_id=channel_restriction_plan_id, channel_object_id=channel_restriction_plan_id,
dfrom=date_from, dto=date_to) dfrom=date_from, dto=date_to)
return False return False

View File

@@ -13,12 +13,6 @@
parent="menu_channel_connector_root" parent="menu_channel_connector_root"
action="action_channel_backend"/> action="action_channel_backend"/>
<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_issue_menu" <menuitem id="hotel_channel_connector_issue_menu"
name="Issues" name="Issues"
sequence="3" sequence="3"

View File

@@ -5,7 +5,7 @@ from . import channel_binding
from . import channel_backend from . import channel_backend
from . import hotel_room_type from . import hotel_room_type
from . import product_pricelist from . import product_pricelist
from . import inherited_product_pricelist_item from . import product_pricelist_item
from . import hotel_room_type_restriction from . import hotel_room_type_restriction
from . import hotel_room_type_restriction_item from . import hotel_room_type_restriction_item
from . import hotel_room_type_availability from . import hotel_room_type_availability
@@ -14,4 +14,3 @@ from . import inherited_hotel_folio
from . import inherited_res_partner from . import inherited_res_partner
from . import channel_ota_info from . import channel_ota_info
from . import hotel_channel_connector_issue from . import hotel_channel_connector_issue
from . import res_config

View File

@@ -31,6 +31,26 @@ class ChannelBackend(models.Model):
pkey = fields.Char('Channel Service PKey') pkey = fields.Char('Channel Service PKey')
security_token = fields.Char('Channel Service Security Token') security_token = fields.Char('Channel Service Security Token')
avail_from = fields.Date('Availability From')
avail_to = fields.Date('Availability To')
restriction_from = fields.Date('Restriction From')
restriction_to = fields.Date('Restriction To')
restriction_id = fields.Many2one('channel.hotel.room.type.restriction',
'Channel Restriction')
pricelist_from = fields.Date('Pricelist From')
pricelist_to = fields.Date('Pricelist To')
pricelist_id = fields.Many2one('channel.product.pricelist',
'Channel Product Pricelist')
issue_ids = fields.One2many('hotel.channel.connector.issue',
'backend_id',
string='Issues')
ota_ids = fields.One2many('channel.ota.info',
'backend_id',
string="OTA's")
@api.multi @api.multi
def generate_key(self): def generate_key(self):
for record in self: for record in self:
@@ -57,6 +77,55 @@ class ChannelBackend(models.Model):
channel_ota_info_obj.import_otas_info(backend) channel_ota_info_obj.import_otas_info(backend)
return True return True
@api.multi
def import_availability(self):
channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
for backend in self:
channel_hotel_room_type_avail_obj.import_availability(backend)
return True
@api.multi
def push_availability(self):
channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
for backend in self:
channel_hotel_room_type_avail_obj.push_availability(backend)
return True
@api.multi
def import_restriction_plans(self):
channel_hotel_room_type_restr_obj = self.env['channel.hotel.room.type.restriction']
for backend in self:
channel_hotel_room_type_restr_obj.import_restriction_plans(backend)
return True
@api.multi
def import_restriction_values(self):
channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item']
for backend in self:
channel_hotel_restr_item_obj.import_restriction_values(backend)
return True
@api.multi
def push_restriction(self):
channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item']
for backend in self:
channel_hotel_restr_item_obj.push_restriction(backend)
return True
@api.multi
def import_pricelist_plans(self):
channel_product_pricelist_obj = self.env['channel.product.pricelist']
for backend in self:
channel_product_pricelist_obj.import_price_plans(backend)
return True
@api.multi
def import_pricelist_values(self):
channel_product_pricelist_item_obj = self.env['channel.product.pricelist.item']
for backend in self:
channel_product_pricelist_item_obj.import_pricelist_values(backend)
return True
@contextmanager @contextmanager
@api.multi @api.multi
def work_on(self, model_name, **kwargs): def work_on(self, model_name, **kwargs):

View File

@@ -16,7 +16,21 @@ class ChannelBinding(models.AbstractModel):
required=True, required=True,
ondelete='restrict') ondelete='restrict')
external_id = fields.Char(string='ID on Channel')
_sql_constraints = [ _sql_constraints = [
('channel_uniq', 'unique(backend_id, external_id)', ('channel_uniq', 'unique(backend_id, external_id)',
'A binding already exists with the same Channel ID.'), 'A binding already exists with the same Channel ID.'),
] ]
@api.model
def create_issue(self, **kwargs):
self.env['hotel.channel.connector.issue'].sudo().create({
'backend_id': kwargs.get('backend', self.backend_id.id),
'section': kwargs.get('section', False),
'internal_message': kwargs.get('internal_message', False),
'channel_object_id': kwargs.get('channel_object_id', False),
'channel_message': kwargs.get('channel_message', False),
'date_start': kwargs.get('dfrom', False),
'date_end': kwargs.get('dto', False),
})

View File

@@ -4,6 +4,7 @@
from odoo import api, models, fields from odoo import api, models, fields
from odoo.addons.queue_job.job import job from odoo.addons.queue_job.job import job
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
class ChannelOtaInfo(models.Model): class ChannelOtaInfo(models.Model):
_name = 'channel.ota.info' _name = 'channel.ota.info'
@@ -19,7 +20,14 @@ class ChannelOtaInfo(models.Model):
def import_otas_info(self, backend): def import_otas_info(self, backend):
with backend.work_on(self._name) as work: with backend.work_on(self._name) as work:
importer = work.component(usage='ota.info.importer') importer = work.component(usage='ota.info.importer')
return importer.import_otas_info() try:
return importer.import_otas_info()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='room',
internal_message=_("Can't import ota info from WuBook"),
channel_message=err.data['message'])
class HotelRoomTypeAdapter(Component): class HotelRoomTypeAdapter(Component):
_name = 'channel.ota.info.adapter' _name = 'channel.ota.info.adapter'

View File

@@ -3,7 +3,6 @@
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component 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.connector.components.mapper import mapping
from odoo import fields, api, _ from odoo import fields, api, _
from odoo.tools import ( from odoo.tools import (
@@ -19,30 +18,26 @@ class ChannelOtaInfoImporter(Component):
@api.model @api.model
def import_otas_info(self): def import_otas_info(self):
count = 0 results = self.backend_adapter.get_channels_info()
try:
results = self.backend_adapter.get_channels_info()
channel_ota_info_obj = self.env['channel.ota.info'] channel_ota_info_obj = self.env['channel.ota.info']
ota_info_mapper = self.component(usage='import.mapper', ota_info_mapper = self.component(usage='import.mapper',
model_name='channel.ota.info') model_name='channel.ota.info')
for ota_id in results.keys(): for ota_id in results.keys():
vals = { vals = {
'id': ota_id, 'id': ota_id,
'name': results[ota_id]['name'], 'name': results[ota_id]['name'],
'ical': results[ota_id]['ical'] == 1, 'ical': results[ota_id]['ical'] == 1,
} }
map_record = ota_info_mapper.map_record(vals) map_record = ota_info_mapper.map_record(vals)
ota_info_bind = channel_ota_info_obj.search([ ota_info_bind = channel_ota_info_obj.search([
('ota_id', '=', ota_id) ('ota_id', '=', ota_id)
], limit=1) ], limit=1)
if ota_info_bind: if ota_info_bind:
ota_info_bind.write(map_record.values()) ota_info_bind.write(map_record.values())
else: else:
ota_info_bind.create(map_record.values(for_create=True)) ota_info_bind.create(map_record.values(for_create=True))
count = count + 1 count = count + 1
except ChannelConnectorError as err:
self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message'])
return count return count

View File

@@ -9,11 +9,17 @@ class HotelChannelConnectorIssue(models.Model):
_name = 'hotel.channel.connector.issue' _name = 'hotel.channel.connector.issue'
_old_name = 'wubook.issue' _old_name = 'wubook.issue'
backend_id = fields.Many2one('channel.backend',
'Restriction Plan',
required=True,
ondelete='cascade',
index=True)
section = fields.Selection([ section = fields.Selection([
('channel', 'Channel'), ('channel', 'Channel'),
('reservation', 'Reservation'), ('reservation', 'Reservation'),
('rplan', 'Restriction Plan'), ('restriction', 'Restriction Plan'),
('plan', 'Price Plan'), ('pricelist', 'Price Plan'),
('room', 'Room'), ('room', 'Room'),
('avail', 'Availability')], required=True) ('avail', 'Availability')], required=True)
to_read = fields.Boolean("To Read", default=True) to_read = fields.Boolean("To Read", default=True)
@@ -41,10 +47,9 @@ class HotelChannelConnectorIssue(models.Model):
reserv_ids.append(record.channel_object_id) reserv_ids.append(record.channel_object_id)
record.to_read = False record.to_read = False
if any(reserv_ids): if any(reserv_ids):
res = self.env['hotel.channel.connector'].mark_bookings(reserv_ids) with self.backend_id.work_on('channel.hotel.reservation') as work:
if not res: exporter = work.component(usage='hotel.reservation.exporter')
raise ValidationError( return exporter.mark_bookings(reserv_ids)
("Can't mark reservation as readed in Channel!"))
@api.model @api.model
def _needaction_domain_get(self): def _needaction_domain_get(self):

View File

@@ -25,7 +25,6 @@ class ChannelHotelReservation(models.Model):
string='Reservation', string='Reservation',
required=True, required=True,
ondelete='cascade') ondelete='cascade')
channel_reservation_id = fields.Char("Channel Reservation ID", readonly=True, old_name='wrid')
ota_id = fields.Many2one('channel.ota.info', ota_id = fields.Many2one('channel.ota.info',
string='Channel OTA ID', string='Channel OTA ID',
readonly=True, readonly=True,

View File

@@ -26,9 +26,9 @@ class HotelReservationImporter(Component):
rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids) rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids)
if rcodeb != 0: if rcodeb != 0:
self.create_issue( self.create_issue(
'wubook', backend=self.backend_adapter.id,
_("Problem trying mark bookings (%s)") % str(processed_rids), section='wubook',
'') internal_message=_("Problem trying mark bookings (%s)") % str(processed_rids))
# Update Odoo availability (don't wait for wubook) # Update Odoo availability (don't wait for wubook)
# This cause abuse service in first import!! # This cause abuse service in first import!!
if checkin_utc_dt and checkout_utc_dt: if checkin_utc_dt and checkout_utc_dt:
@@ -37,8 +37,9 @@ class HotelReservationImporter(Component):
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)) checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
'reservation', backend=self.backend_adapter.id,
_("Can't process reservations from wubook"), section='reservation',
err.data['message']) internal_message=_("Can't process reservations from wubook"),
channel_message=err.data['message'])
return False return False
return True return True

View File

@@ -7,6 +7,7 @@ from odoo.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if from odoo.addons.component_event import skip_if
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class ChannelHotelRoomType(models.Model): class ChannelHotelRoomType(models.Model):
@@ -19,7 +20,6 @@ class ChannelHotelRoomType(models.Model):
string='Room Type', string='Room Type',
required=True, required=True,
ondelete='cascade') ondelete='cascade')
channel_room_id = fields.Char("Channel Room ID", readonly=True, old_name='wrid')
channel_short_code = fields.Char("Channel Short Code", readonly=True, old_name='wscode') 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') ota_capacity = fields.Integer("OTA's Capacity", default=1, old_name='wcapacity')
@@ -37,7 +37,14 @@ class ChannelHotelRoomType(models.Model):
def import_rooms(self, backend): def import_rooms(self, backend):
with backend.work_on(self._name) as work: with backend.work_on(self._name) as work:
importer = work.component(usage='hotel.room.type.importer') importer = work.component(usage='hotel.room.type.importer')
return importer.get_rooms() try:
return importer.get_rooms()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='room',
internal_message=_("Can't import rooms from WuBook"),
channel_message=err.data['message'])
@api.constrains('ota_capacity') @api.constrains('ota_capacity')
def _check_ota_capacity(self): def _check_ota_capacity(self):
@@ -57,30 +64,51 @@ class ChannelHotelRoomType(models.Model):
@api.multi @api.multi
def create_room(self): def create_room(self):
self.ensure_one() self.ensure_one()
if not self.channel_room_id: if not self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.exporter') exporter = work.component(usage='hotel.room.type.exporter')
exporter.create_room(self) try:
exporter.create_room(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='room',
internal_message=_("Can't create room in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def modify_room(self): def modify_room(self):
self.ensure_one() self.ensure_one()
if self.channel_room_id: if self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.exporter') exporter = work.component(usage='hotel.room.type.exporter')
exporter.modify_room(self) try:
exporter.modify_room(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='room',
internal_message=_("Can't modify rooms in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def delete_room(self): def delete_room(self):
self.ensure_one() self.ensure_one()
if self.channel_room_id: if self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.exporter') exporter = work.component(usage='hotel.room.type.exporter')
exporter.delete_room(self) try:
exporter.delete_room(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='room',
internal_message=_("Can't delete room in WuBook"),
channel_message=err.data['message'])
class HotelRoomType(models.Model): class HotelRoomType(models.Model):
_inherit = 'hotel.room.type' _inherit = 'hotel.room.type'
@@ -102,9 +130,7 @@ class HotelRoomType(models.Model):
self._compute_capacity() self._compute_capacity()
@api.multi @api.multi
def get_restrictions(self, date): def get_restrictions(self, date, restriction_plan_id):
restriction_plan_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id'))
self.ensure_one() self.ensure_one()
restriction = self.env['hotel.room.type.restriction.item'].search([ restriction = self.env['hotel.room.type.restriction.item'].search([
('date', '=', date), ('date', '=', date),
@@ -142,7 +168,9 @@ class BindingHotelRoomTypeListener(Component):
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
if 'name' in fields or 'list_price' in fields: if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields or \
'room_ids' in fields:
# FIXME: Supossed that only exists one channel connector per record
record.channel_bind_ids[0].modify_room() record.channel_bind_ids[0].modify_room()
# @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))

View File

@@ -3,7 +3,6 @@
import logging import logging
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api, _ from odoo import api, _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -15,41 +14,31 @@ class HotelRoomTypeExporter(Component):
@api.model @api.model
def modify_room(self, binding): def modify_room(self, binding):
try: return self.backend_adapter.modify_room(
return self.backend_adapter.modify_room( binding.external_id,
binding.channel_room_id, binding.name,
binding.name, binding.ota_capacity,
binding.ota_capacity, binding.list_price,
binding.list_price, binding.total_rooms_count,
binding.total_rooms_count, binding.channel_short_code)
binding.channel_short_code)
except ChannelConnectorError as err:
self.create_issue('room', _("Can't modify rooms in WuBook"), err.data['message'])
@api.model @api.model
def delete_room(self, binding): def delete_room(self, binding):
try: return self.backend_adapter.delete_room(binding.external_id)
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 @api.model
def create_room(self, binding): def create_room(self, binding):
try: seq_obj = self.env['ir.sequence']
seq_obj = self.env['ir.sequence'] short_code = seq_obj.next_by_code('hotel.room.type')[:4]
short_code = seq_obj.next_by_code('hotel.room.type')[:4] external_id = self.backend_adapter.create_room(
external_id = self.backend_adapter.create_room( short_code,
short_code, binding.name,
binding.name, binding.ota_capacity,
binding.ota_capacity, binding.list_price,
binding.list_price, binding.total_rooms_count
binding.total_rooms_count )
) binding.write({
binding.write({ 'external_id': external_id,
'channel_room_id': external_id, 'channel_short_code': short_code,
'channel_short_code': short_code, })
}) self.binder.bind(external_id, binding)
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

@@ -5,7 +5,6 @@ import logging
from datetime import timedelta from datetime import timedelta
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component 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.connector.components.mapper import mapping
from odoo.addons.hotel import date_utils from odoo.addons.hotel import date_utils
from odoo import fields, api, _ from odoo import fields, api, _
@@ -24,27 +23,22 @@ class HotelRoomTypeImporter(Component):
@api.model @api.model
def get_rooms(self): def get_rooms(self):
count = 0 results = self.backend_adapter.fetch_rooms()
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'])
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([
('external_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))
return count return count
@api.model @api.model
@@ -61,18 +55,12 @@ class HotelRoomTypeImporter(Component):
if dfrom_dt > dto_dt: if dfrom_dt > dto_dt:
dfrom_dt, dto_dt = dto_dt, dfrom_dt dfrom_dt, dto_dt = dto_dt, dfrom_dt
try: results = self.backend_adapter.fetch_rooms_values(
results = self.backend_adapter.fetch_rooms_values( dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), rooms)
rooms) self._generate_room_values(dfrom, dto, results,
self._generate_room_values(dfrom, dto, results, set_max_avail=set_max_avail)
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 @api.model
def _map_room_values_availability(self, day_vals, set_max_avail): def _map_room_values_availability(self, day_vals, set_max_avail):
@@ -158,7 +146,7 @@ class HotelRoomTypeImportMapper(Component):
_apply_on = 'channel.hotel.room.type' _apply_on = 'channel.hotel.room.type'
direct = [ direct = [
('id', 'channel_room_id'), ('id', 'external_id'),
('shortname', 'channel_short_code'), ('shortname', 'channel_short_code'),
('occupancy', 'ota_capacity'), ('occupancy', 'ota_capacity'),
('price', 'list_price'), ('price', 'list_price'),

View File

@@ -3,3 +3,4 @@
from . import common from . import common
from . import importer from . import importer
from . import exporter

View File

@@ -2,12 +2,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta from datetime import timedelta
from odoo import api, models, fields from odoo import api, models, fields, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if from odoo.addons.component_event import skip_if
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.hotel_channel_connector.components.backend_adapter import ( from odoo.addons.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT) DEFAULT_WUBOOK_DATE_FORMAT)
@@ -15,7 +16,7 @@ class ChannelHotelRoomTypeAvailability(models.Model):
_name = 'channel.hotel.room.type.availability' _name = 'channel.hotel.room.type.availability'
_inherit = 'channel.binding' _inherit = 'channel.binding'
_inherits = {'hotel.room.type.availability': 'odoo_id'} _inherits = {'hotel.room.type.availability': 'odoo_id'}
_description = 'Channel Product Pricelist' _description = 'Channel Availability'
@api.model @api.model
def _default_channel_max_avail(self): def _default_channel_max_avail(self):
@@ -27,6 +28,8 @@ class ChannelHotelRoomTypeAvailability(models.Model):
string='Pricelist', string='Pricelist',
required=True, required=True,
ondelete='cascade') ondelete='cascade')
no_ota = fields.Boolean('No OTA', default=False)
booked = fields.Boolean('Booked', default=False, readonly=True)
channel_max_avail = fields.Integer("Max. Channel Avail", channel_max_avail = fields.Integer("Max. Channel Avail",
default=_default_channel_max_avail, default=_default_channel_max_avail,
old_name='wmax_avail') old_name='wmax_avail')
@@ -44,19 +47,45 @@ class ChannelHotelRoomTypeAvailability(models.Model):
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def update_availability(self): def update_availability(self, backend):
self.ensure_one() with backend.work_on(self._name) as work:
if self._context.get('channel_action', True): exporter = work.component(usage='hotel.room.type.availability.exporter')
with self.backend_id.work_on(self._name) as work: try:
adapter = work.component(usage='backend.adapter') return exporter.update_availability(self)
date_dt = fields.Date.from_string(self.date) except ChannelConnectorError as err:
adapter.update_availability([{ self.create_issue(
'id': self.odoo_id.room_type_id.channel_room_id, backend=backend.id,
'days': [{ section='avail',
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT), internal_message=_("Can't update availability in WuBook"),
'avail': self.odoo_id.avail, channel_message=err.data['message'])
}],
}]) @job(default_channel='root.channel')
@api.model
def import_availability(self, backend):
with backend.work_on(self._name) as work:
importer = work.component(usage='hotel.room.type.availability.importer')
try:
return importer.get_availability(backend.avail_from, backend.avail_to)
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='avail',
internal_message=_("Can't import availability from WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel')
@api.model
def push_availability(self, backend):
with backend.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.availability.exporter')
try:
return exporter.push_availability()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='avail',
internal_message=_("Can't update availability in WuBook"),
channel_message=err.data['message'])
class HotelRoomTypeAvailability(models.Model): class HotelRoomTypeAvailability(models.Model):
_inherit = 'hotel.room.type.availability' _inherit = 'hotel.room.type.availability'
@@ -79,16 +108,15 @@ class HotelRoomTypeAvailability(models.Model):
if record.avail > max_avail: if record.avail > max_avail:
issue_obj.sudo().create({ issue_obj.sudo().create({
'section': 'avail', 'section': 'avail',
'message': _(r"The new availability can't be greater than \ 'internal_message': _(r"The new availability can't be greater than \
the actual availability \ the max. availability \
\n[%s]\nInput: %d\Limit: %d") % (record.room_type_id.name, (%s) [Input: %d\Max: %d]") % (record.room_type_id.name,
record.avail, record.avail,
record), max_avail),
'channel_id': record.room_type_id.channel_bind_ids[0].channel_plan_id,
'date_start': record.date, 'date_start': record.date,
'date_end': record.date, 'date_end': record.date,
}) })
# Auto-Fix wubook availability # Auto-Fix channel availability
self._event('on_fix_channel_availability').notify(record) self._event('on_fix_channel_availability').notify(record)
return super(HotelRoomTypeAvailability, self)._check_avail() return super(HotelRoomTypeAvailability, self)._check_avail()
@@ -97,12 +125,6 @@ class HotelRoomTypeAvailability(models.Model):
if self.room_type_id: if self.room_type_id:
self.channel_max_avail = self.room_type_id.total_rooms_count self.channel_max_avail = self.room_type_id.total_rooms_count
@api.multi
def write(self, vals):
if self._context.get('channel_action', True):
vals.update({'channel_pushed': False})
return super(HotelRoomTypeAvailability, self).write(vals)
@api.model @api.model
def refresh_availability(self, checkin, checkout, product_id): def refresh_availability(self, checkin, checkout, product_id):
date_start = fields.Date.from_string(checkin) date_start = fields.Date.from_string(checkin)
@@ -143,11 +165,41 @@ class HotelRoomTypeAvailability(models.Model):
'avail': avail, 'avail': avail,
}) })
class HotelRoomTypeAvailabilityAdapter(Component):
_name = 'channel.hotel.room.type.availability.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.hotel.room.type.availability'
def fetch_rooms_values(self, date_from, date_to, rooms=False):
return super(HotelRoomTypeAvailabilityAdapter, self).fetch_rooms_values(
date_from,
date_to,
rooms)
def update_availability(self, rooms_avail):
return super(HotelRoomTypeAvailabilityAdapter, self).update_availability(
rooms_avail)
class BindingHotelRoomTypeAvailabilityListener(Component):
_name = 'binding.hotel.room.type.listener'
_inherit = 'base.connector.listener'
_apply_on = ['hotel.room.type.availability']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'avail' in fields:
record.channel_bind_ids.write({'channel_pushed': False})
class ChannelBindingHotelRoomTypeAvailabilityListener(Component): class ChannelBindingHotelRoomTypeAvailabilityListener(Component):
_name = 'channel.binding.hotel.room.type.availability.listener' _name = 'channel.binding.hotel.room.type.availability.listener'
_inherit = 'base.connector.listener' _inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.room.type.availability'] _apply_on = ['channel.hotel.room.type.availability']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'avail' in fields:
record.channel_pushed = False
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_fix_channel_availability(self, record, fields=None): def on_fix_channel_availability(self, record, fields=None):
record.with_delay(priority=20).update_availability() record.update_availability()

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).
import logging
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api, fields, _
from odoo.addons.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
_logger = logging.getLogger(__name__)
class HotelRoomTypeAvailabilityExporter(Component):
_name = 'channel.hotel.room.type.availability.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.hotel.room.type.availability']
_usage = 'hotel.room.type.availability.exporter'
@api.model
def update_availability(self, binding):
if any(binding.room_type_id.channel_bind_ids):
sday_dt = fields.Date.from_string(binding.date)
# FIXME: Supossed that only exists one channel connector per record
binding.channel_pushed = True
return self.backend_adapter.update_availability({
'id': binding.room_type_id.channel_bind_ids[0].channel_room_id,
'days': [{
'date': sday_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
'avail': binding.avail,
'no_ota': binding.no_ota,
}],
})
def push_availability(self):
channel_room_type_avail_ids = self.env['channel.hotel.room.type.availability'].search([
('channel_pushed', '=', False),
('date', '>=', fields.Date.today())
])
room_types = channel_room_type_avail_ids.mapped('room_type_id')
avails = []
for room_type in room_types:
if any(room_type.channel_bind_ids):
channel_room_type_avails = channel_room_type_avail_ids.filtered(
lambda x: x.room_type_id.id == room_type.id)
days = []
for channel_room_type_avail in channel_room_type_avails:
channel_room_type_avail.channel_pushed = True
cavail = channel_room_type_avail.avail
if channel_room_type_avail.channel_max_avail >= 0 and \
cavail > channel_room_type_avail.channel_max_avail:
cavail = channel_room_type_avail.channel_max_avail
date_dt = fields.Date.from_string(channel_room_type_avail.date)
days.append({
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
'avail': cavail,
'no_ota': channel_room_type_avail.no_ota and 1 or 0,
# 'booked': room_type_avail.booked and 1 or 0,
})
avails.append({'id': room_type.channel_bind_ids[0].channel_room_id, 'days': days})
_logger.info("UPDATING AVAILABILITY IN WUBOOK...")
_logger.info(avails)
if any(avails):
self.backend_adapter.update_availability(avails)

View File

@@ -2,11 +2,11 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging import logging
from datetime import timedelta from datetime import date, timedelta
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping from odoo.addons.connector.components.mapper import mapping, only_create
from odoo.addons.hotel import date_utils from odoo.addons.hotel import date_utils
from odoo import fields, api, _ from odoo import fields, api, _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -18,6 +18,53 @@ class HotelRoomTypeAvailabilityImporter(Component):
_apply_on = ['channel.hotel.room.type.availability'] _apply_on = ['channel.hotel.room.type.availability']
_usage = 'hotel.room.type.availability.importer' _usage = 'hotel.room.type.availability.importer'
@api.model
def get_availability(self, date_from, date_to):
now_dt = date.today()
dfrom_dt = fields.Date.from_string(date_from)
dto_dt = fields.Date.from_string(date_to)
if dfrom_dt < now_dt:
dfrom_dt = now_dt
if dfrom_dt > dto_dt:
dfrom_dt, dto_dt = dto_dt, dfrom_dt
if dto_dt < now_dt:
return True
results = self.backend_adapter.fetch_rooms_values(date_from, date_to)
channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
channel_room_type_obj = self.env['channel.hotel.room.type']
room_avail_mapper = self.component(
usage='import.mapper',
model_name='channel.hotel.room.type.availability')
count = len(results)
for room_k, room_v in results.items():
iter_day = dfrom_dt
channel_room_type = channel_room_type_obj.search([
('channel_room_id', '=', room_k)
], limit=1)
if channel_room_type:
for room in room_v:
room.update({
'room_type_id': channel_room_type.odoo_id.id,
'date': fields.Date.to_string(iter_day),
})
map_record = room_avail_mapper.map_record(room)
room_type_avail_bind = channel_room_type_avail_obj.search([
('room_type_id', '=', room['room_type_id']),
('date', '=', room['date'])
], limit=1)
if room_type_avail_bind:
room_type_avail_bind.with_context({
'wubook_action': False
}).write(map_record.values())
else:
room_type_avail_bind = channel_room_type_avail_obj.with_context({
'wubook_action': False
}).create(map_record.values(for_create=True))
iter_day += timedelta(days=1)
return count
class HotelRoomTypeAvailabilityImportMapper(Component): class HotelRoomTypeAvailabilityImportMapper(Component):
_name = 'channel.hotel.room.type.availability.import.mapper' _name = 'channel.hotel.room.type.availability.import.mapper'
@@ -28,10 +75,18 @@ class HotelRoomTypeAvailabilityImportMapper(Component):
('no_ota', 'no_ota'), ('no_ota', 'no_ota'),
('booked', 'booked'), ('booked', 'booked'),
('avail', 'avail'), ('avail', 'avail'),
('room_type_id', 'room_type_id'),
('date', 'date'), ('date', 'date'),
] ]
@only_create
@mapping
def channel_pushed(self, record):
return {'channel_pushed': True}
@mapping @mapping
def backend_id(self, record): def backend_id(self, record):
return {'backend_id': self.backend_record.id} return {'backend_id': self.backend_record.id}
@mapping
def room_type_id(self, record):
return {'room_type_id': record['room_type_id']}

View File

@@ -3,3 +3,4 @@
from . import common from . import common
from . import importer from . import importer
from . import exporter

View File

@@ -1,11 +1,14 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es> # Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import api, models, fields from odoo import api, models, fields
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if from odoo.addons.component_event import skip_if
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
_logger = logging.getLogger(__name__)
class ChannelHotelRoomTypeRestriction(models.Model): class ChannelHotelRoomTypeRestriction(models.Model):
_name = 'channel.hotel.room.type.restriction' _name = 'channel.hotel.room.type.restriction'
@@ -17,57 +20,71 @@ class ChannelHotelRoomTypeRestriction(models.Model):
string='Hotel Virtual Room Restriction', string='Hotel Virtual Room Restriction',
required=True, required=True,
ondelete='cascade') ondelete='cascade')
channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid')
is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan')
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def create_plan(self): def create_plan(self):
self.ensure_one() self.ensure_one()
if self._context.get('channel_action', True): if not self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter') exporter = work.component(usage='hotel.room.type.restriction.exporter')
try: try:
channel_plan_id = adapter.create_rplan(self.name) exporter.create_rplan(self)
if channel_plan_id: except ChannelConnectorError as err:
self.channel_plan_id = channel_plan_id self.create_issue(
except ValidationError as e: backend=self.backend_id.id,
self.create_issue('room', "Can't create restriction plan on channel", "sss") section='restriction',
internal_message=_("Can't create restriction plan in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def update_plan_name(self): def update_plan_name(self):
self.ensure_one() self.ensure_one()
if self._context.get('channel_action', True): if self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter') exporter = work.component(usage='hotel.room.type.restriction.exporter')
try: try:
adapter.rename_rplan(self.channel_plan_id, self.name) exporter.rename_rplan(self)
except ValidationError as e: except ChannelConnectorError as err:
self.create_issue('room', "Can't update restriction plan name on channel", "sss") self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=_("Can't modify restriction plan in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def delete_plan(self): def delete_plan(self):
self.ensure_one() self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id: if self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter') exporter = work.component(usage='hotel.room.type.restriction.exporter')
try: try:
adapter.delete_rplan(self.channel_plan_id) exporter.delete_rplan(self)
except ValidationError as e: except ChannelConnectorError as err:
self.create_issue('room', "Can't delete restriction plan on channel", "sss") self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=_("Can't delete restriction plan in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@api.multi @api.model
def import_restriction_plans(self): def import_restriction_plans(self, backend):
if self._context.get('channel_action', True): with backend.work_on(self._name) as work:
with self.backend_id.work_on(self._name) as work: importer = work.component(usage='hotel.room.type.restriction.importer')
importer = work.component(usage='channel.importer') try:
return importer.import_restriction_plans() return importer.import_restriction_plans()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='restriction',
internal_message=_("Can't fetch restriction plans from wubook"),
channel_message=err.data['message'])
class HotelRoomTypeRestriction(models.Model): class HotelRoomTypeRestriction(models.Model):
_inherit = 'hotel.room.type.restriction' _inherit = 'hotel.room.type.restriction'
@@ -85,12 +102,44 @@ class HotelRoomTypeRestriction(models.Model):
names = [] names = []
for name in org_names: for name in org_names:
restriction_id = room_type_restriction_obj.browse(name[0]) restriction_id = room_type_restriction_obj.browse(name[0])
if restriction_id.channel_bind_ids.channel_plan_id: if any(restriction_id.channel_bind_ids) and \
names.append((name[0], '%s (WuBook)' % name[1])) restriction_id.channel_bind_ids[0].external_id:
names.append((
name[0],
'%s (%s Backend)' % (name[1],
restriction_id.channel_bind_ids[0].backend_id.name),
))
else: else:
names.append((name[0], name[1])) names.append((name[0], name[1]))
return names return names
class HotelRoomTypeRestrictionAdapter(Component):
_name = 'channel.hotel.room.type.restriction.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.hotel.room.type.restriction'
def rplan_rplans(self):
return super(HotelRoomTypeRestrictionAdapter, self).rplan_rplans()
def create_rplan(self, name):
return super(HotelRoomTypeRestrictionAdapter, self).create_rplan(name)
def delete_rplan(self, external_id):
return super(HotelRoomTypeRestrictionAdapter, self).delete_rplan(external_id)
def rename_rplan(self, external_id, new_name):
return super(HotelRoomTypeRestrictionAdapter, self).rename_rplan(external_id, new_name)
class BindingHotelRoomTypeListener(Component):
_name = 'binding.hotel.room.type.restriction.listener'
_inherit = 'base.connector.listener'
_apply_on = ['hotel.room.type.restriction']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if any(record.channel_bind_ids) and 'name' in fields:
record.channel_bind_ids[0].update_plan_name()
class ChannelBindingHotelRoomTypeRestrictionListener(Component): class ChannelBindingHotelRoomTypeRestrictionListener(Component):
_name = 'channel.binding.hotel.room.type.restriction.listener' _name = 'channel.binding.hotel.room.type.restriction.listener'
_inherit = 'base.connector.listener' _inherit = 'base.connector.listener'
@@ -98,13 +147,13 @@ class ChannelBindingHotelRoomTypeRestrictionListener(Component):
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None): def on_record_create(self, record, fields=None):
record.with_delay(priority=20).create_plan() record.create_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None): def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).delete_plan() record.delete_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
if 'name' in fields: if 'name' in fields:
record.with_delay(priority=20).update_plan_name() record.update_plan_name()

View File

@@ -0,0 +1,29 @@
# 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 import api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeRestrictionExporter(Component):
_name = 'channel.hotel.room.type.restriction.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.hotel.room.type.restriction']
_usage = 'hotel.room.type.restriction.exporter'
@api.model
def rename_rplan(self, binding):
return self.backend_adapter.rename_rplan(
binding.external_id,
binding.name)
@api.model
def delete_rplan(self, binding):
return self.backend_adapter.delete_rplan(binding.external_id)
@api.model
def create_rplan(self, binding):
external_id = self.backend_adapter.create_rplan(binding.name)
binding.external_id = external_id
self.binder.bind(external_id, binding)

View File

@@ -5,7 +5,6 @@ import logging
from datetime import timedelta from datetime import timedelta
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component 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.connector.components.mapper import mapping
from odoo.addons.hotel import date_utils from odoo.addons.hotel import date_utils
from odoo import fields, api, _ from odoo import fields, api, _
@@ -18,6 +17,28 @@ class HotelRoomTypeRestrictionImporter(Component):
_apply_on = ['channel.hotel.room.type.restriction'] _apply_on = ['channel.hotel.room.type.restriction']
_usage = 'hotel.room.type.restriction.importer' _usage = 'hotel.room.type.restriction.importer'
@api.model
def import_restriction_plans(self):
results = self.backend_adapter.rplan_rplans()
channel_restriction_obj = self.env['channel.hotel.room.type.restriction']
restriction_mapper = self.component(usage='import.mapper',
model_name='channel.hotel.room.type.restriction')
for plan in results:
plan_record = restriction_mapper.map_record(plan)
plan_bind = channel_restriction_obj.search([
('external_id', '=', str(plan['id']))
], limit=1)
if not plan_bind:
channel_restriction_obj.with_context({
'wubook_action': False,
'rules': plan.get('rules'),
}).create(plan_record.values(for_create=True))
else:
plan_bind.with_context({'wubook_action': False}).write(
plan_record.values())
count = count + 1
return count
class HotelRoomTypeRestrictionImportMapper(Component): class HotelRoomTypeRestrictionImportMapper(Component):
_name = 'channel.hotel.room.type.restriction.import.mapper' _name = 'channel.hotel.room.type.restriction.import.mapper'
@@ -25,11 +46,8 @@ class HotelRoomTypeRestrictionImportMapper(Component):
_apply_on = 'channel.hotel.room.type.restriction' _apply_on = 'channel.hotel.room.type.restriction'
direct = [ direct = [
('no_ota', 'no_ota'), ('name', 'name'),
('booked', 'booked'), ('id', 'external_id'),
('avail', 'avail'),
('room_type_id', 'room_type_id'),
('date', 'date')
] ]
@mapping @mapping

View File

@@ -3,3 +3,4 @@
from . import common from . import common
from . import importer from . import importer
from . import exporter

View File

@@ -6,6 +6,7 @@ from odoo.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if from odoo.addons.component_event import skip_if
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
class ChannelHotelRoomTypeRestrictionItem(models.Model): class ChannelHotelRoomTypeRestrictionItem(models.Model):
_name = 'channel.hotel.room.type.restriction.item' _name = 'channel.hotel.room.type.restriction.item'
@@ -21,10 +22,37 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model):
old_name='wpushed') old_name='wpushed')
@job(default_channel='root.channel') @job(default_channel='root.channel')
@api.multi @api.model
def update_channel_pushed(self, status): def import_restriction_values(self, backend):
self.ensure_one() with backend.work_on(self._name) as work:
self.channel_pushed = status importer = work.component(usage='hotel.room.type.restriction.item.importer')
try:
return importer.import_restriction_values(
backend.restriction_from,
backend.restriction_to,
channel_restr_id=backend.restriction_id)
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='restriction',
internal_message=_("Can't fetch plan restrictions from wubook"),
channel_message=err.data['message'],
channel_object_id=backend.restriction_id,
dfrom=backend.restriction_from, dto=backend.restriction_to)
@job(default_channel='root.channel')
@api.model
def push_restriction(self, backend):
with backend.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.restriction.item.exporter')
try:
return exporter.push_restriction()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='restriction',
internal_message=_("Can't update restrictions in WuBook"),
channel_message=err.data['message'])
class HotelRoomTypeRestrictionItem(models.Model): class HotelRoomTypeRestrictionItem(models.Model):
_inherit = 'hotel.room.type.restriction.item' _inherit = 'hotel.room.type.restriction.item'
@@ -34,15 +62,41 @@ class HotelRoomTypeRestrictionItem(models.Model):
inverse_name='odoo_id', inverse_name='odoo_id',
string='Hotel Channel Connector Bindings') string='Hotel Channel Connector Bindings')
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): class HotelRoomTypeRestrictionItemAdapter(Component):
_name = 'channel.binding.hotel.room.type.restriction.listener' _name = 'channel.hotel.room.type.restriction.item.adapter'
_inherit = 'base.connector.listener' _inherit = 'wubook.adapter'
_apply_on = ['channel.hotel.room.type.restriction'] _apply_on = 'channel.hotel.room.type.restriction.item'
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id):
def on_record_create(self, record, fields=None): return super(HotelRoomTypeRestrictionItemAdapter, self).wired_rplan_get_rplan_values(
record.update_channel_pushed(False) date_from,
date_to,
channel_restriction_plan_id)
class BindingHotelRoomTypeRestrictionItemListener(Component):
_name = 'binding.hotel.room.type.restriction.item.listener'
_inherit = 'base.connector.listener'
_apply_on = ['hotel.room.type.restriction.item']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
record.update_channel_pushed(False) fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival',
'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival',
'date')
fields_checked = [elm for elm in fields_to_check if elm in fields]
if any(fields_checked):
record.channel_bind_ids.write({'channel_pushed': False})
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component):
_name = 'channel.binding.hotel.room.type.restriction.item.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.room.type.restriction.item']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival',
'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival',
'date')
fields_checked = [elm for elm in fields_to_check if elm in fields]
if any(fields_checked):
record.channel_pushed = False

View File

@@ -0,0 +1,93 @@
# 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.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeRestrictionItemExporter(Component):
_name = 'channel.hotel.room.type.restriction.item.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.hotel.room.type.restriction.item']
_usage = 'hotel.room.type.restriction.item.exporter'
@api.model
def update_restriction(self, binding):
if any(binding.restriction_id.channel_bind_ids):
# FIXME: Supossed that only exists one channel connector per record
binding.channel_pushed = True
return self.backend_adapter.update_rplan_values(
binding.restriction_id.channel_bind_ids[0].external_id,
binding.date,
{
'min_stay': binding.min_stay or 0,
'min_stay_arrival': binding.min_stay_arrival or 0,
'max_stay': binding.max_stay or 0,
'max_stay_arrival': binding.max_stay_arrival or 0,
'closed': binding.closed and 1 or 0,
'closed_arrival': binding.closed_arrival and 1 or 0,
'closed_departure': binding.closed_departure and 1 or 0,
})
@api.model
def push_restriction(self):
channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction']
channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item']
unpushed = channel_rest_item_obj.search([
('channel_pushed', '=', False),
('date', '>=', fields.Date.today())
], order="date ASC")
if any(unpushed):
date_start = fields.Date.from_string(unpushed[0].date)
date_end = fields.Date.from_string(unpushed[-1].date)
days_diff = (date_end-date_start).days + 1
restrictions = {}
channel_restr_plan_ids = channel_room_type_rest_obj.search([])
for rp in channel_restr_plan_ids:
restrictions.update({rp.external_id: {}})
unpushed_rp = channel_rest_item_obj.search([
('channel_pushed', '=', False),
('restriction_id', '=', rp.odoo_id.id)
])
room_type_ids = unpushed_rp.mapped('room_type_id')
for room_type in room_type_ids:
if any(room_type.channel_bind_ids):
# FIXME: Supossed that only exists one channel connector per record
room_type_external_id = room_type.channel_bind_ids[0].external_id
restrictions[rp.external_id].update({
room_type_external_id: [],
})
for i in range(0, days_diff):
ndate_dt = date_start + timedelta(days=i)
restr = room_type.get_restrictions(
ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
rp.odoo_id.id)
if restr:
restrictions[rp.external_id][room_type_external_id].append({
'min_stay': restr.min_stay or 0,
'min_stay_arrival': restr.min_stay_arrival or 0,
'max_stay': restr.max_stay or 0,
'max_stay_arrival': restr.max_stay_arrival or 0,
'closed': restr.closed and 1 or 0,
'closed_arrival': restr.closed_arrival and 1 or 0,
'closed_departure': restr.closed_departure and 1 or 0,
})
else:
restrictions[rp.external_id][room_type_external_id].append({})
_logger.info("==[ODOO->CHANNEL]==== UPDATING RESTRICTIONS ==")
_logger.info(restrictions)
for k_res, v_res in restrictions.items():
if any(v_res):
self.backend_adapter.update_rplan_values(
int(k_res),
date_start.strftime(DEFAULT_SERVER_DATE_FORMAT),
v_res)
unpushed.with_context({
'wubook_action': False}).write({'channel_pushed': True})
return True

View File

@@ -2,11 +2,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging import logging
from datetime import timedelta from datetime import datetime, timedelta
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo.addons.connector.components.mapper import mapping, only_create from odoo.addons.connector.components.mapper import mapping, only_create
from odoo.addons.hotel import date_utils from odoo.addons.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import fields, api, _ from odoo import fields, api, _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -17,6 +19,60 @@ class HotelRoomTypeRestrictionImporter(Component):
_apply_on = ['channel.hotel.room.type.restriction.item'] _apply_on = ['channel.hotel.room.type.restriction.item']
_usage = 'hotel.room.type.restriction.item.importer' _usage = 'hotel.room.type.restriction.item.importer'
# FIXME: Reduce Nested Loops!!
@api.model
def _generate_restriction_items(self, plan_restrictions):
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
channel_reserv_restriction_obj = self.env['channel.hotel.room.type.restriction']
channel_restriction_item_obj = self.env['channel.hotel.room.type.restriction.item']
restriction_item_mapper = self.component(
usage='import.mapper',
model_name='channel.hotel.room.type.restriction.item')
_logger.info("==[CHANNEL->ODOO]==== RESTRICTIONS ==")
_logger.info(plan_restrictions)
for k_rpid, v_rpid in plan_restrictions.items():
channel_restriction_id = channel_reserv_restriction_obj.search([
('external_id', '=', k_rpid)
], limit=1)
if channel_restriction_id:
for k_rid, v_rid in v_rpid.items():
channel_room_type = channel_hotel_room_type_obj.search([
('external_id', '=', k_rid)
], limit=1)
if channel_room_type:
for item in v_rid:
map_record = restriction_item_mapper.map_record(item)
date_dt = datetime.strptime(item['date'], DEFAULT_WUBOOK_DATE_FORMAT)
date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
channel_restriction_item = channel_restriction_item_obj.search([
('restriction_id', '=', channel_restriction_id.odoo_id.id),
('date', '=', date_str),
('applied_on', '=', '0_room_type'),
('room_type_id', '=', channel_room_type.odoo_id.id)
], limit=1)
item.update({
'date': date_str,
'room_type_id': channel_room_type.odoo_id.id,
'restriction_id': channel_restriction_id.odoo_id.id,
})
if channel_restriction_item:
channel_restriction_item.with_context({
'wubook_action': False}).write(map_record.values())
else:
channel_restriction_item_obj.with_context({
'wubook_action': False
}).create(map_record.values(for_create=True))
@api.model
def import_restriction_values(self, date_from, date_to, channel_restr_id=False):
channel_restr_plan_id = channel_restr_id.external_id if channel_restr_id else False
results = self.backend_adapter.wired_rplan_get_rplan_values(
date_from,
date_to,
int(channel_restr_plan_id))
if any(results):
self._generate_restriction_items(results)
class HotelRoomTypeRestrictionItemImportMapper(Component): class HotelRoomTypeRestrictionItemImportMapper(Component):
_name = 'channel.hotel.room.type.restriction.item.import.mapper' _name = 'channel.hotel.room.type.restriction.item.import.mapper'
_inherit = 'channel.import.mapper' _inherit = 'channel.import.mapper'
@@ -30,7 +86,6 @@ class HotelRoomTypeRestrictionItemImportMapper(Component):
('closed', 'closed'), ('closed', 'closed'),
('closed_departure', 'closed_departure'), ('closed_departure', 'closed_departure'),
('closed_arrival', 'closed_arrival'), ('closed_arrival', 'closed_arrival'),
('room_type_id', 'room_type_id'),
('date', 'date'), ('date', 'date'),
] ]
@@ -39,6 +94,20 @@ class HotelRoomTypeRestrictionItemImportMapper(Component):
def applied_on(self, record): def applied_on(self, record):
return {'applied_on': '0_room_type'} return {'applied_on': '0_room_type'}
@only_create
@mapping
def channel_pushed(self, record):
return {'channel_pushed': True}
@mapping
def room_type_id(self, record):
return {'room_type_id': record['room_type_id']}
@mapping
def restriction_id(self, record):
return {'restriction_id': record['restriction_id']}
@mapping @mapping
def backend_id(self, record): def backend_id(self, record):
return {'backend_id': self.backend_record.id} return {'backend_id': self.backend_record.id}

View File

@@ -1,56 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
from openerp.exceptions import ValidationError
class ProductPricelistItem(models.Model):
_inherit = 'product.pricelist.item'
is_channel_pushed = fields.Boolean("WuBook Pushed", default=True, readonly=True,
old_name='wpushed')
is_daily_plan = fields.Boolean(related='pricelist_id.channel_bind_ids.is_daily_plan', readonly=True,
old_name='wdaily')
@api.constrains('fixed_price')
def _check_fixed_price(self):
room_type_obj = self.env['hotel.room.type']
for record in self:
room_type = room_type_obj.search([
('product_id.product_tmpl_id', '=', record.product_tmpl_id.id)
], limit=1)
if room_type and room_type.channel_room_id and record.compute_price == 'fixed' \
and record.fixed_price <= 0.0:
raise ValidationError(_("Price need be greater than zero"))
@api.model
def create(self, vals):
if self._context.get('channel_action', True):
pricelist_id = self.env['product.pricelist'].browse(
vals.get('pricelist_id'))
room_type = self.env['hotel.room.type'].search([
('product_id.product_tmpl_id', '=',
vals.get('product_tmpl_id')),
('channel_room_id', '!=', False)
])
if room_type and pricelist_id.channel_plan_id:
vals.update({'is_channel_pushed': False})
return super(ProductPricelistItem, self).create(vals)
@api.multi
def write(self, vals):
if self._context.get('channel_action', True):
prices_obj = self.env['product.pricelist']
for record in self:
pricelist_id = prices_obj.browse(vals.get('pricelist_id')) if \
vals.get('pricelist_id') else record.pricelist_id
product_tmpl_id = vals.get('product_tmpl_id') or \
record.product_tmpl_id.id
room_type = self.env['hotel.room.type'].search([
('product_id.product_tmpl_id', '=', product_tmpl_id),
('channel_room_id', '!=', False),
])
if room_type and pricelist_id.channel_plan_id:
vals.update({'is_channel_pushed': False})
return super(ProductPricelistItem, self).write(vals)

View File

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

View File

@@ -6,6 +6,7 @@ from odoo.exceptions import ValidationError
from odoo.addons.queue_job.job import job, related_action from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if from odoo.addons.component_event import skip_if
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
class ChannelProductPricelist(models.Model): class ChannelProductPricelist(models.Model):
_name = 'channel.product.pricelist' _name = 'channel.product.pricelist'
@@ -17,7 +18,6 @@ class ChannelProductPricelist(models.Model):
string='Pricelist', string='Pricelist',
required=True, required=True,
ondelete='cascade') ondelete='cascade')
channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid')
is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan') is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan')
@job(default_channel='root.channel') @job(default_channel='root.channel')
@@ -25,52 +25,65 @@ class ChannelProductPricelist(models.Model):
@api.multi @api.multi
def create_plan(self): def create_plan(self):
self.ensure_one() self.ensure_one()
if self._context.get('channel_action', True): if not self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter') exporter = work.component(usage='product.pricelist.exporter')
try: try:
channel_plan_id = adapter.create_plan(self.name, exporter.create_plan(self)
self.is_daily_plan and 1 or 0) except ChannelConnectorError as err:
if channel_plan_id: self.create_issue(
self.channel_plan_id = channel_plan_id backend=self.backend_id.id,
except ValidationError as e: section='restriction',
self.create_issue('room', "Can't create plan on channel", "sss") internal_message=_("Can't create pricelist plan in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def update_plan_name(self): def update_plan_name(self):
self.ensure_one() self.ensure_one()
if self._context.get('channel_action', True): if self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter') exporter = work.component(usage='product.pricelist.exporter')
try: try:
adapter.update_plan_name( exporter.rename_plan(self)
self.channel_plan_id, except ChannelConnectorError as err:
self.name) self.create_issue(
except ValidationError as e: backend=self.backend_id.id,
self.create_issue('room', "Can't update plan name on channel", "sss") section='restriction',
internal_message=_("Can't modify pricelist plan in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding') @related_action(action='related_action_unwrap_binding')
@api.multi @api.multi
def delete_plan(self): def delete_plan(self):
self.ensure_one() self.ensure_one()
if self._context.get('channel_action', True) and self.channel_room_id: if self.external_id:
with self.backend_id.work_on(self._name) as work: with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter') exporter = work.component(usage='product.pricelist.exporter')
try: try:
adapter.delete_plan(self.channel_plan_id) exporter.delete_plan(self)
except ValidationError as e: except ChannelConnectorError as err:
self.create_issue('room', "Can't delete plan on channel", "sss") self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=_("Can't delete pricelist plan in WuBook"),
channel_message=err.data['message'])
@job(default_channel='root.channel') @job(default_channel='root.channel')
@api.multi @api.model
def import_price_plans(self): def import_price_plans(self, backend):
if self._context.get('channel_action', True): with backend.work_on(self._name) as work:
with self.backend_id.work_on(self._name) as work: importer = work.component(usage='product.pricelist.importer')
importer = work.component(usage='channel.importer') try:
return importer.import_pricing_plans() return importer.import_pricing_plans()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='pricelist',
internal_message=_("Can't get pricing plans from wubook"),
channel_message=err.data['message'])
class ProductPricelist(models.Model): class ProductPricelist(models.Model):
_inherit = 'product.pricelist' _inherit = 'product.pricelist'
@@ -83,19 +96,47 @@ class ProductPricelist(models.Model):
@api.multi @api.multi
@api.depends('name') @api.depends('name')
def name_get(self): def name_get(self):
self.ensure_one()
pricelist_obj = self.env['product.pricelist'] pricelist_obj = self.env['product.pricelist']
org_names = super(ProductPricelist, self).name_get() org_names = super(ProductPricelist, self).name_get()
names = [] names = []
for name in org_names: for name in org_names:
priclist_id = pricelist_obj.browse(name[0]) priclist_id = pricelist_obj.browse(name[0])
if any(priclist_id.channel_bind_ids) and \ if any(priclist_id.channel_bind_ids) and \
priclist_id.channel_bind_ids[0].channel_plan_id: priclist_id.channel_bind_ids[0].external_id:
names.append((name[0], '%s (Channel)' % name[1])) names.append((name[0], '%s (%s Backend)' % (
name[1],
priclist_id.channel_bind_ids[0].backend_id.name)))
else: else:
names.append((name[0], name[1])) names.append((name[0], name[1]))
return names return names
class ProductPricelistAdapter(Component):
_name = 'channel.product.pricelist.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.product.pricelist'
def get_pricing_plans(self):
return super(ProductPricelistAdapter, self).get_pricing_plans()
def create_plan(self, name):
return super(ProductPricelistAdapter, self).create_plan(name)
def delete_plan(self, external_id):
return super(ProductPricelistAdapter, self).delete_plan(external_id)
def rename_plan(self, external_id, new_name):
return super(ProductPricelistAdapter, self).rename_plan(external_id, new_name)
class BindingProductPricelistListener(Component):
_name = 'binding.product.pricelist.listener'
_inherit = 'base.connector.listener'
_apply_on = ['product.pricelist']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if any(record.channel_bind_ids) and 'name' in fields:
record.channel_bind_ids[0].update_plan_name()
class ChannelBindingProductPricelistListener(Component): class ChannelBindingProductPricelistListener(Component):
_name = 'channel.binding.product.pricelist.listener' _name = 'channel.binding.product.pricelist.listener'
_inherit = 'base.connector.listener' _inherit = 'base.connector.listener'
@@ -103,13 +144,13 @@ class ChannelBindingProductPricelistListener(Component):
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None): def on_record_create(self, record, fields=None):
record.with_delay(priority=20).create_plan() record.create_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None): def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).delete_plan() record.delete_plan()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
if 'name' in fields: if 'name' in fields:
record.with_delay(priority=20).update_plan_name() record.update_plan_name()

View File

@@ -0,0 +1,28 @@
# 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 import api, _
_logger = logging.getLogger(__name__)
class ProductPricelistExporter(Component):
_name = 'channel.product.pricelist.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.product.pricelist']
_usage = 'product.pricelist.exporter'
@api.model
def rename_plan(self, binding):
return self.backend_adapter.rename_plan(
binding.external_id,
binding.name)
@api.model
def delete_plan(self, binding):
return self.backend_adapter.delete_plan(binding.external_id)
@api.model
def create_plan(self, binding):
external_id = self.backend_adapter.create_plan(binding.name)
binding.external_id = external_id

View File

@@ -0,0 +1,57 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from datetime import datetime, timedelta
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping, only_create
from odoo.addons.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class ProductPricelistImporter(Component):
_name = 'channel.product.pricelist.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.product.pricelist']
_usage = 'product.pricelist.importer'
@api.model
def import_pricing_plans(self):
channel_product_listprice_obj = self.env['channel.product.pricelist']
pricelist_mapper = self.component(usage='import.mapper',
model_name='channel.product.pricelist')
results = self.backend_adapter.get_pricing_plans()
count = 0
for plan in results:
if 'vpid' in plan:
continue # FIXME: Ignore Virtual Plans
plan_record = pricelist_mapper.map_record(plan)
plan_bind = channel_product_listprice_obj.search([
('external_id', '=', str(plan['id']))
], limit=1)
if not plan_bind:
channel_product_listprice_obj.with_context({
'wubook_action': False}).create(plan_record.values(for_create=True))
else:
channel_product_listprice_obj.write(plan_record.values())
count = count + 1
return count
class ProductPricelistImportMapper(Component):
_name = 'channel.product.pricelist.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.product.pricelist'
direct = [
('id', 'external_id'),
('name', 'name'),
('daily', 'is_daily_plan'),
]
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -0,0 +1,6 @@
# 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
from . import exporter

View File

@@ -0,0 +1,98 @@
# 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
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
class ChannelProductPricelistItem(models.Model):
_name = 'channel.product.pricelist.item'
_inherit = 'channel.binding'
_inherits = {'product.pricelist.item': 'odoo_id'}
_description = 'Channel Product Pricelist Item'
odoo_id = fields.Many2one(comodel_name='product.pricelist.item',
string='Hotel Product Pricelist Item',
required=True,
ondelete='cascade')
channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False,
old_name='wpushed')
@job(default_channel='root.channel')
@api.model
def import_pricelist_values(self, backend):
with backend.work_on(self._name) as work:
importer = work.component(usage='product.pricelist.item.importer')
try:
if not backend.pricelist_id:
return importer.import_all_pricelist_values(
backend.pricelist_from,
backend.pricelist_to)
return importer.import_pricelist_values(
backend.pricelist_id.external_id,
backend.pricelist_from,
backend.pricelist_to)
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='pricelist',
internal_message="Can't fetch plan prices from wubook!",
channel_message=err.data['message'],
channel_object_id=backend.pricelist_id.external_id,
dfrom=backend.pricelist_from,
dto=backend.pricelist_to)
return False
@job(default_channel='root.channel')
@api.model
def push_pricelist(self, backend):
with backend.work_on(self._name) as work:
exporter = work.component(usage='product.pricelist.item.exporter')
return exporter.push_pricelist()
class ProductPricelistItem(models.Model):
_inherit = 'product.pricelist.item'
channel_bind_ids = fields.One2many(
comodel_name='channel.product.pricelist.item',
inverse_name='odoo_id',
string='Hotel Channel Connector Bindings')
class ProducrPricelistItemAdapter(Component):
_name = 'channel.product.pricelist.item.adapter'
_inherit = 'wubook.adapter'
_apply_on = 'channel.product.pricelist.item'
def fetch_plan_prices(self, external_id, date_from, date_to, rooms):
return super(ProducrPricelistItemAdapter, self).fetch_plan_prices(
external_id,
date_from,
date_to,
rooms)
class BindingProductPricelistItemListener(Component):
_name = 'binding.product.pricelist.item.listener'
_inherit = 'base.connector.listener'
_apply_on = ['product.pricelist.item']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
fields_to_check = ('date_start', 'date_end', 'fixed_price', 'product_tmpl_id')
fields_checked = [elm for elm in fields_to_check if elm in fields]
if any(fields_checked):
record.channel_bind_ids.write({'channel_pushed': False})
class ChannelBindingProductPricelistItemListener(Component):
_name = 'channel.binding.product.pricelist.item.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.product.pricelist.item']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
fields_to_check = ('date_start', 'date_end', 'fixed_price', 'product_tmpl_id')
fields_checked = [elm for elm in fields_to_check if elm in fields]
if any(fields_checked):
record.channel_pushed = False

View File

@@ -0,0 +1,106 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from datetime import timedelta
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class ProductPricelistItemExporter(Component):
_name = 'channel.product.pricelist.item.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.product.pricelist.item']
_usage = 'product.pricelist.item.exporter'
@api.model
def update_restriction(self, binding):
if any(binding.restriction_id.channel_bind_ids):
try:
# FIXME: Supossed that only exists one channel connector per record
binding.channel_pushed = True
return self.backend_adapter.update_rplan_values(
binding.restriction_id.channel_bind_ids[0].external_id,
binding.date,
{
'min_stay': binding.min_stay or 0,
'min_stay_arrival': binding.min_stay_arrival or 0,
'max_stay': binding.max_stay or 0,
'max_stay_arrival': binding.max_stay_arrival or 0,
'closed': binding.closed and 1 or 0,
'closed_arrival': binding.closed_arrival and 1 or 0,
'closed_departure': binding.closed_departure and 1 or 0,
})
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_adapter.id,
section='restriction',
internal_message=_("Can't update restriction in WuBook"),
channel_message=err.data['message'])
@api.model
def push_restriction(self):
channel_room_type_rest_obj = self.env['channel.hotel.room.type.restriction']
channel_rest_item_obj = self.env['channel.hotel.room.type.restriction.item']
unpushed = channel_rest_item_obj.search([
('channel_pushed', '=', False),
('date', '>=', fields.Date.today())
], order="date ASC")
if any(unpushed):
date_start = fields.Date.from_string(unpushed[0].date)
date_end = fields.Date.from_string(unpushed[-1].date)
days_diff = (date_end-date_start).days + 1
restrictions = {}
channel_restr_plan_ids = channel_room_type_rest_obj.search([])
for rp in channel_restr_plan_ids:
restrictions.update({rp.external_id: {}})
unpushed_rp = channel_rest_item_obj.search([
('channel_pushed', '=', False),
('restriction_id', '=', rp.odoo_id.id)
])
room_type_ids = unpushed_rp.mapped('room_type_id')
for room_type in room_type_ids:
if any(room_type.channel_bind_ids):
# FIXME: Supossed that only exists one channel connector per record
room_type_external_id = room_type.channel_bind_ids[0].external_id
restrictions[rp.external_id].update({
room_type_external_id: [],
})
for i in range(0, days_diff):
ndate_dt = date_start + timedelta(days=i)
restr = room_type.get_restrictions(
ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
rp.odoo_id.id)
if restr:
restrictions[rp.external_id][room_type_external_id].append({
'min_stay': restr.min_stay or 0,
'min_stay_arrival': restr.min_stay_arrival or 0,
'max_stay': restr.max_stay or 0,
'max_stay_arrival': restr.max_stay_arrival or 0,
'closed': restr.closed and 1 or 0,
'closed_arrival': restr.closed_arrival and 1 or 0,
'closed_departure': restr.closed_departure and 1 or 0,
})
else:
restrictions[rp.external_id][room_type_external_id].append({})
_logger.info("==[ODOO->CHANNEL]==== UPDATING RESTRICTIONS ==")
_logger.info(restrictions)
for k_res, v_res in restrictions.items():
if any(v_res):
try:
self.backend_adapter.update_rplan_values(
int(k_res),
date_start.strftime(DEFAULT_SERVER_DATE_FORMAT),
v_res)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_adapter.id,
section='restriction',
internal_message=_("Can't update restrictions in WuBook"),
channel_message=err.data['message'])
unpushed.with_context({
'wubook_action': False}).write({'channel_pushed': True})
return True

View File

@@ -0,0 +1,120 @@
# 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.connector.components.mapper import mapping, only_create
from odoo.addons.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class ProductPricelistItemImporter(Component):
_name = 'channel.product.pricelist.item.importer'
_inherit = 'hotel.channel.importer'
_apply_on = ['channel.product.pricelist.item']
_usage = 'product.pricelist.item.importer'
@api.model
def _generate_pricelist_items(self, channel_plan_id, date_from, date_to, plan_prices):
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
pricelist_bind = self.env['channel.product.pricelist'].search([
('external_id', '=', channel_plan_id)
], limit=1)
pricelist_item_mapper = self.component(
usage='import.mapper',
model_name='channel.product.pricelist.item')
if pricelist_bind:
channel_pricelist_item_obj = self.env['channel.product.pricelist.item']
dfrom_dt = fields.Date.from_string(date_from)
dto_dt = fields.Date.from_string(date_to)
days_diff = (dto_dt-dfrom_dt).days + 1
for i in range(0, days_diff):
ndate_dt = dfrom_dt + timedelta(days=i)
for k_rid, v_rid in plan_prices.items():
channel_room_type = channel_hotel_room_type_obj.search([
('external_id', '=', k_rid)
], limit=1)
if channel_room_type:
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
item = {
'price': plan_prices[k_rid][i],
'channel_room_type': channel_room_type,
'pricelist_id': pricelist_bind.odoo_id.id,
'date': ndate_str,
}
map_record = pricelist_item_mapper.map_record(item)
pricelist_item = channel_pricelist_item_obj.search([
('pricelist_id', '=', pricelist_bind.odoo_id.id),
('date_start', '=', ndate_str),
('date_end', '=', ndate_str),
('compute_price', '=', 'fixed'),
('applied_on', '=', '1_product'),
('product_tmpl_id', '=',
channel_room_type.product_id.product_tmpl_id.id)
], limit=1)
if pricelist_item:
pricelist_item.with_context({
'wubook_action': False}).write(map_record.values())
else:
channel_pricelist_item_obj.with_context({
'wubook_action': False}).create(map_record.values(for_create=True))
return True
@api.model
def import_all_pricelist_values(self, date_from, date_to, rooms=None):
external_ids = self.env['channel.product.pricelist'].search([]).mapped('external_id')
for external_id in external_ids:
if external_id:
self.import_pricelist_values(external_id, date_from, date_to, rooms=rooms)
return True
@api.model
def import_pricelist_values(self, external_id, date_from, date_to, rooms=None):
results = self.backend_adapter.fetch_plan_prices(
external_id,
date_from,
date_to,
rooms)
self._generate_pricelist_items(external_id, date_from, date_to, results)
class ProductPricelistItemImportMapper(Component):
_name = 'channel.product.pricelist.item.import.mapper'
_inherit = 'channel.import.mapper'
_apply_on = 'channel.product.pricelist.item'
direct = [
('price', 'fixed_price'),
('date', 'date_start'),
('date', 'date_end'),
]
@only_create
@mapping
def compute_price(self, record):
return {'compute_price': 'fixed'}
@only_create
@mapping
def channel_pushed(self, record):
return {'channel_pushed': True}
@only_create
@mapping
def applied_on(self, record):
return {'applied_on': '1_product'}
@mapping
def product_tmpl_id(self, record):
return {'product_tmpl_id': record['channel_room_type'].product_id.product_tmpl_id.id}
@mapping
def pricelist_id(self, record):
return {'pricelist_id': record['pricelist_id']}
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -1,32 +0,0 @@
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api
class HotelConfiguration(models.TransientModel):
_inherit = 'res.config.settings'
default_channel_connector = fields.Many2one(
'channel.backend',
'Default Channel Connector Backend')
@api.multi
def set_values(self):
super(HotelConfiguration, self).set_values()
self.env['ir.default'].sudo().set(
'res.config.settings', 'default_channel_connector',
self.default_channel_connector.id)
@api.model
def get_values(self):
res = super(HotelConfiguration, self).get_values()
# ONLY FOR v11. DO NOT FORWARD-PORT
default_channel_connector = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_channel_connector')
res.update(
default_channel_connector=default_channel_connector,
)
return res

View File

@@ -28,6 +28,8 @@
<field name="username" colspan="2"/> <field name="username" colspan="2"/>
<field name="passwd" password="1" colspan="2"/> <field name="passwd" password="1" colspan="2"/>
</group> </group>
</page>
<page string="Security" name="security">
<group colspan="4" col="4"> <group colspan="4" col="4">
<field name="security_token" colspan="4"/> <field name="security_token" colspan="4"/>
<button colspan="1" name="generate_key" <button colspan="1" name="generate_key"
@@ -84,6 +86,94 @@
string="Import in background"/> string="Import in background"/>
</div> </div>
</group> </group>
<group>
<label string="Import Availability" class="oe_inline"/>
<div>
<field name="avail_from" class="oe_inline" nolabel="1"/>
<field name="avail_to" class="oe_inline" nolabel="1"/>
<button name="import_availability"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
<group>
<label string="Import Restriction Plans" class="oe_inline"/>
<div>
<button name="import_restriction_plans"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
<label string="Import Restriction Values" class="oe_inline"/>
<div>
<field name="restriction_id" class="oe_inline" nolabel="1"/>
<field name="restriction_from" class="oe_inline" nolabel="1"/>
<field name="restriction_to" class="oe_inline" nolabel="1"/>
<button name="import_restriction_values"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
<group>
<label string="Import Pricelist Plans" class="oe_inline"/>
<div>
<button name="import_pricelist_plans"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
<label string="Import Pricelist Values" class="oe_inline"/>
<div>
<field name="pricelist_id" class="oe_inline" nolabel="1"/>
<field name="pricelist_from" class="oe_inline" nolabel="1"/>
<field name="pricelist_to" class="oe_inline" nolabel="1"/>
<button name="import_pricelist_values"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
</page>
<page name="export" string="Exports">
<group>
<label string="Push Availability" class="oe_inline"/>
<div>
<button name="push_availability"
type="object"
class="oe_highlight"
string="Export in background"/>
</div>
</group>
<group>
<label string="Push Restriction" class="oe_inline"/>
<div>
<button name="push_restriction"
type="object"
class="oe_highlight"
string="Export in background"/>
</div>
</group>
</page>
<page name="otas" string="OTA's">
<field name="ota_ids" nolabel="1">
<tree>
<field name="name"/>
<field name="ota_id"/>
</tree>
</field>
</page>
<page name="issues" string="Issues">
<field name="issue_ids" nolabel="1">
<tree>
<field name="section"/>
<field name="internal_message"/>
<field name="channel_message"/>
<field name="date_start"/>
<field name="date_end"/>
</tree>
</field>
</page> </page>
</notebook> </notebook>
</sheet> </sheet>

View File

@@ -7,7 +7,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Hotel Channel Reservation"> <form string="Hotel Channel Reservation">
<group> <group>
<field name="channel_reservation_id" /> <field name="external_id" />
<field name="ota_reservation_id" /> <field name="ota_reservation_id" />
<field name="wstatus" /> <field name="wstatus" />
<field name="wstatus_reason" /> <field name="wstatus_reason" />

View File

@@ -6,6 +6,10 @@
<field name="model">channel.hotel.room.type.availability</field> <field name="model">channel.hotel.room.type.availability</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Hotel Channel Room Availability"> <form string="Hotel Channel Room Availability">
<group>
<field name="id" invisible="1" />
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
<group> <group>
<field name="channel_max_avail" /> <field name="channel_max_avail" />
<field name="channel_pushed" /> <field name="channel_pushed" />

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_channel_hotel_room_type_restriction_item_form" model="ir.ui.view">
<field name="name">channel.hotel.room.type.restriction.item.form</field>
<field name="model">channel.hotel.room.type.restriction.item</field>
<field name="arch" type="xml">
<form string="Hotel Channel Room Restriction Item">
<group>
<field name="id" invisible="1" />
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
<group>
<field name="channel_pushed" />
</group>
</form>
</field>
</record>
<record id="view_channel_hotel_room_type_restriction_item_tree" model="ir.ui.view">
<field name="name">channel.hotel.room.type.restriction.item.tree</field>
<field name="model">channel.hotel.room.type.restriction.item</field>
<field name="arch" type="xml">
<tree string="Hotel Channel Room Restriction Item">
<field name="backend_id"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -7,8 +7,11 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Hotel Channel Room Restriction"> <form string="Hotel Channel Room Restriction">
<group> <group>
<field name="channel_plan_id" /> <field name="id" invisible="1" />
<field name="is_daily_plan" /> <field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
<group>
<field name="external_id" />
</group> </group>
</form> </form>
</field> </field>

View File

@@ -7,10 +7,11 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Hotel Channel Virtual Room"> <form string="Hotel Channel Virtual Room">
<group> <group>
<field name="id" invisible="1" />
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" /> <field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group> </group>
<group> <group>
<field name="channel_room_id" /> <field name="external_id" />
<field name="channel_short_code" /> <field name="channel_short_code" />
<field name="ota_capacity" /> <field name="ota_capacity" />
</group> </group>

View File

@@ -8,6 +8,10 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Channel OTA's Info" > <form string="Channel OTA's Info" >
<sheet> <sheet>
<group>
<field name="id" invisible="1" />
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
<group> <group>
<field name="ota_id" /> <field name="ota_id" />
<field name="name" /> <field name="name" />
@@ -30,11 +34,4 @@
</field> </field>
</record> </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> </odoo>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_channel_product_pricelist_item_form" model="ir.ui.view">
<field name="name">channel.product.pricelist.item.form</field>
<field name="model">channel.product.pricelist.item</field>
<field name="arch" type="xml">
<form string="Hotel Channel Product Pricelist Item">
<group>
<field name="id" invisible="1" />
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
</form>
</field>
</record>
<record id="view_channel_hotel_product_pricelist_item_tree" model="ir.ui.view">
<field name="name">channel.hotel.product.pricelist.item.tree</field>
<field name="model">channel.product.pricelist.item</field>
<field name="arch" type="xml">
<tree string="Hotel Channel Product Pricelist Item">
<field name="backend_id"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -7,14 +7,18 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Hotel Channel Product Pricelist"> <form string="Hotel Channel Product Pricelist">
<group> <group>
<field name="channel_plan_id" /> <field name="id" invisible="1" />
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
</group>
<group>
<field name="external_id" />
<field name="is_daily_plan" /> <field name="is_daily_plan" />
</group> </group>
</form> </form>
</field> </field>
</record> </record>
<record id="view_channel_hotel_room_type_restriction_tree" model="ir.ui.view"> <record id="view_channel_product_pricelist_tree" model="ir.ui.view">
<field name="name">channel.hotel.product.pricelist.tree</field> <field name="name">channel.hotel.product.pricelist.tree</field>
<field name="model">channel.product.pricelist</field> <field name="model">channel.product.pricelist</field>
<field name="arch" type="xml"> <field name="arch" type="xml">

View File

@@ -18,6 +18,9 @@
class="oe_stat_button" icon="fa-warning" class="oe_stat_button" icon="fa-warning"
attrs="{'invisible':['|', ['section', '!=', 'reservation'], ['channel_object_id', '=', False]]}"/> attrs="{'invisible':['|', ['section', '!=', 'reservation'], ['channel_object_id', '=', False]]}"/>
</div> </div>
<group>
<field name="backend_id"/>
</group>
<group> <group>
<field name="section"/> <field name="section"/>
<field name="internal_message"/> <field name="internal_message"/>
@@ -42,6 +45,7 @@
<field name="type">tree</field> <field name="type">tree</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Channel Connector Issues"> <tree string="Channel Connector Issues">
<field name="backend_id"/>
<field name="create_date"/> <field name="create_date"/>
<field name="section"/> <field name="section"/>
<field name="internal_message"/> <field name="internal_message"/>
@@ -58,6 +62,7 @@
<field name="model">hotel.channel.connector.issue</field> <field name="model">hotel.channel.connector.issue</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Issues"> <search string="Issues">
<field name="backend_id"/>
<field name="create_date"/> <field name="create_date"/>
<field name="section"/> <field name="section"/>
<field name="internal_message"/> <field name="internal_message"/>

View File

@@ -23,7 +23,7 @@
</xpath> </xpath>
<xpath expr="//page[@name='days']" position="after"> <xpath expr="//page[@name='days']" position="after">
<page name="connector"> <page name="connector" string="Channel Connector">
<group string="Hotel Channel Bindings"> <group string="Hotel Channel Bindings">
<field name="channel_bind_ids" nolabel="1"> <field name="channel_bind_ids" nolabel="1">
<tree> <tree>

View File

@@ -8,7 +8,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//sheet" position="inside"> <xpath expr="//sheet" position="inside">
<notebook> <notebook>
<page name="connector"> <page name="connector" string="Channel Connector">
<group string="Hotel Channel Bindings"> <group string="Hotel Channel Bindings">
<field name="channel_bind_ids" nolabel="1"> <field name="channel_bind_ids" nolabel="1">
<tree> <tree>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<odoo>
<record id="room_type_restriction_item_view" model="ir.ui.view">
<field name="model">hotel.room.type.restriction.item</field>
<field name="inherit_id" ref="hotel.room_type_restriction_item_view_form" />
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<notebook>
<page name="connector" string="Channel Connector">
<group string="Hotel Channel Bindings">
<field name="channel_bind_ids" nolabel="1">
<tree>
<field name="backend_id"/>
</tree>
</field>
</group>
</page>
</notebook>
</xpath>
</field>
</record>
</odoo>

View File

@@ -5,11 +5,19 @@
<field name="model">hotel.room.type.restriction</field> <field name="model">hotel.room.type.restriction</field>
<field name="inherit_id" ref="hotel.room_type_restriction_view_form" /> <field name="inherit_id" ref="hotel.room_type_restriction_view_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//form[1]//sheet" position="before"> <xpath expr="//sheet" position="inside">
<header> <notebook>
<button name="%(action_wubook_import_plan_restrictions)d" string="Import Restrictions From Channel" type="action" class="oe_highlight" /> <page name="connector" string="Channel Connector">
</header> <group string="Hotel Channel Bindings">
</xpath> <field name="channel_bind_ids" nolabel="1">
<tree>
<field name="backend_id"/>
</tree>
</field>
</group>
</page>
</notebook>
</xpath>
</field> </field>
</record> </record>

View File

@@ -7,7 +7,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//sheet" position="inside"> <xpath expr="//sheet" position="inside">
<notebook> <notebook>
<page name="connector"> <page name="connector" string="Channel Connector">
<group string="Hotel Channel Bindings"> <group string="Hotel Channel Bindings">
<field name="capacity" invisible="1" /> <field name="capacity" invisible="1" />
<field name="channel_bind_ids" context="{'default_ota_capacity': capacity}" nolabel="1"> <field name="channel_bind_ids" context="{'default_ota_capacity': capacity}" nolabel="1">

View File

@@ -5,12 +5,19 @@
<field name="model">product.pricelist.item</field> <field name="model">product.pricelist.item</field>
<field name="inherit_id" ref="product.product_pricelist_item_form_view" /> <field name="inherit_id" ref="product.product_pricelist_item_form_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="after"> <xpath expr="//form" position="inside">
<!--field name="channel_pushed" readonly="True" /--> <notebook>
<field name="is_daily_plan" invisible="1" /> <page name="connector" string="Channel Connector">
</xpath> <group string="Hotel Channel Bindings">
<xpath expr="//field[@name='date_end']" position="attributes"> <field name="channel_bind_ids" nolabel="1">
<attribute name="attrs">{'readonly': [('is_daily_plan', '=', True)]}</attribute> <tree>
<field name="backend_id"/>
<field name="channel_pushed"/>
</tree>
</field>
</group>
</page>
</notebook>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -5,14 +5,19 @@
<field name="model">product.pricelist</field> <field name="model">product.pricelist</field>
<field name="inherit_id" ref="product.product_pricelist_view" /> <field name="inherit_id" ref="product.product_pricelist_view" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//form[1]//sheet" position="before"> <xpath expr="//sheet" position="inside">
<header> <notebook>
<button name="%(action_wubook_import_plan_prices)d" string="Import Prices From Channel" type="action" class="oe_highlight" /> <page name="connector" string="Channel Connector">
</header> <group string="Hotel Channel Bindings">
</xpath> <field name="channel_bind_ids" nolabel="1">
<xpath expr="//field[@name='company_id']" position="after"> <tree>
<field name="id" invisible="1"/> <field name="backend_id"/>
</xpath> </tree>
</field>
</group>
</page>
</notebook>
</xpath>
</field> </field>
</record> </record>