mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
Merge branch 'hotel_calendar' of https://github.com/hootel/hootel into 11.0
This commit is contained in:
@@ -110,7 +110,7 @@ class BusHotelCalendar(models.TransientModel):
|
||||
return {
|
||||
'type': 'availability',
|
||||
'availability': {
|
||||
vals['room_id']: {
|
||||
vals['room_type_id']: {
|
||||
date_dt.strftime("%d/%m/%Y"): [
|
||||
vals['avail'],
|
||||
vals['no_ota'],
|
||||
|
||||
@@ -8,7 +8,7 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super(HotelVirtualRoomAvailability, self).create(vals)
|
||||
res = super(HotelRoomTypeAvailability, self).create(vals)
|
||||
self.env['bus.hotel.calendar'].send_availability_notification({
|
||||
'date': res.date,
|
||||
'avail': res.avail,
|
||||
@@ -20,7 +20,7 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
|
||||
@api.multi
|
||||
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']
|
||||
for record in self:
|
||||
bus_hotel_calendar_obj.send_availability_notification({
|
||||
@@ -44,7 +44,7 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
'no_ota': False,
|
||||
'id': record.id,
|
||||
})
|
||||
res = super(HotelVirtualRoomAvailability, self).unlink()
|
||||
res = super(HotelRoomTypeAvailability, self).unlink()
|
||||
bus_hotel_calendar_obj = self.env['bus.hotel.calendar']
|
||||
for uval in unlink_vals:
|
||||
bus_hotel_calendar_obj.send_availability_notification(uval)
|
||||
|
||||
@@ -25,10 +25,10 @@ class IrDefault(models.Model):
|
||||
fixed_price = pitem.fixed_price
|
||||
room_type = room_type_obj.search([
|
||||
('product_id.product_tmpl_id', '=', product_tmpl_id),
|
||||
('date_start', '>=', fields.Date.today())
|
||||
], limit=1)
|
||||
room_pr_cached_obj.create({
|
||||
'room_type_id': room_type.id,
|
||||
'date': date_start,
|
||||
'price': fixed_price,
|
||||
})
|
||||
if room_type:
|
||||
room_pr_cached_obj.create({
|
||||
'room_id': room_type.id,
|
||||
'date': date_start,
|
||||
'price': fixed_price,
|
||||
})
|
||||
|
||||
@@ -8,24 +8,22 @@
|
||||
<field name="priority" eval="80"/>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('settings')]" 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>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<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_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_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" />
|
||||
</div>
|
||||
<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_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_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" />
|
||||
</div>
|
||||
<xpath expr="//div[@data-key='hotel']" position="inside">
|
||||
<h2>Calendar colors</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<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_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_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" />
|
||||
</div>
|
||||
<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_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_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" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
'wizard/wubook_import_plan_restrictions.xml',
|
||||
'wizard/wubook_import_availability.xml',
|
||||
'views/general.xml',
|
||||
'views/hotel_channel_connector_issue_views.xml',
|
||||
'views/inherited_hotel_reservation_views.xml',
|
||||
'views/inherited_hotel_room_type_views.xml',
|
||||
'views/inherited_hotel_room_type_availability_views.xml',
|
||||
@@ -29,14 +30,16 @@
|
||||
'views/inherited_product_pricelist_views.xml',
|
||||
'views/inherited_product_pricelist_item_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/channel_ota_info_views.xml',
|
||||
'views/hotel_channel_connector_issue_views.xml',
|
||||
'views/channel_hotel_reservation_views.xml',
|
||||
'views/channel_hotel_room_type_views.xml',
|
||||
'views/channel_hotel_room_type_availability_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_item_views.xml',
|
||||
'views/channel_connector_backend_views.xml',
|
||||
'data/menus.xml',
|
||||
'data/sequences.xml',
|
||||
|
||||
@@ -9,7 +9,6 @@ from odoo.tools import (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from odoo.addons.payment.models.payment_acquirer import _partner_split_name
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo import fields, _
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -276,8 +275,8 @@ class WuBookAdapter(AbstractComponent):
|
||||
rcode, results = self._server.fetch_rooms_values(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
date_utils.get_datetime(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
date_utils.get_datetime(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
rooms)
|
||||
if rcode != 0:
|
||||
raise ChannelConnectorError("Can't fetch rooms values from WuBook", {
|
||||
@@ -317,14 +316,14 @@ class WuBookAdapter(AbstractComponent):
|
||||
'phone': phone,
|
||||
'street': address,
|
||||
'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
|
||||
}
|
||||
rcode, results = self._server.new_reservation(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
date_utils.get_datetime(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
date_utils.get_datetime(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(checkin).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(checkout).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
{channel_room_id: [adults+children, 'nb']},
|
||||
customer,
|
||||
adults+children)
|
||||
@@ -431,7 +430,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
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)
|
||||
if rcode != 0:
|
||||
raise ChannelConnectorError("Can't update pricing plan in wubook", {
|
||||
@@ -442,7 +441,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
return results
|
||||
|
||||
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[1],
|
||||
channel_plan_id,
|
||||
@@ -455,7 +454,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
return results
|
||||
|
||||
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[1])
|
||||
if rcode != 0:
|
||||
@@ -469,8 +468,8 @@ class WuBookAdapter(AbstractComponent):
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
channel_plan_id,
|
||||
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
rooms or [])
|
||||
if rcode != 0:
|
||||
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(
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
date_utils(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
date_utils(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_from).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
fields.Date.from_string(date_to).strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
channel_restriction_plan_id)
|
||||
if rcode != 0:
|
||||
raise ChannelConnectorError("Can't fetch restriction plans from wubook", {
|
||||
@@ -513,7 +512,7 @@ class WuBookAdapter(AbstractComponent):
|
||||
self._session_info[0],
|
||||
self._session_info[1],
|
||||
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)
|
||||
if rcode != 0:
|
||||
raise ChannelConnectorError("Can't update plan restrictions on wubook", {
|
||||
|
||||
@@ -11,6 +11,8 @@ class HotelConnectorModelBinder(Component):
|
||||
'channel.hotel.room.type',
|
||||
'channel.hotel.room.type.availability',
|
||||
'channel.hotel.room.type.restriction',
|
||||
'channel.hotel.room.type.restriction.item',
|
||||
'channel.product.pricelist',
|
||||
'channel.product.pricelist.item',
|
||||
'channel.ota.info',
|
||||
]
|
||||
|
||||
@@ -9,18 +9,6 @@ class BaseHotelChannelConnectorComponent(AbstractComponent):
|
||||
_inherit = 'base.connector'
|
||||
_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):
|
||||
def __init__(self, message, data):
|
||||
super().__init__(message)
|
||||
|
||||
@@ -12,7 +12,7 @@ from odoo.tools import (
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo import api
|
||||
from odoo import api, fields
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class HotelChannelConnectorExporter(AbstractComponent):
|
||||
@@ -25,42 +25,6 @@ class HotelChannelConnectorExporter(AbstractComponent):
|
||||
return self.push_availability() and self.push_priceplans() and \
|
||||
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
|
||||
def push_priceplans(self):
|
||||
unpushed = self.env['product.pricelist.item'].search([
|
||||
|
||||
@@ -238,9 +238,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
# the same transaction an cancellation)
|
||||
if crcode in failed_reservations:
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
"Can't process a reservation that previusly failed!",
|
||||
'', channel_object_id=book['reservation_code'])
|
||||
section='reservation',
|
||||
internal_emssage="Can't process a reservation that previusly failed!",
|
||||
channel_object_id=book['reservation_code'])
|
||||
continue
|
||||
|
||||
# Get dates for the reservation (GMT->UTC)
|
||||
@@ -342,10 +342,10 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
], limit=1)
|
||||
if not room_type:
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
"Can't found any room type associated to '%s' \
|
||||
in this hotel" % book['rooms'],
|
||||
'', channel_object_id=book['reservation_code'])
|
||||
section='reservation',
|
||||
internal_message="Can't found any room type associated to '%s' \
|
||||
in this hotel" % book['rooms'],
|
||||
channel_object_id=book['reservation_code'])
|
||||
failed_reservations.append(crcode)
|
||||
continue
|
||||
|
||||
@@ -376,9 +376,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
)
|
||||
if vals['price_unit'] != book['amount']:
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
"Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']),
|
||||
'', channel_object_id=book['reservation_code'])
|
||||
section='reservation',
|
||||
internal_message="Invalid reservation total price! %.2f != %.2f" % (vals['price_unit'], book['amount']),
|
||||
channel_object_id=book['reservation_code'])
|
||||
|
||||
free_rooms = room_type.odoo_id.check_availability_room(
|
||||
checkin_str,
|
||||
@@ -439,9 +439,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
})
|
||||
reservations.append((0, False, vals))
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
"Reservation imported with overbooking state",
|
||||
'', channel_object_id=rcode)
|
||||
section='reservation',
|
||||
internal_message="Reservation imported with overbooking state",
|
||||
channel_object_id=rcode)
|
||||
dates_checkin = [False, False]
|
||||
dates_checkout = [False, False]
|
||||
split_booking = False
|
||||
@@ -458,9 +458,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
|
||||
if split_booking:
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
"Reservation Splitted",
|
||||
'', channel_object_id=rcode)
|
||||
section='reservation',
|
||||
internal_message="Reservation Splitted",
|
||||
channel_object_id=rcode)
|
||||
|
||||
# Create Folio
|
||||
if not any(failed_reservations) and any(reservations):
|
||||
@@ -498,9 +498,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
processed_rids.append(rcode)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
err.data['message'],
|
||||
'', channel_object_id=rcode)
|
||||
section='reservation',
|
||||
internal_message=err.data['message'],
|
||||
channel_object_id=rcode)
|
||||
failed_reservations.append(crcode)
|
||||
return (processed_rids, any(failed_reservations),
|
||||
checkin_utc_dt, checkout_utc_dt)
|
||||
@@ -692,9 +692,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
count = self._generate_pricelists(results)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'plan',
|
||||
_("Can't get pricing plans from wubook"),
|
||||
err.data['message'])
|
||||
section='plan',
|
||||
internal_message=_("Can't get pricing plans from wubook"),
|
||||
channel_message=err.data['message'])
|
||||
return 0
|
||||
return count
|
||||
|
||||
@@ -709,9 +709,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'plan',
|
||||
_("Can't fetch plan prices from wubook"),
|
||||
err.data['message'])
|
||||
section='plan',
|
||||
internal_message=_("Can't fetch plan prices from wubook"),
|
||||
channel_message=err.data['message'])
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -732,9 +732,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'plan',
|
||||
"Can't fetch all plan prices from wubook!",
|
||||
err.data['message'],
|
||||
section='plan',
|
||||
internal_message="Can't fetch all plan prices from wubook!",
|
||||
channel_message=err.data['message'],
|
||||
channel_object_id=channel_plan_id, dfrom=date_from, dto=date_to)
|
||||
return False
|
||||
return no_errors
|
||||
@@ -746,9 +746,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
count = self._generate_restrictions(results)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'rplan',
|
||||
_("Can't fetch restriction plans from wubook"),
|
||||
err.data['message'])
|
||||
section='rplan',
|
||||
internal_message=_("Can't fetch restriction plans from wubook"),
|
||||
channel_message=err.data['message'])
|
||||
return 0
|
||||
return count
|
||||
|
||||
@@ -763,9 +763,9 @@ class HotelChannelConnectorImporter(AbstractComponent):
|
||||
self._generate_restriction_items(results)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'rplan',
|
||||
_("Can't fetch plan restrictions from wubook"),
|
||||
err.data['message'],
|
||||
section='rplan',
|
||||
internal_message=_("Can't fetch plan restrictions from wubook"),
|
||||
channel_message=err.data['message'],
|
||||
channel_object_id=channel_restriction_plan_id,
|
||||
dfrom=date_from, dto=date_to)
|
||||
return False
|
||||
|
||||
@@ -13,12 +13,6 @@
|
||||
parent="menu_channel_connector_root"
|
||||
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"
|
||||
name="Issues"
|
||||
sequence="3"
|
||||
|
||||
@@ -5,7 +5,7 @@ from . import channel_binding
|
||||
from . import channel_backend
|
||||
from . import hotel_room_type
|
||||
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_item
|
||||
from . import hotel_room_type_availability
|
||||
@@ -14,4 +14,3 @@ from . import inherited_hotel_folio
|
||||
from . import inherited_res_partner
|
||||
from . import channel_ota_info
|
||||
from . import hotel_channel_connector_issue
|
||||
from . import res_config
|
||||
|
||||
@@ -31,6 +31,26 @@ class ChannelBackend(models.Model):
|
||||
pkey = fields.Char('Channel Service PKey')
|
||||
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
|
||||
def generate_key(self):
|
||||
for record in self:
|
||||
@@ -57,6 +77,55 @@ class ChannelBackend(models.Model):
|
||||
channel_ota_info_obj.import_otas_info(backend)
|
||||
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
|
||||
@api.multi
|
||||
def work_on(self, model_name, **kwargs):
|
||||
|
||||
@@ -16,7 +16,21 @@ class ChannelBinding(models.AbstractModel):
|
||||
required=True,
|
||||
ondelete='restrict')
|
||||
|
||||
external_id = fields.Char(string='ID on Channel')
|
||||
|
||||
_sql_constraints = [
|
||||
('channel_uniq', 'unique(backend_id, external_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),
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from odoo import api, models, fields
|
||||
from odoo.addons.queue_job.job import job
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
|
||||
class ChannelOtaInfo(models.Model):
|
||||
_name = 'channel.ota.info'
|
||||
@@ -19,7 +20,14 @@ class ChannelOtaInfo(models.Model):
|
||||
def import_otas_info(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(usage='ota.info.importer')
|
||||
return importer.import_otas_info()
|
||||
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):
|
||||
_name = 'channel.ota.info.adapter'
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping
|
||||
from odoo import fields, api, _
|
||||
from odoo.tools import (
|
||||
@@ -19,30 +18,26 @@ class ChannelOtaInfoImporter(Component):
|
||||
|
||||
@api.model
|
||||
def import_otas_info(self):
|
||||
count = 0
|
||||
try:
|
||||
results = self.backend_adapter.get_channels_info()
|
||||
results = self.backend_adapter.get_channels_info()
|
||||
|
||||
channel_ota_info_obj = self.env['channel.ota.info']
|
||||
ota_info_mapper = self.component(usage='import.mapper',
|
||||
model_name='channel.ota.info')
|
||||
for ota_id in results.keys():
|
||||
vals = {
|
||||
'id': ota_id,
|
||||
'name': results[ota_id]['name'],
|
||||
'ical': results[ota_id]['ical'] == 1,
|
||||
}
|
||||
map_record = ota_info_mapper.map_record(vals)
|
||||
ota_info_bind = channel_ota_info_obj.search([
|
||||
('ota_id', '=', ota_id)
|
||||
], limit=1)
|
||||
if ota_info_bind:
|
||||
ota_info_bind.write(map_record.values())
|
||||
else:
|
||||
ota_info_bind.create(map_record.values(for_create=True))
|
||||
count = count + 1
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message'])
|
||||
channel_ota_info_obj = self.env['channel.ota.info']
|
||||
ota_info_mapper = self.component(usage='import.mapper',
|
||||
model_name='channel.ota.info')
|
||||
for ota_id in results.keys():
|
||||
vals = {
|
||||
'id': ota_id,
|
||||
'name': results[ota_id]['name'],
|
||||
'ical': results[ota_id]['ical'] == 1,
|
||||
}
|
||||
map_record = ota_info_mapper.map_record(vals)
|
||||
ota_info_bind = channel_ota_info_obj.search([
|
||||
('ota_id', '=', ota_id)
|
||||
], limit=1)
|
||||
if ota_info_bind:
|
||||
ota_info_bind.write(map_record.values())
|
||||
else:
|
||||
ota_info_bind.create(map_record.values(for_create=True))
|
||||
count = count + 1
|
||||
return count
|
||||
|
||||
|
||||
|
||||
@@ -9,11 +9,17 @@ class HotelChannelConnectorIssue(models.Model):
|
||||
_name = 'hotel.channel.connector.issue'
|
||||
_old_name = 'wubook.issue'
|
||||
|
||||
backend_id = fields.Many2one('channel.backend',
|
||||
'Restriction Plan',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
index=True)
|
||||
|
||||
section = fields.Selection([
|
||||
('channel', 'Channel'),
|
||||
('reservation', 'Reservation'),
|
||||
('rplan', 'Restriction Plan'),
|
||||
('plan', 'Price Plan'),
|
||||
('restriction', 'Restriction Plan'),
|
||||
('pricelist', 'Price Plan'),
|
||||
('room', 'Room'),
|
||||
('avail', 'Availability')], required=True)
|
||||
to_read = fields.Boolean("To Read", default=True)
|
||||
@@ -41,10 +47,9 @@ class HotelChannelConnectorIssue(models.Model):
|
||||
reserv_ids.append(record.channel_object_id)
|
||||
record.to_read = False
|
||||
if any(reserv_ids):
|
||||
res = self.env['hotel.channel.connector'].mark_bookings(reserv_ids)
|
||||
if not res:
|
||||
raise ValidationError(
|
||||
("Can't mark reservation as readed in Channel!"))
|
||||
with self.backend_id.work_on('channel.hotel.reservation') as work:
|
||||
exporter = work.component(usage='hotel.reservation.exporter')
|
||||
return exporter.mark_bookings(reserv_ids)
|
||||
|
||||
@api.model
|
||||
def _needaction_domain_get(self):
|
||||
|
||||
@@ -25,7 +25,6 @@ class ChannelHotelReservation(models.Model):
|
||||
string='Reservation',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
channel_reservation_id = fields.Char("Channel Reservation ID", readonly=True, old_name='wrid')
|
||||
ota_id = fields.Many2one('channel.ota.info',
|
||||
string='Channel OTA ID',
|
||||
readonly=True,
|
||||
|
||||
@@ -26,9 +26,9 @@ class HotelReservationImporter(Component):
|
||||
rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids)
|
||||
if rcodeb != 0:
|
||||
self.create_issue(
|
||||
'wubook',
|
||||
_("Problem trying mark bookings (%s)") % str(processed_rids),
|
||||
'')
|
||||
backend=self.backend_adapter.id,
|
||||
section='wubook',
|
||||
internal_message=_("Problem trying mark bookings (%s)") % str(processed_rids))
|
||||
# Update Odoo availability (don't wait for wubook)
|
||||
# This cause abuse service in first import!!
|
||||
if checkin_utc_dt and checkout_utc_dt:
|
||||
@@ -37,8 +37,9 @@ class HotelReservationImporter(Component):
|
||||
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'reservation',
|
||||
_("Can't process reservations from wubook"),
|
||||
err.data['message'])
|
||||
backend=self.backend_adapter.id,
|
||||
section='reservation',
|
||||
internal_message=_("Can't process reservations from wubook"),
|
||||
channel_message=err.data['message'])
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ChannelHotelRoomType(models.Model):
|
||||
@@ -19,7 +20,6 @@ class ChannelHotelRoomType(models.Model):
|
||||
string='Room Type',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
channel_room_id = fields.Char("Channel Room ID", readonly=True, old_name='wrid')
|
||||
channel_short_code = fields.Char("Channel Short Code", readonly=True, old_name='wscode')
|
||||
ota_capacity = fields.Integer("OTA's Capacity", default=1, old_name='wcapacity')
|
||||
|
||||
@@ -37,7 +37,14 @@ class ChannelHotelRoomType(models.Model):
|
||||
def import_rooms(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(usage='hotel.room.type.importer')
|
||||
return importer.get_rooms()
|
||||
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')
|
||||
def _check_ota_capacity(self):
|
||||
@@ -57,30 +64,51 @@ class ChannelHotelRoomType(models.Model):
|
||||
@api.multi
|
||||
def create_room(self):
|
||||
self.ensure_one()
|
||||
if not self.channel_room_id:
|
||||
if not self.external_id:
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
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')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def modify_room(self):
|
||||
self.ensure_one()
|
||||
if self.channel_room_id:
|
||||
if self.external_id:
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
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')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def delete_room(self):
|
||||
self.ensure_one()
|
||||
if self.channel_room_id:
|
||||
if self.external_id:
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
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):
|
||||
_inherit = 'hotel.room.type'
|
||||
@@ -102,9 +130,7 @@ class HotelRoomType(models.Model):
|
||||
self._compute_capacity()
|
||||
|
||||
@api.multi
|
||||
def get_restrictions(self, date):
|
||||
restriction_plan_id = int(self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_restrictions_id'))
|
||||
def get_restrictions(self, date, restriction_plan_id):
|
||||
self.ensure_one()
|
||||
restriction = self.env['hotel.room.type.restriction.item'].search([
|
||||
('date', '=', date),
|
||||
@@ -142,7 +168,9 @@ class BindingHotelRoomTypeListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if 'name' in fields or 'list_price' in fields:
|
||||
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()
|
||||
|
||||
# @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import logging
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo import api, _
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -15,41 +14,31 @@ class HotelRoomTypeExporter(Component):
|
||||
|
||||
@api.model
|
||||
def modify_room(self, binding):
|
||||
try:
|
||||
return self.backend_adapter.modify_room(
|
||||
binding.channel_room_id,
|
||||
binding.name,
|
||||
binding.ota_capacity,
|
||||
binding.list_price,
|
||||
binding.total_rooms_count,
|
||||
binding.channel_short_code)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue('room', _("Can't modify rooms in WuBook"), err.data['message'])
|
||||
return self.backend_adapter.modify_room(
|
||||
binding.external_id,
|
||||
binding.name,
|
||||
binding.ota_capacity,
|
||||
binding.list_price,
|
||||
binding.total_rooms_count,
|
||||
binding.channel_short_code)
|
||||
|
||||
@api.model
|
||||
def delete_room(self, binding):
|
||||
try:
|
||||
return self.backend_adapter.delete_room(binding.channel_room_id)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue('room', _("Can't delete room in WuBook"), err.data['message'])
|
||||
return self.backend_adapter.delete_room(binding.external_id)
|
||||
|
||||
@api.model
|
||||
def create_room(self, binding):
|
||||
try:
|
||||
seq_obj = self.env['ir.sequence']
|
||||
short_code = seq_obj.next_by_code('hotel.room.type')[:4]
|
||||
external_id = self.backend_adapter.create_room(
|
||||
short_code,
|
||||
binding.name,
|
||||
binding.ota_capacity,
|
||||
binding.list_price,
|
||||
binding.total_rooms_count
|
||||
)
|
||||
binding.write({
|
||||
'channel_room_id': external_id,
|
||||
'channel_short_code': short_code,
|
||||
})
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue('room', _("Can't delete room in WuBook"), err.data['message'])
|
||||
else:
|
||||
self.binder.bind(external_id, binding)
|
||||
seq_obj = self.env['ir.sequence']
|
||||
short_code = seq_obj.next_by_code('hotel.room.type')[:4]
|
||||
external_id = self.backend_adapter.create_room(
|
||||
short_code,
|
||||
binding.name,
|
||||
binding.ota_capacity,
|
||||
binding.list_price,
|
||||
binding.total_rooms_count
|
||||
)
|
||||
binding.write({
|
||||
'external_id': external_id,
|
||||
'channel_short_code': short_code,
|
||||
})
|
||||
self.binder.bind(external_id, binding)
|
||||
|
||||
@@ -5,7 +5,6 @@ import logging
|
||||
from datetime import timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo import fields, api, _
|
||||
@@ -24,27 +23,22 @@ class HotelRoomTypeImporter(Component):
|
||||
|
||||
@api.model
|
||||
def get_rooms(self):
|
||||
count = 0
|
||||
try:
|
||||
results = self.backend_adapter.fetch_rooms()
|
||||
|
||||
channel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
room_mapper = self.component(usage='import.mapper',
|
||||
model_name='channel.hotel.room.type')
|
||||
count = len(results)
|
||||
for room in results:
|
||||
map_record = room_mapper.map_record(room)
|
||||
room_bind = channel_room_type_obj.search([
|
||||
('channel_room_id', '=', room['id'])
|
||||
], limit=1)
|
||||
if room_bind:
|
||||
room_bind.with_context({'wubook_action': False}).write(map_record.values())
|
||||
else:
|
||||
room_bind = channel_room_type_obj.with_context({'wubook_action': False}).create(
|
||||
map_record.values(for_create=True))
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue('room', _("Can't import rooms from WuBook"), err.data['message'])
|
||||
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([
|
||||
('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
|
||||
|
||||
@api.model
|
||||
@@ -61,18 +55,12 @@ class HotelRoomTypeImporter(Component):
|
||||
if dfrom_dt > dto_dt:
|
||||
dfrom_dt, dto_dt = dto_dt, dfrom_dt
|
||||
|
||||
try:
|
||||
results = self.backend_adapter.fetch_rooms_values(
|
||||
dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
rooms)
|
||||
self._generate_room_values(dfrom, dto, results,
|
||||
set_max_avail=set_max_avail)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue('room', _("Can't fetch rooms values from WuBook"),
|
||||
err.data['message'], dfrom=dfrom, dto=dto)
|
||||
return False
|
||||
return True
|
||||
results = self.backend_adapter.fetch_rooms_values(
|
||||
dfrom_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
dto_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
rooms)
|
||||
self._generate_room_values(dfrom, dto, results,
|
||||
set_max_avail=set_max_avail)
|
||||
|
||||
@api.model
|
||||
def _map_room_values_availability(self, day_vals, set_max_avail):
|
||||
@@ -158,7 +146,7 @@ class HotelRoomTypeImportMapper(Component):
|
||||
_apply_on = 'channel.hotel.room.type'
|
||||
|
||||
direct = [
|
||||
('id', 'channel_room_id'),
|
||||
('id', 'external_id'),
|
||||
('shortname', 'channel_short_code'),
|
||||
('occupancy', 'ota_capacity'),
|
||||
('price', 'list_price'),
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import common
|
||||
from . import importer
|
||||
from . import exporter
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
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.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
|
||||
from odoo.addons.hotel_channel_connector.components.backend_adapter import (
|
||||
DEFAULT_WUBOOK_DATE_FORMAT)
|
||||
|
||||
@@ -15,7 +16,7 @@ class ChannelHotelRoomTypeAvailability(models.Model):
|
||||
_name = 'channel.hotel.room.type.availability'
|
||||
_inherit = 'channel.binding'
|
||||
_inherits = {'hotel.room.type.availability': 'odoo_id'}
|
||||
_description = 'Channel Product Pricelist'
|
||||
_description = 'Channel Availability'
|
||||
|
||||
@api.model
|
||||
def _default_channel_max_avail(self):
|
||||
@@ -27,6 +28,8 @@ class ChannelHotelRoomTypeAvailability(models.Model):
|
||||
string='Pricelist',
|
||||
required=True,
|
||||
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",
|
||||
default=_default_channel_max_avail,
|
||||
old_name='wmax_avail')
|
||||
@@ -44,19 +47,45 @@ class ChannelHotelRoomTypeAvailability(models.Model):
|
||||
@job(default_channel='root.channel')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def update_availability(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True):
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
adapter = work.component(usage='backend.adapter')
|
||||
date_dt = fields.Date.from_string(self.date)
|
||||
adapter.update_availability([{
|
||||
'id': self.odoo_id.room_type_id.channel_room_id,
|
||||
'days': [{
|
||||
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
'avail': self.odoo_id.avail,
|
||||
}],
|
||||
}])
|
||||
def update_availability(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(usage='hotel.room.type.availability.exporter')
|
||||
try:
|
||||
return exporter.update_availability(self)
|
||||
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'])
|
||||
|
||||
@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):
|
||||
_inherit = 'hotel.room.type.availability'
|
||||
@@ -79,16 +108,15 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
if record.avail > max_avail:
|
||||
issue_obj.sudo().create({
|
||||
'section': 'avail',
|
||||
'message': _(r"The new availability can't be greater than \
|
||||
the actual availability \
|
||||
\n[%s]\nInput: %d\Limit: %d") % (record.room_type_id.name,
|
||||
record.avail,
|
||||
record),
|
||||
'channel_id': record.room_type_id.channel_bind_ids[0].channel_plan_id,
|
||||
'internal_message': _(r"The new availability can't be greater than \
|
||||
the max. availability \
|
||||
(%s) [Input: %d\Max: %d]") % (record.room_type_id.name,
|
||||
record.avail,
|
||||
max_avail),
|
||||
'date_start': record.date,
|
||||
'date_end': record.date,
|
||||
})
|
||||
# Auto-Fix wubook availability
|
||||
# Auto-Fix channel availability
|
||||
self._event('on_fix_channel_availability').notify(record)
|
||||
return super(HotelRoomTypeAvailability, self)._check_avail()
|
||||
|
||||
@@ -97,12 +125,6 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
if self.room_type_id:
|
||||
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
|
||||
def refresh_availability(self, checkin, checkout, product_id):
|
||||
date_start = fields.Date.from_string(checkin)
|
||||
@@ -143,11 +165,41 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
'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):
|
||||
_name = 'channel.binding.hotel.room.type.availability.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
_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))
|
||||
def on_fix_channel_availability(self, record, fields=None):
|
||||
record.with_delay(priority=20).update_availability()
|
||||
record.update_availability()
|
||||
|
||||
@@ -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)
|
||||
@@ -2,11 +2,11 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from datetime import date, timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping
|
||||
from odoo.addons.connector.components.mapper import mapping, only_create
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo import fields, api, _
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -18,6 +18,53 @@ class HotelRoomTypeAvailabilityImporter(Component):
|
||||
_apply_on = ['channel.hotel.room.type.availability']
|
||||
_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):
|
||||
_name = 'channel.hotel.room.type.availability.import.mapper'
|
||||
@@ -28,10 +75,18 @@ class HotelRoomTypeAvailabilityImportMapper(Component):
|
||||
('no_ota', 'no_ota'),
|
||||
('booked', 'booked'),
|
||||
('avail', 'avail'),
|
||||
('room_type_id', 'room_type_id'),
|
||||
('date', 'date'),
|
||||
]
|
||||
|
||||
@only_create
|
||||
@mapping
|
||||
def channel_pushed(self, record):
|
||||
return {'channel_pushed': True}
|
||||
|
||||
@mapping
|
||||
def backend_id(self, record):
|
||||
return {'backend_id': self.backend_record.id}
|
||||
|
||||
@mapping
|
||||
def room_type_id(self, record):
|
||||
return {'room_type_id': record['room_type_id']}
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import common
|
||||
from . import importer
|
||||
from . import exporter
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from odoo import api, models, fields
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.queue_job.job import job, related_action
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.component_event import skip_if
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ChannelHotelRoomTypeRestriction(models.Model):
|
||||
_name = 'channel.hotel.room.type.restriction'
|
||||
@@ -17,57 +20,71 @@ class ChannelHotelRoomTypeRestriction(models.Model):
|
||||
string='Hotel Virtual Room Restriction',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid')
|
||||
is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan')
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def create_plan(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True):
|
||||
if not self.external_id:
|
||||
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:
|
||||
channel_plan_id = adapter.create_rplan(self.name)
|
||||
if channel_plan_id:
|
||||
self.channel_plan_id = channel_plan_id
|
||||
except ValidationError as e:
|
||||
self.create_issue('room', "Can't create restriction plan on channel", "sss")
|
||||
exporter.create_rplan(self)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
backend=self.backend_id.id,
|
||||
section='restriction',
|
||||
internal_message=_("Can't create restriction plan in WuBook"),
|
||||
channel_message=err.data['message'])
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def update_plan_name(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True):
|
||||
if self.external_id:
|
||||
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:
|
||||
adapter.rename_rplan(self.channel_plan_id, self.name)
|
||||
except ValidationError as e:
|
||||
self.create_issue('room', "Can't update restriction plan name on channel", "sss")
|
||||
exporter.rename_rplan(self)
|
||||
except ChannelConnectorError as err:
|
||||
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')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def delete_plan(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True) and self.channel_room_id:
|
||||
if self.external_id:
|
||||
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:
|
||||
adapter.delete_rplan(self.channel_plan_id)
|
||||
except ValidationError as e:
|
||||
self.create_issue('room', "Can't delete restriction plan on channel", "sss")
|
||||
exporter.delete_rplan(self)
|
||||
except ChannelConnectorError as err:
|
||||
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')
|
||||
@api.multi
|
||||
def import_restriction_plans(self):
|
||||
if self._context.get('channel_action', True):
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
importer = work.component(usage='channel.importer')
|
||||
@api.model
|
||||
def import_restriction_plans(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(usage='hotel.room.type.restriction.importer')
|
||||
try:
|
||||
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):
|
||||
_inherit = 'hotel.room.type.restriction'
|
||||
@@ -85,12 +102,44 @@ class HotelRoomTypeRestriction(models.Model):
|
||||
names = []
|
||||
for name in org_names:
|
||||
restriction_id = room_type_restriction_obj.browse(name[0])
|
||||
if restriction_id.channel_bind_ids.channel_plan_id:
|
||||
names.append((name[0], '%s (WuBook)' % name[1]))
|
||||
if any(restriction_id.channel_bind_ids) and \
|
||||
restriction_id.channel_bind_ids[0].external_id:
|
||||
names.append((
|
||||
name[0],
|
||||
'%s (%s Backend)' % (name[1],
|
||||
restriction_id.channel_bind_ids[0].backend_id.name),
|
||||
))
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
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):
|
||||
_name = 'channel.binding.hotel.room.type.restriction.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
@@ -98,13 +147,13 @@ class ChannelBindingHotelRoomTypeRestrictionListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
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))
|
||||
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))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if 'name' in fields:
|
||||
record.with_delay(priority=20).update_plan_name()
|
||||
record.update_plan_name()
|
||||
|
||||
@@ -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)
|
||||
@@ -5,7 +5,6 @@ import logging
|
||||
from datetime import timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo import fields, api, _
|
||||
@@ -18,6 +17,28 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
_apply_on = ['channel.hotel.room.type.restriction']
|
||||
_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):
|
||||
_name = 'channel.hotel.room.type.restriction.import.mapper'
|
||||
@@ -25,11 +46,8 @@ class HotelRoomTypeRestrictionImportMapper(Component):
|
||||
_apply_on = 'channel.hotel.room.type.restriction'
|
||||
|
||||
direct = [
|
||||
('no_ota', 'no_ota'),
|
||||
('booked', 'booked'),
|
||||
('avail', 'avail'),
|
||||
('room_type_id', 'room_type_id'),
|
||||
('date', 'date')
|
||||
('name', 'name'),
|
||||
('id', 'external_id'),
|
||||
]
|
||||
|
||||
@mapping
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import common
|
||||
from . import importer
|
||||
from . import exporter
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 ChannelHotelRoomTypeRestrictionItem(models.Model):
|
||||
_name = 'channel.hotel.room.type.restriction.item'
|
||||
@@ -21,10 +22,37 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model):
|
||||
old_name='wpushed')
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@api.multi
|
||||
def update_channel_pushed(self, status):
|
||||
self.ensure_one()
|
||||
self.channel_pushed = status
|
||||
@api.model
|
||||
def import_restriction_values(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
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):
|
||||
_inherit = 'hotel.room.type.restriction.item'
|
||||
@@ -34,15 +62,41 @@ class HotelRoomTypeRestrictionItem(models.Model):
|
||||
inverse_name='odoo_id',
|
||||
string='Hotel Channel Connector Bindings')
|
||||
|
||||
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component):
|
||||
_name = 'channel.binding.hotel.room.type.restriction.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
_apply_on = ['channel.hotel.room.type.restriction']
|
||||
class HotelRoomTypeRestrictionItemAdapter(Component):
|
||||
_name = 'channel.hotel.room.type.restriction.item.adapter'
|
||||
_inherit = 'wubook.adapter'
|
||||
_apply_on = 'channel.hotel.room.type.restriction.item'
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_create(self, record, fields=None):
|
||||
record.update_channel_pushed(False)
|
||||
def wired_rplan_get_rplan_values(self, date_from, date_to, channel_restriction_plan_id):
|
||||
return super(HotelRoomTypeRestrictionItemAdapter, self).wired_rplan_get_rplan_values(
|
||||
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))
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -2,11 +2,13 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping, only_create
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo.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__)
|
||||
|
||||
@@ -17,6 +19,60 @@ class HotelRoomTypeRestrictionImporter(Component):
|
||||
_apply_on = ['channel.hotel.room.type.restriction.item']
|
||||
_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):
|
||||
_name = 'channel.hotel.room.type.restriction.item.import.mapper'
|
||||
_inherit = 'channel.import.mapper'
|
||||
@@ -30,7 +86,6 @@ class HotelRoomTypeRestrictionItemImportMapper(Component):
|
||||
('closed', 'closed'),
|
||||
('closed_departure', 'closed_departure'),
|
||||
('closed_arrival', 'closed_arrival'),
|
||||
('room_type_id', 'room_type_id'),
|
||||
('date', 'date'),
|
||||
]
|
||||
|
||||
@@ -39,6 +94,20 @@ class HotelRoomTypeRestrictionItemImportMapper(Component):
|
||||
def applied_on(self, record):
|
||||
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
|
||||
def backend_id(self, record):
|
||||
return {'backend_id': self.backend_record.id}
|
||||
|
||||
@@ -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)
|
||||
@@ -2,3 +2,5 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import common
|
||||
from . import importer
|
||||
from . import exporter
|
||||
|
||||
@@ -6,6 +6,7 @@ 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 ChannelProductPricelist(models.Model):
|
||||
_name = 'channel.product.pricelist'
|
||||
@@ -17,7 +18,6 @@ class ChannelProductPricelist(models.Model):
|
||||
string='Pricelist',
|
||||
required=True,
|
||||
ondelete='cascade')
|
||||
channel_plan_id = fields.Char("Channel Plan ID", readonly=True, old_name='wpid')
|
||||
is_daily_plan = fields.Boolean("Channel Daily Plan", default=True, old_name='wdaily_plan')
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@@ -25,52 +25,65 @@ class ChannelProductPricelist(models.Model):
|
||||
@api.multi
|
||||
def create_plan(self):
|
||||
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:
|
||||
adapter = work.component(usage='backend.adapter')
|
||||
exporter = work.component(usage='product.pricelist.exporter')
|
||||
try:
|
||||
channel_plan_id = adapter.create_plan(self.name,
|
||||
self.is_daily_plan and 1 or 0)
|
||||
if channel_plan_id:
|
||||
self.channel_plan_id = channel_plan_id
|
||||
except ValidationError as e:
|
||||
self.create_issue('room', "Can't create plan on channel", "sss")
|
||||
exporter.create_plan(self)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
backend=self.backend_id.id,
|
||||
section='restriction',
|
||||
internal_message=_("Can't create pricelist plan in WuBook"),
|
||||
channel_message=err.data['message'])
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def update_plan_name(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True):
|
||||
if self.external_id:
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
adapter = work.component(usage='backend.adapter')
|
||||
exporter = work.component(usage='product.pricelist.exporter')
|
||||
try:
|
||||
adapter.update_plan_name(
|
||||
self.channel_plan_id,
|
||||
self.name)
|
||||
except ValidationError as e:
|
||||
self.create_issue('room', "Can't update plan name on channel", "sss")
|
||||
exporter.rename_plan(self)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
backend=self.backend_id.id,
|
||||
section='restriction',
|
||||
internal_message=_("Can't modify pricelist plan in WuBook"),
|
||||
channel_message=err.data['message'])
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def delete_plan(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True) and self.channel_room_id:
|
||||
if self.external_id:
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
adapter = work.component(usage='backend.adapter')
|
||||
exporter = work.component(usage='product.pricelist.exporter')
|
||||
try:
|
||||
adapter.delete_plan(self.channel_plan_id)
|
||||
except ValidationError as e:
|
||||
self.create_issue('room', "Can't delete plan on channel", "sss")
|
||||
exporter.delete_plan(self)
|
||||
except ChannelConnectorError as err:
|
||||
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')
|
||||
@api.multi
|
||||
def import_price_plans(self):
|
||||
if self._context.get('channel_action', True):
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
importer = work.component(usage='channel.importer')
|
||||
@api.model
|
||||
def import_price_plans(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(usage='product.pricelist.importer')
|
||||
try:
|
||||
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):
|
||||
_inherit = 'product.pricelist'
|
||||
@@ -83,19 +96,47 @@ class ProductPricelist(models.Model):
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
def name_get(self):
|
||||
self.ensure_one()
|
||||
pricelist_obj = self.env['product.pricelist']
|
||||
org_names = super(ProductPricelist, self).name_get()
|
||||
names = []
|
||||
for name in org_names:
|
||||
priclist_id = pricelist_obj.browse(name[0])
|
||||
if any(priclist_id.channel_bind_ids) and \
|
||||
priclist_id.channel_bind_ids[0].channel_plan_id:
|
||||
names.append((name[0], '%s (Channel)' % name[1]))
|
||||
priclist_id.channel_bind_ids[0].external_id:
|
||||
names.append((name[0], '%s (%s Backend)' % (
|
||||
name[1],
|
||||
priclist_id.channel_bind_ids[0].backend_id.name)))
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
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):
|
||||
_name = 'channel.binding.product.pricelist.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
@@ -103,13 +144,13 @@ class ChannelBindingProductPricelistListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
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))
|
||||
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))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if 'name' in fields:
|
||||
record.with_delay(priority=20).update_plan_name()
|
||||
record.update_plan_name()
|
||||
|
||||
28
hotel_channel_connector/models/product_pricelist/exporter.py
Normal file
28
hotel_channel_connector/models/product_pricelist/exporter.py
Normal 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
|
||||
57
hotel_channel_connector/models/product_pricelist/importer.py
Normal file
57
hotel_channel_connector/models/product_pricelist/importer.py
Normal 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}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -28,6 +28,8 @@
|
||||
<field name="username" colspan="2"/>
|
||||
<field name="passwd" password="1" colspan="2"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Security" name="security">
|
||||
<group colspan="4" col="4">
|
||||
<field name="security_token" colspan="4"/>
|
||||
<button colspan="1" name="generate_key"
|
||||
@@ -84,6 +86,94 @@
|
||||
string="Import in background"/>
|
||||
</div>
|
||||
</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>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Channel Reservation">
|
||||
<group>
|
||||
<field name="channel_reservation_id" />
|
||||
<field name="external_id" />
|
||||
<field name="ota_reservation_id" />
|
||||
<field name="wstatus" />
|
||||
<field name="wstatus_reason" />
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<field name="model">channel.hotel.room.type.availability</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Channel Room Availability">
|
||||
<group>
|
||||
<field name="id" invisible="1" />
|
||||
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="channel_max_avail" />
|
||||
<field name="channel_pushed" />
|
||||
|
||||
@@ -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>
|
||||
@@ -7,8 +7,11 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Channel Room Restriction">
|
||||
<group>
|
||||
<field name="channel_plan_id" />
|
||||
<field name="is_daily_plan" />
|
||||
<field name="id" invisible="1" />
|
||||
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="external_id" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Channel Virtual Room">
|
||||
<group>
|
||||
<field name="id" invisible="1" />
|
||||
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="channel_room_id" />
|
||||
<field name="external_id" />
|
||||
<field name="channel_short_code" />
|
||||
<field name="ota_capacity" />
|
||||
</group>
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Channel OTA's Info" >
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="id" invisible="1" />
|
||||
<field name="backend_id" attrs="{'visible': [('id','=', False)]}" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="ota_id" />
|
||||
<field name="name" />
|
||||
@@ -30,11 +34,4 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="open_channel_ota_info_tree_all">
|
||||
<field name="name">Hotel Channel Connector OTA's Info</field>
|
||||
<field name="res_model">channel.ota.info</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -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>
|
||||
@@ -7,14 +7,18 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Channel Product Pricelist">
|
||||
<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" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</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="model">channel.product.pricelist</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
class="oe_stat_button" icon="fa-warning"
|
||||
attrs="{'invisible':['|', ['section', '!=', 'reservation'], ['channel_object_id', '=', False]]}"/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="backend_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="section"/>
|
||||
<field name="internal_message"/>
|
||||
@@ -42,6 +45,7 @@
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Channel Connector Issues">
|
||||
<field name="backend_id"/>
|
||||
<field name="create_date"/>
|
||||
<field name="section"/>
|
||||
<field name="internal_message"/>
|
||||
@@ -58,6 +62,7 @@
|
||||
<field name="model">hotel.channel.connector.issue</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Issues">
|
||||
<field name="backend_id"/>
|
||||
<field name="create_date"/>
|
||||
<field name="section"/>
|
||||
<field name="internal_message"/>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//page[@name='days']" position="after">
|
||||
<page name="connector">
|
||||
<page name="connector" string="Channel Connector">
|
||||
<group string="Hotel Channel Bindings">
|
||||
<field name="channel_bind_ids" nolabel="1">
|
||||
<tree>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<notebook>
|
||||
<page name="connector">
|
||||
<page name="connector" string="Channel Connector">
|
||||
<group string="Hotel Channel Bindings">
|
||||
<field name="channel_bind_ids" nolabel="1">
|
||||
<tree>
|
||||
|
||||
@@ -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>
|
||||
@@ -5,11 +5,19 @@
|
||||
<field name="model">hotel.room.type.restriction</field>
|
||||
<field name="inherit_id" ref="hotel.room_type_restriction_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form[1]//sheet" position="before">
|
||||
<header>
|
||||
<button name="%(action_wubook_import_plan_restrictions)d" string="Import Restrictions From Channel" type="action" class="oe_highlight" />
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="//sheet" 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>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<notebook>
|
||||
<page name="connector">
|
||||
<page name="connector" string="Channel Connector">
|
||||
<group string="Hotel Channel Bindings">
|
||||
<field name="capacity" invisible="1" />
|
||||
<field name="channel_bind_ids" context="{'default_ota_capacity': capacity}" nolabel="1">
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
<field name="model">product.pricelist.item</field>
|
||||
<field name="inherit_id" ref="product.product_pricelist_item_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<!--field name="channel_pushed" readonly="True" /-->
|
||||
<field name="is_daily_plan" invisible="1" />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='date_end']" position="attributes">
|
||||
<attribute name="attrs">{'readonly': [('is_daily_plan', '=', True)]}</attribute>
|
||||
<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"/>
|
||||
<field name="channel_pushed"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -5,14 +5,19 @@
|
||||
<field name="model">product.pricelist</field>
|
||||
<field name="inherit_id" ref="product.product_pricelist_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form[1]//sheet" position="before">
|
||||
<header>
|
||||
<button name="%(action_wubook_import_plan_prices)d" string="Import Prices From Channel" type="action" class="oe_highlight" />
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='company_id']" position="after">
|
||||
<field name="id" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//sheet" 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user