Finalize Hotel Channel Connector (#64)

* [FIX] Export pricelist

* [IMP] Import Reservations

* [IMP] Better exception handle

* [IMP] Notifiactions

* [FIX] Issues [IMP] Notifications

* [FIX] Cron Jobs
This commit is contained in:
QS5ELkMu
2018-11-14 23:32:58 +01:00
committed by GitHub
parent 8622ff1948
commit 77cddb44f1
47 changed files with 1178 additions and 1863 deletions

View File

@@ -11,6 +11,7 @@
'description': "Hotel Channel Connector Base",
'depends': [
'connector',
'web_notify',
'hotel',
],
'external_dependencies': {
@@ -18,7 +19,6 @@
},
'data': [
'data/cron_jobs.xml',
'views/general.xml',
'views/hotel_channel_connector_issue_views.xml',
'views/inherited_hotel_reservation_views.xml',
'views/inherited_hotel_room_type_views.xml',
@@ -29,7 +29,6 @@
'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/channel_hotel_reservation_views.xml',
'views/channel_hotel_room_type_views.xml',
'views/channel_hotel_room_type_availability_views.xml',
@@ -38,6 +37,7 @@
'views/channel_product_pricelist_views.xml',
'views/channel_product_pricelist_item_views.xml',
'views/channel_connector_backend_views.xml',
'views/channel_ota_info_views.xml',
'wizard/inherited_massive_changes.xml',
'data/menus.xml',
'data/sequences.xml',

View File

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

View File

@@ -361,7 +361,7 @@ class WuBookAdapter(AbstractComponent):
return results
def fetch_booking(self, channel_reservation_id):
rcode, results = self.backend_adapter.fetch_booking(
rcode, results = self._server.fetch_booking(
self._session_info[0],
self._session_info[1],
channel_reservation_id)
@@ -372,10 +372,6 @@ class WuBookAdapter(AbstractComponent):
return results
def mark_bookings(self, channel_reservation_ids):
init_connection = self._context.get('init_connection', True)
if init_connection:
if not self.init_connection():
return False
rcode, results = self._server.mark_bookings(
self._session_info[0],
self._session_info[1],

View File

@@ -9,6 +9,18 @@ class BaseHotelChannelConnectorComponent(AbstractComponent):
_inherit = 'base.connector'
_collection = 'channel.backend'
@api.model
def create_issue(self, **kwargs):
self.env['hotel.channel.connector.issue'].sudo().create({
'backend_id': kwargs.get('backend', self.backend_record.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),
})
class ChannelConnectorError(Exception):
def __init__(self, message, data):
super().__init__(message)

View File

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

View File

@@ -1,784 +1,9 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import pytz
import json
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import AbstractComponent, Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from .backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT,
DEFAULT_WUBOOK_DATETIME_FORMAT,
WUBOOK_STATUS_BAD)
from odoo import api
_logger = logging.getLogger(__name__)
from odoo.addons.component.core import AbstractComponent
class HotelChannelConnectorImporter(AbstractComponent):
_name = 'hotel.channel.importer'
_inherit = ['base.importer', 'base.hotel.channel.connector']
_usage = 'channel.importer'
@api.model
def _get_room_values_availability(self, room_type_id, date_str, day_vals, set_max_avail):
room_type_avail_obj = self.env['hotel.room.type.availability']
room_type_avail = room_type_avail_obj.search([
('room_type_id', '=', room_type_id),
('date', '=', date_str)
], limit=1)
vals = {
'no_ota': day_vals.get('no_ota'),
'booked': day_vals.get('booked'),
'avail': day_vals.get('avail', 0),
'wpushed': True,
}
if set_max_avail:
vals.update({'max_avail': day_vals.get('avail', 0)})
if room_type_avail:
room_type_avail.with_context({
'wubook_action': False,
}).write(vals)
else:
vals.update({
'room_type_id': room_type_id,
'date': date_str,
})
room_type_avail_obj.with_context({
'wubook_action': False,
'mail_create_nosubscribe': True,
}).create(vals)
@api.model
def _get_room_values_restrictions(self, restriction_plan_id, room_type_id, date_str, day_vals):
room_type_restr_item_obj = self.env['hotel.room.type.restriction.item']
room_type_restr = room_type_restr_item_obj.search([
('room_type_id', '=', room_type_id),
('applied_on', '=', '0_room_type'),
('date_start', '=', date_str),
('date_end', '=', date_str),
('restriction_id', '=', restriction_plan_id),
])
vals = {
'min_stay': int(day_vals.get('min_stay', 0)),
'min_stay_arrival': int(day_vals.get(
'min_stay_arrival',
0)),
'max_stay': int(day_vals.get('max_stay', 0)),
'max_stay_arrival': int(day_vals.get(
'max_stay_arrival',
0)),
'closed': int(day_vals.get('closed', False)),
'closed_departure': int(day_vals.get(
'closed_departure',
False)),
'closed_arrival': int(day_vals.get(
'closed_arrival',
False)),
'wpushed': True,
}
if room_type_restr:
room_type_restr.with_context({
'wubook_action': False,
}).write(vals)
else:
vals.update({
'restriction_id': restriction_plan_id,
'room_type_id': room_type_id,
'date_start': date_str,
'date_end': date_str,
'applied_on': '0_room_type',
})
room_type_restr_item_obj.with_context({
'wubook_action': False,
}).create(vals)
@api.model
def _generate_room_values(self, dfrom, dto, values, set_max_avail=False):
room_type_restr_obj = self.env['hotel.room.type.restriction']
hotel_room_type_obj = self.env['hotel.room.type']
def_restr_plan = room_type_restr_obj.search([('wpid', '=', '0')])
_logger.info("==== ROOM VALUES (%s -- %s)", dfrom, dto)
_logger.info(values)
for k_rid, v_rid in values.iteritems():
room_type = hotel_room_type_obj.search([
('wrid', '=', k_rid)
], limit=1)
if room_type:
date_dt = fields.Date.from_string(dfrom)
for day_vals in v_rid:
date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
self._get_room_values_availability(
room_type.id,
date_str,
day_vals,
set_max_avail)
if def_restr_plan:
self._get_room_values_restrictions(
def_restr_plan.id,
room_type.id,
date_str,
day_vals)
date_dt = date_dt + timedelta(days=1)
return True
@api.model
def _generate_booking_vals(self, broom, checkin_str, checkout_str,
is_cancellation, channel_info, wstatus, crcode,
rcode, room_type, split_booking, dates_checkin,
dates_checkout, book):
# Generate Reservation Day Lines
reservation_line_ids = []
tprice = 0.0
for brday in broom['roomdays']:
wndate = fields.Date.from_string(
brday['day']
)
if wndate >= dates_checkin[0] and wndate <= (dates_checkout[0] - timedelta(days=1)):
reservation_line_ids.append((0, False, {
'date': wndate.strftime(
DEFAULT_SERVER_DATE_FORMAT),
'price': brday['price']
}))
tprice += brday['price']
persons = room_type.wcapacity
if 'ancillary' in broom and 'guests' in broom['ancillary']:
persons = broom['ancillary']['guests']
vals = {
'channel_reservation_id': rcode,
'ota_id': channel_info and channel_info.id,
'ota_reservation_id': crcode,
'channel_raw_data': json.dumps(book),
'wstatus': wstatus,
'wmodified': book['was_modified'],
'odoo_id': [0, False, {
'checkin': checkin_str,
'checkout': checkout_str,
'adults': persons,
'children': book['children'],
'reservation_line_ids': reservation_line_ids,
'price_unit': tprice,
'to_assign': True,
'to_read': True,
'state': is_cancellation and 'cancelled' or 'draft',
'room_type_id': room_type.id,
'splitted': split_booking,
}],
}
_logger.info("===== CONTRUCT RESERV")
_logger.info(vals)
return vals
@api.model
def _generate_partner_vals(self, book):
country_id = self.env['res.country'].search([
('code', '=', str(book['customer_country']))
], limit=1)
# lang = self.env['res.lang'].search([('code', '=', book['customer_language_iso'])], limit=1)
return {
'name': "%s, %s" %
(book['customer_surname'], book['customer_name']),
'country_id': country_id and country_id.id,
'city': book['customer_city'],
'phone': book['customer_phone'],
'zip': book['customer_zip'],
'street': book['customer_address'],
'email': book['customer_mail'],
'unconfirmed': True,
# 'lang': lang and lang.id,
}
# FIXME: Super big method!!! O_o
@api.model
def _generate_reservations(self, bookings):
_logger.info("=== BOOKINGS FROM WUBOOK")
_logger.info(bookings)
default_arrival_hour = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_arrival_hour')
default_departure_hour = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_departure_hour')
# Get user timezone
tz_hotel = self.env['ir.default'].sudo().get(
'res.config.settings', 'tz_hotel')
res_partner_obj = self.env['res.partner']
channel_reserv_obj = self.env['channel.hotel.reservation']
hotel_reserv_obj = self.env['hotel.reservation']
hotel_folio_obj = self.env['hotel.folio']
channel_room_type_obj = self.env['channel.hotel.room.type']
hotel_room_type_obj = self.env['hotel.room.type']
# Space for store some data for construct folios
processed_rids = []
failed_reservations = []
checkin_utc_dt = False
checkout_utc_dt = False
split_booking = False
for book in bookings: # This create a new folio
splitted_map = {}
is_cancellation = book['status'] in WUBOOK_STATUS_BAD
bstatus = str(book['status'])
rcode = str(book['reservation_code'])
crcode = str(book['channel_reservation_code']) \
if book['channel_reservation_code'] else 'undefined'
# Can't process failed reservations
# (for example set a invalid new reservation and receive in
# the same transaction an cancellation)
if crcode in failed_reservations:
self.create_issue(
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)
arr_hour = default_arrival_hour if book['arrival_hour'] == "--" \
else book['arrival_hour']
checkin = "%s %s" % (book['date_arrival'], arr_hour)
checkin_dt = fields.Date.from_string(checkin)
checkin_utc_dt = date_utils.dt_as_timezone(checkin_dt, 'UTC')
checkin = checkin_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
checkout = "%s %s" % (book['date_departure'],
default_departure_hour)
checkout_dt = fields.Date.from_string(checkout)
checkout_utc_dt = date_utils.dt_as_timezone(checkout_dt, 'UTC')
checkout = checkout_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
# Search Folio. If exists.
folio_id = False
if crcode != 'undefined':
reserv_folio = channel_reserv_obj.search([
('ota_reservation_id', '=', crcode)
], limit=1)
if reserv_folio:
folio_id = reserv_folio.odoo_id.folio_id
else:
reserv_folio = channel_reserv_obj.search([
('channel_reservation_id', '=', rcode)
], limit=1)
if reserv_folio:
folio_id = reserv_folio.odoo_id.folio_id
# Need update reservations?
sreservs = channel_reserv_obj.search([('channel_reservation_id', '=', rcode)])
reservs = folio_id.room_lines if folio_id else sreservs.mapped(lambda x: x.odoo_id)
reservs_processed = False
if any(reservs):
folio_id = reservs[0].folio_id
for reserv in reservs:
if reserv.channel_reservation_id == rcode:
binding_id = reserv.channel_bind_ids[0]
binding_id.write({
'channel_raw_data': json.dumps(book),
'wstatus': str(book['status']),
'wstatus_reason': book.get('status_reason', ''),
})
reserv.with_context({'wubook_action': False}).write({
'to_read': True,
'to_assign': True,
'price_unit': book['amount'],
'customer_notes': book['customer_notes'],
})
if reserv.partner_id.unconfirmed:
reserv.partner_id.write(
self._generate_partner_vals(book)
)
reservs_processed = True
if is_cancellation:
reserv.with_context({
'wubook_action': False}).action_cancel()
elif reserv.state == 'cancelled':
reserv.with_context({
'wubook_action': False,
}).write({
'discount': 0.0,
'state': 'confirm',
})
# Do Nothing if already processed 'wrid'
if reservs_processed:
processed_rids.append(rcode)
continue
# Search Customer
customer_mail = book.get('customer_mail', False)
partner_id = False
if customer_mail:
partner_id = res_partner_obj.search([
('email', '=', customer_mail)
], limit=1)
if not partner_id:
partner_id = res_partner_obj.create(self._generate_partner_vals(book))
# Search Wubook Channel Info
channel_info = self.env['hotel.channel.connector.ota.info'].search(
[('ota_id', '=', str(book['id_channel']))], limit=1)
reservations = []
used_rooms = []
# Iterate booked rooms
for broom in book['booked_rooms']:
room_type = channel_room_type_obj.search([
('channel_room_id', '=', broom['room_id'])
], limit=1)
if not room_type:
self.create_issue(
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
dates_checkin = [checkin_utc_dt, False]
dates_checkout = [checkout_utc_dt, False]
split_booking = False
split_booking_parent = False
# This perhaps create splitted reservation
while dates_checkin[0]:
checkin_str = dates_checkin[0].strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
checkout_str = dates_checkout[0].strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
vals = self._generate_booking_vals(
broom,
checkin_str,
checkout_str,
is_cancellation,
channel_info,
bstatus,
crcode,
rcode,
room_type.odoo_id,
split_booking,
dates_checkin,
dates_checkout,
book,
)
if vals['price_unit'] != book['amount']:
self.create_issue(
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_type(
checkin_str,
checkout_str,
room_type_id=room_type.odoo_id.id,
notthis=used_rooms)
if any(free_rooms):
vals.update({
'product_id': free_rooms[0].product_id.id,
'name': free_rooms[0].name,
})
reservations.append((0, False, vals))
used_rooms.append(free_rooms[0].id)
if split_booking:
if not split_booking_parent:
split_booking_parent = len(reservations)
else:
splitted_map.setdefault(
split_booking_parent,
[]).append(len(reservations))
dates_checkin = [dates_checkin[1], False]
dates_checkout = [dates_checkout[1], False]
else:
date_diff = (dates_checkout[0].replace(
hour=0, minute=0, second=0,
microsecond=0) -
dates_checkin[0].replace(
hour=0, minute=0, second=0,
microsecond=0)).days
if date_diff <= 0:
if split_booking:
if split_booking_parent:
del reservations[split_booking_parent-1:]
if split_booking_parent in splitted_map:
del splitted_map[split_booking_parent]
# Can't found space for reservation
vals = self._generate_booking_vals(
broom,
checkin_utc_dt,
checkout_utc_dt,
is_cancellation,
channel_info,
bstatus,
crcode,
rcode,
room_type.odoo_id,
False,
(checkin_utc_dt, False),
(checkout_utc_dt, False),
book,
)
vals.update({
'product_id':
room_type.odoo_id.room_ids[0].product_id.id,
'name': room_type.odoo_id.name,
'overbooking': True,
})
reservations.append((0, False, vals))
self.create_issue(
section='reservation',
internal_message="Reservation imported with overbooking state",
channel_object_id=rcode)
dates_checkin = [False, False]
dates_checkout = [False, False]
split_booking = False
else:
split_booking = True
dates_checkin = [
dates_checkin[0],
dates_checkin[0] + timedelta(days=date_diff-1)
]
dates_checkout = [
dates_checkout[0] - timedelta(days=1),
checkout_utc_dt
]
if split_booking:
self.create_issue(
section='reservation',
internal_message="Reservation Splitted",
channel_object_id=rcode)
# Create Folio
if not any(failed_reservations) and any(reservations):
try:
# TODO: Improve 'addons_list' & discounts
addons = str(book['addons_list']) if any(book['addons_list']) else ''
discounts = book.get('discount', '')
vals = {
'room_lines': reservations,
'wcustomer_notes': "%s\nADDONS:\n%s\nDISCOUNT:\n%s" % (
book['customer_notes'], addons, discounts),
'channel_type': 'web',
}
_logger.info("=== FOLIO CREATE")
_logger.info(reservations)
if folio_id:
folio_id.with_context({
'wubook_action': False}).write(vals)
else:
vals.update({
'partner_id': partner_id.id,
'wseed': book['sessionSeed']
})
folio_id = hotel_folio_obj.with_context({
'wubook_action': False}).create(vals)
# Update Reservation Spitted Parents
sorted_rlines = folio_id.room_lines.sorted(key='id')
for k_pid, v_pid in splitted_map.iteritems():
preserv = sorted_rlines[k_pid-1]
for pid in v_pid:
creserv = sorted_rlines[pid-1]
creserv.parent_reservation = preserv.id
processed_rids.append(rcode)
except ChannelConnectorError as err:
self.create_issue(
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)
@api.model
def _generate_pricelists(self, price_plans):
product_listprice_obj = self.env['product.pricelist']
count = 0
for plan in price_plans:
if 'vpid' in plan:
continue # Ignore Virtual Plans
vals = {
'name': plan['name'],
'is_daily_plan': plan['daily'] == 1,
}
plan_id = product_listprice_obj.search([
('wpid', '=', str(plan['id']))
], limit=1)
if not plan_id:
vals.update({
'wpid': str(plan['id']),
})
product_listprice_obj.with_context({
'wubook_action': False}).create(vals)
else:
plan_id.with_context({'wubook_action': False}).write(vals)
count = count + 1
return count
@api.model
def _generate_pricelist_items(self, channel_plan_id, date_from, date_to, plan_prices):
hotel_room_type_obj = self.env['hotel.room.type']
pricelist = self.env['product.pricelist'].search([
('wpid', '=', channel_plan_id)
], limit=1)
if pricelist:
pricelist_item_obj = self.env['product.pricelist.item']
dfrom_dt = fields.Date.from_string(date_from)
dto_dt = fields.Date.from_string(date_to)
days_diff = (dfrom_dt - dto_dt) + 1
for i in range(0, days_diff):
ndate_dt = dfrom_dt + timedelta(days=i)
for k_rid, v_rid in plan_prices.iteritems():
room_type = hotel_room_type_obj.search([
('wrid', '=', k_rid)
], limit=1)
if room_type:
pricelist_item = pricelist_item_obj.search([
('pricelist_id', '=', pricelist.id),
('date_start', '=', ndate_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT)),
('date_end', '=', ndate_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT)),
('compute_price', '=', 'fixed'),
('applied_on', '=', '1_product'),
('product_tmpl_id', '=', room_type.product_id.product_tmpl_id.id)
], limit=1)
vals = {
'fixed_price': plan_prices[k_rid][i],
'wpushed': True,
}
if pricelist_item:
pricelist_item.with_context({
'wubook_action': False}).write(vals)
else:
vals.update({
'pricelist_id': pricelist.id,
'date_start': ndate_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT),
'date_end': ndate_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT),
'compute_price': 'fixed',
'applied_on': '1_product',
'product_tmpl_id': room_type.product_id.product_tmpl_id.id
})
pricelist_item_obj.with_context({
'wubook_action': False}).create(vals)
return True
@api.model
def _generate_restrictions(self, restriction_plans):
restriction_obj = self.env['hotel.room.type.restriction']
count = 0
for plan in restriction_plans:
vals = {
'name': plan['name'],
}
plan_id = restriction_obj.search([
('wpid', '=', str(plan['id']))
], limit=1)
if not plan_id:
vals.update({
'wpid': str(plan['id']),
})
restriction_obj.with_context({
'wubook_action': False,
'rules': plan.get('rules'),
}).create(vals)
else:
plan_id.with_context({'wubook_action': False}).write(vals)
count = count + 1
return count
@api.model
def _generate_restriction_items(self, plan_restrictions):
hotel_room_type_obj = self.env['hotel.room.type']
reserv_restriction_obj = self.env['hotel.room.type.restriction']
restriction_item_obj = self.env['hotel.room.type.restriction.item']
_logger.info("===== RESTRICTIONS")
_logger.info(plan_restrictions)
for k_rpid, v_rpid in plan_restrictions.iteritems():
restriction_id = reserv_restriction_obj.search([
('wpid', '=', k_rpid)
], limit=1)
if restriction_id:
for k_rid, v_rid in v_rpid.iteritems():
room_type = hotel_room_type_obj.search([
('wrid', '=', k_rid)
], limit=1)
if room_type:
for item in v_rid:
date_dt = fields.Date.from_string(item['date'])
restriction_item = restriction_item_obj.search([
('restriction_id', '=', restriction_id.id),
('date_start', '=', date_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT)),
('date_end', '=', date_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT)),
('applied_on', '=', '0_room_type'),
('room_type_id', '=', room_type.id)
], limit=1)
vals = {
'closed_arrival': item['closed_arrival'],
'closed': item['closed'],
'min_stay': item['min_stay'],
'closed_departure': item['closed_departure'],
'max_stay': item['max_stay'],
'max_stay_arrival': item['max_stay_arrival'],
'min_stay_arrival': item['min_stay_arrival'],
'wpushed': True,
}
if restriction_item:
restriction_item.with_context({
'wubook_action': False}).write(vals)
else:
vals.update({
'restriction_id': restriction_id.id,
'date_start': date_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT),
'date_end': date_dt.strftime(
DEFAULT_SERVER_DATE_FORMAT),
'applied_on': '0_room_type',
'room_type_id': room_type.id
})
restriction_item_obj.with_context({
'wubook_action': False}).create(vals)
return True
@api.model
def fetch_booking(self, channel_reservation_id):
try:
results = self.backend_adapter.fetch_booking(channel_reservation_id)
processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \
self.generate_reservations(results)
if any(processed_rids):
self.backend_adapter.mark_bookings(list(set(processed_rids)))
# Update Odoo availability (don't wait for wubook)
if checkin_utc_dt and checkout_utc_dt:
self.backend_adapter.fetch_rooms_values(
checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
except ChannelConnectorError as err:
self.create_issue(
'reservation',
_("Can't process reservations from wubook"),
err.data['message'], channel_object_id=channel_reservation_id)
return False
return True
@api.model
def import_pricing_plans(self):
try:
results = self.backend_adapter.get_pricing_plans()
count = self._generate_pricelists(results)
except ChannelConnectorError as err:
self.create_issue(
section='plan',
internal_message=_("Can't get pricing plans from wubook"),
channel_message=err.data['message'])
return 0
return count
@api.model
def fetch_plan_prices(self, channel_plan_id, date_from, date_to, rooms=None):
try:
results = self.backend_adapter.fetch_plan_prices(
channel_plan_id,
date_from,
date_to,
rooms)
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
except ChannelConnectorError as err:
self.create_issue(
section='plan',
internal_message=_("Can't fetch plan prices from wubook"),
channel_message=err.data['message'])
return False
return True
@api.model
def fetch_all_plan_prices(self, date_from, date_to, rooms=None):
no_errors = True
channel_plan_ids = self.env['product.pricelist'].search([
('wpid', '!=', False), ('wpid', '!=', '')
]).mapped('wpid')
if any(channel_plan_ids):
try:
for channel_plan_id in channel_plan_ids:
results = self.backend_adapter.fetch_plan_prices(
channel_plan_id,
date_from,
date_to,
rooms)
self._generate_pricelist_items(channel_plan_id, date_from, date_to, results)
except ChannelConnectorError as err:
self.create_issue(
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
@api.model
def import_restriction_plans(self):
try:
results = self.backend_adapter.rplan_rplans()
count = self._generate_restrictions(results)
except ChannelConnectorError as err:
self.create_issue(
section='rplan',
internal_message=_("Can't fetch restriction plans from wubook"),
channel_message=err.data['message'])
return 0
return count
@api.model
def fetch_rplan_restrictions(self, date_from, date_to, channel_restriction_plan_id=False):
try:
results = self.backend_adapter.wired_rplan_get_rplan_values(
date_from,
date_to,
int(channel_restriction_plan_id))
if any(results):
self._generate_restriction_items(results)
except ChannelConnectorError as err:
self.create_issue(
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
return True
class BatchImporter(AbstractComponent):
""" The role of a BatchImporter is to search for a list of
items to import, then it can either import them directly or delay
the import of each item separately.
"""
_name = 'channel.batch.importer'
_inherit = ['base.importer', 'base.hotel.channel.connector']
_usage = 'batch.importer'
def _import_record(self, external_id):
""" Import a record directly or delay the import of the record.
Method to implement in sub-classes.
"""
raise NotImplementedError
class DirectBatchImporter(AbstractComponent):
""" Import the records directly, without delaying the jobs. """
_name = 'channel.direct.batch.importer'
_inherit = 'channel.batch.importer'
def _import_record(self, external_id):
""" Import the record directly """
self.model.import_record(self.backend_record, external_id)

View File

@@ -39,9 +39,7 @@ class website_wubook(http.Controller):
if not backend:
raise ValidationError(_("Can't found a backend!"))
_logger.info(_("[WUBOOK->ODOO] Importing Booking..."))
# Create Reservation
request.env['wubook'].sudo().fetch_booking(lcode, rcode)
request.env['channel.hotel.reservation'].import_reservation(rcode)
return request.make_response('200 OK', [('Content-Type', 'text/plain')])
@@ -72,18 +70,12 @@ class website_wubook(http.Controller):
odoo_dto = datetime.strptime(
dto,
DEFAULT_WUBOOK_DATE_FORMAT).strftime(DEFAULT_SERVER_DATE_FORMAT)
backend.write({
'avail_from': odoo_dfrom,
'avail_to': odoo_dto,
'restriction_id': False,
'restriction_from': odoo_dfrom,
'restriction_to': odoo_dto,
'pricelist_id': False,
'pricelist_from': odoo_dfrom,
'pricelist_to': odoo_dto,
})
backend.import_availability()
backend.import_restriction()
backend.import_pricelist()
request.env['channel.hotel.room.type.availability'].import_availability(
backend, odoo_dfrom, odoo_dto)
request.env['channel.hotel.room.type.restriction.item'].import_restriction_values(
backend, odoo_dfrom, odoo_dto, False)
request.env['channel.product.pricelist.item'].import_pricelist_values(
backend, odoo_dfrom, odoo_dto, False)
return request.make_response('200 OK', [('Content-Type', 'text/plain')])

View File

@@ -1,26 +1,29 @@
<odoo noupdate="1">
<!--record model="ir.cron" id="wubook_push_avail">
<field name="name">WuBook Push Availability</field>
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record model="ir.cron" id="channel_connector_push_changes">
<field name="name">Channel Connector Push Changes</field>
<field name="interval_number">5</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="state">code</field>
<field name="doall" eval="False" />
<field name="model_id" ref="model_wubook" />
<field name="code">model.push_changes()</field>
</record-->
<field name="model_id" ref="model_channel_backend" />
<field name="code">model.cron_push_changes()</field>
</record>
<!-- <record id="wubook_corporate_fetch" model="ir.cron">
<field name="name">WuBook Corporate Fetchable Properties</field>
<field name="active" eval="True" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">4</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="doal">1</field>
<field name="nextcall" >2016-12-31 23:59:59</field>
<field name="model" eval="'wubook'" />
<field name="function" eval="'corporate_fetch'" />
<field name="args" eval="" />
<field name="priority" eval="5" />
</record> -->
<record model="ir.cron" id="channel_connector_fetch_new_bookings">
<field name="name">Channel Connector Fetch New Bookings</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="state">code</field>
<field name="doall" eval="False" />
<field name="model_id" ref="model_channel_backend" />
<field name="code">model.cron_import_reservations()</field>
</record>
</data>
</odoo>

View File

@@ -1,11 +1,13 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import os
import binascii
from contextlib import contextmanager
from odoo import models, api, fields
from ...components.backend_adapter import WuBookLogin, WuBookServer
_logger = logging.getLogger(__name__)
class ChannelBackend(models.Model):
_name = 'channel.backend'
@@ -31,18 +33,22 @@ 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')
reservation_id_str = fields.Char('Channel Reservation ID', store=False)
restriction_from = fields.Date('Restriction From')
restriction_to = fields.Date('Restriction To')
avail_from = fields.Date('Availability From', store=False)
avail_to = fields.Date('Availability To', store=False)
restriction_from = fields.Date('Restriction From', store=False)
restriction_to = fields.Date('Restriction To', store=False)
restriction_id = fields.Many2one('channel.hotel.room.type.restriction',
'Channel Restriction')
'Channel Restriction',
store=False)
pricelist_from = fields.Date('Pricelist From')
pricelist_to = fields.Date('Pricelist To')
pricelist_from = fields.Date('Pricelist From', store=False)
pricelist_to = fields.Date('Pricelist To', store=False)
pricelist_id = fields.Many2one('channel.product.pricelist',
'Channel Product Pricelist')
'Channel Product Pricelist',
store=False)
issue_ids = fields.One2many('hotel.channel.connector.issue',
'backend_id',
@@ -60,84 +66,159 @@ class ChannelBackend(models.Model):
def import_reservations(self):
channel_hotel_reservation_obj = self.env['channel.hotel.reservation']
for backend in self:
channel_hotel_reservation_obj.import_reservations(backend)
count = channel_hotel_reservation_obj.import_reservations(backend)
if count == 0:
self.env.user.notify_info("No reservations to import. All done :)",
title="Import Reservations")
else:
self.env.user.notify_info("%d reservations successfully imported" % count,
title="Import Reservations")
return True
@api.multi
def import_reservation(self):
channel_hotel_reservation_obj = self.env['channel.hotel.reservation']
for backend in self:
res = channel_hotel_reservation_obj.import_reservation(
backend,
backend.reservation_id_str)
if not res:
self.env.user.notify_warning(
"Can't import '%s' reservation" % backend.reservation_id_str,
title="Import Reservations")
return True
@api.multi
def import_rooms(self):
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
for backend in self:
channel_hotel_room_type_obj.import_rooms(backend)
count = channel_hotel_room_type_obj.import_rooms(backend)
if count == 0:
self.env.user.notify_info("No rooms to import. All done :)",
title="Import Rooms")
else:
self.env.user.notify_info("%d rooms successfully imported" % count,
title="Import Rooms")
return True
@api.multi
def import_otas_info(self):
channel_ota_info_obj = self.env['channel.ota.info']
for backend in self:
channel_ota_info_obj.import_otas_info(backend)
count = channel_ota_info_obj.import_otas_info(backend)
self.env.user.notify_info("%d ota's successfully imported" % count,
title="Import OTA's")
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)
res = channel_hotel_room_type_avail_obj.import_availability(
backend,
backend.avail_from,
backend.avail_to)
if not res:
self.env.user.notify_warning("Error importing availability",
title="Import Availability")
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)
res = channel_hotel_room_type_avail_obj.push_availability(backend)
if not res:
self.env.user.notify_warning("Error pushing availability",
title="Export Availability")
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)
count = channel_hotel_room_type_restr_obj.import_restriction_plans(backend)
if count == 0:
self.env.user.notify_info("No restiction plans to import. All done :)",
title="Import Restrictions")
else:
self.env.user.notify_info("%d restriction plans successfully imported" % count,
title="Import Restrictions")
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)
res = channel_hotel_restr_item_obj.import_restriction_values(
backend,
backend.restriction_from,
backend.restriction_to,
backend.restriction_id and backend.restriction_id.external_id or False)
if not res:
self.env.user.notify_warning("Error importing restrictions",
title="Import Restrictions")
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)
res = channel_hotel_restr_item_obj.push_restriction(backend)
if not res:
self.env.user.notify_warning("Error pushing restrictions",
title="Export Restrictions")
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)
count = channel_product_pricelist_obj.import_price_plans(backend)
if count == 0:
self.env.user.notify_info("No pricelist plans to import. All done :)",
title="Import Pricelists")
else:
self.env.user.notify_info("%d pricelist plans successfully imported" % count,
title="Import Pricelists")
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)
res = channel_product_pricelist_item_obj.import_pricelist_values(
backend,
backend.pricelist_from,
backend.pricelist_to,
backend.pricelist_id and backend.pricelist_id.external_id or False)
if not res:
self.env.user.notify_warning("Error importing pricelists",
title="Import Pricelists")
return True
@api.multi
def push_pricelist(self):
channel_product_pricelist_item_obj = self.env['channel.product.pricelist.item']
for backend in self:
channel_product_pricelist_item_obj.push_pricelist(backend)
res = channel_product_pricelist_item_obj.push_pricelist(backend)
if not res:
self.env.user.notify_warning("Error pushing pricelists",
title="Export Pricelists")
return True
@api.multi
def push_changes(self):
self.push_availability()
self.push_restriction()
self.push_pricelist()
@api.model
def cron_push_changes(self):
_logger.info("======== PASA POR AKI!! AAAAA")
backends = self.env[self._name].search([])
backends.push_availability()
backends.push_restriction()
backends.push_pricelist()
@api.model
def cron_import_reservations(self):
_logger.info("======== PASA POR AKI!! BBBBBB")
self.env[self._name].search([]).import_reservations()
@contextmanager
@api.multi
@@ -153,163 +234,3 @@ class ChannelBackend(models.Model):
_super = super(ChannelBackend, self)
with _super.work_on(model_name, channel_api=channel_api, **kwargs) as work:
yield work
# Dangerus method: Usefull for cloned instances with new wubook account
@api.multi
def resync(self):
self.ensure_one()
now_utc_dt = fields.Date.now()
now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
# Reset Issues
issue_ids = self.env['wubook.issue'].search([])
issue_ids.write({
'to_read': False
})
# Push Virtual Rooms
wubook_obj = self.env['wubook'].with_context({
'init_connection': False
})
if wubook_obj.init_connection():
ir_seq_obj = self.env['ir.sequence']
room_types = self.env['hotel.room.type'].search([])
for room_type in room_types:
shortcode = ir_seq_obj.next_by_code('hotel.room.type')[:4]
channel_room_id = wubook_obj.create_room(
shortcode,
room_type.name,
room_type.wcapacity,
room_type.list_price,
room_type.total_rooms_count
)
if channel_room_id:
room_type.with_context(wubook_action=False).write({
'channel_room_id': channel_room_id,
'wscode': shortcode,
})
else:
room_type.with_context(wubook_action=False).write({
'channel_room_id': '',
'wscode': '',
})
# Create Restrictions
room_type_rest_obj = self.env['hotel.room.type.restriction']
restriction_ids = room_type_rest_obj.search([])
for restriction in restriction_ids:
if restriction.wpid != '0':
channel_plan_id = wubook_obj.create_rplan(restriction.name)
restriction.write({
'channel_plan_id': channel_plan_id or ''
})
# Create Pricelist
pricelist_ids = self.env['product.pricelist'].search([])
for pricelist in pricelist_ids:
channel_plan_id = wubook_obj.create_plan(pricelist.name, pricelist.is_daily_plan)
pricelist.write({
'channel_plan_id': channel_plan_id or ''
})
wubook_obj.close_connection()
# Reset Folios
folio_ids = self.env['hotel.folio'].search([])
folio_ids.with_context(wubook_action=False).write({
'wseed': '',
})
# Reset Reservations
reservation_ids = self.env['hotel.reservation'].search([
('channel_reservation_id', '!=', ''),
('channel_reservation_id', '!=', False)
])
reservation_ids.with_context(wubook_action=False).write({
'channel_reservation_id': '',
'ota_id': False,
'ota_reservation_id': '',
'is_from_ota': False,
'wstatus': 0
})
# Get Default Models
pricelist_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'default_pricelist_id'))
restriction_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'default_restriction_id'))
room_type_restr_it_obj = self.env['hotel.room.type.restriction.item']
# Secure Wubook Input
restriction_item_ids = room_type_restr_it_obj.search([
('applied_on', '=', '0_room_type'),
('date_start', '<', now_utc_str),
])
if any(restriction_item_ids):
restriction_item_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push restrictions
restriction_item_ids = room_type_restr_it_obj.search([
('restriction_id', '=', restriction_id),
('applied_on', '=', '0_room_type'),
('wpushed', '=', True),
('date_start', '>=', now_utc_str),
])
if any(restriction_item_ids):
restriction_item_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Secure Wubook Input
pricelist_item_ids = self.env['product.pricelist.item'].search([
('applied_on', '=', '1_product'),
('compute_price', '=', 'fixed'),
('date_start', '<', now_utc_str),
])
if any(pricelist_item_ids):
pricelist_item_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push pricelists
pricelist_item_ids = self.env['product.pricelist.item'].search([
('pricelist_id', '=', pricelist_id),
('applied_on', '=', '1_product'),
('compute_price', '=', 'fixed'),
('wpushed', '=', True),
('date_start', '>=', now_utc_str),
])
if any(pricelist_item_ids):
pricelist_item_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Secure Wubook Input
availabity_ids = self.env['hotel.room.type.availability'].search([
('date', '<', now_utc_str),
])
if any(availabity_ids):
availabity_ids.with_context(wubook_action=False).write({
'wpushed': True
})
# Put to push availability
availabity_ids = self.env['hotel.room.type.availability'].search([
('wpushed', '=', True),
('date', '>=', now_utc_str),
])
if any(availabity_ids):
availabity_ids.with_context(wubook_action=False).write({
'wpushed': False
})
# Generate Security Token
self.env['ir.default'].sudo().set(
'wubook.config.settings',
'wubook_push_security_token',
binascii.hexlify(os.urandom(16)).decode())
self.env.cr.commit() # FIXME: Need do this
# Push Changes
if wubook_obj.init_connection():
wubook_obj.push_activation()
wubook_obj.import_channels_info()
wubook_obj.push_changes()
wubook_obj.close_connection()

