diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 9e18492e6..668106463 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -314,20 +314,21 @@ class HotelFolio(models.Model): @api.model def create(self, vals): - if vals.get('name', _('New')) == _('New'): + if vals.get('name', _('New')) == _('New') or 'name' not in vals: if 'company_id' in vals: vals['name'] = self.env['ir.sequence'].with_context( force_company=vals['company_id'] ).next_by_code('sale.order') or _('New') else: vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New') + # Makes sure partner_invoice_id' and 'pricelist_id' are defined lfields = ('partner_invoice_id', 'partner_shipping_id', 'pricelist_id') if any(f not in vals for f in lfields): partner = self.env['res.partner'].browse(vals.get('partner_id')) addr = partner.address_get(['delivery', 'invoice']) - vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice']) + #~ vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice']) vals['pricelist_id'] = vals.setdefault( 'pricelist_id', partner.property_product_pricelist and partner.property_product_pricelist.id) @@ -344,20 +345,25 @@ class HotelFolio(models.Model): - user_id """ if not self.partner_id: - self.update({ - 'partner_invoice_id': False, - 'payment_term_id': False, - 'fiscal_position_id': False, - }) + #~ self.update({ + #~ 'partner_invoice_id': False, + #~ 'payment_term_id': False, + #~ 'fiscal_position_id': False, + #~ }) return addr = self.partner_id.address_get(['invoice']) - values = { - 'pricelist_id': self.partner_id.property_product_pricelist and \ - self.partner_id.property_product_pricelist.id or False, - 'partner_invoice_id': addr['invoice'], - 'user_id': self.partner_id.user_id.id or self.env.uid - } + #TEMP: + values = { 'user_id': self.partner_id.user_id.id or self.env.uid, + 'pricelist_id':self.partner_id.property_product_pricelist and \ + self.partner_id.property_product_pricelist.id or \ + self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id')} + #~ values = { + #~ 'pricelist_id': self.partner_id.property_product_pricelist and \ + #~ self.partner_id.property_product_pricelist.id or False, + #~ 'partner_invoice_id': addr['invoice'], + #~ 'user_id': self.partner_id.user_id.id or self.env.uid + #~ } if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and \ self.env.user.company_id.sale_note: values['note'] = self.with_context( @@ -486,18 +492,16 @@ class HotelFolio(models.Model): def _compute_cardex_count(self): _logger.info('_compute_cardex_amount') for record in self: - if record.reservation_type == 'normal': + if record.reservation_type == 'normal' and record.room_lines: write_vals = {} - filtered_reservs = record.filtered( - lambda x: x.room_lines.state != 'cancelled' and \ - not x.room_lines.parent_reservation) - + filtered_reservs = record.room_lines.filtered( + lambda x: x.state != 'cancelled' and \ + not x.parent_reservation) mapped_cardex = filtered_reservs.mapped('cardex_ids.id') - write_vals.update({'cardex_count': len(mapped_cardex)}) + record.cardex_count = len(mapped_cardex) mapped_cardex_count = filtered_reservs.mapped( lambda x: (x.adults + x.children) - len(x.cardex_ids)) - write_vals.update({'cardex_pending_count': sum(mapped_cardex_count)}) - record.write(write_vals) + record.cardex_pending_count = sum(mapped_cardex_count) """ MAILING PROCESS diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 56a00dd74..094fe6179 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -270,6 +270,8 @@ class HotelReservation(models.Model): @api.model def create(self, vals): + if 'room_id' not in vals: + vals.update(self._autoassign(vals)) vals.update(self._prepare_add_missing_fields(vals)) if 'folio_id' in vals: folio = self.env["hotel.folio"].browse(vals['folio_id']) @@ -332,16 +334,32 @@ class HotelReservation(models.Model): def _prepare_add_missing_fields(self, values): """ Deduce missing required fields from the onchange """ res = {} - onchange_fields = ['room_id', 'pricelist_id', 'reservation_type', 'currency_id'] - if values.get('partner_id') and values.get('room_type_id') and \ - any(f not in values for f in onchange_fields): + onchange_fields = ['room_id', 'reservation_type', 'currency_id', 'name'] + if values.get('room_type_id'): line = self.new(values) - line.onchange_room_id() + if any(f not in values for f in onchange_fields): + line.onchange_room_id() + line.onchange_compute_reservation_description() + if 'pricelist_id' not in values: + line.onchange_partner_id() for field in onchange_fields: if field not in values: res[field] = line._fields[field].convert_to_write(line[field], line) return res + @api.model + def _autoassign(self, values): + res = {} + checkin = values.get('checkin') + checkout = values.get('checkout') + room_type = values.get('room_type_id') + if checkin and checkout and room_type: + room_chosen = self.env['hotel.room.type'].check_availability_room(checkin, checkout, room_type)[0] + res.update({ + 'room_id': room_chosen.id + }) + return res + @api.multi def notify_update(self, vals): if 'checkin' in vals or \ @@ -807,11 +825,16 @@ class HotelReservation(models.Model): @param dto: range date to @return: array with the reservations _confirmed_ between dfrom and dto """ + domain = self._get_domain_reservations_occupation(dfrom, dto) + return self.env['hotel.reservation'].search(domain) + + @api.model + def _get_domain_reservations_occupation(self, dfrom, dto): domain = [('reservation_line_ids.date', '>=', dfrom), ('reservation_line_ids.date', '<', dto), ('state', '!=', 'cancelled'), ('overbooking', '=', False)] - return self.env['hotel.reservation'].search(domain) + return domain @api.model def get_reservations_dates(self, dfrom, dto, room_type=False): @@ -873,11 +896,9 @@ class HotelReservation(models.Model): def _compute_cardex_count(self): _logger.info('_compute_cardex_count') for record in self: - record.write({ - 'cardex_count': len(record.cardex_ids), - 'cardex_pending_count': (record.adults + record.children) \ + record.cardex_count = len(record.cardex_ids) + record.cardex_pending_count = (record.adults + record.children) \ - len(record.cardex_ids) - }) # https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501 @api.multi diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 8ce8d5f3d..7dcd4abe0 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -74,14 +74,12 @@ class HotelRoomType(models.Model): reservations_rooms = reservations.mapped('room_id.id') free_rooms = self.env['hotel.room'].search([ ('id', 'not in', reservations_rooms), - ('id', 'not in', notthis) + ('room_type_id.id', 'not in', notthis) ]) if room_type_id: - room_type_id = self.env['hotel.room.type'].search([ + rooms_linked = self.env['hotel.room.type'].search([ ('id', '=', room_type_id) - ]) - # QUESTION What linked represent? Rooms in this type ? - rooms_linked = self.room_ids + ]).room_ids free_rooms = free_rooms & rooms_linked return free_rooms.sorted(key=lambda r: r.sequence) diff --git a/hotel/views/hotel_folio.xml b/hotel/views/hotel_folio.xml index be1003cce..a3a3c73a7 100644 --- a/hotel/views/hotel_folio.xml +++ b/hotel/views/hotel_folio.xml @@ -64,7 +64,7 @@ Checks - + - - + diff --git a/hotel/wizard/massive_changes.py b/hotel/wizard/massive_changes.py index d15c6339f..c71c0e3ef 100644 --- a/hotel/wizard/massive_changes.py +++ b/hotel/wizard/massive_changes.py @@ -255,7 +255,7 @@ class MassiveChangesWizard(models.TransientModel): diff_days = abs((date_end_dt - date_start_dt).days) + 1 wedays = (record.dmo, record.dtu, record.dwe, record.dth, record.dfr, record.dsa, record.dsu) - room_types = record.room_type_id if record.applied_on == '1' \ + room_types = record.room_type_ids if record.applied_on == '1' \ else hotel_room_type_obj.search([]) for i in range(0, diff_days): diff --git a/hotel_calendar/wizard/__init__.py b/hotel_calendar/wizard/__init__.py index cc636fed6..a75922538 100644 --- a/hotel_calendar/wizard/__init__.py +++ b/hotel_calendar/wizard/__init__.py @@ -1 +1 @@ -from . import wizard_reservation \ No newline at end of file +from . import wizard_reservation diff --git a/hotel_node_helper/README.rst b/hotel_node_helper/README.rst new file mode 100644 index 000000000..4a067e3c4 --- /dev/null +++ b/hotel_node_helper/README.rst @@ -0,0 +1,22 @@ +================= +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 new file mode 100644 index 000000000..69f7babdf --- /dev/null +++ b/hotel_node_helper/__init__.py @@ -0,0 +1,3 @@ +# 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 new file mode 100644 index 000000000..917ab41b4 --- /dev/null +++ b/hotel_node_helper/__manifest__.py @@ -0,0 +1,21 @@ +{ + '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 new file mode 100644 index 000000000..c21375169 --- /dev/null +++ b/hotel_node_helper/models/__init__.py @@ -0,0 +1,3 @@ +# 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 new file mode 100644 index 000000000..52d14f20a --- /dev/null +++ b/hotel_node_helper/models/inherited_hotel_room_type.py @@ -0,0 +1,65 @@ +# 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 wdb +from odoo import models, fields, api + + +class HotelRoomType(models.Model): + + _inherit = 'hotel.room.type' + + @api.model + def check_availability_room_ids(self, dfrom, dto, + room_type_id=False, notthis=[]): + """ + Check availability for all or specific room types between dates + @return: A list of `ids` with free rooms + """ + free_rooms = super().check_availability_room(dfrom, dto, room_type_id, notthis) + return free_rooms.ids + + @api.model + def get_room_type_availability(self, dfrom, dto, room_type_id): + free_rooms = self.check_availability_room(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 diff --git a/hotel_node_helper/security/hotel_node_security.xml b/hotel_node_helper/security/hotel_node_security.xml new file mode 100644 index 000000000..74979936c --- /dev/null +++ b/hotel_node_helper/security/hotel_node_security.xml @@ -0,0 +1,3 @@ + + + diff --git a/hotel_node_helper/security/ir.model.access.csv b/hotel_node_helper/security/ir.model.access.csv new file mode 100644 index 000000000..97dd8b917 --- /dev/null +++ b/hotel_node_helper/security/ir.model.access.csv @@ -0,0 +1 @@ +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 new file mode 100644 index 000000000..0b47e0cf9 Binary files /dev/null and b/hotel_node_helper/static/description/icon.png differ diff --git a/hotel_node_master/README.rst b/hotel_node_master/README.rst index a8612512e..0511c648d 100644 --- a/hotel_node_master/README.rst +++ b/hotel_node_master/README.rst @@ -1,5 +1,5 @@ ================= -Hotel Master Node +Hotel Node Master ================= This module is for providing centralized hotel management features for hootel. @@ -8,6 +8,7 @@ You can manage: - Node connection data - Remote users and access groups +- Hotel reservations **Installation** diff --git a/hotel_node_master/__manifest__.py b/hotel_node_master/__manifest__.py index 88ed89063..ab8d8f1b7 100644 --- a/hotel_node_master/__manifest__.py +++ b/hotel_node_master/__manifest__.py @@ -1,5 +1,5 @@ { - 'name': 'Hotel Master Node', + 'name': 'Hotel Node Master', 'summary': """Provides centralized hotel management features""", 'version': '0.1.0', 'author': 'Pablo Q. Barriuso, \ @@ -14,11 +14,12 @@ {'python' : ['odoorpc']}, 'license': "AGPL-3", 'data': [ + 'wizards/wizard_hotel_node_reservation.xml', 'views/hotel_node.xml', 'views/hotel_node_user.xml', 'views/hotel_node_group.xml', 'views/hotel_node_room_type.xml', - 'wizards/wizard_hotel_node_reservation.xml', + 'views/inherited_res_partner_views.xml', 'security/hotel_node_security.xml', 'security/ir.model.access.csv' ], diff --git a/hotel_node_master/models/__init__.py b/hotel_node_master/models/__init__.py index 2555cdee7..9d741ca83 100644 --- a/hotel_node_master/models/__init__.py +++ b/hotel_node_master/models/__init__.py @@ -5,3 +5,4 @@ from . import hotel_node_user from . import hotel_node_group from . import hotel_node_room from . import hotel_node_room_type +from . import inherited_res_partner diff --git a/hotel_node_master/models/hotel_node.py b/hotel_node_master/models/hotel_node.py index 2cb8adb3c..f6863cbb4 100644 --- a/hotel_node_master/models/hotel_node.py +++ b/hotel_node_master/models/hotel_node.py @@ -57,9 +57,8 @@ class HotelNode(models.Model): """ for node in self: domain = [('id', 'in', node.group_ids.ids), ('odoo_version', '!=', node.odoo_version)] - # TODO Use search_count - invalid_groups = self.env["hotel.node.group"].search(domain) - if len(invalid_groups) > 0: + 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) @@ -83,6 +82,8 @@ class HotelNode(models.Model): 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: @@ -98,13 +99,14 @@ class HotelNode(models.Model): 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 - domain = [('model', '=', 'res.groups')] - fields = ['complete_name', 'display_name'] - remote_groups = noderpc.env['ir.model.data'].search_read(domain, fields) + 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']) @@ -129,13 +131,122 @@ class HotelNode(models.Model): except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err: raise ValidationError(err) - # TODO logout from node in any case. Take into account each try / except block + + 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 - fields = ['name', 'active', 'sequence', 'room_ids'] - remote_room_types = noderpc.env['hotel.room.type'].search_read([], fields) + 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']) @@ -170,8 +281,9 @@ class HotelNode(models.Model): try: vals = {} # import remote rooms - fields = ['name', 'active', 'sequence', 'capacity', 'room_type_id'] - remote_rooms = noderpc.env['hotel.room'].search_read([], fields) + 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']) @@ -216,6 +328,5 @@ class HotelNode(models.Model): 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_user.py b/hotel_node_master/models/hotel_node_user.py index 7da26092a..d6d0455dd 100644 --- a/hotel_node_master/models/hotel_node_user.py +++ b/hotel_node_master/models/hotel_node_user.py @@ -29,7 +29,10 @@ class HotelNodeUser(models.Model): 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) - login = fields.Char(require=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.") @@ -45,9 +48,8 @@ class HotelNodeUser(models.Model): 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)] - # TODO Use search_count - invalid_groups = self.env["hotel.node.group"].search(domain) - if len(invalid_groups) > 0: + 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) @@ -73,37 +75,37 @@ class HotelNodeUser(models.Model): 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(domain) - # TODO Use search_count - if len(invalid_groups) > 0: + 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: - noderpc = odoorpc.ODOO(node.odoo_host, node.odoo_protocol, node.odoo_port) - noderpc.login(node.odoo_db, node.odoo_user, node.odoo_password) + 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'], - } + 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]]}) + 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}) + # 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() + noderpc.logout() except (odoorpc.error.RPCError, odoorpc.error.InternalError, urllib.error.URLError) as err: _logger.error(err) @@ -117,6 +119,7 @@ class HotelNodeUser(models.Model): :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.") @@ -127,41 +130,44 @@ class HotelNodeUser(models.Model): 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(domain) - # TODO Use search_count - if len(invalid_groups) > 0: + 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: - noderpc = odoorpc.ODOO(node.odoo_host, node.odoo_protocol, node.odoo_port) - noderpc.login(node.odoo_db, node.odoo_user, node.odoo_password) + 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 = {} + remote_vals = {} - if 'active' in vals: - remote_vals.update({'active': vals['active']}) + if 'login' in vals: + remote_vals.update({'login': vals['login']}) - if 'password' in vals: - remote_vals.update({'password': vals['password']}) + if 'active' in vals: + remote_vals.update({'active': vals['active']}) - if 'partner_id' in vals: - partner = self.env["res.partner"].browse(vals['partner_id']) - remote_vals.update({'name': partner.name}) + if 'password' in vals: + remote_vals.update({'password': vals['password']}) - 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]]}) + if 'partner_id' in vals: + partner = self.env["res.partner"].browse(vals['partner_id']) + remote_vals.update({'name': partner.name}) - 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) + 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.logout() + 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) @@ -175,19 +181,21 @@ class HotelNodeUser(models.Model): """ :raise: ValidationError """ - # TODO In production users are archived instead of removed 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) - - noderpc.env['res.users'].unlink([rec.remote_user_id]) + # 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) diff --git a/hotel_node_master/models/inherited_res_partner.py b/hotel_node_master/models/inherited_res_partner.py new file mode 100644 index 000000000..89167ee77 --- /dev/null +++ b/hotel_node_master/models/inherited_res_partner.py @@ -0,0 +1,16 @@ +# 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/views/hotel_node.xml b/hotel_node_master/views/hotel_node.xml index a0a923591..b7db33479 100644 --- a/hotel_node_master/views/hotel_node.xml +++ b/hotel_node_master/views/hotel_node.xml @@ -8,6 +8,16 @@
+ +