mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
Merge branch 'hotel_node_master' of https://github.com/hootel/hootel into hotel_node_master
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1 +1 @@
|
||||
from . import wizard_reservation
|
||||
from . import wizard_reservation
|
||||
|
||||
22
hotel_node_helper/README.rst
Normal file
22
hotel_node_helper/README.rst
Normal 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
|
||||
|
||||
3
hotel_node_helper/__init__.py
Normal file
3
hotel_node_helper/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
21
hotel_node_helper/__manifest__.py
Normal file
21
hotel_node_helper/__manifest__.py
Normal 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
|
||||
}
|
||||
3
hotel_node_helper/models/__init__.py
Normal file
3
hotel_node_helper/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import inherited_hotel_room_type
|
||||
65
hotel_node_helper/models/inherited_hotel_room_type.py
Normal file
65
hotel_node_helper/models/inherited_hotel_room_type.py
Normal 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
|
||||
3
hotel_node_helper/security/hotel_node_security.xml
Normal file
3
hotel_node_helper/security/hotel_node_security.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="0">
|
||||
</odoo>
|
||||
1
hotel_node_helper/security/ir.model.access.csv
Normal file
1
hotel_node_helper/security/ir.model.access.csv
Normal file
@@ -0,0 +1 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
|
BIN
hotel_node_helper/static/description/icon.png
Normal file
BIN
hotel_node_helper/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -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**
|
||||
|
||||
|
||||
@@ -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'
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
16
hotel_node_master/models/inherited_res_partner.py
Normal file
16
hotel_node_master/models/inherited_res_partner.py
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
16
hotel_node_master/views/inherited_res_partner_views.xml
Normal file
16
hotel_node_master/views/inherited_res_partner_views.xml
Normal 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 & Users' autofocus="autofocus">
|
||||
<field name="node_user_ids" />
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user