View File

@@ -22,15 +22,3 @@ class ChannelBinding(models.AbstractModel):
('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),
})

View File

@@ -4,7 +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'
@@ -20,14 +20,7 @@ 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')
try:
return importer.import_otas_info()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='room',
internal_message=str(err),
channel_message=err.data['message'])
return importer.import_otas_info()
class HotelRoomTypeAdapter(Component):
_name = 'channel.ota.info.adapter'

View File

@@ -4,6 +4,7 @@
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, api, _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
@@ -18,31 +19,37 @@ class ChannelOtaInfoImporter(Component):
@api.model
def import_otas_info(self):
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')
count = 0
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.with_context({
'connector_no_export': True,
}).write(map_record.values())
else:
ota_info_bind.with_context({
'connector_no_export': True,
}).create(map_record.values(for_create=True))
count = count + 1
try:
results = self.backend_adapter.get_channels_info()
except ChannelConnectorError as err:
self.create_issue(
section='room',
internal_message=str(err),
channel_message=err.data['message'])
else:
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.with_context({
'connector_no_export': True,
}).write(map_record.values())
else:
ota_info_bind.with_context({
'connector_no_export': True,
}).create(map_record.values(for_create=True))
count = count + 1
return count

View File

@@ -10,7 +10,7 @@ class HotelChannelConnectorIssue(models.Model):
_old_name = 'wubook.issue'
backend_id = fields.Many2one('channel.backend',
'Restriction Plan',
'Backend',
required=True,
ondelete='cascade',
index=True)

