[DEL] master/helper deprecated for this branch

This commit is contained in:
Pablo
2019-07-15 08:17:36 +02:00
parent 1025f8725c
commit 7dd7533a99
51 changed files with 0 additions and 2539 deletions

View File

@@ -1,22 +0,0 @@
=================
Hotel Node Helper
=================
This module is for providing helper functions to the hotel node master module.
**Try me on Runbot**
**Known issues / Roadmap**
...
**Bug Tracker**
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback here.
Credits
Contributors
Maintainer

View File

@@ -1,3 +0,0 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models

View File

@@ -1,21 +0,0 @@
{
'name': 'Hotel Node Helper',
'summary': """Provides helper functions to the hotel node master module""",
'version': '0.1.0',
'author': 'Pablo Q. Barriuso, \
Darío Lodeiros, \
Alexandre Díaz, \
Odoo Community Association (OCA)',
'category': 'Generic Modules/Hotel Management',
'depends': [
'hotel'
],
'license': "AGPL-3",
'data': [
'security/hotel_node_security.xml',
'security/ir.model.access.csv'
],
'demo': [],
'auto_install': False,
'installable': True
}

View File

@@ -1,3 +0,0 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import inherited_hotel_room_type

View File

@@ -1,64 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class HotelRoomType(models.Model):
_inherit = 'hotel.room.type'
@api.model
def get_room_type_availability(self, dfrom, dto, room_type_id):
free_rooms = self.check_availability_room_type(dfrom, dto)
availability_real = self.env['hotel.room'].search_count([
('id', 'in', free_rooms.ids),
('room_type_id', '=', room_type_id),
])
availability_plan = self.env['hotel.room.type.availability'].search_read([
('date', '>=', dfrom),
('date', '<', dto),
('room_type_id', '=', room_type_id),
], ['avail']) or [{'avail': availability_real}]
availability_plan = min([r['avail'] for r in availability_plan])
return min(availability_real, availability_plan)
@api.model
def get_room_type_price_unit(self, dfrom, dto, room_type_id):
# TODO review how to get the prices
reservation_line_ids = self.env['hotel.reservation'].prepare_reservation_lines(
dfrom,
(fields.Date.from_string(dto) - fields.Date.from_string(dfrom)).days,
{'room_type_id': room_type_id}
)
reservation_line_ids = reservation_line_ids['reservation_line_ids']
# QUESTION Why add [[5, 0, 0], ¿?
# del reservation_line_ids[0]
return reservation_line_ids
@api.model
def get_room_type_restrictions(self, dfrom, dto, room_type_id):
restrictions_plan = self.env['hotel.room.type.restriction.item'].search_read([
('date', '>=', dfrom),
('date', '<', dto),
('room_type_id', '=', room_type_id),
], ['min_stay']) or [{'min_stay': 0}]
min_stay = max([r['min_stay'] for r in restrictions_plan])
return min_stay
@api.model
def get_room_type_planning(self, dfrom, dto, room_type_id):
availability = self.get_room_type_availability(dfrom, dto, room_type_id)
price_unit = self.get_room_type_price_unit(dfrom, dto, room_type_id)
restrictions = self.get_room_type_restrictions(dfrom, dto, room_type_id)
return {'availability': availability, 'price_unit': price_unit, 'restrictions': restrictions}

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="0">
</odoo>

View File

@@ -1 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -1,43 +0,0 @@
=================
Hotel Node Master
=================
This module is for providing centralized hotel management features for hootel.
You can manage:
- Node connection data
- Remote users and access groups
- Hotel reservations
**Installation**
To install this module, you need to:
**External dependencies**
- OdooRPC, a Python package providing an easy way to pilot your Odoo servers through RPC
**Configuration**
To configure this module, you need to:
**Usage**
To use this module, you need to:
**Try me on Runbot**
**Known issues / Roadmap**
...
**Bug Tracker**
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback here.
Credits
Contributors
Maintainer

View File

@@ -1,4 +0,0 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import components
from . import models
from . import wizards

View File

@@ -1,33 +0,0 @@
{
'name': 'Hotel Node Master',
'summary': """Provides centralized hotel management features""",
'version': '0.1.0',
'author': 'Pablo Q. Barriuso, \
Darío Lodeiros, \
Alexandre Díaz, \
Odoo Community Association (OCA)',
'category': 'Generic Modules/Hotel Management',
'depends': [
'project',
'connector'
],
'external_dependencies':
{'python' : ['odoorpc']},
'license': "AGPL-3",
'data': [
'wizards/wizard_hotel_node_reservation.xml',
'views/node_backend_views.xml',
'views/hotel_node.xml',
'views/hotel_node_user.xml',
'views/hotel_node_group.xml',
'views/hotel_node_group_remote.xml',
'views/hotel_node_room_type.xml',
'views/inherited_res_partner_views.xml',
'security/hotel_node_security.xml',
'security/ir.model.access.csv',
'data/menus.xml',
],
'demo': [],
'auto_install': False,
'installable': True
}

View File

@@ -1,9 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import core
from . import backend_adapter
from . import binder
from . import importer
from . import exporter
from . import mapper

View File

@@ -1,107 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import odoorpc
import logging
from odoo.addons.component.core import AbstractComponent
from odoo.addons.queue_job.exception import RetryableJobError
_logger = logging.getLogger(__name__)
class NodeLogin(object):
def __init__(self, address, protocol, port, db, user, passwd):
self.address = address
self.protocol = protocol
self.port = port
self.db = db
self.user = user
self.passwd = passwd
class NodeServer(object):
def __init__(self, login_data):
self._server = None
self._login_data = login_data
def __enter__(self):
# we do nothing, api is lazy
return self
def __exit__(self, type, value, traceback):
if self._server is not None:
self.close()
@property
def server(self):
if self._server is None:
try:
self._server = odoorpc.ODOO(self._login_data.address,
self._login_data.protocol,
self._login_data.port)
self._server.login(self._login_data.db,
self._login_data.user,
self._login_data.passwd)
except Exception:
self._server = None
raise RetryableJobError("Can't connect with node!")
return self._server
def close(self):
self._server.logout()
self._server = None
class HotelNodeInterfaceAdapter(AbstractComponent):
_name = 'hotel.node.interface.adapter'
_inherit = ['base.backend.adapter', 'base.node.connector']
_usage = 'backend.adapter'
def create_room_type(self, name, room_ids):
raise NotImplementedError
def modify_room_type(self, room_type_id, name, room_ids):
raise NotImplementedError
def delete_room_type(self, room_type_id):
raise NotImplementedError
def fetch_room_types(self):
raise NotImplementedError
@property
def _server(self):
try:
node_server = getattr(self.work, 'node_api')
except AttributeError:
raise AttributeError(
'You must provide a node_api attribute with a '
'WuBookServer instance to be able to use the '
'Backend Adapter.'
)
return node_server.server
class HotelNodeAdapter(AbstractComponent):
_name = 'hotel.node.adapter'
_inherit = 'hotel.node.interface.adapter'
# === ROOMS
def create_room_type(self, name, room_ids):
return self._server.env['hotel.room.type'].create({
'name': name
})
def modify_room_type(self, room_type_id, name, rooms_id):
return self._server.env['hotel.room.type'].write(
[room_type_id],
{
'name': name
})
def delete_room_type(self, room_type_id):
_logger.warning("_delete_room_type(%s, room_type_id) is not yet implemented.", self)
return True
# return self._server.env['hotel.room.type'].unlink(room_type_id)
def fetch_room_types(self):
return self._server.env['hotel.room.type'].search_read(
[],
['name']
)

View File

@@ -1,11 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.addons.component.core import Component
class NodeConnectorModelBinder(Component):
_name = 'node.connector.binder'
_inherit = ['base.binder', 'base.node.connector']
_apply_on = [
'node.room.type',
]

View File

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

View File

@@ -1,12 +0,0 @@
# 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 AbstractComponent
_logger = logging.getLogger(__name__)
class NodeExporter(AbstractComponent):
_name = 'node.exporter'
_inherit = ['base.exporter', 'base.node.connector']
_usage = 'node.exporter'

View File

@@ -1,11 +0,0 @@
# 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 AbstractComponent, Component
_logger = logging.getLogger(__name__)
class NodeImporter(AbstractComponent):
_name = 'node.importer'
_inherit = ['base.importer', 'base.node.connector']
_usage = 'node.importer'

View File

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

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_node_connector_root"
parent="connector.menu_connector_root"
name="Hotel Node"
sequence="10"/>
<menuitem id="menu_node_backend"
name="Backends"
sequence="1"
parent="menu_node_connector_root"
action="action_node_backend"/>
<menuitem id="menu_node_room_type"
name="Room Types"
sequence="1"
parent="menu_node_connector_root"
action="action_node_room_type"/>
</odoo>

View File

