mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[DEL] master/helper deprecated for this branch
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import inherited_hotel_room_type
|
||||
@@ -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}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="0">
|
||||
</odoo>
|
||||
@@ -1 +0,0 @@
|
||||
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 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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']
|
||||
)
|
||||
@@ -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',
|
||||
]
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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!')
|
||||
]
|
||||
|
||||
@@ -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!')
|
||||
]
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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.'),
|
||||
]
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="0">
|
||||
</odoo>
|
||||
@@ -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
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 & Users' autofocus="autofocus">
|
||||
<field name="node_user_ids" />
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -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>
|
||||
@@ -1,3 +0,0 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import wizard_hotel_node_reservation
|
||||
@@ -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
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user