View File

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

View File

@@ -34,19 +34,37 @@ class ChannelHotelReservation(models.Model):
old_name='wchannel_reservation_code')
channel_raw_data = fields.Text(readonly=True, old_name='wbook_json')
wstatus = fields.Selection([
channel_status = fields.Selection([
('0', 'No Channel'),
(str(WUBOOK_STATUS_CONFIRMED), 'Confirmed'),
(str(WUBOOK_STATUS_WAITING), 'Waiting'),
(str(WUBOOK_STATUS_REFUSED), 'Refused'),
(str(WUBOOK_STATUS_ACCEPTED), 'Accepted'),
(str(WUBOOK_STATUS_CANCELLED), 'Cancelled'),
(str(WUBOOK_STATUS_CANCELLED_PENALTY), 'Cancelled with penalty')],
string='WuBook Status',
default='0',
readonly=True)
wstatus_reason = fields.Char("WuBook Status Reason", readonly=True)
wmodified = fields.Boolean("WuBook Modified", readonly=True, default=False)
(str(WUBOOK_STATUS_CANCELLED_PENALTY), 'Cancelled with penalty'),
], string='Channel Status', default='0', readonly=True, old_name='wstatus')
channel_status_reason = fields.Char("Channel Status Reason", readonly=True,
old_name='wstatus_reason')
channel_modified = fields.Boolean("Channel Modified", readonly=True,
default=False, old_name='wmodified')
@api.depends('channel_reservation_id', 'ota_id')
def _is_from_ota(self):
for record in self:
record.is_from_ota = (record.external_id and record.ota_id)
@job(default_channel='root.channel')
@api.model
def refresh_availability(self, checkin, checkout, product_id):
self.env['channel.hotel.room.type.availability'].refresh_availability(
checkin, checkout, product_id)
@job(default_channel='root.channel')
@api.model
def import_reservation(self, backend, channel_reservation_id):
with backend.work_on(self._name) as work:
importer = work.component(usage='hotel.reservation.importer')
return importer.fetch_booking(channel_reservation_id)
@job(default_channel='root.channel')
@api.model
@@ -55,36 +73,19 @@ class ChannelHotelReservation(models.Model):
importer = work.component(usage='hotel.reservation.importer')
return importer.fetch_new_bookings()
@api.depends('channel_reservation_id', 'ota_id')
def _is_from_ota(self):
for record in self:
record.odoo_id.is_from_ota = (record.channel_reservation_id and \
record.ota_id)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def push_availability(self):
self.ensure_one()
if self._context.get('channel_action', True):
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='channel.exporter')
exporter.push_availability()
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def cancel_reservation(self):
self.ensure_one()
if self._context.get('channel_action', True):
user = self.env['res.user'].browse(self.env.uid)
with self.backend_id.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
wres = adapter.cancel_reservation(
self.channel_reservation_id,
_('Cancelled by %s') % user.partner_id.name)
if not wres:
raise ValidationError(_("Can't cancel reservation on WuBook"))
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.reservation.exporter')
return exporter.cancel_reservation(self)
@job(default_channel='root.channel')
@api.multi
def mark_booking(self):
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.reservation.exporter')
return exporter.mark_booking(self)
class HotelReservation(models.Model):
_inherit = 'hotel.reservation'
@@ -100,11 +101,14 @@ class HotelReservation(models.Model):
for record in self:
if not record.channel_type:
record.channel_type = 'door'
record.origin_sale = dict(
self.fields_get(
allfields=['channel_type'])['channel_type']['selection'])[record.channel_type] \
if record.channel_type != 'web' or not record.channel_bind_ids[0].ota_id \
else record.channel_bind_ids[0].ota_id.name
if record.channel_type == 'web' and any(record.channel_bind_ids) and \
record.channel_bind_ids[0].ota_id:
record.origin_sale = record.channel_bind_ids[0].ota_id.name
else:
record.origin_sale = dict(
self.fields_get(allfields=['channel_type'])['channel_type']['selection']
)[record.channel_type]
channel_bind_ids = fields.One2many(
comodel_name='channel.hotel.reservation',
@@ -124,17 +128,12 @@ class HotelReservation(models.Model):
@api.model
def create(self, vals):
if vals.get('channel_reservation_id') != None:
if vals.get('external_id') is not None:
vals.update({'preconfirm': False})
user = self.env['res.users'].browse(self.env.uid)
if user.has_group('hotel.group_hotel_call'):
vals.update({'to_read': True})
res = super(HotelReservation, self).create(vals)
self.env['hotel.room.type.availability'].refresh_availability(
vals['checkin'],
vals['checkout'],
vals['product_id'])
return res
return super(HotelReservation, self).create(vals)
@api.multi
def write(self, vals):
@@ -198,8 +197,8 @@ class HotelReservation(models.Model):
@api.multi
def action_cancel(self):
waction = self._context.get('wubook_action', True)
if waction:
no_export = self._context.get('connector_no_export', True)
if no_export:
for record in self:
# Can't cancel in Odoo
if record.is_from_ota:
@@ -209,12 +208,13 @@ class HotelReservation(models.Model):
self.write({'to_read': True, 'to_assign': True})
res = super(HotelReservation, self).action_cancel()
if waction:
if no_export:
for record in self:
# Only can cancel reservations created directly in wubook
if record.channel_bind_ids[0].channel_reservation_id and \
if any(record.channel_bind_ids) and \
record.channel_bind_ids[0].external_id and \
not record.channel_bind_ids[0].ota_id and \
record.channel_bind_ids[0].wstatus in ['1', '2', '4']:
record.channel_bind_ids[0].channel_status in ['1', '2', '4']:
self._event('on_record_cancel').notify(record)
return res
@@ -222,7 +222,8 @@ class HotelReservation(models.Model):
def confirm(self):
can_confirm = True
for record in self:
if record.is_from_ota and int(record.wstatus) in WUBOOK_STATUS_BAD:
if record.is_from_ota and any(record.channel_bind_ids) and \
int(record.channel_bind_ids[0].channel_status) in WUBOOK_STATUS_BAD:
can_confirm = False
break
if not can_confirm:
@@ -276,22 +277,39 @@ class HotelReservationAdapter(Component):
_inherit = 'wubook.adapter'
_apply_on = 'channel.hotel.reservation'
def mark_bookings(self, channel_reservation_ids):
return super(HotelReservationAdapter, self).mark_bookings(
channel_reservation_ids)
def fetch_new_bookings(self):
return super(HotelReservationAdapter, self).fetch_new_bookings()
def fetch_booking(self, channel_reservation_id):
return super(HotelReservationAdapter, self).fetch_booking(
channel_reservation_id)
def cancel_reservation(self, channel_reservation_id, message):
return super(HotelReservationAdapter, self).cancel_reservation(
channel_reservation_id, message)
class ChannelBindingHotelReservationListener(Component):
_name = 'channel.binding.hotel.reservation.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.reservation']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None):
record.refresh_availability()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.with_delay(priority=20).push_availability()
record.push_availability()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.with_delay(priority=20).push_availability()
record.push_availability()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_cancel(self, record, fields=None):
record.with_delay(priority=20).cancel_reservation()
record.cancel_reservation()