@@ -1,13 +0,0 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import node_backend
from . import node_binding
from . import hotel_node
from . import hotel_node_user
from . import hotel_node_group
from . import hotel_node_group_remote
from . import hotel_node_room
from . import hotel_node_room_type
from . import inherited_res_partner
from . import hotel_room_type

View File

@@ -1,333 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import urllib.error
import odoorpc.odoo
from odoo.exceptions import ValidationError
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class HotelNode(models.Model):
_inherit = ['project.project']
_description = 'Centralized hotel management features'
active = fields.Boolean('Active', default=True,
help='The active field allows you to hide the \
node without removing it.')
sequence = fields.Integer('Sequence', default=0,
help='Gives the sequence order when displaying the list of Nodes.')
odoo_version = fields.Char()
odoo_host = fields.Char('Host', required=True,
help='Full URL to the host.', default='localhost')
odoo_db = fields.Char('Database Name',
help='Odoo database name.')
odoo_user = fields.Char('Username',
help='Odoo administration user.')
odoo_password = fields.Char('Password',
help='Odoo password.')
odoo_port = fields.Integer(string='TCP Port', default=443,
help='Specify the TCP port for the XML-RPC protocol.')
odoo_protocol = fields.Selection([('jsonrpc', 'jsonrpc'), ('jsonrpc+ssl', 'jsonrpc+ssl')],
'Protocol', required=True, default='jsonrpc+ssl')
user_ids = fields.One2many('hotel.node.user', 'node_id',
'Users with access to this hotel')
# group_ids = fields.Many2many('hotel.node.group', 'hotel_node_group_rel', 'node_id', 'group_id',
# string='Access Groups')
group_ids = fields.One2many('hotel.node.group.remote', 'node_id',
'Access Groups')
room_type_ids = fields.One2many('hotel.node.room.type', 'node_id',
'Rooms Type in this hotel')
room_ids = fields.One2many('hotel.node.room', 'node_id',
'Rooms in this hotel')
@api.constrains('group_ids')
def _check_group_version(self):
"""
:raise: ValidationError
"""
for node in self:
domain = [('id', 'in', node.group_ids.ids), ('odoo_version', '!=', node.odoo_version)]
invalid_groups = self.env["hotel.node.group"].search_count(domain)
if invalid_groups > 0:
msg = _("At least one group is not within the node version.") + " " + \
_("Odoo version of the node: %s") % node.odoo_version
_logger.warning(msg)
raise ValidationError(msg)
_sql_constraints = [
('db_node_id_uniq', 'unique (odoo_db, id)',
'The database name of the hotel must be unique within the Master Node!'),
]
@api.model
def create(self, vals):
"""
:param dict vals: the model's fields as a dictionary
:return: new hotel node record created.
:raise: ValidationError
"""
try:
noderpc = odoorpc.ODOO(vals['odoo_host'], vals['odoo_protocol'], vals['odoo_port'])
noderpc.login(vals['odoo_db'], vals['odoo_user'], vals['odoo_password'])
vals.update({'odoo_version': noderpc.version})
# TODO Check if hotel_node_helper module is installed / available in the node.
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
else:
node_id = super().create(vals)
noderpc.logout()
return node_id
@api.multi
def action_sync_from_node(self):
self.ensure_one()
try:
noderpc = odoorpc.ODOO(self.odoo_host, self.odoo_protocol, self.odoo_port)
noderpc.login(self.odoo_db, self.odoo_user, self.odoo_password)
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
# TODO synchronize only if write_date in remote node is newer ¿?
try:
vals = {}
# import remote groups
remote_groups = noderpc.env['ir.model.data'].search_read(
[('model', '=', 'res.groups')],
['complete_name', 'display_name'])
master_groups = self.env["hotel.node.group"].search_read(
[('odoo_version', '=', self.odoo_version)], ['xml_id'])
gui_ids = [r['id'] for r in master_groups]
xml_ids = [r['xml_id'] for r in master_groups]
group_ids = []
for group in remote_groups:
if group['complete_name'] in xml_ids:
idx = xml_ids.index(group['complete_name'])
group_ids.append((4, gui_ids[idx], 0))
else:
group_ids.append((0, 0, {
'name': group['display_name'],
'xml_id': group['complete_name'],
'odoo_version': self.odoo_version,
}))
vals.update({'group_ids': group_ids})
self.write(vals)
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
try:
vals = {}
# import remote users
remote_users = noderpc.env['res.users'].search_read(
[('login', '!=', 'admin')],
['name', 'login', 'email', 'is_company', 'partner_id', 'groups_id', 'active'])
master_users = self.env["hotel.node.user"].search_read(
[('node_id', '=', self.id)], ['remote_user_id'])
master_ids = [r['id'] for r in master_users]
remote_ids = [r['remote_user_id'] for r in master_users]
# For the first hotel, gui_ids and xml_ids is empty. You must recover the previously written groups
master_groups = self.env["hotel.node.group"].search_read(
[('odoo_version', '=', self.odoo_version)], ['xml_id'])
gui_ids = [r['id'] for r in master_groups]
xml_ids = [r['xml_id'] for r in master_groups]
user_ids = []
for user in remote_users:
group_ids = []
# retrieve the remote external ID(s) of group records
remote_xml_ids = noderpc.env['res.groups'].browse(user['groups_id']).get_external_id()
for key, value in remote_xml_ids.items():
group_ids.append(gui_ids[xml_ids.index(value)])
if user['id'] in remote_ids:
idx = remote_ids.index(user['id'])
user_ids.append((1, master_ids[idx], {
'name': user['name'],
'login': user['login'],
'email': user['email'],
'active': user['active'],
'remote_user_id': user['id'],
'group_ids': [[
6,
False,
group_ids
]]
}))
else:
partner = self.env['res.partner'].search([('email', '=', user['email'])])
if not partner:
partner = self.env['res.partner'].create({
'name': user['name'],
'is_company': False,
'email': user['email'],
})
user_ids.append((0, 0, {
'name': user['name'],
'login': user['login'],
'email': user['email'],
'active': user['active'],
'remote_user_id': user['id'],
'partner_id': partner.id,
'group_ids': [[
6,
False,
group_ids
]]
}))
vals.update({'user_ids': user_ids})
self.with_context({
'is_synchronizing': True,
}).write(vals)
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
try:
# import remote partners
node_partners = noderpc.env['res.partner'].search_read(
[('email', '!=', '')], # TODO import remote partners (exclude unconfirmed using DNI)
['name', 'email', 'is_company', 'website', 'type', 'active'])
master_partners = self.env['res.partner'].search([('email', 'in', [r['email'] for r in node_partners])])
master_partner_emails = [r['email'] for r in master_partners]
master_partner_ids = master_partners.ids
for partner in node_partners:
if partner['email'] not in master_partner_emails:
new_partner = self.env['res.partner'].create({
'name': partner['name'],
'email': partner['email'],
'is_company': partner['is_company'],
'website': partner['website'],
'type': partner['type'],
'active': partner['active'],
})
_logger.info('User #%s created res.partner with ID: [%s]',
self._context.get('uid'), new_partner.id)
else:
partner_id = master_partner_ids[master_partner_emails.index(partner['email'])]
self.env['res.partner'].browse(partner_id).write({
'name': partner['name'],
'is_company': partner['is_company'],
'website': partner['website'],
'type': partner['type'],
'active': partner['active'],
# Partners in different Nodes may have different parent_id
# TODO How to manage parent_id for related company ¿?
})
_logger.info('User #%s update res.partner with ID: [%s]',
self._context.get('uid'), partner_id)
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
try:
vals = {}
# import remote room types
remote_room_types = noderpc.env['hotel.room.type'].search_read(
[], ['name', 'active', 'sequence', 'room_ids'])
master_room_types = self.env["hotel.node.room.type"].search_read(
[('node_id', '=', self.id)], ['remote_room_type_id'])
master_ids = [r['id'] for r in master_room_types]
remote_ids = [r['remote_room_type_id'] for r in master_room_types]
room_type_ids = []
for room_type in remote_room_types:
if room_type['id'] in remote_ids:
idx = remote_ids.index(room_type['id'])
room_type_ids.append((1, master_ids[idx], {
'name': room_type['name'],
'active': room_type['active'],
'sequence': room_type['sequence'],
'remote_room_type_id': room_type['id'],
}))
else:
room_type_ids.append((0, 0, {
'name': room_type['name'],
'active': room_type['active'],
'sequence': room_type['sequence'],
'remote_room_type_id': room_type['id'],
}))
vals.update({'room_type_ids': room_type_ids})
self.write(vals)
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
try:
vals = {}
# import remote rooms
remote_rooms = noderpc.env['hotel.room'].search_read(
[],
['name', 'active', 'sequence', 'capacity', 'room_type_id'])
master_rooms = self.env["hotel.node.room"].search_read(
[('node_id', '=', self.id)], ['remote_room_id'])
master_ids = [r['id'] for r in master_rooms]
remote_ids = [r['remote_room_id'] for r in master_rooms]
room_ids = []
for room in remote_rooms:
room_type_id = self.env["hotel.node.room.type"].search(
[('node_id', '=', self.id),
('remote_room_type_id', '=', room['room_type_id'][0])]) or None
if room_type_id is None:
msg = _("Something was completely wrong for Remote Room ID: [%s]") % room['id']
_logger.critical(msg)
raise ValidationError(msg)
if room['id'] in remote_ids:
idx = remote_ids.index(room['id'])
room_ids.append((1, master_ids[idx], {
'name': room['name'],
'active': room['active'],
'sequence': room['sequence'],
'capacity': room['capacity'],
'room_type_id': room_type_id.id,
'remote_room_id': room['id'],
}))
else:
room_ids.append((0, 0, {
'name': room['name'],
'active': room['active'],
'sequence': room['sequence'],
'capacity': room['capacity'],
'room_type_id': room_type_id.id,
'remote_room_id': room['id'],
}))
vals.update({'room_ids': room_ids})
self.write(vals)
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
noderpc.logout()
return True

