mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[WIP] Channel Connector: Import Pricelist
This commit is contained in:
@@ -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,
|
||||||
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
|
|
||||||
from . import common
|
from . import common
|
||||||
from . import importer
|
from . import importer
|
||||||
|
from . import exporter
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
52
hotel_channel_connector/models/product_pricelist/exporter.py
Normal file
52
hotel_channel_connector/models/product_pricelist/exporter.py
Normal 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)
|
||||||
65
hotel_channel_connector/models/product_pricelist/importer.py
Normal file
65
hotel_channel_connector/models/product_pricelist/importer.py
Normal 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}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user