[WIP] Channel Connector: Import Pricelist

This commit is contained in:
QS5ELkMu
2018-11-06 00:56:22 +01:00
parent 19127e3091
commit 52ff989dc4
20 changed files with 426 additions and 102 deletions

View File

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

View File

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

View File

@@ -22,6 +22,7 @@
'wizard/wubook_import_plan_restrictions.xml', 'wizard/wubook_import_plan_restrictions.xml',
'wizard/wubook_import_availability.xml', 'wizard/wubook_import_availability.xml',
'views/general.xml', 'views/general.xml',
'views/hotel_channel_connector_issue_views.xml',
'views/inherited_hotel_reservation_views.xml', 'views/inherited_hotel_reservation_views.xml',
'views/inherited_hotel_room_type_views.xml', 'views/inherited_hotel_room_type_views.xml',
'views/inherited_hotel_room_type_availability_views.xml', 'views/inherited_hotel_room_type_availability_views.xml',
@@ -32,11 +33,11 @@
'views/inherited_hotel_room_type_restriction_item_views.xml', 'views/inherited_hotel_room_type_restriction_item_views.xml',
'views/inherited_res_partner_views.xml', 'views/inherited_res_partner_views.xml',
'views/channel_ota_info_views.xml', 'views/channel_ota_info_views.xml',
'views/hotel_channel_connector_issue_views.xml',
'views/channel_hotel_reservation_views.xml', 'views/channel_hotel_reservation_views.xml',
'views/channel_hotel_room_type_views.xml', 'views/channel_hotel_room_type_views.xml',
'views/channel_hotel_room_type_availability_views.xml', 'views/channel_hotel_room_type_availability_views.xml',
'views/channel_hotel_room_type_restriction_views.xml', 'views/channel_hotel_room_type_restriction_views.xml',
'views/channel_hotel_room_type_restriction_item_views.xml',
'views/channel_product_pricelist_views.xml', 'views/channel_product_pricelist_views.xml',
'views/channel_product_pricelist_item_views.xml', 'views/channel_product_pricelist_item_views.xml',
'views/channel_connector_backend_views.xml', 'views/channel_connector_backend_views.xml',

View File