View File

@@ -1,39 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class HotelNodeGroup(models.Model):
_name = "hotel.node.group"
_description = "Hotel Access Groups"
active = fields.Boolean(default=True,
help="The active field allows you to hide the \
group without removing it.")
sequence = fields.Integer(default=0,
help="Gives the sequence order when displaying the list of Groups.")
name = fields.Char(required=True, translate=True)
# node_ids = fields.Many2many('project.project', 'hotel_node_group_rel', 'group_id', 'node_id',
# string='Hotels')
remote_group_ids = fields.One2many('hotel.node.group.remote', 'group_id',
'Access Groups')
user_ids = fields.Many2many('hotel.node.user', 'hotel_node_user_group_rel', 'group_id', 'user_id',
string='Users')
# xml_id represents the complete module.name, xml_id = ("%s.%s" % (data['module'], data['name']))
xml_id = fields.Char(string='External Identifier', required=True,
help="External Key/Identifier that can be used for "
"data integration with third-party systems")
odoo_version = fields.Char('Odoo Version')
_sql_constraints = [
('xml_id_uniq', 'unique (odoo_version, xml_id)',
'The external identifier of the group must be unique within an Odoo version!')
]

View File

@@ -1,25 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class HotelNodeGroupRemote(models.Model):
_name = "hotel.node.group.remote"
_description = "Remote Access Groups IDs"
node_id = fields.Many2one('project.project', 'Hotel', required=True)
group_id = fields.Many2one('hotel.node.group', 'Group', require=True)
name = fields.Char(related='group_id.name')
remote_group_id = fields.Integer(require=True, copy=False, readonly=True,
help="ID of the target record in the remote database")
_sql_constraints = [
('node_remote_group_id_uniq', 'unique (node_id, remote_group_id)',
'The remote identifier of the group must be unique within a Node!')
]

View File

@@ -1,35 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class HotelNodeRoom(models.Model):
_name = "hotel.node.room"
_description = "Rooms"
name = fields.Char(required=True, translate=True)
remote_room_id = fields.Integer(require=True, invisible=True, copy=False, readonly=True,
help="ID of the target record in the remote database")
room_type_id = fields.Many2one('hotel.node.room.type', 'Hotel Room Type', required=True)
node_id = fields.Many2one('project.project', 'Hotel', required=True, readonly=True)
capacity = fields.Integer('Capacity')
active = fields.Boolean(default=True)
sequence = fields.Integer(default=0)
_sql_constraints = [
('db_remote_room_id_uniq', 'unique (remote_room_id, node_id)',
'The Room must be unique within the Node!'),
]
# TODO Changing a room between nodes. Do not allow change the node at create / write

View File

@@ -1,72 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class HotelNodeRoomType(models.Model):
_name = "hotel.node.room.type"
_description = "Room Type"
active = fields.Boolean(default=True)
sequence = fields.Integer(default=0)
name = fields.Char(required=True, translate=True)
remote_room_type_id = fields.Integer(require=True, invisible=True, copy=False, readonly=True,
help="ID of the target record in the remote database")
room_ids = fields.One2many('hotel.node.room', 'room_type_id', 'Rooms')
node_id = fields.Many2one('project.project', 'Hotel', required=True)
_sql_constraints = [
('db_remote_room_type_id_uniq', 'unique (remote_room_type_id, node_id)',
'The Room Type must be unique within the Node!'),
]
@api.onchange('node_id')
def _onchange_node_id(self):
if self.node_id:
return {'domain': {'room_ids': [('room_ids', 'in', self.room_ids.ids)]}}
return {'domain': {'room_ids': []}}
@api.model
def create(self, vals):
"""
:param dict vals: the model's fields as a dictionary
:return: new hotel room type record created.
:raise: ValidationError
"""
_logger.warning("This fuction is not yet implemented for remote create.")
return super().create(vals)
@api.multi
def write(self, vals):
"""
:param dict vals: a dictionary of fields to update and the value to set on them.
:raise: ValidationError
"""
for rec in self:
if 'node_id' in vals and vals['node_id'] != rec.node_id.id:
msg = _("Changing a room type between nodes is not allowed. Please create a new room type instead.")
_logger.error(msg)
raise ValidationError(msg)
_logger.warning("This fuction is not yet implemented for remote update.")
return super().write(vals)
@api.multi
def unlink(self):
"""
:raise: ValidationError
"""
_logger.warning("This fuction is not yet implemented for remote delete.")
return super().unlink()

View File

@@ -1,202 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import urllib.error
import odoorpc.odoo
from odoo.exceptions import ValidationError
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class HotelNodeUser(models.Model):
_name = "hotel.node.user"
_description = "Users with access to a hotel"
def _default_groups(self):
pass
active = fields.Boolean(default=True,
help="The active field allows you to hide the \
user without removing it.")
sequence = fields.Integer(default=0,
help="Gives the sequence order when displaying the list of Users.")
node_id = fields.Many2one('project.project', 'Hotel', required=True)
# remote users are managed as partners into the central node
partner_id = fields.Many2one('res.partner', required=True)
name = fields.Char(related='partner_id.name')
email = fields.Char(related='partner_id.email', readonly=True)
login = fields.Char(related='partner_id.email', require=True,
help="Used to log into the hotel")
password = fields.Char(default='', invisible=True, copy=False,
help="Keep empty if you don't want the user to be able to connect on the hotel.")
remote_user_id = fields.Integer(require=True, invisible=True, copy=False, readonly=True,
help="ID of the target record in the remote database")
group_ids = fields.Many2many('hotel.node.group', 'hotel_node_user_group_rel', 'user_id', 'group_id',
string='Groups', default=_default_groups, require=True,
help="Access rights for this user in this hotel.")
# Constraints and onchanges
@api.constrains('group_ids')
def _check_group_ids(self):
# TODO ensure all group_ids are within the node version
domain = [('id', 'in', self.group_ids.ids), ('odoo_version', '!=', self.node_id.odoo_version)]
invalid_groups = self.env["hotel.node.group"].search_count(domain)
if invalid_groups > 0:
msg = _("At least one group is not within the node version.") + " " + \
_("Odoo version of the node: %s") % self.node_id.odoo_version
_logger.warning(msg)
raise ValidationError(msg)
@api.onchange('node_id')
def _onchange_node_id(self):
if self.node_id:
# TODO clean group_ids
# self.group_ids = []
return {'domain': {'group_ids': [('odoo_version', '=', self.node_id.odoo_version)]}}
return {'domain': {'group_ids': []}}
@api.model
def create(self, vals):
"""
:param dict vals: the model's fields as a dictionary
:return: new hotel user record created.
:raise: ValidationError
"""
node = self.env["project.project"].browse(vals['node_id'])
if 'group_ids' in vals:
domain = [('id', 'in', vals['group_ids'][0][2]), ('odoo_version', '!=', node.odoo_version)]
invalid_groups = self.env["hotel.node.group"].search_count(domain)
if invalid_groups > 0:
msg = _("At least one group is not within the node version.") + " " + \
_("Odoo version in node: %s") % node.odoo_version
_logger.error(msg)
raise ValidationError(msg)
try:
if 'is_synchronizing' not in self._context:
noderpc = odoorpc.ODOO(node.odoo_host, node.odoo_protocol, node.odoo_port)
noderpc.login(node.odoo_db, node.odoo_user, node.odoo_password)
partner = self.env["res.partner"].browse(vals['partner_id'])
remote_vals = {
'name': partner.name,
'login': vals['login'],
}
if 'group_ids' in vals:
groups = self.env["hotel.node.group"].browse(vals['group_ids'][0][2])
# TODO Improve one rpc call per remote group for better performance
remote_groups = [noderpc.env.ref(r.xml_id).id for r in groups]
remote_vals.update({'groups_id': [[6, False, remote_groups]]})
# create user and delegate in remote node the default values for the user
remote_user_id = noderpc.env['res.users'].create(remote_vals)
_logger.info('User #%s created remote res.users with ID: [%s]',
self._context.get('uid'), remote_user_id)
vals.update({'remote_user_id': remote_user_id})
noderpc.logout()
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
_logger.error(err)
raise ValidationError(err)
else:
return super().create(vals)
@api.multi
def write(self, vals):
"""
:param dict vals: a dictionary of fields to update and the value to set on them.
:raise: ValidationError
"""
for rec in self:
if 'node_id' in vals and vals['node_id'] != rec.node_id.id:
msg = _("Changing a user between nodes is not allowed. Please create a new user instead.")
_logger.error(msg)
raise ValidationError(msg)
node = rec.node_id
if 'group_ids' in vals:
domain = [('id', 'in', vals['group_ids'][0][2]), ('odoo_version', '!=', node.odoo_version)]
invalid_groups = self.env["hotel.node.group"].search_count(domain)
if invalid_groups > 0:
msg = _("At least one group is not within the node version.") + " " + \
_("Odoo version in node: %s") % node.odoo_version
_logger.error(msg)
raise ValidationError(msg)
try:
if 'is_synchronizing' not in self._context:
noderpc = odoorpc.ODOO(node.odoo_host, node.odoo_protocol, node.odoo_port)
noderpc.login(node.odoo_db, node.odoo_user, node.odoo_password)
remote_vals = {}
if 'login' in vals:
remote_vals.update({'login': vals['login']})
if 'active' in vals:
remote_vals.update({'active': vals['active']})
if 'password' in vals:
remote_vals.update({'password': vals['password']})
if 'partner_id' in vals:
partner = self.env["res.partner"].browse(vals['partner_id'])
remote_vals.update({'name': partner.name})
if 'group_ids' in vals:
groups = self.env["hotel.node.group"].browse(vals['group_ids'][0][2])
# TODO Improve one rpc call per remote group for better performance
remote_groups = [noderpc.env.ref(r.xml_id).id for r in groups]
remote_vals.update({'groups_id': [[6, False, remote_groups]]})
noderpc.env['res.users'].write([rec.remote_user_id], remote_vals)
_logger.info('User #%s updated remote res.users with ID: [%s]',
self._context.get('uid'), rec.remote_user_id)
noderpc.logout()
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
_logger.error(err)
raise ValidationError(err)
# TODO update record in central node only if the corresponding remote call was successfully
return super().write(vals)
@api.multi
def unlink(self):
"""
:raise: ValidationError
"""
for rec in self:
try:
node = rec.node_id
noderpc = odoorpc.ODOO(node.odoo_host, node.odoo_protocol, node.odoo_port)
noderpc.login(node.odoo_db, node.odoo_user, node.odoo_password)
# TODO In production users are archived instead of removed
# noderpc.env['res.users'].unlink([rec.remote_user_id])
noderpc.env['res.users'].write([rec.remote_user_id], {'active': False})
_logger.info('User #%s deleted remote res.users with ID: [%s]',
self._context.get('uid'), rec.remote_user_id)
noderpc.logout()
# TODO How to manage the relationship with the partner? Also deleted?
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
_logger.error(err)
raise ValidationError(err)
return super().unlink()

