From 7dd7533a99fb536c591647d8b1ea04c3a8644212 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 15 Jul 2019 08:17:36 +0200 Subject: [PATCH] [DEL] master/helper deprecated for this branch --- hotel_node_helper/README.rst | 22 - hotel_node_helper/__init__.py | 3 - hotel_node_helper/__manifest__.py | 21 - hotel_node_helper/models/__init__.py | 3 - .../models/inherited_hotel_room_type.py | 64 --- .../security/hotel_node_security.xml | 3 - .../security/ir.model.access.csv | 1 - hotel_node_helper/static/description/icon.png | Bin 29724 -> 0 bytes hotel_node_master/README.rst | 43 -- hotel_node_master/__init__.py | 4 - hotel_node_master/__manifest__.py | 33 -- hotel_node_master/components/__init__.py | 9 - .../components/backend_adapter.py | 107 ---- hotel_node_master/components/binder.py | 11 - hotel_node_master/components/core.py | 9 - hotel_node_master/components/exporter.py | 12 - hotel_node_master/components/importer.py | 11 - hotel_node_master/components/mapper.py | 16 - hotel_node_master/data/menus.xml | 21 - hotel_node_master/models/__init__.py | 13 - hotel_node_master/models/hotel_node.py | 333 ------------ hotel_node_master/models/hotel_node_group.py | 39 -- .../models/hotel_node_group_remote.py | 25 - hotel_node_master/models/hotel_node_room.py | 35 -- .../models/hotel_node_room_type.py | 72 --- hotel_node_master/models/hotel_node_user.py | 202 -------- .../models/hotel_room_type/__init__.py | 6 - .../models/hotel_room_type/common.py | 83 --- .../models/hotel_room_type/exporter.py | 33 -- .../models/hotel_room_type/importer.py | 47 -- .../models/inherited_res_partner.py | 16 - .../models/node_backend/__init__.py | 4 - .../models/node_backend/common.py | 52 -- .../models/node_binding/__init__.py | 4 - .../models/node_binding/common.py | 22 - .../security/hotel_node_security.xml | 3 - .../security/ir.model.access.csv | 8 - hotel_node_master/static/description/icon.png | Bin 22165 -> 0 bytes hotel_node_master/tests/__init__.py | 23 - hotel_node_master/tests/common.py | 38 -- .../tests/test_hotel_node_master.py | 30 -- hotel_node_master/views/hotel_node.xml | 137 ----- hotel_node_master/views/hotel_node_group.xml | 29 -- .../views/hotel_node_group_remote.xml | 14 - .../views/hotel_node_room_type.xml | 72 --- hotel_node_master/views/hotel_node_user.xml | 69 --- .../views/inherited_res_partner_views.xml | 16 - .../views/node_backend_views.xml | 81 --- hotel_node_master/wizards/__init__.py | 3 - .../wizards/wizard_hotel_node_reservation.py | 489 ------------------ .../wizards/wizard_hotel_node_reservation.xml | 148 ------ 51 files changed, 2539 deletions(-) delete mode 100644 hotel_node_helper/README.rst delete mode 100644 hotel_node_helper/__init__.py delete mode 100644 hotel_node_helper/__manifest__.py delete mode 100644 hotel_node_helper/models/__init__.py delete mode 100644 hotel_node_helper/models/inherited_hotel_room_type.py delete mode 100644 hotel_node_helper/security/hotel_node_security.xml delete mode 100644 hotel_node_helper/security/ir.model.access.csv delete mode 100644 hotel_node_helper/static/description/icon.png delete mode 100644 hotel_node_master/README.rst delete mode 100644 hotel_node_master/__init__.py delete mode 100644 hotel_node_master/__manifest__.py delete mode 100644 hotel_node_master/components/__init__.py delete mode 100644 hotel_node_master/components/backend_adapter.py delete mode 100644 hotel_node_master/components/binder.py delete mode 100644 hotel_node_master/components/core.py delete mode 100644 hotel_node_master/components/exporter.py delete mode 100644 hotel_node_master/components/importer.py delete mode 100644 hotel_node_master/components/mapper.py delete mode 100644 hotel_node_master/data/menus.xml delete mode 100644 hotel_node_master/models/__init__.py delete mode 100644 hotel_node_master/models/hotel_node.py delete mode 100644 hotel_node_master/models/hotel_node_group.py delete mode 100644 hotel_node_master/models/hotel_node_group_remote.py delete mode 100644 hotel_node_master/models/hotel_node_room.py delete mode 100644 hotel_node_master/models/hotel_node_room_type.py delete mode 100644 hotel_node_master/models/hotel_node_user.py delete mode 100644 hotel_node_master/models/hotel_room_type/__init__.py delete mode 100644 hotel_node_master/models/hotel_room_type/common.py delete mode 100644 hotel_node_master/models/hotel_room_type/exporter.py delete mode 100644 hotel_node_master/models/hotel_room_type/importer.py delete mode 100644 hotel_node_master/models/inherited_res_partner.py delete mode 100644 hotel_node_master/models/node_backend/__init__.py delete mode 100644 hotel_node_master/models/node_backend/common.py delete mode 100644 hotel_node_master/models/node_binding/__init__.py delete mode 100644 hotel_node_master/models/node_binding/common.py delete mode 100644 hotel_node_master/security/hotel_node_security.xml delete mode 100644 hotel_node_master/security/ir.model.access.csv delete mode 100644 hotel_node_master/static/description/icon.png delete mode 100644 hotel_node_master/tests/__init__.py delete mode 100644 hotel_node_master/tests/common.py delete mode 100644 hotel_node_master/tests/test_hotel_node_master.py delete mode 100644 hotel_node_master/views/hotel_node.xml delete mode 100644 hotel_node_master/views/hotel_node_group.xml delete mode 100644 hotel_node_master/views/hotel_node_group_remote.xml delete mode 100644 hotel_node_master/views/hotel_node_room_type.xml delete mode 100644 hotel_node_master/views/hotel_node_user.xml delete mode 100644 hotel_node_master/views/inherited_res_partner_views.xml delete mode 100644 hotel_node_master/views/node_backend_views.xml delete mode 100644 hotel_node_master/wizards/__init__.py delete mode 100644 hotel_node_master/wizards/wizard_hotel_node_reservation.py delete mode 100644 hotel_node_master/wizards/wizard_hotel_node_reservation.xml diff --git a/hotel_node_helper/README.rst b/hotel_node_helper/README.rst deleted file mode 100644 index 4a067e3c4..000000000 --- a/hotel_node_helper/README.rst +++ /dev/null @@ -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 - diff --git a/hotel_node_helper/__init__.py b/hotel_node_helper/__init__.py deleted file mode 100644 index 69f7babdf..000000000 --- a/hotel_node_helper/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import models diff --git a/hotel_node_helper/__manifest__.py b/hotel_node_helper/__manifest__.py deleted file mode 100644 index 917ab41b4..000000000 --- a/hotel_node_helper/__manifest__.py +++ /dev/null @@ -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 -} diff --git a/hotel_node_helper/models/__init__.py b/hotel_node_helper/models/__init__.py deleted file mode 100644 index c21375169..000000000 --- a/hotel_node_helper/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import inherited_hotel_room_type diff --git a/hotel_node_helper/models/inherited_hotel_room_type.py b/hotel_node_helper/models/inherited_hotel_room_type.py deleted file mode 100644 index 6bf42d41a..000000000 --- a/hotel_node_helper/models/inherited_hotel_room_type.py +++ /dev/null @@ -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} diff --git a/hotel_node_helper/security/hotel_node_security.xml b/hotel_node_helper/security/hotel_node_security.xml deleted file mode 100644 index 74979936c..000000000 --- a/hotel_node_helper/security/hotel_node_security.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/hotel_node_helper/security/ir.model.access.csv b/hotel_node_helper/security/ir.model.access.csv deleted file mode 100644 index 97dd8b917..000000000 --- a/hotel_node_helper/security/ir.model.access.csv +++ /dev/null @@ -1 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink diff --git a/hotel_node_helper/static/description/icon.png b/hotel_node_helper/static/description/icon.png deleted file mode 100644 index 0b47e0cf928b031afe5420c03646e8405e11d096..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29724 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 diff --git a/hotel_node_master/README.rst b/hotel_node_master/README.rst deleted file mode 100644 index 0511c648d..000000000 --- a/hotel_node_master/README.rst +++ /dev/null @@ -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 - diff --git a/hotel_node_master/__init__.py b/hotel_node_master/__init__.py deleted file mode 100644 index 9144a9ab1..000000000 --- a/hotel_node_master/__init__.py +++ /dev/null @@ -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 diff --git a/hotel_node_master/__manifest__.py b/hotel_node_master/__manifest__.py deleted file mode 100644 index 20aec9808..000000000 --- a/hotel_node_master/__manifest__.py +++ /dev/null @@ -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 -} diff --git a/hotel_node_master/components/__init__.py b/hotel_node_master/components/__init__.py deleted file mode 100644 index fbeb3cccb..000000000 --- a/hotel_node_master/components/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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 diff --git a/hotel_node_master/components/backend_adapter.py b/hotel_node_master/components/backend_adapter.py deleted file mode 100644 index 7fbb884f1..000000000 --- a/hotel_node_master/components/backend_adapter.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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'] - ) diff --git a/hotel_node_master/components/binder.py b/hotel_node_master/components/binder.py deleted file mode 100644 index 3a2e68676..000000000 --- a/hotel_node_master/components/binder.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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', - ] diff --git a/hotel_node_master/components/core.py b/hotel_node_master/components/core.py deleted file mode 100644 index b3c34937f..000000000 --- a/hotel_node_master/components/core.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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' diff --git a/hotel_node_master/components/exporter.py b/hotel_node_master/components/exporter.py deleted file mode 100644 index 401f761a0..000000000 --- a/hotel_node_master/components/exporter.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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' diff --git a/hotel_node_master/components/importer.py b/hotel_node_master/components/importer.py deleted file mode 100644 index 73f80a4e3..000000000 --- a/hotel_node_master/components/importer.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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' diff --git a/hotel_node_master/components/mapper.py b/hotel_node_master/components/mapper.py deleted file mode 100644 index 7759923ca..000000000 --- a/hotel_node_master/components/mapper.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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' diff --git a/hotel_node_master/data/menus.xml b/hotel_node_master/data/menus.xml deleted file mode 100644 index c8a55a4f0..000000000 --- a/hotel_node_master/data/menus.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - diff --git a/hotel_node_master/models/__init__.py b/hotel_node_master/models/__init__.py deleted file mode 100644 index f1f67dff9..000000000 --- a/hotel_node_master/models/__init__.py +++ /dev/null @@ -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 - diff --git a/hotel_node_master/models/hotel_node.py b/hotel_node_master/models/hotel_node.py deleted file mode 100644 index 096a77902..000000000 --- a/hotel_node_master/models/hotel_node.py +++ /dev/null @@ -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 diff --git a/hotel_node_master/models/hotel_node_group.py b/hotel_node_master/models/hotel_node_group.py deleted file mode 100644 index a1100b0d2..000000000 --- a/hotel_node_master/models/hotel_node_group.py +++ /dev/null @@ -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!') - ] - diff --git a/hotel_node_master/models/hotel_node_group_remote.py b/hotel_node_master/models/hotel_node_group_remote.py deleted file mode 100644 index 768b2b1f9..000000000 --- a/hotel_node_master/models/hotel_node_group_remote.py +++ /dev/null @@ -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!') - ] diff --git a/hotel_node_master/models/hotel_node_room.py b/hotel_node_master/models/hotel_node_room.py deleted file mode 100644 index 270283f49..000000000 --- a/hotel_node_master/models/hotel_node_room.py +++ /dev/null @@ -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 diff --git a/hotel_node_master/models/hotel_node_room_type.py b/hotel_node_master/models/hotel_node_room_type.py deleted file mode 100644 index 4c4c5e1f6..000000000 --- a/hotel_node_master/models/hotel_node_room_type.py +++ /dev/null @@ -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() diff --git a/hotel_node_master/models/hotel_node_user.py b/hotel_node_master/models/hotel_node_user.py deleted file mode 100644 index 0cedaec0f..000000000 --- a/hotel_node_master/models/hotel_node_user.py +++ /dev/null @@ -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() diff --git a/hotel_node_master/models/hotel_room_type/__init__.py b/hotel_node_master/models/hotel_room_type/__init__.py deleted file mode 100644 index c7a2c2b51..000000000 --- a/hotel_node_master/models/hotel_room_type/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import common -from . import exporter -from . import importer diff --git a/hotel_node_master/models/hotel_room_type/common.py b/hotel_node_master/models/hotel_room_type/common.py deleted file mode 100644 index d1c8f0774..000000000 --- a/hotel_node_master/models/hotel_room_type/common.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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() diff --git a/hotel_node_master/models/hotel_room_type/exporter.py b/hotel_node_master/models/hotel_room_type/exporter.py deleted file mode 100644 index 0a506fa6f..000000000 --- a/hotel_node_master/models/hotel_room_type/exporter.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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) diff --git a/hotel_node_master/models/hotel_room_type/importer.py b/hotel_node_master/models/hotel_room_type/importer.py deleted file mode 100644 index 2013622ae..000000000 --- a/hotel_node_master/models/hotel_room_type/importer.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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} diff --git a/hotel_node_master/models/inherited_res_partner.py b/hotel_node_master/models/inherited_res_partner.py deleted file mode 100644 index 89167ee77..000000000 --- a/hotel_node_master/models/inherited_res_partner.py +++ /dev/null @@ -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 diff --git a/hotel_node_master/models/node_backend/__init__.py b/hotel_node_master/models/node_backend/__init__.py deleted file mode 100644 index 257ab04fc..000000000 --- a/hotel_node_master/models/node_backend/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import common diff --git a/hotel_node_master/models/node_backend/common.py b/hotel_node_master/models/node_backend/common.py deleted file mode 100644 index b44de8c37..000000000 --- a/hotel_node_master/models/node_backend/common.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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) diff --git a/hotel_node_master/models/node_binding/__init__.py b/hotel_node_master/models/node_binding/__init__.py deleted file mode 100644 index 257ab04fc..000000000 --- a/hotel_node_master/models/node_binding/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import common diff --git a/hotel_node_master/models/node_binding/common.py b/hotel_node_master/models/node_binding/common.py deleted file mode 100644 index f2bf75638..000000000 --- a/hotel_node_master/models/node_binding/common.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2018 Alexandre Díaz -# 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.'), - ] diff --git a/hotel_node_master/security/hotel_node_security.xml b/hotel_node_master/security/hotel_node_security.xml deleted file mode 100644 index 74979936c..000000000 --- a/hotel_node_master/security/hotel_node_security.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/hotel_node_master/security/ir.model.access.csv b/hotel_node_master/security/ir.model.access.csv deleted file mode 100644 index 0720251a5..000000000 --- a/hotel_node_master/security/ir.model.access.csv +++ /dev/null @@ -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 diff --git a/hotel_node_master/static/description/icon.png b/hotel_node_master/static/description/icon.png deleted file mode 100644 index 0ea70ef8a4e53f1d5dd9072e3bcb0cdeaeeb0d61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22165 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