@@ -441,7 +441,7 @@ class WuBookAdapter(AbstractComponent):
return results return results
def update_plan_periods(self, channel_plan_id, periods): def update_plan_periods(self, channel_plan_id, periods):
rcode, results = self.SERVER.update_plan_periods( rcode, results = self._server.update_plan_periods(
self._session_info[0], self._session_info[0],
self._session_info[1], self._session_info[1],
channel_plan_id, channel_plan_id,
@@ -454,7 +454,7 @@ class WuBookAdapter(AbstractComponent):
return results return results
def get_pricing_plans(self): def get_pricing_plans(self):
rcode, results = self.SERVER.get_pricing_plans( rcode, results = self._server.get_pricing_plans(
self._session_info[0], self._session_info[0],
self._session_info[1]) self._session_info[1])
if rcode != 0: if rcode != 0:

View File

@@ -39,6 +39,11 @@ class ChannelBackend(models.Model):
restriction_id = fields.Many2one('channel.hotel.room.type.restriction', restriction_id = fields.Many2one('channel.hotel.room.type.restriction',
'Channel Restriction') 'Channel Restriction')
pricelist_from = fields.Date('Pricelist From')
pricelist_to = fields.Date('Pricelist To')
pricelist_id = fields.Many2one('channel.product.pricelist',
'Channel Product Pricelist')
issue_ids = fields.One2many('hotel.channel.connector.issue', issue_ids = fields.One2many('hotel.channel.connector.issue',
'backend_id', 'backend_id',
string='Issues', string='Issues',
@@ -98,6 +103,20 @@ class ChannelBackend(models.Model):
channel_hotel_restr_item_obj.import_restriction_values(backend) channel_hotel_restr_item_obj.import_restriction_values(backend)
return True return True
@api.multi
def push_restriction(self):
channel_hotel_restr_item_obj = self.env['channel.hotel.room.type.restriction.item']
for backend in self:
channel_hotel_restr_item_obj.push_restriction(backend)
return True
@api.multi
def import_pricelist_plans(self):
channel_product_pricelist_obj = self.env['channel.product.pricelist']
for backend in self:
channel_product_pricelist_obj.import_price_plans(backend)
return True
@contextmanager @contextmanager
@api.multi @api.multi
def work_on(self, model_name, **kwargs): def work_on(self, model_name, **kwargs):

View File

@@ -11,6 +11,7 @@ class HotelChannelConnectorIssue(models.Model):
backend_id = fields.Many2one('channel.backend', backend_id = fields.Many2one('channel.backend',
'Restriction Plan', 'Restriction Plan',
required=True,
ondelete='cascade', ondelete='cascade',
index=True) index=True)
@@ -46,10 +47,9 @@ class HotelChannelConnectorIssue(models.Model):
reserv_ids.append(record.channel_object_id) reserv_ids.append(record.channel_object_id)
record.to_read = False record.to_read = False
if any(reserv_ids): if any(reserv_ids):
res = self.env['hotel.channel.connector'].mark_bookings(reserv_ids) with self.backend_id.work_on('channel.hotel.reservation') as work:
if not res: exporter = work.component(usage='hotel.reservation.exporter')
raise ValidationError( return exporter.mark_bookings(reserv_ids)
("Can't mark reservation as readed in Channel!"))
@api.model @api.model
def _needaction_domain_get(self): def _needaction_domain_get(self):

View File

@@ -101,9 +101,7 @@ class HotelRoomType(models.Model):
self._compute_capacity() self._compute_capacity()
@api.multi @api.multi
def get_restrictions(self, date): def get_restrictions(self, date, restriction_plan_id):
restriction_plan_id = int(self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id'))
self.ensure_one() self.ensure_one()
restriction = self.env['hotel.room.type.restriction.item'].search([ restriction = self.env['hotel.room.type.restriction.item'].search([
('date', '=', date), ('date', '=', date),
@@ -141,7 +139,9 @@ class BindingHotelRoomTypeListener(Component):
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields: if any(record.channel_bind_ids) and 'name' in fields or 'list_price' in fields or \
'room_ids' in fields:
# FIXME: Supossed that only exists one channel connector per record
record.channel_bind_ids[0].modify_room() record.channel_bind_ids[0].modify_room()
# @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) # @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))

View File

@@ -165,7 +165,7 @@ class HotelRoomTypeImportMapper(Component):
_apply_on = 'channel.hotel.room.type' _apply_on = 'channel.hotel.room.type'
direct = [ direct = [
('id', 'externa_id'), ('id', 'external_id'),
('shortname', 'channel_short_code'), ('shortname', 'channel_short_code'),
('occupancy', 'ota_capacity'), ('occupancy', 'ota_capacity'),
('price', 'list_price'), ('price', 'list_price'),

View File

@@ -164,14 +164,22 @@ class BindingHotelRoomTypeAvailabilityListener(Component):
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
if 'avail' in fields: if 'avail' in fields:
for binding in record.channel_bind_ids: record.channel_bind_ids.write({'channel_pushed': False})
binding.channel_pushed = False
class ChannelBindingHotelRoomTypeAvailabilityListener(Component): class ChannelBindingHotelRoomTypeAvailabilityListener(Component):
_name = 'channel.binding.hotel.room.type.availability.listener' _name = 'channel.binding.hotel.room.type.availability.listener'
_inherit = 'base.connector.listener' _inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.room.type.availability'] _apply_on = ['channel.hotel.room.type.availability']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None):
record.channel_pushed = False
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
if 'avail' in fields:
record.channel_pushed = False
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_fix_channel_availability(self, record, fields=None): def on_fix_channel_availability(self, record, fields=None):
record.with_delay(priority=20).update_availability() record.update_availability()

View File

@@ -20,7 +20,7 @@ class HotelRoomTypeAvailabilityExporter(Component):
if any(binding.room_type_id.channel_bind_ids): if any(binding.room_type_id.channel_bind_ids):
try: try:
sday_dt = fields.Date.from_string(binding.date) sday_dt = fields.Date.from_string(binding.date)
# Supossed that only exists one channel connector per record # FIXME: Supossed that only exists one channel connector per record
binding.channel_pushed = True binding.channel_pushed = True
return self.backend_adapter.update_availability({ return self.backend_adapter.update_availability({
'id': binding.room_type_id.channel_bind_ids[0].channel_room_id, 'id': binding.room_type_id.channel_bind_ids[0].channel_room_id,

View File

@@ -35,15 +35,15 @@ class HotelRoomTypeRestrictionImporter(Component):
channel_restriction_obj.with_context({ channel_restriction_obj.with_context({
'wubook_action': False, 'wubook_action': False,
'rules': plan.get('rules'), 'rules': plan.get('rules'),
}).create(plan_record.values()) }).create(plan_record.values(for_create=True))
else: else:
plan_bind.with_context({'wubook_action': False}).write( plan_bind.with_context({'wubook_action': False}).write(
plan_record.values(for_create=True)) plan_record.values())
count = count + 1 count = count + 1
except ChannelConnectorError as err: except ChannelConnectorError as err:
self.create_issue( self.create_issue(
backend=self.backend_adapter.id, backend=self.backend_adapter.id,
section='rplan', section='restriction',
internal_message=_("Can't fetch restriction plans from wubook"), internal_message=_("Can't fetch restriction plans from wubook"),
channel_message=err.data['message']) channel_message=err.data['message'])
return count return count

View File

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

View File

@@ -20,12 +20,6 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model):
channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False, channel_pushed = fields.Boolean("Channel Pushed", readonly=True, default=False,
old_name='wpushed') old_name='wpushed')
@job(default_channel='root.channel')
@api.multi
def update_channel_pushed(self, status):
self.ensure_one()
self.channel_pushed = status
@job(default_channel='root.channel') @job(default_channel='root.channel')
@api.model @api.model
def import_restriction_values(self, backend): def import_restriction_values(self, backend):
@@ -36,6 +30,13 @@ class ChannelHotelRoomTypeRestrictionItem(models.Model):
backend.restriction_to, backend.restriction_to,
channel_restr_id=backend.restriction_id) channel_restr_id=backend.restriction_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')
return exporter.push_restriction()
class HotelRoomTypeRestrictionItem(models.Model): class HotelRoomTypeRestrictionItem(models.Model):
_inherit = 'hotel.room.type.restriction.item' _inherit = 'hotel.room.type.restriction.item'
@@ -55,15 +56,34 @@ class HotelRoomTypeRestrictionItemAdapter(Component):
date_to, date_to,
channel_restriction_plan_id) channel_restriction_plan_id)
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component): class BindingHotelRoomTypeRestrictionItemListener(Component):
_name = 'channel.binding.hotel.room.type.restriction.item.listener' _name = 'binding.hotel.room.type.restriction.item.listener'
_inherit = 'base.connector.listener' _inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.room.type.restriction'] _apply_on = ['hotel.room.type.restriction.item']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None):
return True
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) @skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None): def on_record_write(self, record, fields=None):
return True fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival',
'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival',
'date')
fields_checked = [elm for elm in fields_to_check if elm in fields]
if any(fields_checked):
record.channel_bind_ids.write({'channel_pushed': False})
class ChannelBindingHotelRoomTypeRestrictionItemListener(Component):
_name = 'channel.binding.hotel.room.type.restriction.item.listener'
_inherit = 'base.connector.listener'
_apply_on = ['channel.hotel.room.type.restriction.item']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None):
record.channel_pushed = False
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
fields_to_check = ('min_stay', 'min_stay_arrival', 'max_stay', 'max_stay_arrival',
'max_stay_arrival', 'closed', 'closed_departure', 'closed_arrival',
'date')
fields_checked = [elm for elm in fields_to_check if elm in fields]
if any(fields_checked):
record.channel_pushed = False

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo.addons.component.core import Component
from odoo.addons.hotel_channel_connector.components.core import ChannelConnectorError
from odoo import api, _
_logger = logging.getLogger(__name__)
class ProductPricelistExporter(Component):
_name = 'channel.product.pricelist.exporter'
_inherit = 'hotel.channel.exporter'
_apply_on = ['channel.product.pricelist']
_usage = 'product.pricelist.exporter'
@api.model
def rename_plan(self, binding):
try:
return self.backend_adapter.rename_plan(
binding.external_id,
binding.name)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_adapter.id,
section='restriction',
internal_message=_("Can't modify pricelist plan in WuBook"),
channel_message=err.data['message'])
@api.model
def delete_plan(self, binding):
try:
return self.backend_adapter.delete_plan(binding.external_id)
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_adapter.id,
section='restriction',
internal_message=_("Can't delete pricelist plan in WuBook"),
channel_message=err.data['message'])
@api.model
def create_plan(self, binding):
try:
external_id = self.backend_adapter.create_plan(binding.name)
binding.external_id = external_id
except ChannelConnectorError as err:
self.create_issue(
backend=self.backend_adapter.id,
section='restriction',
internal_message=_("Can't create pricelist plan in WuBook"),
channel_message=err.data['message'])
else:
self.binder.bind(external_id, binding)

View File

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

View File

@@ -116,6 +116,15 @@
string="Import in background"/> string="Import in background"/>
</div> </div>
</group> </group>
<group>
<label string="Import Pricelist Plans" class="oe_inline"/>
<div>
<button name="import_pricelist_plans"
type="object"
class="oe_highlight"
string="Import in background"/>
</div>
</group>
</page> </page>
<page name="export" string="Exports"> <page name="export" string="Exports">
<group> <group>
@@ -127,6 +136,15 @@
string="Export in background"/> string="Export in background"/>
</div> </div>
</group> </group>
<group>
<label string="Push Restriction" class="oe_inline"/>
<div>
<button name="push_restriction"
type="object"
class="oe_highlight"
string="Export in background"/>
</div>
</group>
</page> </page>
<page name="issues" string="Issues"> <page name="issues" string="Issues">
<field name="issue_ids" nolabel="1"> <field name="issue_ids" nolabel="1">

View File

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