View File

@@ -1,6 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common
from . import exporter
from . import importer

View File

@@ -1,83 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import api, models, fields, _
from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component
from odoo.addons.component_event import skip_if
_logger = logging.getLogger(__name__)
class NodeRoomType(models.Model):
_name = 'node.room.type'
_inherit = 'node.binding'
_description = 'Node Hotel Room Type'
name = fields.Char(required=True, translate=True)
room_ids = fields.Integer()
# fields.One2many('node.room', 'room_type_id', 'Rooms')
active = fields.Boolean(default=True)
sequence = fields.Integer(default=0)
@job(default_channel='root.channel')
@api.model
def create_room_type(self):
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='node.room.type.exporter')
return exporter.create_room_type(self)
@job(default_channel='root.channel')
@api.model
def modify_room_type(self):
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='node.room.type.exporter')
return exporter.modify_room_type(self)
@job(default_channel='root.channel')
@api.model
def delete_room_type(self):
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='node.room.type.exporter')
return exporter.delete_room_type(self)
@job(default_channel='root.channel')
@api.model
def fetch_room_types(self, backend):
with backend.work_on(self._name) as work:
importer = work.component(usage='node.room.type.importer')
return importer.fetch_room_types()
class NodeRoomTypeAdapter(Component):
_name = 'node.room.type.adapter'
_inherit = 'hotel.node.adapter'
_apply_on = 'node.room.type'
def create_room_type(self, name, room_ids):
return super().create_room_type(name, room_ids)
def modify_room_type(self, room_type_id, name, room_ids):
return super().modify_room_type(room_type_id, name, room_ids)
def delete_room_type(self, room_type_id):
return super().delete_room_type(room_type_id)
def fetch_room_types(self):
return super().fetch_room_types()
class NodeBindingRoomTypeListener(Component):
_name = 'node.binding.room.type.listener'
_inherit = 'base.connector.listener'
_apply_on = ['node.room.type']
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_create(self, record, fields=None):
record.create_room_type()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_unlink(self, record, fields=None):
record.delete_room_type()
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
record.modify_room_type()

View File

@@ -1,33 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo.addons.component.core import Component
from odoo import api, _
_logger = logging.getLogger(__name__)
class NodeRoomTypeExporter(Component):
_name = 'node.room.type.exporter'
_inherit = 'node.exporter'
_apply_on = ['node.room.type']
_usage = 'node.room.type.exporter'
@api.model
def modify_room_type(self, binding):
return self.backend_adapter.modify_room_type(
binding.external_id,
binding.name,
binding.room_ids
)
@api.model
def delete_room_type(self, binding):
return self.backend_adapter.delete_room_type(binding.external_id)
@api.model
def create_room_type(self, binding):
external_id = self.backend_adapter.create_room_type(
binding.name,
binding.room_ids
)
self.binder.bind(external_id, binding)

View File

@@ -1,47 +0,0 @@
# 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.connector.components.mapper import mapping
from odoo import fields, api, _
_logger = logging.getLogger(__name__)
class HotelRoomTypeImporter(Component):
_name = 'node.room.type.importer'
_inherit = 'node.importer'
_apply_on = ['node.room.type']
_usage = 'node.room.type.importer'
@api.model
def fetch_room_types(self):
results = self.backend_adapter.fetch_room_types()
room_type_mapper = self.component(usage='import.mapper',
model_name='node.room.type')
node_room_type_obj = self.env['node.room.type']
for rec in results:
map_record = room_type_mapper.map_record(rec)
room_type = node_room_type_obj.search([('external_id', '=', rec['id'])],
limit=1)
# NEED REVIEW Import a record triggers a room_type.write / room_type.create back to the node
if room_type:
room_type.write(map_record.values())
else:
room_type.create(map_record.values(for_create=True))
class NodeRoomTypeImportMapper(Component):
_name = 'node.room.type.import.mapper'
_inherit = 'node.import.mapper'
_apply_on = 'node.room.type'
direct = [
('id', 'external_id'),
('name', 'name'),
]
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -1,16 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
class ResPartner(models.Model):
_inherit = 'res.partner'
# As res.partner has already a `user_ids` field, you can not use that name in this inheritance
node_user_ids = fields.One2many('hotel.node.user', 'partner_id',
'Users associated to this partner')
# TODO Override write for updating in remote nodes

View File

@@ -1,4 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -1,52 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from contextlib import contextmanager
from odoo import models, api, fields
from ...components.backend_adapter import NodeLogin, NodeServer
class NodeBackend(models.Model):
_name = 'node.backend'
_description = 'Hotel Node Backend'
_inherit = 'connector.backend'
name = fields.Char('Name')
address = fields.Char('Host', required=True,
help='Full URL to the host.')
db = fields.Char('Database Name',
help='Odoo database name.')
user = fields.Char('Username',
help='Odoo administration user.')
passwd = fields.Char('Password',
help='Odoo password.')
port = fields.Integer(string='TCP Port', default=443,
help='Specify the TCP port for the XML-RPC protocol.')
protocol = fields.Selection([('jsonrpc', 'jsonrpc'), ('jsonrpc+ssl', 'jsonrpc+ssl')],
'Protocol', required=True, default='jsonrpc+ssl')
odoo_version = fields.Char()
@contextmanager
@api.multi
def work_on(self, model_name, **kwargs):
self.ensure_one()
node_login = NodeLogin(
self.address,
self.protocol,
self.port,
self.db,
self.user,
self.passwd)
with NodeServer(node_login) as node_api:
_super = super(NodeBackend, self)
with _super.work_on(model_name, node_api=node_api, **kwargs) as work:
yield work
@api.multi
def test_connection(self):
pass
@api.multi
def import_room_types(self):
node_room_type_obj = self.env['node.room.type']
for backend in self:
node_room_type_obj.fetch_room_types(backend)