FASuT& 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 SqigB_0000 -# Alexandre Díaz -# -# -# 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 . -# -############################################################################## -from . import test_hotel_node_master diff --git a/hotel_node_master/tests/common.py b/hotel_node_master/tests/common.py deleted file mode 100644 index 285651553..000000000 --- a/hotel_node_master/tests/common.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2017 Solucións Aloxa S.L. -# Alexandre Díaz -# -# -# 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 . -# -############################################################################## -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() diff --git a/hotel_node_master/tests/test_hotel_node_master.py b/hotel_node_master/tests/test_hotel_node_master.py deleted file mode 100644 index 2b63fd301..000000000 --- a/hotel_node_master/tests/test_hotel_node_master.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2017 Solucións Aloxa S.L. -# Alexandre Díaz -# -# -# 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 . -# -############################################################################## -from .common import TestHotel -from odoo.exceptions import ValidationError - - -class TestHotelNodeMaster(TestHotel): - - def test_wizard_hotel_node_reservation(self): - pass diff --git a/hotel_node_master/views/hotel_node.xml b/hotel_node_master/views/hotel_node.xml deleted file mode 100644 index b7db33479..000000000 --- a/hotel_node_master/views/hotel_node.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - hotel.node.view.form - project.project - -

- -
- - - - - -
-
-