View File

@@ -0,0 +1,48 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api, _
class HotelReservationExporter(Component):
_name = 'channel.hotel.reservation.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.hotel.reservation']
_usage = 'hotel.reservation.exporter'
@api.model
def cancel_reservation(self, binding):
user = self.env['res.user'].browse(self.env.uid)
try:
return self.backend_adapter.cancel_reservation(
binding.external_id,
_('Cancelled by %s') % user.partner_id.name)
except ChannelConnectorError as err:
self.create_issue(
section='reservation',
internal_message=str(err),
channel_object_id=binding.external_id,
channel_message=err.data['message'])
@api.model
def mark_booking(self, binding):
try:
return self.backend_adapter.mark_bookings([binding.external_id])
except ChannelConnectorError as err:
self.create_issue(
section='reservation',
internal_message=str(err),
channel_object_id=binding.external_id,
channel_message=err.data['message'])
@api.model
def mark_bookings(self, external_ids):
try:
return self.backend_adapter.mark_bookings(external_ids)
except ChannelConnectorError as err:
self.create_issue(
section='reservation',
internal_message=str(err),
channel_object_id=external_ids,
channel_message=err.data['message'])

View File

@@ -1,13 +1,22 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import json
from datetime import datetime, timedelta
from dateutil import tz
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, api, _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
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,
DEFAULT_WUBOOK_DATETIME_FORMAT,
WUBOOK_STATUS_BAD)
_logger = logging.getLogger(__name__)
class HotelReservationImporter(Component):
@@ -16,30 +25,424 @@ class HotelReservationImporter(Component):
_apply_on = ['channel.hotel.reservation']
_usage = 'hotel.reservation.importer'
def fetch_new_bookings(self):
@api.model
def fetch_booking(self, channel_reservation_id):
try:
results = self.backend_adapter.fetch_new_bookings()
results = self.backend_adapter.fetch_booking(channel_reservation_id)
except ChannelConnectorError as err:
self.create_issue(
section='reservation',
internal_message=str(err),
channel_message=err.data['message'])
return False
else:
processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \
self._generate_reservations(results)
if any(processed_rids):
uniq_rids = list(set(processed_rids))
rcodeb, resultsb = self.backend_adapter.mark_bookings(uniq_rids)
if rcodeb != 0:
self.create_issue(
backend=self.backend_adapter.id,
section='wubook',
internal_message=_("Problem trying mark bookings (%s)") % str(processed_rids))
self.backend_adapter.mark_bookings(list(set(processed_rids)))
# Update Odoo availability (don't wait for wubook)
# This cause abuse service in first import!!
# FIXME: This cause abuse service in first import!!
if checkin_utc_dt and checkout_utc_dt:
self.backend_adapter.fetch_rooms_values(
checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
return True
def fetch_new_bookings(self):
count = 0
try:
results = self.backend_adapter.fetch_new_bookings()
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_adapter.id,
section='reservation',
internal_message=_("Can't process reservations from wubook"),
internal_message=str(err),
channel_message=err.data['message'])
return False
return True
else:
processed_rids, errors, checkin_utc_dt, checkout_utc_dt = \
self._generate_reservations(results)
if any(processed_rids):
uniq_rids = list(set(processed_rids))
self.backend_adapter.mark_bookings(uniq_rids)
count = len(uniq_rids)
# Update Odoo availability (don't wait for wubook)
# FIXME: This cause abuse service in first import!!
if checkin_utc_dt and checkout_utc_dt:
self.backend_adapter.fetch_rooms_values(
checkin_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
checkout_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
return count
@api.model
def _generate_booking_vals(self, broom, crcode, rcode, room_type_bind,
split_booking, dates_checkin, dates_checkout, book):
is_cancellation = book['status'] in WUBOOK_STATUS_BAD
tax_inclusive = True
persons = room_type_bind.channel_capacity
# Dates
checkin_str = dates_checkin[0].strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
checkout_str = dates_checkout[0].strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
# Parse 'ancyllary' info
if 'ancillary' in broom:
if 'guests' in broom['ancillary']:
persons = broom['ancillary']['guests']
if 'tax_inclusive' in broom['ancillary'] and not broom['ancillary']['tax_inclusive']:
_logger.info("--- Incoming Reservation without taxes included!")
tax_inclusive = False
# Generate Reservation Day Lines
reservation_lines = []
tprice = 0.0
for brday in broom['roomdays']:
wndate = datetime.strptime(
brday['day'],
DEFAULT_WUBOOK_DATE_FORMAT
).replace(tzinfo=tz.gettz('UTC'))
if dates_checkin[0] >= wndate <= (dates_checkout[0] - timedelta(days=1)):
# HOT-FIX: Hard-Coded Tax 10%
room_day_price = round(brday['price'] * 1.1, 2) if not tax_inclusive else brday['price']
reservation_lines.append((0, False, {
'date': wndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'price': room_day_price,
}))
tprice += room_day_price
# Get OTA
ota_id = self.env['channel.ota.info'].search([
('ota_id', '=', str(book['id_channel'])),
], limit=1)
vals = {
'backend_id': self.backend_record.id,
'checkin': checkin_str,
'checkout': checkout_str,
'adults': persons,
'children': book['children'],
'reservation_lines': reservation_lines,
'price_unit': tprice,
'to_assign': True,
'wrid': rcode,
'ota_id': ota_id and ota_id.id,
'wchannel_reservation_code': crcode,
'channel_status': str(book['status']),
'to_read': True,
'state': is_cancellation and 'cancelled' or 'draft',
'room_type_id': room_type_bind.odoo_id.id,
'splitted': split_booking,
'wbook_json': json.dumps(book),
'wmodified': book['was_modified'],
'product_id': room_type_bind and room_type_bind.product_id.id,
'name': room_type_bind and room_type_bind.name,
}
return vals
@api.model
def _generate_partner_vals(self, book):
country_id = self.env['res.country'].search([
('code', '=', str(book['customer_country']))
], limit=1)
# lang = self.env['res.lang'].search([('code', '=', book['customer_language_iso'])], limit=1)
return {
'name': "%s, %s" % (book['customer_surname'], book['customer_name']),
'country_id': country_id and country_id.id,
'city': book['customer_city'],
'phone': book['customer_phone'],
'zip': book['customer_zip'],
'street': book['customer_address'],
'email': book['customer_mail'],
'unconfirmed': True,
# 'lang': lang and lang.id,
}
def _get_book_dates(self, book):
tz_hotel = self.env['ir.default'].sudo().get('res.config.settings', 'tz_hotel')
default_arrival_hour = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_arrival_hour')
default_departure_hour = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_departure_hour')
# Get dates for the reservation (GMT->UTC)
arr_hour = default_arrival_hour if book['arrival_hour'] == "--" \
else book['arrival_hour']
# HOT-FIX: Wubook 24:00 hour
arr_hour_s = arr_hour.split(':')
if arr_hour_s[0] == '24':
arr_hour_s[0] = '00'
arr_hour = ':'.join(arr_hour_s)
checkin = "%s %s" % (book['date_arrival'], arr_hour)
checkin_dt = datetime.strptime(checkin, DEFAULT_WUBOOK_DATETIME_FORMAT).replace(
tzinfo=tz.gettz(str(tz_hotel)))
checkin_utc_dt = checkin_dt.astimezone(tz.gettz('UTC'))
#checkin = checkin_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
checkout = "%s %s" % (book['date_departure'],
default_departure_hour)
checkout_dt = datetime.strptime(checkout, DEFAULT_WUBOOK_DATETIME_FORMAT).replace(
tzinfo=tz.gettz(str(tz_hotel)))
checkout_utc_dt = checkout_dt.astimezone(tz.gettz('UTC'))
#checkout = checkout_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
return (checkin_utc_dt, checkout_utc_dt)
def _update_reservation_binding(self, binding, book):
is_cancellation = book['status'] in WUBOOK_STATUS_BAD
binding.with_context({'connector_no_export': True}).write({
'channel_raw_data': json.dumps(book),
'channel_status': str(book['status']),
'channel_status_reason': book.get('status_reason', ''),
'to_read': True,
'to_assign': True,
'price_unit': book['amount'],
'customer_notes': book['customer_notes'],
})
if binding.partner_id.unconfirmed:
binding.partner_id.write(
self._generate_partner_vals(book)
)
if is_cancellation:
binding.with_context({
'connector_no_export': True}).action_cancel()
elif binding.state == 'cancelled':
binding.with_context({
'connector_no_export': True,
}).write({
'discount': 0.0,
'state': 'confirm',
})
# FIXME: Super big method!!! O_o
@api.model
def _generate_reservations(self, bookings):
_logger.info("==[CHANNEL->ODOO]==== READING BOOKING ==")
_logger.info(bookings)
# Get user timezone
res_partner_obj = self.env['res.partner']
channel_reserv_obj = self.env['channel.hotel.reservation']
hotel_folio_obj = self.env['hotel.folio']
channel_room_type_obj = self.env['channel.hotel.room.type']
# Space for store some data for construct folios
processed_rids = []
failed_reservations = []
checkin_utc_dt = False
checkout_utc_dt = False
split_booking = False
for book in bookings: # This create a new folio
splitted_map = {}
rcode = str(book['reservation_code'])
crcode = str(book['channel_reservation_code']) \
if book['channel_reservation_code'] else 'undefined'
# Can't process failed reservations
# (for example set a invalid new reservation and receive in
# the same transaction an cancellation)
if crcode in failed_reservations:
self.create_issue(
section='reservation',
internal_emssage="Can't process a reservation that previusly failed!",
channel_object_id=book['reservation_code'])
continue
checkin_utc_dt, checkout_utc_dt = self._get_book_dates(book)
# Search Folio. If exists.
folio_id = False
if crcode != 'undefined':
reserv_folio = channel_reserv_obj.search([
('ota_reservation_id', '=', crcode)
], limit=1)
if reserv_folio:
folio_id = reserv_folio.odoo_id.folio_id
else:
reserv_folio = channel_reserv_obj.search([
('external_id', '=', rcode)
], limit=1)
if reserv_folio:
folio_id = reserv_folio.odoo_id.folio_id
# Need update reservations?
reservs_processed = False
reservs_binds = channel_reserv_obj.search([('external_id', '=', rcode)])
for reserv_bind in reservs_binds:
self._update_reservation_binding(reserv_bind, book)
reservs_processed = True
# Do Nothing if already processed 'external_id'
if reservs_processed:
processed_rids.append(rcode)
continue
# Search Customer
customer_mail = book.get('customer_mail', False)
partner_id = False
if customer_mail:
partner_id = res_partner_obj.search([
('email', '=', customer_mail)
], limit=1)
if not partner_id:
partner_id = res_partner_obj.create(self._generate_partner_vals(book))
reservations = []
used_rooms = []
# Iterate booked rooms
for broom in book['booked_rooms']:
room_type_bind = channel_room_type_obj.search([
('external_id', '=', broom['room_id'])
], limit=1)
if not room_type_bind:
self.create_issue(
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
if not any(room_type_bind.room_ids):
self.create_issue(
section='reservation',
internal_message="Selected room type (%s) doesn't have any \
real room" % book['rooms'],
channel_object_id=book['reservation_code'])
failed_reservations.append(crcode)
continue
dates_checkin = [checkin_utc_dt, False]
dates_checkout = [checkout_utc_dt, False]
split_booking = False
split_booking_parent = False
# This perhaps create splitted reservation
while dates_checkin[0]:
vals = self._generate_booking_vals(
broom,
crcode,
rcode,
room_type_bind,
split_booking,
dates_checkin,
dates_checkout,
book,
)
if vals['price_unit'] != book['amount']:
self.create_issue(
section='reservation',
internal_message="Invalid reservation total price! %.2f (calculated) != %.2f (wubook)" % (vals['price_unit'], book['amount']),
channel_object_id=book['reservation_code'])
free_rooms = room_type_bind.odoo_id.check_availability_room_type(
vals['checkin'],
vals['checkout'],
room_type_id=room_type_bind.odoo_id.id,
notthis=used_rooms)
if any(free_rooms):
vals.update({
'product_id': room_type_bind.product_id.id,
'name': free_rooms[0].name,
})
reservations.append((0, False, vals))
used_rooms.append(free_rooms[0].id)
if split_booking:
if not split_booking_parent:
split_booking_parent = len(reservations)
else:
splitted_map.setdefault(
split_booking_parent,
[]).append(len(reservations))
dates_checkin = [dates_checkin[1], False]
dates_checkout = [dates_checkout[1], False]
else:
date_diff = (dates_checkout[0].replace(
hour=0, minute=0, second=0,
microsecond=0) -
dates_checkin[0].replace(
hour=0, minute=0, second=0,
microsecond=0)).days
if date_diff <= 0:
if split_booking:
if split_booking_parent:
del reservations[split_booking_parent-1:]
if split_booking_parent in splitted_map:
del splitted_map[split_booking_parent]
# Can't found space for reservation: Overbooking
vals = self._generate_booking_vals(
broom,
crcode,
rcode,
room_type_bind,
False,
(checkin_utc_dt, False),
(checkout_utc_dt, False),
book,
)
vals.update({
'product_id': room_type_bind.product_id.id,
'name': room_type_bind.name,
'overbooking': True,
})
reservations.append((0, False, vals))
self.create_issue(
section='reservation',
internal_message="Reservation imported with overbooking state",
channel_object_id=rcode,
dfrom=vals['checkin'], dto=vals['checkout'])
dates_checkin = [False, False]
dates_checkout = [False, False]
split_booking = False
else:
split_booking = True
dates_checkin = [
dates_checkin[0],
dates_checkin[0] + timedelta(days=date_diff-1)
]
dates_checkout = [
dates_checkout[0] - timedelta(days=1),
checkout_utc_dt
]
# Create Splitted Issue Information
if split_booking:
self.create_issue(
section='reservation',
internal_message="Reservation Splitted",
channel_object_id=rcode)
# Create Folio
if not any(failed_reservations) and any(reservations):
# TODO: Improve 'addons_list' & discounts
addons = str(book['addons_list']) if any(book['addons_list']) else ''
discounts = book.get('discount', '')
vals = {
'room_lines': reservations,
'customer_notes': "%s\nADDONS:\n%s\nDISCOUNT:\n%s" % (
book['customer_notes'], addons, discounts),
'channel_type': 'web',
}
_logger.info("==[CHANNEL->ODOO]==== CREATING/UPDATING FOLIO ==")
_logger.info(reservations)
if folio_id:
folio_id.with_context({
'connector_no_export': True}).write(vals)
else:
vals.update({
'partner_id': partner_id.id,
'wseed': book['sessionSeed']
})
folio_id = hotel_folio_obj.with_context({
'connector_no_export': True}).create(vals)
# Update Reservation Spitted Parents
sorted_rlines = folio_id.room_lines.sorted(key='id')
for k_pid, v_pid in splitted_map.items():
preserv = sorted_rlines[k_pid-1]
for pid in v_pid:
creserv = sorted_rlines[pid-1]
creserv.parent_reservation = preserv.id
# Bind reservations
rlines = sorted_rlines = folio_id.room_lines
for rline in rlines:
for rline_bind in rline.channel_bind_ids:
self.binder(rline_bind.external_id, rline_bind)
processed_rids.append(rcode)
return (processed_rids, any(failed_reservations),
checkin_utc_dt, checkout_utc_dt)

View File

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

View File

@@ -7,7 +7,6 @@ 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):
@@ -37,14 +36,7 @@ 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')
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'])
return importer.get_rooms()
@api.constrains('ota_capacity')
def _check_ota_capacity(self):
@@ -60,55 +52,31 @@ class ChannelHotelRoomType(models.Model):
raise ValidationError(_("Chanel short code can't be longer than 4 characters"))
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def create_room(self):
self.ensure_one()
if not self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.exporter')
try:
exporter.create_room(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='room',
internal_message=str(err),
channel_message=err.data['message'])
exporter.create_room(self)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def modify_room(self):
self.ensure_one()
if self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.exporter')
try:
exporter.modify_room(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='room',
internal_message=str(err),
channel_message=err.data['message'])
exporter.modify_room(self)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_room(self):
self.ensure_one()
if self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.exporter')
try:
exporter.delete_room(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='room',
internal_message=str(err),
channel_message=err.data['message'])
deleter = work.component(usage='hotel.room.type.deleter')
deleter.delete_room(self)
class HotelRoomType(models.Model):
_inherit = 'hotel.room.type'
@@ -158,9 +126,20 @@ class HotelRoomTypeAdapter(Component):
_inherit = 'wubook.adapter'
_apply_on = 'channel.hotel.room.type'
def create_room(self, shortcode, name, capacity, price, availability):
return super(HotelRoomTypeAdapter, self).create_room(
shortcode, name, capacity, price, availability)
def fetch_rooms(self):
return super(HotelRoomTypeAdapter, self).fetch_rooms()
def modify_room(self, channel_room_id, name, capacity, price, availability, scode):
return super(HotelRoomTypeAdapter, self).modify_room(
channel_room_id, name, capacity, price, availability, scode)
def delete_room(self, channel_room_id):
return super(HotelRoomTypeAdapter, self).delete_room(channel_room_id)
class BindingHotelRoomTypeListener(Component):
_name = 'binding.hotel.room.type.listener'
_inherit = 'base.connector.listener'

View File

@@ -0,0 +1,22 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api
class HotelRoomTypeDeleter(Component):
_name = 'channel.hotel.room.type.deleter'
_inherit = 'hotel.channel.deleter'
_apply_on = ['channel.hotel.room.type']
_usage = 'hotel.room.type.deleter'
@api.model
def delete_room(self, binding):
try:
return self.backend_adapter.delete_room(binding.external_id)
except ChannelConnectorError as err:
self.create_issue(
section='room',
internal_message=str(err),
channel_message=err.data['message'])

View File

@@ -3,6 +3,7 @@
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__)
@@ -14,31 +15,40 @@ class HotelRoomTypeExporter(Component):
@api.model
def modify_room(self, binding):
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):
return self.backend_adapter.delete_room(binding.external_id)
try:
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)
except ChannelConnectorError as err:
self.create_issue(
section='room',
internal_message=str(err),
channel_message=err.data['message'])
@api.model
def create_room(self, 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)
try:
external_id = self.backend_adapter.create_room(
short_code,
binding.name,
binding.ota_capacity,
binding.list_price,
binding.total_rooms_count
)
except ChannelConnectorError as err:
self.create_issue(
section='room',
internal_message=str(err),
channel_message=err.data['message'])
else:
binding.write({
'external_id': external_id,
'channel_short_code': short_code,
})
self.binder.bind(external_id, binding)