View File

@@ -1,4 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common

View File

@@ -1,22 +0,0 @@
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class NodeBinding(models.AbstractModel):
_name = 'node.binding'
_inherit = 'external.binding'
_description = 'Hotel Node Connector Binding (abstract)'
external_id = fields.Integer()
backend_id = fields.Many2one(
comodel_name='node.backend',
string='Hotel Node Connector Backend',
required=True,
ondelete='restrict')
_sql_constraints = [
('backend_external_id_uniq', 'unique(backend_id, external_id)',
'A binding already exists with the same Backend ID.'),
]

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="0">
</odoo>

View File

@@ -1,8 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_node_backend,access_node_backend,model_node_backend,base.group_user,1,0,0,0
access_hotel_node_user,access_hotel_node_user,model_hotel_node_user,base.group_user,1,0,0,0
access_hotel_node_group,access_hotel_node_group,model_hotel_node_group,base.group_user,1,0,0,0
access_hotel_node_group_remote,access_hotel_node_group_remote,model_hotel_node_group_remote,base.group_user,1,0,0,0
access_hotel_node_room,access_hotel_node_room,model_hotel_node_room,base.group_user,1,0,0,0
access_hotel_node_room_type,access_hotel_node_room_type,model_hotel_node_room_type,base.group_user,1,0,0,0
access_node_room_type,access_node_room_type,model_node_room_type,base.group_user,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_node_backend access_node_backend model_node_backend base.group_user 1 0 0 0
3 access_hotel_node_user access_hotel_node_user model_hotel_node_user base.group_user 1 0 0 0
4 access_hotel_node_group access_hotel_node_group model_hotel_node_group base.group_user 1 0 0 0
5 access_hotel_node_group_remote access_hotel_node_group_remote model_hotel_node_group_remote base.group_user 1 0 0 0
6 access_hotel_node_room access_hotel_node_room model_hotel_node_room base.group_user 1 0 0 0
7 access_hotel_node_room_type access_hotel_node_room_type model_hotel_node_room_type base.group_user 1 0 0 0
8 access_node_room_type access_node_room_type model_node_room_type base.group_user 1 0 0 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_hotel_node_master

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo.tests import common
import logging
_logger = logging.getLogger(__name__)
class TestHotel(common.SavepointCase):
@classmethod
def _init_mock_hotel(cls):
return True
@classmethod
def setUpClass(cls):
super(TestHotel, cls).setUpClass()
cls._init_mock_hotel()

View File

@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from .common import TestHotel
from odoo.exceptions import ValidationError
class TestHotelNodeMaster(TestHotel):
def test_wizard_hotel_node_reservation(self):
pass

View File

@@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hotel_node_view_form" model="ir.ui.view">
<field name="name">hotel.node.view.form</field>
<field name="model">project.project</field>
<field name="arch" type="xml">
<form string="Hotel Node">
<sheet string="Hotel Node">
<div class="oe_button_box" name="button_box" groups="base.group_user">
<button name="%(hotel_node_reservation_wizard_action)d" type="action"
string="Reserve" help="Make a reservation in this hotel"
class="oe_stat_button" icon="fa-suitcase"
context="{'node_id': id}">
</button>
<button name="%(hotel_node_reservation_wizard_action_search)d" type="action"
string="Search" help="Search a reservation in this hotel"
class="oe_stat_button" icon="fa-search"
context="{'node_id': id}">
</button>
<button class="oe_stat_button" type="action"
name="" icon="fa-tasks">
<field string="Tasks" name="task_count" widget="statinfo" options="{'label_field': 'label_tasks'}"/>
</button>
<button name="action_sync_from_node" type="object" string="Synchronize"
attrs="{'invisible':[('id','=',False)]}"
confirm="Synchronizing a hotel node automatically updates Groups, Users, Room Types and Rooms in the Central Node. Do you want to proceed?"
class="oe_stat_button" icon="fa-cloud-download">
</button>
<button name="toggle_active" type="object"
confirm="(Un)archiving a hotel node automatically (un)archives its issues. Do you want to proceed?"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="name" placeholder="Hotel Name"/>
</h1>
<div name="options_active" class="oe_edit_only">
<div>
<label for="label_tasks" class="oe_inline" string="Name of the tasks :"/>
<field name="label_tasks" class="oe_inline oe_input_align"/>
</div>
</div>
</div>
<notebook>
<page name="remote_access" string="Remote Access">
<group name="remote_access" string="Hotel Node">
<group>
<field name="odoo_host" />
<field name="odoo_protocol" />
<field name="odoo_port" />
</group>
<group>
<field name="odoo_db" />
<field name="odoo_user" />
<field name="odoo_password" password="True" />
</group>
</group>
</page>
<page name="groups" string="Groups" attrs="{'invisible':[('id','=',False)]}">
<group>
<field name="odoo_version" readonly="1"/>
<!-- TODO Make group_ids readonly in hotel node form -->
<field name="group_ids" domain="[('odoo_version','=',odoo_version)]"/>
</group>
</page>
<page name="users" string="Users" attrs="{'invisible':[('id','=',False)]}">
<group>
<field name="user_ids" context="{'default_node_id': active_id}" />
</group>
</page>
<page name="room_type" string="Room Type" attrs="{'invisible':[('id','=',False)]}">
<group>
<field name="room_type_ids" context="{'default_node_id': active_id}" />
</group>
</page>
<page name="settings" string="Settings">
<group>
<field name="user_id" string="Node Manager"
attrs="{'readonly':[('active','=',False)]}"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"
help="Follow this node to automatically track the events associated to tasks and issues of this node."
groups="base.group_user"/>
</div>
</form>
</field>
</record>
<record id="hotel_node_view_tree" model="ir.ui.view">
<field name="name">hotel.node.view.tree</field>
<field name="model">project.project</field>
<field name="arch" type="xml">
<tree decoration-bf="message_needaction==True" decoration-muted="active == False" string="Hotels">
<field name="sequence" widget="handle"/>
<field name="message_needaction" invisible="1"/>
<field name="active" invisible="1"/>
<field name="name" string="Hotel Name"/>
<field name="odoo_host"/>
<field name="odoo_db"/>
<field name="odoo_version"/>
</tree>
</field>
</record>
<record id="hotel_node_action_kanban" model="ir.actions.act_window">
<field name="name">Hotels</field>
<field name="res_model">project.project</field>
<field name="view_type">form</field>
<field name="domain">[]</field>
<field name="view_mode">kanban,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_menu"
name="Hotel Central Dashboard"
action="hotel_node_action_kanban"
/>
<menuitem id="hotel_node_menu_dashboard"
name="Management"
parent="hotel_node_menu"
/>
<!--<menuitem id="hotel_node_menu_tree"-->
<!--name="Hotels"-->
<!--parent="hotel_node_menu_dashboard"-->
<!--sequence="0"-->
<!--/>-->
</odoo>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hotel_node_group_view_tree" model="ir.ui.view">
<field name="name">hotel.node.group.tree</field>
<field name="model">hotel.node.group</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="odoo_version"/>
</tree>
</field>
</record>
<record id="hotel_node_group_action" model="ir.actions.act_window">
<field name="name">List of Access Groups in Hotels</field>
<field name="res_model">hotel.node.group</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_group_menu"
name="Access Groups"
action="hotel_node_group_action"
parent="hotel_node_menu_dashboard"
sequence="3"
/>
</odoo>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hotel_node_group_remote_view_tree" model="ir.ui.view">
<field name="name">hotel.node.group.remote.tree</field>
<field name="model">hotel.node.group.remote</field>
<field name="arch" type="xml">
<tree>
<field name="node_id"/>
<field name="group_id"/>
<field name="remote_group_id"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hotel_node_room_type_view_form" model="ir.ui.view">
<field name="name">hotel.node.room.type.view.form</field>
<field name="model">hotel.node.room.type</field>
<field name="arch" type="xml">
<form string="Hotel Room Type">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="node_id" placeholder="Hotel" required="1"
attrs="{'readonly': [('node_id', '!=', False)]}"/>
</h1>
</div>
<group name="room_type" attrs="{'invisible':[('node_id','=',False)]}">
<group>
<field name="name" placeholder="Room Type Name" required="1"/>
</group>
<group>
<div name="options_active">
<label for="remote_room_type_id" class="oe_inline" string="Remote Room Type :"/>
<field name="remote_room_type_id" readonly="1" attrs="{'invisible': [('id', '=', False)]}" />
</div>
</group>
</group>
<notebook>
<page name="Rooms" string="Rooms" attrs="{'invisible':[('id','=',False)]}">
<group>
<field name="room_ids" domain="[('room_type_id','=',active_id)]"
context="{'default_node_id': node_id, 'default_room_type_id': active_id}"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="hotel_node_room_type_view_tree" model="ir.ui.view">
<field name="name">hotel.node.room.type.tree</field>
<field name="model">hotel.node.room.type</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="node_id"/>
</tree>
</field>
</record>
<record id="hotel_node_room_type_action" model="ir.actions.act_window">
<field name="name">List of Room Types in Hotels</field>
<field name="res_model">hotel.node.room.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_room_type_menu"
name="Room Types"
action="hotel_node_room_type_action"
parent="hotel_node_menu_dashboard"
sequence="1"
/>
</odoo>