- -

-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
- - - - - - hotel.node.view.tree - project.project - - - - - - - - - - - - - - - Hotels - project.project - form - [] - kanban,form - main - - - - - - - - - - - - diff --git a/hotel_node_master/views/hotel_node_group.xml b/hotel_node_master/views/hotel_node_group.xml deleted file mode 100644 index 23f52d869..000000000 --- a/hotel_node_master/views/hotel_node_group.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - hotel.node.group.tree - hotel.node.group - - - - - - - - - - - List of Access Groups in Hotels - hotel.node.group - form - tree,form - main - - - - diff --git a/hotel_node_master/views/hotel_node_group_remote.xml b/hotel_node_master/views/hotel_node_group_remote.xml deleted file mode 100644 index c8c56c1d6..000000000 --- a/hotel_node_master/views/hotel_node_group_remote.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - hotel.node.group.remote.tree - hotel.node.group.remote - - - - - - - - - diff --git a/hotel_node_master/views/hotel_node_room_type.xml b/hotel_node_master/views/hotel_node_room_type.xml deleted file mode 100644 index 32680aac1..000000000 --- a/hotel_node_master/views/hotel_node_room_type.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - hotel.node.room.type.view.form - hotel.node.room.type - -
- -
- -
-
-

- -

-
- - - - - -
-
-
-
- - - - - - - -
-
-
-
- - - hotel.node.room.type.tree - hotel.node.room.type - - - - - - - - - - - List of Room Types in Hotels - hotel.node.room.type - form - tree,form - main - - - -
diff --git a/hotel_node_master/views/hotel_node_user.xml b/hotel_node_master/views/hotel_node_user.xml deleted file mode 100644 index 7a16d78bd..000000000 --- a/hotel_node_master/views/hotel_node_user.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - hotel.node.user.view.form - hotel.node.user - -
- -
- -
-
-