View File

@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, api, _
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT,
@@ -22,123 +23,33 @@ class HotelRoomTypeImporter(Component):
@api.model
def get_rooms(self):
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({'connector_no_export':True}).write(map_record.values())
else:
room_bind = channel_room_type_obj.with_context({'connector_no_export':True}).create(
map_record.values(for_create=True))
count = 0
try:
results = self.backend_adapter.fetch_rooms()
except ChannelConnectorError as err:
self.create_issue(
section='room',
internal_message=str(err),
channel_message=err.data['message'])
else:
channel_room_type_obj = self.env['channel.hotel.room.type']
room_mapper = self.component(usage='import.mapper',
model_name='channel.hotel.room.type')
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({'connector_no_export':True}).write(map_record.values())
else:
room_bind = channel_room_type_obj.with_context({
'connector_no_export':True}).create(
map_record.values(for_create=True))
self.binder.bind(room['id'], room_bind)
count = count + 1
return count
@api.model
def fetch_rooms_values(self, dfrom, dto, rooms=False,
set_max_avail=False):
# Sanitize Dates
now_dt = datetime.now()
dfrom_dt = fields.Date.from_string(dfrom)
dto_dt = fields.Date.from_string(dto)
if dto_dt < now_dt:
return True
if dfrom_dt < now_dt:
dfrom_dt = now_dt
if dfrom_dt > dto_dt:
dfrom_dt, dto_dt = dto_dt, dfrom_dt
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):
channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
room_avail_mapper = self.component(usage='import.mapper',
model_name='channel.hotel.room.type.availability')
map_record = room_avail_mapper.map_record(day_vals)
map_record.update(channel_pushed=True)
if set_max_avail:
map_record.update(max_avail=day_vals.get('avail', 0))
channel_room_type_avail = channel_room_type_avail_obj.search([
('room_type_id', '=', day_vals['room_type_id']),
('date', '=', day_vals['date'])
], limit=1)
if channel_room_type_avail:
channel_room_type_avail.with_context({
'connector_no_export': True,
}).write(map_record.values())
else:
channel_room_type_avail_obj.with_context({
'connector_no_export': True,
'mail_create_nosubscribe': True,
}).create(map_record.values(for_create=True))
@api.model
def _map_room_values_restrictions(self, day_vals):
channel_room_type_restr_item_obj = self.env['channel.hotel.room.type.restriction.item']
room_restriction_mapper = self.component(
usage='import.mapper',
model_name='channel.hotel.room.type.restriction.item')
map_record = room_restriction_mapper.map_record(day_vals)
map_record.update(channel_pushed=True)
room_type_restr = channel_room_type_restr_item_obj.search([
('room_type_id', '=', day_vals['room_type_id']),
('applied_on', '=', '0_room_type'),
('date', '=', day_vals['date']),
('restriction_id', '=', day_vals['restriction_plan_id']),
])
if room_type_restr:
room_type_restr.with_context({
'connector_no_export': True,
}).write(map_record.values())
else:
channel_room_type_restr_item_obj.with_context({
'connector_no_export': True,
}).create(map_record.values(for_create=True))
@api.model
def _generate_room_values(self, dfrom, dto, values, set_max_avail=False):
channel_room_type_restr_obj = self.env['channel.hotel.room.type.restriction']
channel_hotel_room_type_obj = self.env['channel.hotel.room.type']
def_restr_plan = channel_room_type_restr_obj.search([('channel_plan_id', '=', '0')])
_logger.info("==== ROOM VALUES (%s -- %s)", dfrom, dto)
_logger.info(values)
for k_rid, v_rid in values.iteritems():
room_type = channel_hotel_room_type_obj.search([
('channel_plan_id', '=', k_rid)
], limit=1)
if room_type:
date_dt = fields.Date.from_string(
dfrom,
dtformat=DEFAULT_WUBOOK_DATE_FORMAT)
for day_vals in v_rid:
date_str = date_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
day_vals.update({
'room_type_id': room_type.odoo_id.id,
'date': date_str,
})
self._map_room_values_availability(day_vals, set_max_avail)
if def_restr_plan:
day_vals.update({
'restriction_plan_id': def_restr_plan.odoo_id.id
})
self._map_room_values_restrictions(day_vals)
date_dt = date_dt + timedelta(days=1)
return True
class HotelRoomTypeImportMapper(Component):
_name = 'channel.hotel.room.type.import.mapper'
_inherit = 'channel.import.mapper'