View File

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hotel_node_user_view_form" model="ir.ui.view">
<field name="name">hotel.node.user.view.form</field>
<field name="model">hotel.node.user</field>
<field name="arch" type="xml">
<form string="Users">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-archive">
<field name="active" widget="boolean_button"
options='{"terminology": "archive"}'/>
</button>
</div>
<div class="oe_title">
<h1>
<field name="node_id" placeholder="Hotel" required="1"
attrs="{'readonly': [('node_id', '!=', False)]}"/>
</h1>
</div>
<group attrs="{'invisible': [('node_id', '=', False)]}">
<group name="nodes">
<field name="partner_id" placeholder="Name" required="1"/>
<field name="remote_user_id" readonly="1" attrs="{'invisible': [('id', '=', False)]}" />
</group>
</group>
<group attrs="{'invisible': [('partner_id', '=', False)]}">
<group name="login">
<field name="login"/>
<field name="password" password="True"/>
</group>
</group>
<group name="groups" attrs="{'invisible': [('node_id', '=', False)]}" collapse="2">
<field name="group_ids"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="hotel_node_user_view_tree" model="ir.ui.view">
<field name="name">hotel.node.user.tree</field>
<field name="model">hotel.node.user</field>
<field name="arch" type="xml">
<tree>
<field name="sequence" widget="handle"/>
<field name="partner_id"/>
<field name="login"/>
<field name="node_id"/>
</tree>
</field>
</record>
<record id="hotel_node_user_action" model="ir.actions.act_window">
<field name="name">List of Users in Hotels</field>
<field name="res_model">hotel.node.user</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_user_menu"
name="Users"
action="hotel_node_user_action"
parent="hotel_node_menu_dashboard"
sequence="2"
/>
</odoo>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_form" model="ir.ui.view">
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position='after'>
<page string='Hotels &amp; Users' autofocus="autofocus">
<field name="node_user_ids" />
</page>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,81 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="node_backend_views_form" model="ir.ui.view">
<field name="name">node.backend.form</field>
<field name="model">node.backend</field>
<field name="arch" type="xml">
<form string="Hotel Node Backend">
<header>
<button name="test_connection"
type="object"
class="oe_highlight"
string="Test Connection"/>
</header>
<sheet>
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name" class="oe_inline" />
</h1>
<group name="channel" string="Node Configuration">
<notebook>
<page string="API" name="api">
<group colspan="4" col="4">
<field name="address" colspan="2"/>
<field name="db" colspan="2"/>
<field name="user" colspan="2"/>
<field name="passwd" password="1" colspan="2"/>
<field name="port" colspan="2"/>
<field name="protocol" colspan="2"/>
</group>
</page>
</notebook>
</group>
<notebook>
<page name="import" string="Imports">
<group>
<label string="Import Room Types" class="oe_inline"/>
<div>
<button name="import_room_types"
type="object"
class="oe_highlight"
string="Import in background"
confirm="Synchronizing a node automatically updates Room Types in the Central Node. Do you want to proceed?"/>
</div>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="node_backend_views_tree" model="ir.ui.view">
<field name="name">node.backend.tree</field>
<field name="model">node.backend</field>
<field name="arch" type="xml">
<tree string="Node Backend">
<field name="name"/>
<field name="address"/>
<field name="db"/>
</tree>
</field>
</record>
<record id="action_node_backend" model="ir.actions.act_window">
<field name="name">Hotel Node Backends</field>
<field name="res_model">node.backend</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="node_backend_views_tree"/>
</record>
<record id="action_node_room_type" model="ir.actions.act_window">
<field name="name">Hotel Node Room Types</field>
<field name="res_model">node.room.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<!--<field name="view_id" ref="node_backend_views_tree"/>-->
</record>
</odoo>

View File

@@ -1,3 +0,0 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import wizard_hotel_node_reservation

View File

