mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[WIP] Channel connector
This commit is contained in:
@@ -12,7 +12,7 @@ from odoo.tools import (
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from .backend_adapter import DEFAULT_WUBOOK_DATE_FORMAT
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo import api
|
||||
from odoo import api, fields
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class HotelChannelConnectorExporter(AbstractComponent):
|
||||
@@ -25,42 +25,6 @@ class HotelChannelConnectorExporter(AbstractComponent):
|
||||
return self.push_availability() and self.push_priceplans() and \
|
||||
self.push_restrictions()
|
||||
|
||||
@api.model
|
||||
def push_availability(self):
|
||||
room_type_avail_ids = self.env['hotel.room.type.availability'].search([
|
||||
('wpushed', '=', False),
|
||||
('date', '>=', date_utils.now(hours=False).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT))
|
||||
])
|
||||
|
||||
room_types = room_type_avail_ids.mapped('room_type_id')
|
||||
avails = []
|
||||
for room_type in room_types:
|
||||
room_type_avails = room_type_avail_ids.filtered(
|
||||
lambda x: x.room_type_id.id == room_type.id)
|
||||
days = []
|
||||
for room_type_avail in room_type_avails:
|
||||
room_type_avail.with_context({
|
||||
'wubook_action': False}).write({'wpushed': True})
|
||||
wavail = room_type_avail.avail
|
||||
if wavail > room_type_avail.wmax_avail:
|
||||
wavail = room_type_avail.wmax_avail
|
||||
date_dt = date_utils.get_datetime(
|
||||
room_type_avail.date,
|
||||
dtformat=DEFAULT_SERVER_DATE_FORMAT)
|
||||
days.append({
|
||||
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
'avail': wavail,
|
||||
'no_ota': room_type_avail.no_ota and 1 or 0,
|
||||
# 'booked': room_type_avail.booked and 1 or 0,
|
||||
})
|
||||
avails.append({'id': room_type.wrid, 'days': days})
|
||||
_logger.info("UPDATING AVAILABILITY IN WUBOOK...")
|
||||
_logger.info(avails)
|
||||
if any(avails):
|
||||
self.backend_adapter.update_availability(avails)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def push_priceplans(self):
|
||||
unpushed = self.env['product.pricelist.item'].search([
|
||||
|
||||
@@ -31,6 +31,9 @@ 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')
|
||||
|
||||
@api.multi
|
||||
def generate_key(self):
|
||||
for record in self:
|
||||
@@ -57,6 +60,23 @@ class ChannelBackend(models.Model):
|
||||
channel_ota_info_obj.import_otas_info(backend)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def import_availability(self):
|
||||
channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
|
||||
for backend in self:
|
||||
channel_hotel_room_type_avail_obj.import_availability(
|
||||
backend,
|
||||
self.avail_from,
|
||||
self.avail_to)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def push_availability(self):
|
||||
channel_hotel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
|
||||
for backend in self:
|
||||
channel_hotel_room_type_avail_obj.push_availability(backend)
|
||||
return True
|
||||
|
||||
@contextmanager
|
||||
@api.multi
|
||||
def work_on(self, model_name, **kwargs):
|
||||
|
||||
@@ -142,7 +142,7 @@ class BindingHotelRoomTypeListener(Component):
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if 'name' in fields or 'list_price' in fields:
|
||||
if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields:
|
||||
record.channel_bind_ids[0].modify_room()
|
||||
|
||||
# @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import common
|
||||
from . import importer
|
||||
from . import exporter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from datetime import timedelta
|
||||
from odoo import api, models, fields
|
||||
from odoo import api, models, fields, _
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.queue_job.job import job, related_action
|
||||
@@ -15,7 +15,7 @@ class ChannelHotelRoomTypeAvailability(models.Model):
|
||||
_name = 'channel.hotel.room.type.availability'
|
||||
_inherit = 'channel.binding'
|
||||
_inherits = {'hotel.room.type.availability': 'odoo_id'}
|
||||
_description = 'Channel Product Pricelist'
|
||||
_description = 'Channel Availability'
|
||||
|
||||
@api.model
|
||||
def _default_channel_max_avail(self):
|
||||
@@ -44,19 +44,24 @@ class ChannelHotelRoomTypeAvailability(models.Model):
|
||||
@job(default_channel='root.channel')
|
||||
@related_action(action='related_action_unwrap_binding')
|
||||
@api.multi
|
||||
def update_availability(self):
|
||||
self.ensure_one()
|
||||
if self._context.get('channel_action', True):
|
||||
with self.backend_id.work_on(self._name) as work:
|
||||
adapter = work.component(usage='backend.adapter')
|
||||
date_dt = fields.Date.from_string(self.date)
|
||||
adapter.update_availability([{
|
||||
'id': self.odoo_id.room_type_id.channel_room_id,
|
||||
'days': [{
|
||||
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
'avail': self.odoo_id.avail,
|
||||
}],
|
||||
}])
|
||||
def update_availability(self, backend):
|
||||
with backend.work_on(self._name) as work:
|
||||
exporter = work.component(usage='hotel.room.type.availability.exporter')
|
||||
return exporter.update_availability(self)
|
||||
|
||||
@job(default_channel='root.channel')
|
||||
@api.model
|
||||
def import_availability(self, backend, date_from, date_to):
|
||||
with backend.work_on(self._name) as work:
|
||||
importer = work.component(usage='hotel.room.type.availability.importer')
|
||||
return importer.get_availability(date_from, date_to)
|
||||
|
||||
@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')
|
||||
return exporter.push_availability()
|
||||
|
||||
class HotelRoomTypeAvailability(models.Model):
|
||||
_inherit = 'hotel.room.type.availability'
|
||||
@@ -79,16 +84,15 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
if record.avail > max_avail:
|
||||
issue_obj.sudo().create({
|
||||
'section': 'avail',
|
||||
'message': _(r"The new availability can't be greater than \
|
||||
the actual availability \
|
||||
\n[%s]\nInput: %d\Limit: %d") % (record.room_type_id.name,
|
||||
record.avail,
|
||||
record),
|
||||
'channel_id': record.room_type_id.channel_bind_ids[0].channel_plan_id,
|
||||
'internal_message': _(r"The new availability can't be greater than \
|
||||
the max. availability \
|
||||
(%s) [Input: %d\Max: %d]") % (record.room_type_id.name,
|
||||
record.avail,
|
||||
max_avail),
|
||||
'date_start': record.date,
|
||||
'date_end': record.date,
|
||||
})
|
||||
# Auto-Fix wubook availability
|
||||
# Auto-Fix channel availability
|
||||
self._event('on_fix_channel_availability').notify(record)
|
||||
return super(HotelRoomTypeAvailability, self)._check_avail()
|
||||
|
||||
@@ -97,12 +101,6 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
if self.room_type_id:
|
||||
self.channel_max_avail = self.room_type_id.total_rooms_count
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if self._context.get('channel_action', True):
|
||||
vals.update({'channel_pushed': False})
|
||||
return super(HotelRoomTypeAvailability, self).write(vals)
|
||||
|
||||
@api.model
|
||||
def refresh_availability(self, checkin, checkout, product_id):
|
||||
date_start = fields.Date.from_string(checkin)
|
||||
@@ -143,6 +141,32 @@ class HotelRoomTypeAvailability(models.Model):
|
||||
'avail': avail,
|
||||
})
|
||||
|
||||
class HotelRoomTypeAvailabilityAdapter(Component):
|
||||
_name = 'channel.hotel.room.type.availability.adapter'
|
||||
_inherit = 'wubook.adapter'
|
||||
_apply_on = 'channel.hotel.room.type.availability'
|
||||
|
||||
def fetch_rooms_values(self, date_from, date_to, rooms=False):
|
||||
return super(HotelRoomTypeAvailabilityAdapter, self).fetch_rooms_values(
|
||||
date_from,
|
||||
date_to,
|
||||
rooms)
|
||||
|
||||
def update_availability(self, rooms_avail):
|
||||
return super(HotelRoomTypeAvailabilityAdapter, self).update_availability(
|
||||
rooms_avail)
|
||||
|
||||
class BindingHotelRoomTypeAvailabilityListener(Component):
|
||||
_name = 'binding.hotel.room.type.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
_apply_on = ['hotel.room.type.availability']
|
||||
|
||||
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
|
||||
def on_record_write(self, record, fields=None):
|
||||
if 'avail' in fields:
|
||||
for binding in record.channel_bind_ids:
|
||||
binding.channel_pushed = False
|
||||
|
||||
class ChannelBindingHotelRoomTypeAvailabilityListener(Component):
|
||||
_name = 'channel.binding.hotel.room.type.availability.listener'
|
||||
_inherit = 'base.connector.listener'
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo import api, fields, _
|
||||
from odoo.addons.hotel_channel_connector.components.backend_adapter import (
|
||||
DEFAULT_WUBOOK_DATE_FORMAT)
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class HotelRoomTypeAvailabilityExporter(Component):
|
||||
_name = 'channel.hotel.room.type.availability.exporter'
|
||||
_inherit = 'hotel.channel.exporter'
|
||||
_apply_on = ['channel.hotel.room.type.availability']
|
||||
_usage = 'hotel.room.type.availability.exporter'
|
||||
|
||||
@api.model
|
||||
def update_availability(self, binding):
|
||||
if any(binding.room_type_id.channel_bind_ids):
|
||||
try:
|
||||
sday_dt = fields.Date.from_string(binding.date)
|
||||
# Supossed that only exists one channel connector per record
|
||||
binding.channel_pushed = True
|
||||
return self.backend_adapter.update_availability({
|
||||
'id': binding.room_type_id.channel_bind_ids[0].channel_room_id,
|
||||
'days': [{
|
||||
'date': sday_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
'avail': binding.avail,
|
||||
'no_ota': binding.no_ota,
|
||||
}],
|
||||
})
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'room',
|
||||
_("Can't update availability in WuBook"),
|
||||
err.data['message'])
|
||||
|
||||
def push_availability(self):
|
||||
channel_room_type_avail_ids = self.env['channel.hotel.room.type.availability'].search([
|
||||
('channel_pushed', '=', False),
|
||||
('date', '>=', fields.Date.today())
|
||||
])
|
||||
room_types = channel_room_type_avail_ids.mapped('room_type_id')
|
||||
avails = []
|
||||
for room_type in room_types:
|
||||
if any(room_type.channel_bind_ids):
|
||||
channel_room_type_avails = channel_room_type_avail_ids.filtered(
|
||||
lambda x: x.room_type_id.id == room_type.id)
|
||||
days = []
|
||||
for channel_room_type_avail in channel_room_type_avails:
|
||||
channel_room_type_avail.channel_pushed = True
|
||||
cavail = channel_room_type_avail.avail
|
||||
if channel_room_type_avail.channel_max_avail >= 0 and \
|
||||
cavail > channel_room_type_avail.channel_max_avail:
|
||||
cavail = channel_room_type_avail.channel_max_avail
|
||||
date_dt = fields.Date.from_string(channel_room_type_avail.date)
|
||||
days.append({
|
||||
'date': date_dt.strftime(DEFAULT_WUBOOK_DATE_FORMAT),
|
||||
'avail': cavail,
|
||||
'no_ota': channel_room_type_avail.no_ota and 1 or 0,
|
||||
# 'booked': room_type_avail.booked and 1 or 0,
|
||||
})
|
||||
avails.append({'id': room_type.channel_bind_ids[0].channel_room_id, 'days': days})
|
||||
_logger.info("UPDATING AVAILABILITY IN WUBOOK...")
|
||||
_logger.info(avails)
|
||||
if any(avails):
|
||||
try:
|
||||
self.backend_adapter.update_availability(avails)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'room',
|
||||
_("Can't update availability in WuBook"),
|
||||
err.data['message'])
|
||||
@@ -2,11 +2,11 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from datetime import date, timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons.component.core import Component
|
||||
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
|
||||
from odoo.addons.connector.components.mapper import mapping
|
||||
from odoo.addons.connector.components.mapper import mapping, external_to_m2o
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo import fields, api, _
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -18,6 +18,59 @@ class HotelRoomTypeAvailabilityImporter(Component):
|
||||
_apply_on = ['channel.hotel.room.type.availability']
|
||||
_usage = 'hotel.room.type.availability.importer'
|
||||
|
||||
@api.model
|
||||
def get_availability(self, date_from, date_to):
|
||||
now_dt = date.today()
|
||||
dfrom_dt = fields.Date.from_string(date_from)
|
||||
dto_dt = fields.Date.from_string(date_to)
|
||||
if dfrom_dt < now_dt:
|
||||
dfrom_dt = now_dt
|
||||
if dfrom_dt > dto_dt:
|
||||
dfrom_dt, dto_dt = dto_dt, dfrom_dt
|
||||
if dto_dt < now_dt:
|
||||
return True
|
||||
count = 0
|
||||
try:
|
||||
results = self.backend_adapter.fetch_rooms_values(date_from, date_to)
|
||||
|
||||
channel_room_type_avail_obj = self.env['channel.hotel.room.type.availability']
|
||||
channel_room_type_obj = self.env['channel.hotel.room.type']
|
||||
room_avail_mapper = self.component(
|
||||
usage='import.mapper',
|
||||
model_name='channel.hotel.room.type.availability')
|
||||
count = len(results)
|
||||
for room_k, room_v in results.items():
|
||||
iter_day = dfrom_dt
|
||||
channel_room_type = channel_room_type_obj.search([
|
||||
('channel_room_id', '=', room_k)
|
||||
], limit=1)
|
||||
if channel_room_type:
|
||||
for room in room_v:
|
||||
room.update({
|
||||
'room_type_id': channel_room_type.odoo_id.id,
|
||||
'date': fields.Date.to_string(iter_day),
|
||||
})
|
||||
map_record = room_avail_mapper.map_record(room)
|
||||
room_type_avail_bind = channel_room_type_avail_obj.search([
|
||||
('room_type_id', '=', room['room_type_id']),
|
||||
('date', '=', room['date'])
|
||||
], limit=1)
|
||||
if room_type_avail_bind:
|
||||
room_type_avail_bind.with_context({
|
||||
'wubook_action': False
|
||||
}).write(map_record.values())
|
||||
else:
|
||||
room_type_avail_bind = channel_room_type_avail_obj.with_context({
|
||||
'wubook_action': False
|
||||
}).create(map_record.values(for_create=True))
|
||||
iter_day += timedelta(days=1)
|
||||
except ChannelConnectorError as err:
|
||||
self.create_issue(
|
||||
'room',
|
||||
_("Can't import availability from WuBook"),
|
||||
err.data['message'])
|
||||
return count
|
||||
|
||||
|
||||
class HotelRoomTypeAvailabilityImportMapper(Component):
|
||||
_name = 'channel.hotel.room.type.availability.import.mapper'
|
||||
@@ -28,10 +81,13 @@ class HotelRoomTypeAvailabilityImportMapper(Component):
|
||||
('no_ota', 'no_ota'),
|
||||
('booked', 'booked'),
|
||||
('avail', 'avail'),
|
||||
('room_type_id', 'room_type_id'),
|
||||
('date', 'date'),
|
||||
]
|
||||
|
||||
@mapping
|
||||
def backend_id(self, record):
|
||||
return {'backend_id': self.backend_record.id}
|
||||
|
||||
@mapping
|
||||
def room_type_id(self, record):
|
||||
return {'room_type_id': record['room_type_id']}
|
||||
|
||||
@@ -85,8 +85,13 @@ class HotelRoomTypeRestriction(models.Model):
|
||||
names = []
|
||||
for name in org_names:
|
||||
restriction_id = room_type_restriction_obj.browse(name[0])
|
||||
if restriction_id.channel_bind_ids.channel_plan_id:
|
||||
names.append((name[0], '%s (WuBook)' % name[1]))
|
||||
if any(restriction_id.channel_bind_ids) and \
|
||||
restriction_id.channel_bind_ids[0].channel_plan_id:
|
||||
names.append((
|
||||
name[0],
|
||||
'%s (%s Backend)' % (name[1],
|
||||
restriction_id.channel_bind_ids[0].backend_id.name),
|
||||
))
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
return names
|
||||
|
||||
@@ -84,6 +84,28 @@
|
||||
string="Import in background"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<label string="Import Availability" class="oe_inline"/>
|
||||
<div>
|
||||
<field name="avail_from" class="oe_inline" nolabel="1"/>
|
||||
<field name="avail_to" class="oe_inline" nolabel="1"/>
|
||||
<button name="import_availability"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
string="Import in background"/>
|
||||
</div>
|
||||
</group>
|
||||
</page>
|
||||
<page name="export" string="Exports">
|
||||
<group>
|
||||
<label string="Push Availability" class="oe_inline"/>
|
||||
<div>
|
||||
<button name="push_availability"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
string="Export in background"/>
|
||||
</div>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
||||
Reference in New Issue
Block a user