View File

@@ -8,7 +8,6 @@ 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)
@@ -42,49 +41,57 @@ class ChannelHotelRoomTypeAvailability(models.Model):
than total rooms \
count: %d") % record.odoo_id.room_type_id.total_rooms_count)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
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=str(err),
channel_message=err.data['message'])
@api.model
def refresh_availability(self, checkin, checkout, product_id):
date_start = fields.Date.from_string(checkin)
date_end = fields.Date.from_string(checkout)
# Not count end day of the reservation
date_diff = (date_end - date_start).days
channel_room_type_obj = self.env['channel.hotel.room.type']
channel_room_type_avail_obj = self.env['hotel.room.type.availability']
room_type_binds = channel_room_type_obj.search([('product_id', '=', product_id)])
for room_type_bind in room_type_binds:
if room_type_bind.external_id:
for i in range(0, date_diff):
ndate_dt = date_start + timedelta(days=i)
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
avail = len(channel_room_type_obj.odoo_id.check_availability_room_type(
ndate_str,
ndate_str,
room_type_id=room_type_bind.odoo.id))
max_avail = room_type_bind.total_rooms_count
room_type_avail_id = channel_room_type_avail_obj.search([
('room_type_id', '=', room_type_bind.odoo.id),
('date', '=', ndate_str)], limit=1)
if room_type_avail_id and room_type_avail_id.channel_max_avail >= 0:
max_avail = room_type_avail_id.channel_max_avail
avail = max(
min(avail, room_type_bind.total_rooms_count, max_avail), 0)
if room_type_avail_id:
room_type_avail_id.write({'avail': avail})
else:
channel_room_type_avail_obj.create({
'room_type_id': room_type_bind.odoo.id,
'date': ndate_str,
'avail': avail,
})
@job(default_channel='root.channel')
@api.model
def import_availability(self, backend):
def import_availability(self, backend, dfrom, dto):
with backend.work_on(self._name) as work:
importer = work.component(usage='hotel.room.type.availability.importer')
try:
return importer.import_availability_values(backend.avail_from,
backend.avail_to)
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='avail',
internal_message=str(err),
channel_message=err.data['message'])
return importer.import_availability_values(dfrom, dto)
@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=str(err),
channel_message=err.data['message'])
return exporter.push_availability()
class HotelRoomTypeAvailability(models.Model):
_inherit = 'hotel.room.type.availability'
@@ -134,46 +141,6 @@ class HotelRoomTypeAvailability(models.Model):
if self.room_type_id:
self.channel_max_avail = self.room_type_id.total_rooms_count
@api.model
def refresh_availability(self, checkin, checkout, product_id):
date_start = fields.Date.from_string(checkin)
date_end = fields.Date.from_string(checkout)
# Not count end day of the reservation
date_diff = (date_end - date_start).days
room_type_obj = self.env['hotel.room.type']
room_type_avail_obj = self.env['hotel.room.type.availability']
room_types = room_type_obj.search([
('room_ids.product_id', '=', product_id)
])
for room_type in room_types:
if room_type.channel_room_id:
for i in range(0, date_diff):
ndate_dt = date_start + timedelta(days=i)
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
avail = len(room_type_obj.check_availability_room_type(
ndate_str,
ndate_str,
room_type_id=room_type.id))
max_avail = room_type.total_rooms_count
room_type_avail_id = room_type_avail_obj.search([
('room_type_id', '=', room_type.id),
('date', '=', ndate_str)], limit=1)
if room_type_avail_id and room_type_avail_id.channel_max_avail >= 0:
max_avail = room_type_avail_id.channel_max_avail
avail = max(
min(avail, room_type.total_rooms_count, max_avail), 0)
if room_type_avail_id:
room_type_avail_id.write({'avail': avail})
else:
room_type_avail_obj.create({
'room_type_id': room_type.id,
'date': ndate_str,
'avail': avail,
})
class HotelRoomTypeAvailabilityAdapter(Component):
_name = 'channel.hotel.room.type.availability.adapter'
_inherit = 'wubook.adapter'