@@ -1,489 +0,0 @@
# Copyright 2018 Pablo Q. Barriuso
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import urllib.error
import odoorpc.odoo
from datetime import timedelta
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError
from odoo.tools import (
DEFAULT_SERVER_DATE_FORMAT)
_logger = logging.getLogger(__name__)
class HotelNodeReservationWizard(models.TransientModel):
# TODO Rename to node.engine.reservation.wizard
_name = "hotel.node.reservation.wizard"
_description = "Hotel Node Reservation Wizard"
@api.model
def _default_node_id(self):
return self._context.get('node_id') or None
@api.model
def _default_checkin(self):
today = fields.Date.context_today(self.with_context())
return fields.Date.from_string(today).strftime(DEFAULT_SERVER_DATE_FORMAT)
@api.model
def _default_checkout(self):
today = fields.Date.context_today(self.with_context())
return (fields.Date.from_string(today) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT)
node_id = fields.Many2one('project.project', 'Hotel', required=True, default=_default_node_id)
partner_id = fields.Many2one('res.partner', string="Customer", required=True)
room_type_wizard_ids = fields.One2many('node.room.type.wizard', 'node_reservation_wizard_id',
string="Room Types")
price_total = fields.Float(string='Total Price', compute='_compute_price_total', store=True)
# FIXED @constrains parameter 'room_type_wizard_ids.room_qty' is not a field name
# @api.constrains('room_type_wizard_ids')
# def _check_room_type_wizard_total_qty(self):
# for rec in self:
# total_qty = 0
# for rec_room_type in rec.room_type_wizard_ids:
# total_qty += rec_room_type.room_qty
#
# if total_qty == 0:
# msg = _("It is not possible to create the reservation.") + " " + \
# _("Maybe you forgot adding the quantity to at least one type of room?.")
# raise ValidationError(msg)
@api.depends('room_type_wizard_ids.price_total')
def _compute_price_total(self):
for rec in self:
_logger.info('_compute_price_total for wizard %s', rec.id)
rec.price_total = 0.0
for rec_room_type in rec.room_type_wizard_ids:
rec.price_total += rec_room_type.price_total
@api.onchange('node_id')
def _onchange_node_id(self):
if self.node_id:
_logger.info('_onchange_node_id(self): %s', self)
# TODO Save your credentials (session)
_logger.info('_compute_room_types for node %s', self.node_id)
cmds = self.node_id.room_type_ids.mapped(lambda room_type_id: (0, False, {
'node_id': self.node_id.id,
'room_type_id': room_type_id.id,
'checkin': self._default_checkin(),
'checkout': self._default_checkout(),
}))
self.room_type_wizard_ids = cmds
@api.model
def create(self, vals):
# TODO review node.room.type.wizard @api.constrains('room_qty')
from pprint import pprint
try:
node = self.env["project.project"].browse(vals['node_id'])
noderpc = odoorpc.ODOO(node.odoo_host, node.odoo_protocol, node.odoo_port)
noderpc.login(node.odoo_db, node.odoo_user, node.odoo_password)
# prepare required fields for hotel.folio
remote_vals = {}
partner = self.env["res.partner"].browse(vals['partner_id'])
remote_partner_id = noderpc.env['res.partner'].search([('email', '=', partner.email)]).pop()
# TODO create partner if does not exist in remote node
remote_vals.update({
'partner_id': remote_partner_id,
})
# prepare hotel.folio.room_lines
room_lines = []
for cmds in vals['room_type_wizard_ids']:
# cmds is a list of triples: [0, 'virtual_1008', {'checkin': '2018-11-05', ...
room_type_wizard_values = cmds[2]
remote_room_type_id = self.env['hotel.node.room.type'].search([
('id', '=', room_type_wizard_values['room_type_id'])
]).remote_room_type_id
# prepare room_lines a number of times `room_qty` times
for room in range(room_type_wizard_values['room_qty']):
# prepare hotel.reservation.reservation_line_ids
reservation_line_cmds = []
for room_type_line_cmds in room_type_wizard_values['room_type_line_ids']:
reservation_line = room_type_line_cmds[2]
reservation_line_cmds.append((0, False, {
'date': reservation_line['date'],
'price': reservation_line['price'],
}))
# add discount ¿?
room_lines.append((0, False, {
'room_type_id': remote_room_type_id,
'checkin': room_type_wizard_values['checkin'],
'checkout': room_type_wizard_values['checkout'],
'reservation_line_ids': reservation_line_cmds,
}))
remote_vals.update({'room_lines': room_lines})
pprint(remote_vals)
# if total_qty == 0:
# msg = _("It is not possible to create the reservation.") + " " + \
# _("Maybe you forgot adding the quantity to at least one type of room?.")
# raise ValidationError(msg)
folio_id = noderpc.env['hotel.folio'].create(remote_vals)
_logger.info('User #%s created a remote hotel.folio with ID: [%s]',
self._context.get('uid'), folio_id)
noderpc.logout()
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
_logger.error(err)
raise ValidationError(err)
else:
return super().create(vals)
@api.multi
def create_node_reservation(self):
_logger.info('# TODO: return a wizard and preview the reservation')
# self.ensure_one()
# # NOTE This function is executed __after__ create(self, vals) where _compute_restrictions are executed again
# try:
# noderpc = odoorpc.ODOO(self.node_id.odoo_host, self.node_id.odoo_protocol, self.node_id.odoo_port)
# noderpc.login(self.node_id.odoo_db, self.node_id.odoo_user, self.node_id.odoo_password)
#
# # prepare required fields for hotel folio
# remote_partner_id = noderpc.env['res.partner'].search([('email', '=', self.partner_id.email)]).pop()
# vals = {
# 'partner_id': remote_partner_id,
# }
# # prepare hotel folio room_lines
# room_lines = []
# for rec in self.room_type_wizard_ids:
# for x in range(rec.room_qty):
# # prepare hotel reservation lines with details by day
# reservation_line_cmds = rec.room_type_line_ids.mapped(lambda reservation_line: (0, False, {
# 'date': reservation_line.date,
# 'price': reservation_line.price,
# }))
# # add discount
# room_lines.append((0, False, {
# 'room_type_id': rec.room_type_id.remote_room_type_id,
# 'checkin': rec.checkin,
# 'checkout': rec.checkout,
# 'reservation_line_ids': reservation_line_cmds,
# }))
# vals.update({'room_lines': room_lines})
#
# from pprint import pprint
# pprint(vals)
#
# folio_id = noderpc.env['hotel.folio'].create(vals)
# _logger.info('User #%s created a hotel.folio with ID: [%s]',
# self._context.get('uid'), folio_id)
#
# noderpc.logout()
# except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
# raise ValidationError(err)
@api.multi
def _open_wizard_action_search(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': self._name,
'res_id': self.id,
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
}
class NodeRoomTypeWizard(models.TransientModel):
_name = "node.room.type.wizard"
_description = "Node Room Type Wizard"
@api.model
def _default_node_id(self):
return self._context.get('node_id') or None
@api.model
def _default_checkin(self):
today = fields.Date.context_today(self.with_context())
return fields.Date.from_string(today).strftime(DEFAULT_SERVER_DATE_FORMAT)
@api.model
def _default_checkout(self):
today = fields.Date.context_today(self.with_context())
return (fields.Date.from_string(today) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT)
node_reservation_wizard_id = fields.Many2one('hotel.node.reservation.wizard',
ondelete = 'cascade', required = True)
node_id = fields.Many2one('project.project', 'Hotel', default=_default_node_id, required=True)
room_type_id = fields.Many2one('hotel.node.room.type', 'Rooms Type')
room_type_availability = fields.Integer('Availability', compute="_compute_restrictions", readonly=True, store=True)
room_qty = fields.Integer('Quantity', default=0)
room_type_line_ids = fields.One2many('node.room.type.line.wizard', 'node_room_type_line_wizard_id',
string="Room type detail per day")
checkin = fields.Date('Check In', default=_default_checkin, required=True)
checkout = fields.Date('Check Out', default=_default_checkout, required=True)
nights = fields.Integer('Nights', compute='_compute_nights', readonly=True)
min_stay = fields.Integer('Min. Days', compute="_compute_restrictions", readonly=True, store=True)
price_unit = fields.Float(string='Room Price', compute="_compute_restrictions", readonly=True, store=True)
discount = fields.Float(string='Discount (%)', default=0.0)
price_total = fields.Float(string='Total Price', compute='_compute_price_total', readonly=True, store=True)
@api.constrains('room_qty')
def _check_room_qty(self):
# At least one model cache has been invalidated, signaling through the database.
for rec in self:
if (rec.room_type_availability < rec.room_qty) or (rec.room_qty > 0 and rec.nights < rec.min_stay):
msg = _("At least one room type has not availability or does not meet restrictions.") + " " + \
_("Please, review room type %s between %s and %s.") % (rec.room_type_id.name, rec.checkin, rec.checkout)
_logger.warning(msg)
raise ValidationError(msg)
@api.depends('room_qty', 'price_unit', 'discount')
def _compute_price_total(self):
for rec in self:
_logger.info('_compute_price_total for room type %s', rec.room_type_id)
rec.price_total = (rec.room_qty * rec.price_unit) * (1.0 - rec.discount * 0.01)
@api.depends('checkin', 'checkout')
def _compute_nights(self):
for rec in self:
rec.nights = (fields.Date.from_string(rec.checkout) - fields.Date.from_string(rec.checkin)).days
@api.depends('checkin', 'checkout')
def _compute_restrictions(self):
for rec in self:
if rec.checkin and rec.checkout:
try:
# TODO Load your credentials (session) ... should be faster?
noderpc = odoorpc.ODOO(rec.node_id.odoo_host, rec.node_id.odoo_protocol, rec.node_id.odoo_port)
noderpc.login(rec.node_id.odoo_db, rec.node_id.odoo_user, rec.node_id.odoo_password)
_logger.info('_compute_restrictions [availability] for room type %s', rec.room_type_id)
rec.room_type_availability = noderpc.env['hotel.room.type'].get_room_type_availability(
rec.checkin,
rec.checkout,
rec.room_type_id.remote_room_type_id)
_logger.info('_compute_restrictions [price_unit] for room type %s', rec.room_type_id)
rec.room_type_line_ids = noderpc.env['hotel.room.type'].get_room_type_price_unit(
rec.checkin,
rec.checkout,
rec.room_type_id.remote_room_type_id)
# cmds = []
# for x in range(rec.nights):
# cmds.append((0, False, {
# 'date': (fields.Date.from_string(rec.checkin) + timedelta(days=x)).strftime(
# DEFAULT_SERVER_DATE_FORMAT),
# 'price': 11.50,
# }))
# from pprint import pprint
# pprint(cmds)
# rec.room_type_line_ids = cmds
rec.price_unit = sum(rec.room_type_line_ids.mapped('price'))
_logger.info('_compute_restrictions [min days] for room type %s', rec.room_type_id)
rec.min_stay = noderpc.env['hotel.room.type'].get_room_type_restrictions(
rec.checkin,
rec.checkout,
rec.room_type_id.remote_room_type_id)
noderpc.logout()
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
@api.onchange('room_qty')
def _onchange_room_qty(self):
if self.room_type_availability < self.room_qty:
msg = _("Please, review room type %s between %s and %s.") % (self.room_type_id.name, self.checkin, self.checkout)
return {
'warning': {
'title': 'Warning: Invalid room quantity',
'message': msg,
}
}
@api.onchange('checkin', 'checkout')
def _onchange_dates(self):
_logger.info('_onchange_dates for room type: %s', self.room_type_id)
if not self.checkin:
self.checkin = self._default_checkin()
if not self.checkout:
self.checkout = self._default_checkout()
if fields.Date.from_string(self.checkin) >= fields.Date.from_string(self.checkout):
self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(
DEFAULT_SERVER_DATE_FORMAT)
class NodeRoomTypeLineWizard(models.TransientModel):
_name = "node.room.type.line.wizard"
_description = "Node Room Type Detail per Day Wizard"
node_room_type_line_wizard_id = fields.Many2one('node.room.type.wizard',
ondelete='cascade', required=True)
date = fields.Date('Date')
price = fields.Float('Price')
class NodeSearchWizard(models.TransientModel):
_name = "node.search.wizard"
_description = "Node Search Wizard"
@api.model
def _default_node_id(self):
return self._context.get('node_id') or None
node_id = fields.Many2one('project.project', 'Hotel', default=_default_node_id)
node_folio_wizard_id = fields.Many2one('node.folio.wizard')
folio = fields.Char('Folio Number')
partner_id = fields.Many2one('res.partner', string="Customer")
email = fields.Char('E-mail', related='partner_id.email')
checkin = fields.Date('Check In')
checkout = fields.Date('Check Out')
@api.multi
def search_node_reservation(self):
self.ensure_one()
try:
noderpc = odoorpc.ODOO(self.node_id.odoo_host, self.node_id.odoo_protocol, self.node_id.odoo_port)
noderpc.login(self.node_id.odoo_db, self.node_id.odoo_user, self.node_id.odoo_password)
domain = []
if self.folio:
domain.append(('name', '=', 'F/' + self.folio))
if self.partner_id:
domain.append(('email', '=', self.email))
if self.checkin:
domain.append(('checkin', '=', self.checkin))
folio_ids = noderpc.env['hotel.folio'].search(domain)
if not folio_ids:
raise UserError(_("No reservations found for [%s].") % domain)
noderpc.logout()
if len(folio_ids) > 1:
# TODO Need to manage more than one folio
return self._open_wizard_action_select(folio_ids)
return self._open_wizard_action_edit(folio_ids.pop())
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
@api.multi
def _open_wizard_action_select(self, folio_ids):
self.ensure_one()
return {
'name': _('Hotel Reservation Wizard Select'),
'type': 'ir.actions.act_window',
'res_model': 'node.folio.wizard',
'view_id': self.env.ref('hotel_node_master.hotel_node_reservation_wizard_view_tree', False).id,
'view_type': 'tree',
'view_mode': 'tree',
}
@api.multi
def _open_wizard_action_edit(self, folio_id):
self.ensure_one()
return {
'name': _('Hotel Reservation Wizard Edit'),
'type': 'ir.actions.act_window',
'res_model': 'node.folio.wizard',
'view_id': self.env.ref('hotel_node_master.hotel_node_reservation_wizard_view_edit_form', False).id,
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'context': {'folio_id': folio_id},
}
class NodeFolioWizard(models.TransientModel):
_name = 'node.folio.wizard'
@api.model
def _default_node_id(self):
return self._context.get('node_id') or None
@api.model
def _default_folio_id(self):
return self._context.get('folio_id') or None
node_id = fields.Many2one('project.project', 'Hotel', required=True, default=_default_node_id)
folio_id = fields.Integer(required=True, default=_default_folio_id)
folio_name = fields.Char('Folio Number', readonly=True)
partner_id = fields.Many2one('res.partner', string="Customer", required=True)
internal_comment = fields.Text(string='Internal Folio Notes')
# For being used directly in the Folio views
email = fields.Char('E-mail', related='partner_id.email')
room_lines_wizard_ids = fields.One2many('node.reservation.wizard', 'node_folio_wizard_id')
price_total = fields.Float(string='Total Price')
@api.onchange('node_id')
def _onchange_node_id(self):
self.ensure_one()
_logger.info('_onchange_node_id(self): %s', self)
noderpc = odoorpc.ODOO(self.node_id.odoo_host, self.node_id.odoo_protocol, self.node_id.odoo_port)
noderpc.login(self.node_id.odoo_db, self.node_id.odoo_user, self.node_id.odoo_password)
folio = noderpc.env['hotel.folio'].browse(self.folio_id)
self.folio_name = folio.name
self.partner_id = self.env['res.partner'].search([('email', '=', folio.partner_id.email)])
self.internal_comment = folio.internal_comment
self.price_total = folio.amount_total
cmds = []
for reservation in folio.room_lines:
cmds.append((0, False, {
'node_folio_wizard_id': self.id,
'room_type_id': self.env['hotel.node.room.type'].search([
('node_id', '=', self.node_id.id),
('remote_room_type_id', '=', reservation.room_type_id.id),
]).id,
'adults': reservation.adults,
'children': reservation.children,
'checkin': reservation.checkin,
'checkout': reservation.checkout,
'nights': reservation.nights,
'state': reservation.state,
'price_total': reservation.price_total,
}))
self.room_lines_wizard_ids = cmds
@api.multi
def update_node_reservation(self):
self.ensure_one()
try:
raise UserError(_("Function under development."))
except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err:
raise ValidationError(err)
class NodeReservationWizard(models.TransientModel):
_name = 'node.reservation.wizard'
node_folio_wizard_id = fields.Many2one('node.folio.wizard')
room_type_id = fields.Many2one('hotel.node.room.type', 'Rooms Type')
room_type_name = fields.Char('Name', related='room_type_id.name')
checkin = fields.Date('Check In', required=True)
checkout = fields.Date('Check Out', required=True)
nights = fields.Integer('Nights', compute="_compute_nights", readonly=True)
adults = fields.Integer('Adults', size=64, default=1)
children = fields.Integer('Children', size=64)
state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'),
('booking', 'On Board'), ('done', 'Out'),
('cancelled', 'Cancelled')], 'State')
price_total = fields.Float(string='Total Price', readonly=True)
@api.depends('checkin', 'checkout')
def _compute_nights(self):
for rec in self:
rec.nights = (fields.Date.from_string(rec.checkout) - fields.Date.from_string(rec.checkin)).days