- -

-
- - - - - - - - - - - - - - - -
-
-
-
- - - hotel.node.user.tree - hotel.node.user - - - - - - - - - - - - List of Users in Hotels - hotel.node.user - form - tree,form - main - - - -
diff --git a/hotel_node_master/views/inherited_res_partner_views.xml b/hotel_node_master/views/inherited_res_partner_views.xml deleted file mode 100644 index 18af833e6..000000000 --- a/hotel_node_master/views/inherited_res_partner_views.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - res.partner - - - - - - - - - - - diff --git a/hotel_node_master/views/node_backend_views.xml b/hotel_node_master/views/node_backend_views.xml deleted file mode 100644 index 44feb57cb..000000000 --- a/hotel_node_master/views/node_backend_views.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - node.backend.form - node.backend - -
-
-
- - -
-
-
- - - node.backend.tree - node.backend - - - - - - - - - - - Hotel Node Backends - node.backend - form - tree,form - - - - - Hotel Node Room Types - node.room.type - form - tree,form - - - -
diff --git a/hotel_node_master/wizards/__init__.py b/hotel_node_master/wizards/__init__.py deleted file mode 100644 index 8463c3d08..000000000 --- a/hotel_node_master/wizards/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import wizard_hotel_node_reservation diff --git a/hotel_node_master/wizards/wizard_hotel_node_reservation.py b/hotel_node_master/wizards/wizard_hotel_node_reservation.py deleted file mode 100644 index 5b492412b..000000000 --- a/hotel_node_master/wizards/wizard_hotel_node_reservation.py +++ /dev/null @@ -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 diff --git a/hotel_node_master/wizards/wizard_hotel_node_reservation.xml b/hotel_node_master/wizards/wizard_hotel_node_reservation.xml deleted file mode 100644 index 094127437..000000000 --- a/hotel_node_master/wizards/wizard_hotel_node_reservation.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - hotel.node.reservation.wizard - hotel.node.reservation.wizard - -
- -
-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
- - - hotel.node.reservation.wizard.search - node.search.wizard - -
- -
-

- -

-
- - - - - - - - - -
-
-
-
-
-
- - - hotel.node.reservation.wizard.edit - node.folio.wizard - - - - - - - - - - - - - - - - - - - - - - - - - -