View File

@@ -4,9 +4,9 @@
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)
from odoo import api, fields, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeAvailabilityExporter(Component):
@@ -28,7 +28,6 @@ class HotelRoomTypeAvailabilityExporter(Component):
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:
@@ -44,4 +43,14 @@ class HotelRoomTypeAvailabilityExporter(Component):
_logger.info("==[ODOO->CHANNEL]==== AVAILABILITY ==")
_logger.info(avails)
if any(avails):
self.backend_adapter.update_availability(avails)
try:
self.backend_adapter.update_availability(avails)
except ChannelConnectorError as err:
self.create_issue(
section='avail',
internal_message=str(err),
channel_message=err.data['message'])
return False
else:
channel_room_type_avails.write({'channel_pushed': True})
return True

View File

@@ -29,42 +29,52 @@ class HotelRoomTypeAvailabilityImporter(Component):
if dto_dt < now_dt:
return True
results = self.backend_adapter.fetch_rooms_values(date_from, date_to)
_logger.info("==[CHANNEL->ODOO]==== AVAILABILITY (%s - %s) ==",
date_from, date_to)
_logger.info(results)
count = 0
try:
results = self.backend_adapter.fetch_rooms_values(date_from, date_to)
except ChannelConnectorError as err:
self.create_issue(
section='avail',
internal_message=str(err),
channel_message=err.data['message'],
dfrom=date_from, dto=date_to)
else:
_logger.info("==[CHANNEL->ODOO]==== AVAILABILITY (%s - %s) ==",
date_from, date_to)
_logger.info(results)
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({
'connector_no_export': True,
}).write(map_record.values())
else:
room_type_avail_bind = channel_room_type_avail_obj.with_context({
'connector_no_export': True,
}).create(map_record.values(for_create=True))
iter_day += timedelta(days=1)
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')
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({
'connector_no_export': True,
}).write(map_record.values())
else:
room_type_avail_bind = channel_room_type_avail_obj.with_context({
'connector_no_export': True,
}).create(map_record.values(for_create=True))
room_type_avail_bind.channel_pushed = True
iter_day += timedelta(days=1)
count = count + 1
return count

View File

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

View File

@@ -7,7 +7,6 @@ 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):
@@ -22,69 +21,38 @@ class ChannelHotelRoomTypeRestriction(models.Model):
ondelete='cascade')
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def create_plan(self):
self.ensure_one()
if not self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.restriction.exporter')
try:
exporter.create_rplan(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
exporter.create_rplan(self)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def update_plan_name(self):
self.ensure_one()
if self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.restriction.exporter')
try:
exporter.rename_rplan(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
exporter.rename_rplan(self)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_plan(self):
self.ensure_one()
if self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='hotel.room.type.restriction.exporter')
try:
exporter.delete_rplan(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
deleter = work.component(usage='hotel.room.type.restriction.deleter')
deleter.delete_rplan(self)
@job(default_channel='root.channel')
@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=str(err),
channel_message=err.data['message'])
return importer.import_restriction_plans()
class HotelRoomTypeRestriction(models.Model):
_inherit = 'hotel.room.type.restriction'

View File

@@ -0,0 +1,22 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api
class HotelRoomTypeRestrictionDeleter(Component):
_name = 'channel.hotel.room.type.restriction.deleter'
_inherit = 'hotel.channel.deleter'
_apply_on = ['channel.hotel.room.type.restriction']
_usage = 'hotel.room.type.restriction.deleter'
@api.model
def delete_rplan(self, binding):
try:
return self.backend_adapter.delete_rplan(binding.external_id)
except ChannelConnectorError as err:
self.create_issue(
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])

View File

@@ -3,6 +3,7 @@
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__)
@@ -14,16 +15,25 @@ class HotelRoomTypeRestrictionExporter(Component):
@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)
try:
return self.backend_adapter.rename_rplan(
binding.external_id,
binding.name)
except ChannelConnectorError as err:
self.create_issue(
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
@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)
try:
external_id = self.backend_adapter.create_rplan(binding.name)
except ChannelConnectorError as err:
self.create_issue(
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
else:
binding.external_id = external_id
self.binder.bind(external_id, binding)

View File

@@ -6,6 +6,7 @@ from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
@@ -18,24 +19,32 @@ class HotelRoomTypeRestrictionImporter(Component):
@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({
'connector_no_export': True,
'rules': plan.get('rules'),
}).create(plan_record.values(for_create=True))
else:
plan_bind.with_context({'connector_no_export':True}).write(
plan_record.values())
count = count + 1
count = 0
try:
results = self.backend_adapter.rplan_rplans()
except ChannelConnectorError as err:
self.create_issue(
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
else:
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({
'connector_no_export': True,
'rules': plan.get('rules'),
}).create(plan_record.values(for_create=True))
else:
plan_bind.with_context({'connector_no_export':True}).write(
plan_record.values())
count = count + 1
return count

View File

@@ -6,7 +6,6 @@ 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'
@@ -23,36 +22,20 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model):
@job(default_channel='root.channel')
@api.model
def import_restriction_values(self, backend):
def import_restriction_values(self, backend, dfrom, dto, external_id):
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=str(err),
channel_message=err.data['message'],
channel_object_id=backend.restriction_id,
dfrom=backend.restriction_from, dto=backend.restriction_to)
return importer.import_restriction_values(
dfrom,
dto,
channel_restr_id=external_id)
@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=str(err),
channel_message=err.data['message'])
return exporter.push_restriction()
class HotelRoomTypeRestrictionItem(models.Model):
_inherit = 'hotel.room.type.restriction.item'

View File

@@ -64,12 +64,18 @@ class HotelRoomTypeRestrictionItemExporter(Component):
restrictions[rp.external_id][room_type_external_id].append({})
_logger.info("==[ODOO->CHANNEL]==== 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})
try:
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)
except ChannelConnectorError as err:
self.create_issue(
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
else:
unpushed.write({'channel_pushed': True})
return True

View File

@@ -60,19 +60,29 @@ class HotelRoomTypeRestrictionImporter(Component):
'connector_no_export': True
}).write(map_record.values())
else:
channel_restriction_item_obj.with_context({
channel_restriction_item = channel_restriction_item_obj.with_context({
'connector_no_export': True
}).create(map_record.values(for_create=True))
channel_restriction_item.channel_pushed = 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)
try:
results = self.backend_adapter.wired_rplan_get_rplan_values(
date_from,
date_to,
int(channel_restr_plan_id))
except ChannelConnectorError as err:
self.create_issue(
section='restriction',
internal_message=str(err),
channel_message=err.data['message'],
channel_object_id=channel_restr_id,
dfrom=date_from, dto=date_to)
else:
if any(results):
self._generate_restriction_items(results)
class HotelRoomTypeRestrictionItemImportMapper(Component):
_name = 'channel.hotel.room.type.restriction.item.import.mapper'

View File

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

View File

@@ -6,7 +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'
@@ -21,69 +21,38 @@ class ChannelProductPricelist(models.Model):
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 not self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='product.pricelist.exporter')
try:
exporter.create_plan(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
exporter.create_plan(self)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def update_plan_name(self):
self.ensure_one()
if self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='product.pricelist.exporter')
try:
exporter.rename_plan(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
exporter.rename_plan(self)
@job(default_channel='root.channel')
@related_action(action='related_action_unwrap_binding')
@api.multi
def delete_plan(self):
self.ensure_one()
if self.external_id:
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='product.pricelist.exporter')
try:
exporter.delete_plan(self)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_id.id,
section='restriction',
internal_message=str(err),
channel_message=err.data['message'])
deleter = work.component(usage='product.pricelist.deleter')
deleter.delete_plan(self)
@job(default_channel='root.channel')
@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=str(err),
channel_message=err.data['message'])
return importer.import_pricing_plans()
class ProductPricelist(models.Model):
_inherit = 'product.pricelist'

View File

