diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index 98c2551c5..ba6a25e45 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -50,6 +50,7 @@ 'views/hotel_board_service_views.xml', 'views/hotel_checkin_partner_views.xml', 'views/hotel_room_type_availability_views.xml', + 'views/hotel_board_service_room_type_views.xml', 'data/cron_jobs.xml', 'data/records.xml', 'data/email_template_cancel.xml', diff --git a/hotel/data/hotel_demo.xml b/hotel/data/hotel_demo.xml index ee34db3c8..042b7d0b9 100644 --- a/hotel/data/hotel_demo.xml +++ b/hotel/data/hotel_demo.xml @@ -235,24 +235,90 @@ BreakFast - - 0 + + fixed Half Board - - 10 + + fixed FullBoard - - 10 + + fixed + + + + + + + + + + fixed + + + + + + fixed + + + + + + + fixed + + + + + + + + fixed + + + + + + fixed @@ -280,7 +346,7 @@ 'checkout': (DateTime.today() + timedelta(days=2)).strftime('%Y-%m-%d'), 'adults': 2, 'state': 'confirm', - 'board_service_id': ref('hotel_board_service_1'), + 'board_service_room_id': ref('hotel_board_service_room_1'), })]"/> @@ -292,7 +358,7 @@ 'checkin': (DateTime.today() + timedelta(days=2)).strftime('%Y-%m-%d'), 'checkout': (DateTime.today() + timedelta(days=4)).strftime('%Y-%m-%d'), 'adults': 3, - 'board_service_id': ref('hotel_board_service_2'), + 'board_service_room_id': ref('hotel_board_service_room_3'), })]"/> @@ -329,7 +395,6 @@ 'checkout': (DateTime.today() + timedelta(days=4)).strftime('%Y-%m-%d'), 'adults': 1, 'state': 'confirm', - 'board_service_id': ref('hotel_board_service_1'), })]"/> diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index 29689f309..c68f79c5b 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -29,3 +29,5 @@ from . import hotel_room_type_class from . import hotel_room_closure_reason from . import hotel_service_line from . import hotel_board_service +from . import hotel_board_service_room_type_line +from . import hotel_board_service_line diff --git a/hotel/models/hotel_board_service.py b/hotel/models/hotel_board_service.py index 4fcff0aa4..e9fa26b15 100644 --- a/hotel/models/hotel_board_service.py +++ b/hotel/models/hotel_board_service.py @@ -1,6 +1,7 @@ # Copyright 2017 Dario Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields +from odoo import api, models, fields +from odoo.addons import decimal_precision as dp class HotelBoardService(models.Model): @@ -8,8 +9,23 @@ class HotelBoardService(models.Model): _description = "Board Services" name = fields.Char('Board Name', size=64, required=True, index=True) - service_ids = fields.Many2many(comodel_name='product.product', - relation='hotel_board_services_reservation', - column1='board_id', - column2='service_id') - sequence = fields.Integer('Sequence') + board_service_line_ids = fields.One2many('hotel.board.service.line', + 'hotel_board_service_id') + price_type = fields.Selection([ + ('fixed','Fixed'), + ('percent','Percent')], string='Type', default='fixed', required=True) + hotel_board_service_room_type_ids = fields.One2many( + 'hotel.board.service.room.type', 'hotel_board_service_id') + amount = fields.Float('Amount', + digits=dp.get_precision('Product Price'), + compute='_compute_board_amount', + store=True) + + @api.depends('board_service_line_ids.amount') + def _compute_board_amount(self): + for record in self: + total = 0 + for service in record.board_service_line_ids: + total += service.amount + record.update({'amount': total}) + diff --git a/hotel/models/hotel_board_service_line.py b/hotel/models/hotel_board_service_line.py new file mode 100644 index 000000000..8475f57cc --- /dev/null +++ b/hotel/models/hotel_board_service_line.py @@ -0,0 +1,29 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models, _ +from odoo.addons import decimal_precision as dp +from odoo.exceptions import UserError + + +class HotelBoardServiceLine(models.Model): + _name = 'hotel.board.service.line' + _description = 'Services on Board Service included' + + def _get_default_price(self): + if self.product_id: + return self.product_id.list_price + + hotel_board_service_id = fields.Many2one( + 'hotel.board.service', 'Board Service', ondelete='cascade', required=True) + product_id = fields.Many2one( + 'product.product', 'Product', required=True) + amount = fields.Float('Amount', + digits=dp.get_precision('Product Price'), default=_get_default_price) + + @api.onchange('product_id') + def onchange_product_id(self): + if self.product_id: + self.update({'amount': self.product_id.list_price}) + + + diff --git a/hotel/models/hotel_board_service_room_type.py b/hotel/models/hotel_board_service_room_type.py index ec9111d22..ae09426dc 100644 --- a/hotel/models/hotel_board_service_room_type.py +++ b/hotel/models/hotel_board_service_room_type.py @@ -12,6 +12,17 @@ class HotelBoardServiceRoomType(models.Model): _log_access = False _description = 'Board Service included in Room' + @api.multi + def name_get(self): + result = [] + for res in self: + if res.pricelist_id: + name = u'%s (%s)' % (res.hotel_board_service_id.name, res.pricelist_id.name) + else: + name = u'%s (%s)' % (res.hotel_board_service_id.name, _('Generic')) + result.append((res.id, name)) + return result + hotel_board_service_id = fields.Many2one( 'hotel.board.service', 'Board Service', index=True, ondelete='cascade', required=True) hotel_room_type_id = fields.Many2one( @@ -21,7 +32,11 @@ class HotelBoardServiceRoomType(models.Model): price_type = fields.Selection([ ('fixed','Fixed'), ('percent','Percent')], string='Type', default='fixed', required=True) - amount = fields.Float('Amount', digits=dp.get_precision('Product Price'), default=0.0) + amount = fields.Float('Amount', + digits=dp.get_precision('Product Price'), + compute='_compute_board_amount', + store=True) + board_service_line_ids = fields.One2many('hotel.board.service.room.type.line', 'hotel_board_service_room_type_id') @api.model_cr def init(self): @@ -29,6 +44,52 @@ class HotelBoardServiceRoomType(models.Model): if not self._cr.fetchone(): self._cr.execute('CREATE INDEX hotel_board_service_id_hotel_room_type_id_pricelist_id ON hotel_board_service_room_type_rel (hotel_board_service_id, hotel_room_type_id, pricelist_id)') + @api.model + def create(self, vals): + if 'hotel_board_service_id' in vals: + vals.update( + self.prepare_board_service_room_lines(vals['hotel_board_service_id']) + ) + return super(HotelBoardServiceRoomType, self).create(vals) + + @api.multi + def write(self, vals): + if 'hotel_board_service_id' in vals: + vals.update( + self.prepare_board_service_room_lines(vals['hotel_board_service_id']) + ) + return super(HotelBoardServiceRoomType, self).write(vals) + + @api.multi + def open_board_lines_form(self): + action = self.env.ref('hotel.action_hotel_board_service_room_type_view').read()[0] + action['views'] = [(self.env.ref('hotel.hotel_board_service_room_type_form').id, 'form')] + action['res_id'] = self.id + action['target'] = 'new' + return action + + @api.depends('board_service_line_ids.amount') + def _compute_board_amount(self): + for record in self: + total = 0 + for service in record.board_service_line_ids: + total += service.amount + record.update({'amount': total}) + + @api.model + def prepare_board_service_room_lines(self, board_service_id): + """ + Prepare line to price products config + """ + cmds=[(5,0,0)] + board_service = self.env['hotel.board.service'].browse(board_service_id) + for line in board_service.board_service_line_ids: + cmds.append((0, False, { + 'product_id': line.product_id.id, + 'amount': line.amount + })) + return {'board_service_line_ids': cmds} + @api.constrains('pricelist_id') def constrains_pricelist_id(self): for record in self: diff --git a/hotel/models/hotel_board_service_room_type_line.py b/hotel/models/hotel_board_service_room_type_line.py new file mode 100644 index 000000000..8896c0644 --- /dev/null +++ b/hotel/models/hotel_board_service_room_type_line.py @@ -0,0 +1,21 @@ +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import api, fields, models, _ +from odoo.addons import decimal_precision as dp +from odoo.exceptions import UserError + + +class HotelBoardServiceRoomTypeLine(models.Model): + _name = 'hotel.board.service.room.type.line' + _description = 'Services on Board Service included in Room' + + #TODO def default_amount "amount of service" + + hotel_board_service_room_type_id = fields.Many2one( + 'hotel.board.service.room.type', 'Board Service Room', ondelete='cascade', required=True) + product_id = fields.Many2one( + 'product.product', 'Product', required=True, readonly=True) + amount = fields.Float('Amount', digits=dp.get_precision('Product Price'), default=0.0) + + + diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index be1bcf201..d00a04909 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -4,6 +4,7 @@ import logging import time from datetime import timedelta +from lxml import etree from odoo.exceptions import UserError, ValidationError from odoo.tools import ( misc, @@ -133,7 +134,8 @@ class HotelReservation(models.Model): track_visibility='onchange') reservation_type = fields.Selection(related='folio_id.reservation_type', default=lambda *a: 'normal') - board_service_id = fields.Many2one('hotel.board.service', string='Board Service') + board_service_room_id = fields.Many2one('hotel.board.service.room.type', + string='Board Service') cancelled_reason = fields.Selection([ ('late', 'Late'), ('intime', 'In time'), @@ -290,12 +292,12 @@ class HotelReservation(models.Model): vals.update({ 'last_updated_res': fields.Datetime.now(), }) - if 'board_service_id' in vals: + if 'board_service_room_id' in vals: board_services = [] - board = self.env['hotel.board.service'].browse(vals['board_service_id']) - for product in board.service_ids: + board = self.env['hotel.board.service.room.type'].browse(vals['board_service_room_id']) + for line in board.board_service_line_ids: board_services.append((0, False, { - 'product_id': product.id, + 'product_id': line.product_id.id, 'is_board_service': True, 'folio_id': vals.get('folio_id'), })) @@ -330,10 +332,10 @@ class HotelReservation(models.Model): if self.compute_board_services(vals): record.service_ids.filtered(lambda r: r.is_board_service == True).unlink() board_services = [] - board = self.env['hotel.board.service'].browse(vals['board_service_id']) - for product in board.service_ids: + board = self.env['hotel.board.service.room.type'].browse(vals['board_service_room_id']) + for line in board.board_service_line_ids: board_services.append((0, False, { - 'product_id': product.id, + 'product_id': line.product_id.id, 'is_board_service': True, 'folio_id': record.folio_id.id or vals.get('folio_id') })) @@ -369,10 +371,10 @@ class HotelReservation(models.Model): @api.multi def compute_board_services(self, vals): """ - We must compute service_ids when we hace a board_service_id without + We must compute service_ids when we have a board_service_id without service_ids associated to reservation """ - if 'board_service_id' in vals: + if 'board_service_room_id' in vals: if 'service_ids' in vals: for service in vals['service_ids']: if 'is_board_service' in service[2] and \ @@ -402,7 +404,7 @@ class HotelReservation(models.Model): """ Deduce missing required fields from the onchange """ res = {} onchange_fields = ['room_id', 'reservation_type', - 'currency_id', 'name', 'board_service_id'] + 'currency_id', 'name', 'board_service_room_id'] if values.get('room_type_id'): line = self.new(values) if any(f not in values for f in onchange_fields): @@ -610,11 +612,12 @@ class HotelReservation(models.Model): ] return {'domain': {'room_id': domain_rooms}} - @api.onchange('board_service_id') + @api.onchange('board_service_room_id') def onchange_board_service(self): - if self.board_service_id: + if self.board_service_room_id: board_services = [] - for product in self.board_service_id.service_ids: + for line in self.board_service_room_id.board_service_line_ids: + product = line.product_id if product.per_day: vals = { 'product_id': product.id, diff --git a/hotel/models/hotel_service.py b/hotel/models/hotel_service.py index 459dcded3..e0f38c16c 100644 --- a/hotel/models/hotel_service.py +++ b/hotel/models/hotel_service.py @@ -153,7 +153,7 @@ class HotelService(models.Model): def _compute_tax_ids(self): for record in self: # If company_id is set, always filter taxes by the company - folio = self.folio_id or self.env.context.get('default_folio_id') + folio = record.folio_id or self.env.context.get('default_folio_id') record.tax_id = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == folio.company_id) @api.multi @@ -184,12 +184,12 @@ class HotelService(models.Model): if record.per_day and record.ser_room_line: product = record.product_id reservation = record.ser_room_line - vals.update(self.prepare_service_lines( + vals.update(record.prepare_service_lines( dfrom=reservation.checkin, days=reservation.nights, per_person=product.per_person, persons=reservation.adults, - old_line_days=self.service_line_ids)) + old_line_days=record.service_line_ids)) if record.product_id.daily_limit > 0: for day in record.service_line_ids: day.no_free_resources() @@ -202,21 +202,33 @@ class HotelService(models.Model): @api.multi def _compute_price_unit(self): - """ - Compute tax and price unit - """ + self.ensure_one() folio = self.folio_id or self.env.context.get('default_folio_id') - product = self.product_id.with_context( - lang=folio.partner_id.lang, - partner=folio.partner_id.id, - quantity=self.product_qty, - date=folio.date_order, - pricelist=folio.pricelist_id.id, - uom=self.product_id.uom_id.id, - fiscal_position=False - ) - return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, folio.company_id) - + reservation = self.ser_room_line or self.env.context.get('ser_room_line') + if folio or reservation: + partner = folio.partner_id if folio else reservation.partner_id + pricelist = folio.pricelist_id if folio else reservation.pricelist_id + if reservation and self.is_board_service: + board_room_type = reservation.board_service_room_id + if board_room_type.price_type == 'fixed': + return self.env['hotel.board.service.room.type.line'].search([ + ('hotel_board_service_room_type_id', '=', board_room_type.id), + ('product_id','=',self.product_id.id)]).amount + else: + return (reservation.price_total * self.env['hotel.board.service.room.type.line'].search([ + ('hotel_board_service_room_type_id', '=', board_room_type.id), + ('product_id','=',self.product_id.id)]).amount) / 100 + else: + product = self.product_id.with_context( + lang=partner.lang, + partner=partner.id, + quantity=self.product_qty, + date=folio.date_order or fields.Date.today(), + pricelist=pricelist.id, + uom=self.product_id.uom_id.id, + fiscal_position=False + ) + return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, folio.company_id) @api.model def prepare_service_lines(self, **kwargs): @@ -251,9 +263,12 @@ class HotelService(models.Model): """ for record in self: folio = record.folio_id or self.env.context.get('default_folio_id') + reservation = record.ser_room_line or self.env.context.get('ser_room_line') + currency = folio.currency_id if folio else reservation.currency_id product = record.product_id price = record.price_unit * (1 - (record.discount or 0.0) * 0.01) - taxes = record.tax_ids.compute_all(price, folio.currency_id, record.product_qty, product=product) + taxes = record.tax_ids.compute_all(price, currency, record.product_qty, product=product) + record.update({ 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), 'price_total': taxes['total_included'], diff --git a/hotel/security/ir.model.access.csv b/hotel/security/ir.model.access.csv index 7484025a9..3d41900d3 100644 --- a/hotel/security/ir.model.access.csv +++ b/hotel/security/ir.model.access.csv @@ -17,3 +17,5 @@ access_hotel_reservation,access_hotel_reservation,model_hotel_reservation,base.g access_hotel_folio,access_hotel_folio,model_hotel_folio,base.group_user,1,0,0,0 access_hotel_room_type,access_hotel_room_type,model_hotel_room_type,base.group_user,1,0,0,0 access_hotel_board_service_room_type,access_hotel_board_service_room_type,model_hotel_board_service_room_type,base.group_user,1,0,0,0 +access_hotel_board_service_room_type_line,access_hotel_board_service_room_type_line,model_hotel_board_service_room_type_line,base.group_user,1,0,0,0 +access_hotel_board_service_line,access_hotel_board_service_line,model_hotel_board_service_line,base.group_user,1,0,0,0 diff --git a/hotel/views/hotel_board_service_room_type_views.xml b/hotel/views/hotel_board_service_room_type_views.xml new file mode 100644 index 000000000..b260b933e --- /dev/null +++ b/hotel/views/hotel_board_service_room_type_views.xml @@ -0,0 +1,30 @@ + + + + + hotel.board.service.room.type.form + hotel.board.service.room.type + +
+ + + + + + + + + + +
+
+
+ + + Hotel Board Service + hotel.board.service.room.type + form + form + + +
diff --git a/hotel/views/hotel_board_service_views.xml b/hotel/views/hotel_board_service_views.xml index 0dbc15fba..35faeee72 100644 --- a/hotel/views/hotel_board_service_views.xml +++ b/hotel/views/hotel_board_service_views.xml @@ -3,20 +3,20 @@ - + hotel.board.service.form hotel.board.service -
- - - - - - - - - + + + + + + + + + +
@@ -28,7 +28,7 @@ - +
diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index 91de623b4..71d5cdc71 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -298,7 +298,9 @@ - + diff --git a/hotel/views/hotel_reservation_views.xml b/hotel/views/hotel_reservation_views.xml index 4791dcbe9..e8990fd1e 100644 --- a/hotel/views/hotel_reservation_views.xml +++ b/hotel/views/hotel_reservation_views.xml @@ -191,7 +191,10 @@ - + + @@ -237,11 +240,16 @@ - + - + +