Merge branch 'hotel_node_master' of https://github.com/hootel/hootel into hotel_node_master

This commit is contained in:
Pablo
2018-11-05 21:46:01 +01:00
27 changed files with 1065 additions and 206 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -64,7 +64,7 @@
<span class="o_stat_text">Checks</span>
</div>
</button>
<!-- <field name="currency_id" invisible="1"/> -->
<field name="currency_id" invisible="1"/>
<!-- <button type="object" class="oe_stat_button"
id="invoices_smart_button"
@@ -94,16 +94,6 @@
</div>
</button> -->
<!-- <button type="object" class="oe_stat_button"
id="payment_smart_button"
icon="fa-money"
name="action_recalcule_payment"
help="Calcule the total Price">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Calcule Price</span>
</div>
</button> -->
<!-- <button type="object" class="oe_stat_button"
id="refunds_smart_button"
icon="fa-undo"
@@ -307,8 +297,8 @@
<field name="name"/>
<field name="adults"/>
<field name="children"/>
<!-- <field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
attrs="{'readonly':[('state','not in',('draft'))]}"/> -->
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
attrs="{'readonly':[('state','not in',('draft'))]}"/>
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
</group>
<group class="oe_subtotal_footer" style="margin-right: 20px; !important" colspan="2" name="reservation_total" string="Amounts">

View File

@@ -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):

View File

@@ -1 +1 @@
from . import wizard_reservation
from . import wizard_reservation

View File

@@ -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

View File

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

View File

@@ -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
}

View File

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

View File

@@ -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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -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**

View File

@@ -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'
],

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -8,6 +8,16 @@
<form string="Hotel Node">
<sheet string="Hotel Node">
<div class="oe_button_box" name="button_box" groups="base.group_user">
<button name="%(hotel_node_reservation_wizard_action)d" type="action"
string="Reserve" help="Make a reservation in this hotel"
class="oe_stat_button" icon="fa-suitcase"
context="{'node_id': id}">
</button>
<button name="%(hotel_node_reservation_wizard_action_search)d" type="action"
string="Search" help="Search a reservation in this hotel"
class="oe_stat_button" icon="fa-search"
context="{'node_id': id}">
</button>
<button class="oe_stat_button" type="action"
name="" icon="fa-tasks">
<field string="Tasks" name="task_count" widget="statinfo" options="{'label_field': 'label_tasks'}"/>
@@ -100,7 +110,7 @@
</field>
</record>
<record id="hotel_node_action_open_dashboard" model="ir.actions.act_window">
<record id="hotel_node_action_kanban" model="ir.actions.act_window">
<field name="name">Hotels</field>
<field name="res_model">project.project</field>
<field name="view_type">form</field>
@@ -109,81 +119,19 @@
<field name="target">main</field>
</record>
<!-- Action to open Hotel Nodes List -->
<act_window id="hotel_node_action"
name="List of Hotels"
res_model="project.project"
view_mode="tree,form"
/>
<!-- Action to open Hotel Room Types List -->
<act_window id="hotel_node_room_type_action"
name="List of Room Types in Hotels"
res_model="hotel.node.room.type"
view_mode="tree,form"
/>
<!-- Action to open Hotel Users List -->
<act_window id="hotel_node_user_action"
name="List of Users in Hotels"
res_model="hotel.node.user"
view_mode="tree,form"
/>
<!-- Action to open Hotel Groups List -->
<act_window id="hotel_node_group_action"
name="List of Access Groups in Hotels"
res_model="hotel.node.group"
view_mode="tree,form"
/>
<!-- Action to open Hotel Node Reservation List -->
<act_window id="hotel_node_reservation_action"
name="List of Reservations in Hotels"
res_model="hotel.node.reservation.wizard"
view_mode="tree,form"
/>
<!-- Menu default to open Hotel Nodes Dashboard -->
<menuitem id="hotel_node_dashboard_menu"
name="Hotel Central Dashboard"
action="hotel_node_action_open_dashboard"
/>
<!-- Menu Management Section -->
<menuitem id="dashboard_menu"
name="Management"
parent="hotel_node_dashboard_menu"
/>
<!-- Menu item to open Hotel Nodes List -->
<menuitem id="hotel_node_menu"
name="Hotels"
action="hotel_node_action"
parent="dashboard_menu"
sequence="1"
/>
<!-- Menu item to open Hotel Nodes List -->
<menuitem id="hotel_node_room_type_menu"
name="Room Types"
action="hotel_node_room_type_action"
parent="hotel_node_menu"
sequence="1"
/>
<!-- Menu item to open Hotel Users List -->
<menuitem id="hotel_node_user_menu"
name="Users"
action="hotel_node_user_action"
parent="dashboard_menu"
sequence="2"
/>
<!-- Menu item to open Hotel Users List -->
<menuitem id="hotel_node_group_menu"
name="Access Groups"
action="hotel_node_group_action"
parent="dashboard_menu"
sequence="3"
name="Hotel Central Dashboard"
action="hotel_node_action_kanban"
/>
<!-- Menu item to open Hotel Node Reservation List -->
<menuitem id="hotel_node_reservation_menu"
name="Reservations"
action="hotel_node_reservation_action"
parent="dashboard_menu"
sequence="4"
<menuitem id="hotel_node_menu_dashboard"
name="Management"
parent="hotel_node_menu"
/>
<!--<menuitem id="hotel_node_menu_tree"-->
<!--name="Hotels"-->
<!--parent="hotel_node_menu_dashboard"-->
<!--sequence="0"-->
<!--/>-->
</odoo>

View File

@@ -11,4 +11,19 @@
</tree>
</field>
</record>
<record id="hotel_node_group_action" model="ir.actions.act_window">
<field name="name">List of Access Groups in Hotels</field>
<field name="res_model">hotel.node.group</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_group_menu"
name="Access Groups"
action="hotel_node_group_action"
parent="hotel_node_menu_dashboard"
sequence="3"
/>
</odoo>

View File

@@ -54,4 +54,19 @@
</tree>
</field>
</record>
<record id="hotel_node_room_type_action" model="ir.actions.act_window">
<field name="name">List of Room Types in Hotels</field>
<field name="res_model">hotel.node.room.type</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_room_type_menu"
name="Room Types"
action="hotel_node_room_type_action"
parent="hotel_node_menu_dashboard"
sequence="1"
/>
</odoo>

View File

@@ -51,4 +51,19 @@
</tree>
</field>
</record>
<record id="hotel_node_user_action" model="ir.actions.act_window">
<field name="name">List of Users in Hotels</field>
<field name="res_model">hotel.node.user</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="target">main</field>
</record>
<menuitem id="hotel_node_user_menu"
name="Users"
action="hotel_node_user_action"
parent="hotel_node_menu_dashboard"
sequence="2"
/>
</odoo>

View File

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

View File

@@ -2,21 +2,489 @@
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import wdb
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"
node_id = fields.Many2one('project.project', 'Hotel')
@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)
room_id = fields.Many2one('hotel.node.room', 'Rooms')
adults = fields.Integer('Adults', size=64, default=1)
children = fields.Integer('Children', size=64)
state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'),
('booking', 'On Board'), ('done', 'Out'),
('cancelled', 'Cancelled')], 'State')
price_total = fields.Float(string='Total Price', readonly=True)
@api.depends('checkin', 'checkout')
def _compute_nights(self):
for rec in self:
rec.nights = (fields.Date.from_string(rec.checkout) - fields.Date.from_string(rec.checkin)).days

View File

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