@@ -0,0 +1,22 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api
class ProductPricelistDeleter(Component):
_name = 'channel.product.pricelist.deleter'
_inherit = 'hotel.channel.deleter'
_apply_on = ['channel.product.pricelist']
_usage = 'product.pricelist.deleter'
@api.model
def delete_plan(self, binding):
try:
return self.backend_adapter.delete_plan(binding.external_id)
except ChannelConnectorError as err:
self.create_issue(
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'])

View File

@@ -3,6 +3,7 @@
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__)
@@ -14,15 +15,25 @@ class ProductPricelistExporter(Component):
@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)
try:
return self.backend_adapter.rename_plan(
binding.external_id,
binding.name)
except ChannelConnectorError as err:
self.create_issue(
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'])
@api.model
def create_plan(self, binding):
external_id = self.backend_adapter.create_plan(binding.name)
binding.external_id = external_id
try:
external_id = self.backend_adapter.create_plan(binding.name)
except ChannelConnectorError as err:
self.create_issue(
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'])
else:
binding.external_id = external_id
self.binder.bind(external_id, binding)

View File

@@ -5,6 +5,7 @@ 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.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
@@ -20,27 +21,34 @@ class ProductPricelistImporter(Component):
@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({
'connector_no_export': True,
}).create(plan_record.values(for_create=True))
else:
channel_product_listprice_obj.with_context({
'connector_no_export': True,
}).write(plan_record.values())
count = count + 1
try:
results = self.backend_adapter.get_pricing_plans()
except ChannelConnectorError as err:
self.create_issue(
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'])
else:
channel_product_listprice_obj = self.env['channel.product.pricelist']
pricelist_mapper = self.component(usage='import.mapper',
model_name='channel.product.pricelist')
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({
'connector_no_export': True,
}).create(plan_record.values(for_create=True))
else:
channel_product_listprice_obj.with_context({
'connector_no_export': True,
}).write(plan_record.values())
count = count + 1
return count

View File

@@ -6,7 +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 ChannelProductPricelistItem(models.Model):
_name = 'channel.product.pricelist.item'
@@ -23,42 +23,24 @@ class ChannelProductPricelistItem(models.Model):
@job(default_channel='root.channel')
@api.model
def import_pricelist_values(self, backend):
def import_pricelist_values(self, backend, dfrom, dto, external_id):
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,
if not backend.pricelist_id:
return importer.import_all_pricelist_values(
backend.pricelist_from,
backend.pricelist_to)
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'],
channel_object_id=backend.pricelist_id.external_id,
dfrom=backend.pricelist_from,
dto=backend.pricelist_to)
return False
return importer.import_pricelist_values(
backend.pricelist_id.external_id,
backend.pricelist_from,
backend.pricelist_to)
@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')
try:
return exporter.push_pricelist()
except ChannelConnectorError as err:
self.create_issue(
backend=backend.id,
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'])
return exporter.push_pricelist()
class ProductPricelistItem(models.Model):
_inherit = 'product.pricelist.item'

View File

@@ -4,6 +4,7 @@
import logging
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.hotel_channel_connector.components.backend_adapter import (
DEFAULT_WUBOOK_DATE_FORMAT)
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
@@ -26,10 +27,11 @@ class ProductPricelistItemExporter(Component):
('date_start', '>=', datetime.now().strftime(
DEFAULT_SERVER_DATE_FORMAT))
], order="date_start ASC")
if any(channel_unpushed):
date_start = fields.Date.from_string(channel_unpushed[0].date_start)
date_end = fields.Date.from_string(channel_unpushed[-1].date_start)
days_diff = (date_start - date_end).days + 1
days_diff = (date_end - date_start).days + 1
prices = {}
pricelist_ids = channel_product_pricelist_obj.search([
@@ -39,7 +41,7 @@ class ProductPricelistItemExporter(Component):
for pr in pricelist_ids:
prices.update({pr.external_id: {}})
unpushed_pl = channel_product_pricelist_item_obj.search(
[('channel_pushed', '=', False), ('pricelist_id', '=', pr.id)])
[('channel_pushed', '=', False), ('pricelist_id', '=', pr.odoo_id.id)])
product_tmpl_ids = unpushed_pl.mapped('product_tmpl_id')
for pt_id in product_tmpl_ids:
channel_room_type = channel_room_type_obj.search([
@@ -50,17 +52,23 @@ class ProductPricelistItemExporter(Component):
for i in range(0, days_diff):
prod = channel_room_type.product_id.with_context({
'quantity': 1,
'pricelist': pr.id,
'pricelist': pr.odoo_id.id,
'date': (date_start + timedelta(days=i)).
strftime(DEFAULT_SERVER_DATE_FORMAT),
})
prices[pr.external_id][channel_room_type.external_id].append(prod.price)
_logger.info("==[ODOO->CHANNEL]==== PRICELISTS ==")
_logger.info(prices)
for k_pk, v_pk in prices.items():
if any(v_pk):
self.backend_adapter.update_plan_prices(k_pk, date_start.strftime(
DEFAULT_SERVER_DATE_FORMAT), v_pk)
channel_unpushed.write({'channel_pushed': True})
try:
for k_pk, v_pk in prices.items():
if any(v_pk):
self.backend_adapter.update_plan_prices(k_pk, date_start.strftime(
DEFAULT_SERVER_DATE_FORMAT), v_pk)
except ChannelConnectorError as err:
self.create_issue(
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'])
else:
channel_unpushed.write({'channel_pushed': True})
return True

View File

@@ -5,6 +5,7 @@ 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.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
@@ -64,9 +65,10 @@ class ProductPricelistItemImporter(Component):
'connector_no_export': True,
}).write(map_record.values())
else:
channel_pricelist_item_obj.with_context({
pricelist_item = channel_pricelist_item_obj.with_context({
'connector_no_export': True,
}).create(map_record.values(for_create=True))
pricelist_item.channel_pushed = True
return True
@api.model
@@ -79,12 +81,22 @@ class ProductPricelistItemImporter(Component):
@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)
try:
results = self.backend_adapter.fetch_plan_prices(
external_id,
date_from,
date_to,
rooms)
except ChannelConnectorError as err:
self.create_issue(
section='pricelist',
internal_message=str(err),
channel_message=err.data['message'],
channel_object_id=external_id,
dfrom=date_from,
dto=date_to)
else:
self._generate_pricelist_items(external_id, date_from, date_to, results)
class ProductPricelistItemImportMapper(Component):
_name = 'channel.product.pricelist.item.import.mapper'

View File

@@ -1,187 +0,0 @@
odoo.define('hotel_channel_connector.ListController', function(require) {
'use strict';
/*
* Hotel Channel Connector
* GNU Public License
* Alexandre Díaz <dev@redneboa.es>
*/
var ListController = require('web.ListController');
var Core = require('web.core');
var _t = Core._t;
ListController.include({
renderButtons: function () {
this._super.apply(this, arguments); // Sets this.$buttons
if (this.modelName === 'hotel.room.type') {
this.$buttons.append("<button class='btn btm-sm o_channel_connector_import_rooms' type='button'>"+_t('Fetch from Channel')+"</button>");
this.$buttons.find('.o_channel_connector_import_rooms').on('click', this._importRooms.bind(this));
} else if (this.modelName === 'hotel.folio') {
this.$buttons.append("<button class='btn btm-sm o_channel_connector_import_reservations' type='button'>"+_t('Fetch from Channel')+"</button>");
this.$buttons.find('.o_channel_connector_import_reservations').on('click', this._importReservations.bind(this));
} else if (this.modelName === 'product.pricelist') {
this.$buttons.append("<button class='btn btm-sm o_channel_connector_import_price_plans' type='button'>"+_t('Fetch from Channel')+"</button>");
this.$buttons.find('.o_channel_connector_import_price_plans').on('click', this._importPricePlans.bind(this));
this.$buttons.append("<button class='btn btm-sm btn-danger o_channel_connector_push_price_plans' type='button'>"+_t('Push to Channel')+"</button>");
this.$buttons.find('.o_channel_connector_push_price_plans').on('click', this._pushPricePlans.bind(this));
} else if (this.modelName === 'wubook.channel.info') {
this.$buttons.append("<button class='btn btm-sm o_channel_connector_import_channels_info' type='button'>"+_t('Fetch from Channel')+"</button>");
this.$buttons.find('.o_channel_connector_import_channels_info').on('click', this._importChannelsInfo.bind(this));
} else if (this.modelName === 'hotel.room.type.restriction') {
this.$buttons.append("<button class='btn btm-sm o_channel_connector_import_restriction_plans' type='button'>"+_t('Fetch from Channel')+"</button>");
this.$buttons.find('.o_channel_connector_import_restriction_plans').on('click', this._importRestrictionPlans.bind(this));
this.$buttons.append("<button class='btn btm-sm btn-danger o_channel_connector_push_restriction_plans' type='button'>"+_t('Push to Channel')+"</button>");
this.$buttons.find('.o_channel_connector_push_restriction_plans').on('click', this._pushRestrictionPlans.bind(this));
} else if (this.modelName === 'hotel.room.type.availability') {
this.$buttons.append("<button class='btn btm-sm o_channel_connector_import_availability' type='button'>"+_t('Fetch from Channel')+"</button>");
this.$buttons.find('.o_channel_connector_import_availability').on('click', this._importAvailability.bind(this));
this.$buttons.append("<button class='btn btm-sm btn-danger o_channel_connector_push_availability' type='button'>"+_t('Push to Channel')+"</button>");
this.$buttons.find('.o_channel_connector_push_availability').on('click', this._pushAvailability.bind(this));
}
},
_importRooms: function () {
var self = this;
this.dataset._model.call('import_rooms', [false]).then(function(results){
if (!results[0]) {
self.do_warn(_t('Operation Errors'), _t('Errors while importing rooms. See issues registry.'), true);
}
if (results[0] || results[1] > 0) {
if (results[1] > 0) {
self.do_notify(_t('Operation Success'), `<b>${results[1]}</b>` + ' ' + _t('Rooms successfully imported'), false);
} else {
self.do_notify(_t('Operation Success'), _t('No new rooms found. Everything is done.'), false);
}
var active_view = self.ViewManager.active_view;
active_view.controller.reload(); // list view only has reload
}
});
return false;
},
_importReservations: function () {
var self = this;
console.log(this);
this.model.import_reservations().then(function(results){
console.log(results);
if (!results[0]) {
self.do_warn(_t('Operation Errors'), _t('Errors while importing reservations. See issues registry.'), true);
}
if (results[0] || results[1] > 0) {
if (results[1] > 0) {
self.do_notify(_t('Operation Success'), `<b>${results[1]}</b>` + ' ' + _t('Reservations successfully imported'), false);
} else {
self.do_notify(_t('Operation Success'), _t('No new reservations found. Everything is done.'), false);
}
var active_view = self.ViewManager.active_view;
active_view.controller.reload(); // list view only has reload
}
});
return false;
},
_importPricePlans: function () {
var self = this;
this.dataset._model.call('import_price_plans', [false]).then(function(results){
if (!results[0]) {
self.do_warn(_t('Operation Errors'), _t('Errors while importing price plans from WuBook. See issues log.'), true);
}
if (results[0] || results[1] > 0) {
if (results[1] > 0) {
self.do_notify(_t('Operation Success'), `<b>${results[1]}</b>` + ' ' + _t('Price Plans successfully imported'), false);
} else {
self.do_notify(_t('Operation Success'), _t('No new price plans found. Everything is done.'), false);
}
var active_view = self.ViewManager.active_view;
active_view.controller.reload(); // list view only has reload
}
});
return false;
},
_pushPricePlans: function () {
var self = this;
new Model('wubook').call('push_priceplans', [false]).then(function(results){
self.do_notify(_t('Operation Success'), _t('Price Plans successfully pushed'), false);
}).fail(function(){
self.do_warn(_t('Operation Errors'), _t('Errors while pushing price plans to WuBook. See issues log.'), true);
});
return false;
},
_importChannelsInfo: function () {
var self = this;
this.dataset._model.call('import_channels_info', [false]).then(function(results){
if (!results[0]) {
self.do_warn(_t('Operation Errors'), _t('Errors while importing channels info from WuBook. See issues log.'), true);
}
if (results[0] || results[1] > 0) {
if (results[1] > 0) {
self.do_notify(_t('Operation Success'), `<b>${results[1]}</b>` + ' ' + _t('Channels Info successfully imported'), false);
} else {
self.do_notify(_t('Operation Success'), _t('No new channels info found. Everything is done.'), false);
}
var active_view = self.ViewManager.active_view;
active_view.controller.reload(); // list view only has reload
}
});
return false;
},
_importRestrictionPlans: function () {
var self = this;
this.dataset._model.call('import_restriction_plans', [false]).then(function(results){
if (!results[0]) {
self.do_warn(_t('Operation Errors'), _t('Errors while importing restriction plans from WuBook. See issues log.'), true);
}
if (results[0] || results[1] > 0) {
if (results[1] > 0) {
self.do_notify(_t('Operation Success'), `<b>${results[1]}</b>` + ' ' + _t('Restriction Plans successfully imported'), false);
} else {
self.do_notify(_t('Operation Success'), _t('No new restriction plans found. Everything is done.'), false);
}
var active_view = self.ViewManager.active_view;
active_view.controller.reload(); // list view only has reload
}
});
return false;
},
_pushRestrictionPlans: function () {
var self = this;
new Model('wubook').call('push_restrictions', [false]).then(function(results){
self.do_notify(_t('Operation Success'), _t('Restrictions successfully pushed'), false);
}).fail(function(){
self.do_warn(_t('Operation Errors'), _t('Errors while pushing restrictions to WuBook. See issues log.'), true);
});
return false;
},
_importAvailability: function () {
this.do_action('hotel_wubook_proto.action_wubook_import_availability');
return false;
},
_pushAvailability: function () {
var self = this;
new Model('wubook').call('push_availability', [false]).then(function(results){
self.do_notify(_t('Operation Success'), _t('Availability successfully pushed'), false);
}).fail(function(){
self.do_warn(_t('Operation Errors'), _t('Errors while pushing availability to Channel. See issues log.'), true);
});
return false;
}
});
});

View File

@@ -1,25 +0,0 @@
odoo.define('hotel_channel_connector.ListModel', function(require) {
'use strict';
/*
* Hotel Channel Connector
* GNU Public License
* Alexandre Díaz <dev@redneboa.es>
*/
var BasicModel = require('web.BasicModel'),
Session = require('web.session');
return BasicModel.extend({
import_reservations: function() {
return this._rpc({
model: 'hotel.folio',
method: 'import_reservations',
args: undefined,
context: Session.user_context,
});
},
});
});

View File

@@ -1,18 +0,0 @@
odoo.define('hotel_channel_connector.ListView', function(require) {
'use strict';
/*
* Hotel Channel Connector
* GNU Public License
* Alexandre Díaz <dev@redneboa.es>
*/
var ListView = require('web.ListView'),
ListModel = require('hotel_channel_connector.ListModel');
ListView.include({
config: _.extend({}, ListView.prototype.config, {
Model: ListModel,
}),
});
});

View File

@@ -50,6 +50,16 @@
string="Import in background"/>
</div>
</group>
<group>
<label string="Import Reservation" class="oe_inline"/>
<div>
<field name="reservation_id_str" class="oe_inline" nolabel="1"/>
<button name="import_reservation"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
<group>
<label string="Import Rooms" class="oe_inline"/>
<div>

View File

@@ -9,8 +9,8 @@
<group>
<field name="external_id" />
<field name="ota_reservation_id" />
<field name="wstatus" />
<field name="wstatus_reason" />
<field name="channel_status" />
<field name="channel_status_reason" />
<field name="to_read" />
</group>
</form>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Backend stuff -->
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/hotel_channel_connector/static/src/js/views/list/list_model.js"></script>
<script type="text/javascript" src="/hotel_channel_connector/static/src/js/views/list/list_controller.js"></script>
<script type="text/javascript" src="/hotel_channel_connector/static/src/js/views/list/list_view.js"></script>
</xpath>
</template>
</odoo>