View File

@@ -1,148 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="hotel_node_reservation_wizard_view_form" model="ir.ui.view">
<field name="name">hotel.node.reservation.wizard</field>
<field name="model">hotel.node.reservation.wizard</field>
<field name="arch" type="xml">
<form string="Reservation Wizard" >
<sheet>
<div class="oe_title">
<h1>
<field name="node_id" placeholder="Hotel" required="1" force_save="1"
attrs="{'readonly': [('node_id', '!=', False)]}"/>
</h1>
</div>
<group attrs="{'invisible':[('node_id','=',False)]}">
<!--<group name="dates">-->
<!--<field name="checkin" required="1" widget="date" />-->
<!--<field name="checkout" required="1" widget="date" />-->
<!--</group>-->
<group>
<field name="partner_id"/>
</group>
<group name="room_type_wizard_ids" colspan="2">
<field name="room_type_wizard_ids" nolabel="1"
context="{'default_node_reservation_wizard_id': active_id}">
<tree editable="bottom" create="false" delete="false"
decoration-muted="room_type_availability == 0">
<field name="node_reservation_wizard_id" invisible="1"/>
<field name="node_id" invisible="1"/>
<field name="room_type_id" string="Room Type" readonly="1" force_save="1"/>
<field name="room_type_availability" readonly="1"/>
<field name="room_qty"/>
<field name="room_type_line_ids" widget="one2many_list" invisible="1">
<tree editable="bottom">
<field name="date"/>
<field name="price"/>
</tree>
</field>
<field name="checkin" widget="date"/>
<field name="checkout" widget="date"/>
<field name="nights"/>
<field name="min_stay" readonly="1"/>
<field name="price_unit" widget="monetary" readonly="1" force_save="1"/>
<field name="discount"/>
<field name="price_total" widget="monetary" readonly="1" force_save="1"/>
</tree>
</field>
</group>
<group>
<field name="price_total" widget="monetary" readonly="1" force_save="1"/>
</group>
</group>
<footer attrs="{'invisible':[('node_id','=',False)]}">
<button name="create_node_reservation" string="Create Reservations" type="object"
class="oe_highlight" />
<button name="cancel" string="Cancel" special="cancel"
class="oe_link" />
</footer>
</sheet>
</form>
</field>
</record>
<record id="hotel_node_reservation_wizard_view_search_form" model="ir.ui.view">
<field name="name">hotel.node.reservation.wizard.search</field>
<field name="model">node.search.wizard</field>
<field name="arch" type="xml">
<form string="Reservation Wizard Search" >
<sheet>
<div class="oe_title">
<h1>
<field name="node_id" placeholder="Hotel" required="1" force_save="1"
attrs="{'readonly': [('node_id', '!=', False)]}"/>
</h1>
</div>
<group attrs="{'invisible':[('node_id','=',False)]}">
<group>
<field name="folio"/>
<field name="checkin"/>
</group>
<group>
<field name="partner_id"/>
</group>
</group>
<footer attrs="{'invisible':[('node_id','=',False)]}">
<button name="search_node_reservation" string="Search Reservations" type="object"
class="oe_highlight" />
<button name="cancel" string="Cancel" special="cancel"
class="oe_link" />
</footer>
</sheet>
</form>
</field>
</record>
<record id="hotel_node_reservation_wizard_view_edit_form" model="ir.ui.view">
<field name="name">hotel.node.reservation.wizard.edit</field>
<field name="model">node.folio.wizard</field>
<field name="inherit_id" ref="hotel_node_reservation_wizard_view_form" />
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="folio_id" invisible="1"/>
<field name="folio_name"/>
<field name="email"/>
<field name="internal_comment"/>
</field>
<xpath expr="//group[@name='room_type_wizard_ids']" position="replace">
<group name="room_lines_wizard_ids" colspan="2">
<field name="room_lines_wizard_ids" nolabel="1">
<tree editable="bottom" create="false" delete="false">
<field name="state"/>
<field name="room_type_id" string="Room Type" readonly="1" force_save="1"/>
<field name="adults"/>
<field name="children"/>
<field name="checkin" widget="date"/>
<field name="checkout" widget="date"/>
<field name="nights"/>
<field name="price_total" widget="monetary"/>
</tree>
</field>
</group>
</xpath>
<xpath expr="//button[@name='create_node_reservation']" position="replace">
<button name="update_node_reservation" string="Update Reservations" type="object"
class="oe_highlight"/>
</xpath>
</field>
</record>
<record id="hotel_node_reservation_wizard_action" model="ir.actions.act_window">
<field name="name">Hotel Reservation Wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hotel.node.reservation.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record id="hotel_node_reservation_wizard_action_search" model="ir.actions.act_window">
<field name="name">Hotel Reservation Wizard Search</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">node.search.wizard</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>