From e73be563d2118fb2477d7909c682fbc137df8b14 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Sat, 23 Mar 2019 15:51:24 +0100 Subject: [PATCH] [ADD] Pricelist cancelled rules --- hotel/__manifest__.py | 2 + hotel/models/__init__.py | 1 + hotel/models/hotel_cancelation_rule.py | 30 ++++++ hotel/models/hotel_reservation.py | 102 ++++++++++++++++-- hotel/models/hotel_reservation_line.py | 4 + hotel/models/inherited_product_pricelist.py | 3 + hotel/views/hotel_cancelation_rule_views.xml | 63 +++++++++++ hotel/views/hotel_folio_views.xml | 2 +- hotel/views/hotel_reservation_views.xml | 31 +++--- .../inherited_product_pricelist_views.xml | 17 +++ .../inherited_product_pricelist_views.xml | 4 - 11 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 hotel/models/hotel_cancelation_rule.py create mode 100644 hotel/views/hotel_cancelation_rule_views.xml create mode 100644 hotel/views/inherited_product_pricelist_views.xml diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index dd9d641b8..161a55904 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -41,6 +41,7 @@ 'views/hotel_room_type_class_views.xml', 'views/general.xml', 'views/inherited_product_template_views.xml', + 'views/inherited_product_pricelist_views.xml', 'views/hotel_room_amenities_type_views.xml', 'views/hotel_room_amenities_views.xml', 'views/hotel_room_type_restriction_views.xml', @@ -52,6 +53,7 @@ 'views/hotel_board_service_views.xml', 'views/hotel_checkin_partner_views.xml', 'views/hotel_board_service_room_type_views.xml', + 'views/hotel_cancelation_rule_views.xml', 'data/cron_jobs.xml', 'data/records.xml', 'data/email_template_cancel.xml', diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index dbb314957..ae46acfd3 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -31,3 +31,4 @@ from . import hotel_board_service from . import hotel_board_service_room_type_line from . import hotel_board_service_line from . import inherited_account_invoice_line +from . import hotel_cancelation_rule diff --git a/hotel/models/hotel_cancelation_rule.py b/hotel/models/hotel_cancelation_rule.py new file mode 100644 index 000000000..0415d3b56 --- /dev/null +++ b/hotel/models/hotel_cancelation_rule.py @@ -0,0 +1,30 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models, fields + + +class HotelCancelationRule(models.Model): + _name = 'hotel.cancelation.rule' + _description = 'Cancelation Rules' + + name = fields.Char('Amenity Name', translate=True, required=True) + active = fields.Boolean('Active', default=True) + pricelist_ids = fields.One2many('product.pricelist', + 'cancelation_rule_id', + 'Pricelist that use this rule') + days_intime = fields.Integer( + 'Days Late', + help='Maximum number of days for free cancellation before Checkin') + penalty_late = fields.Integer('% Penalty Late', defaul="100") + apply_on_late = fields.Selection([ + ('first', 'First Day'), + ('all', 'All Days'), + ('days', 'Specify days')], 'Late apply on', default='first') + days_late = fields.Integer('Late first days', default="2") + penalty_noshow = fields.Integer('% Penalty No Show', default="100") + apply_on_noshow = fields.Selection([ + ('first', 'First Day'), + ('all', 'All Days'), + ('days', 'Specify days')], 'No Show apply on', default='all') + days_noshow = fields.Integer('NoShow first days', default="2") diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index af6233398..662d9f872 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -170,12 +170,15 @@ class HotelReservation(models.Model): track_visibility='onchange', help='Number of children there in guest list.') to_assign = fields.Boolean('To Assign', track_visibility='onchange') - state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'), - ('booking', 'On Board'), ('done', 'Out'), - ('cancelled', 'Cancelled')], - 'State', readonly=True, - default=lambda *a: 'draft', - track_visibility='onchange') + state = fields.Selection([ + ('draft', 'Pre-reservation'), + ('confirm', 'Pending Entry'), + ('booking', 'On Board'), + ('done', 'Out'), + ('cancelled', 'Cancelled') + ], string='State', readonly=True, + default=lambda *a: 'draft', copy=False, + track_visibility='onchange') reservation_type = fields.Selection(related='folio_id.reservation_type', default=lambda *a: 'normal') invoice_count = fields.Integer(related='folio_id.invoice_count') @@ -635,6 +638,11 @@ class HotelReservation(models.Model): write_vals.update({'room_type_id': self.room_id.room_type_id.id}) self.update(write_vals) + @api.onchange('cancelled_reason') + def onchange_cancelled_reason(self): + for record in self: + record._compute_cancelled_discount() + @api.onchange('partner_id') def onchange_partner_id(self): addr = self.partner_id.address_get(['invoice']) @@ -815,6 +823,9 @@ class HotelReservation(models.Model): else: vals.update({'state': 'confirm'}) record.write(vals) + record.reservation_line_ids.update({ + 'cancel_discount': 0 + }) if record.splitted: master_reservation = record.parent_reservation or record @@ -846,7 +857,9 @@ class HotelReservation(models.Model): for record in self: record.write({ 'state': 'cancelled', + 'cancelled_reason': record.compute_cancelation_reason() }) + record._compute_cancelled_discount() if record.splitted: master_reservation = record.parent_reservation or record splitted_reservs = self.env['hotel.reservation'].search([ @@ -861,6 +874,25 @@ class HotelReservation(models.Model): splitted_reservs.action_cancel() record.folio_id.compute_amount() + @api.multi + def compute_cancelation_reason(self): + self.ensure_one() + pricelist = self.pricelist_id + if pricelist and pricelist.cancelation_rule_id: + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + today = fields.Date.context_today(self.with_context( + tz=tz_hotel)) + days_diff = (fields.Date.from_string(self.real_checkin) - + fields.Date.from_string(today)).days + if days_diff < 0: + return 'noshow' + elif days_diff < pricelist.cancelation_rule_id.days_intime: + return 'late' + else: + return 'intime' + return False + @api.multi def draft(self): for record in self: @@ -904,11 +936,17 @@ class HotelReservation(models.Model): return True return False - @api.depends('reservation_line_ids.discount') + @api.depends('reservation_line_ids.discount', + 'reservation_line_ids.cancel_discount') def _compute_discount(self): for record in self: - record.discount = sum(line.price * ((line.discount or 0.0) * 0.01) \ - for line in record.reservation_line_ids) + discount = 0 + for line in record.reservation_line_ids: + first_discount = line.price * ((line.discount or 0.0) * 0.01) + price = line.price - first_discount + cancel_discount = price * ((line.cancel_discount or 0.0) * 0.01) + discount += first_discount + cancel_discount + record.discount = discount @api.depends('reservation_line_ids.price', 'discount', 'tax_ids') def _compute_amount_reservation(self): @@ -927,6 +965,51 @@ class HotelReservation(models.Model): 'price_subtotal': taxes['total_excluded'], }) + @api.multi + def _compute_cancelled_discount(self): + self.ensure_one() + pricelist = self.pricelist_id + if self.state == 'cancelled': + if self.cancelled_reason and pricelist and pricelist.cancelation_rule_id: + date_start_dt = fields.Date.from_string(self.real_checkin or self.checkin) + date_end_dt = fields.Date.from_string(self.real_checkout or self.checkout) + days = abs((date_end_dt - date_start_dt).days) + rule = pricelist.cancelation_rule_id + if self.cancelled_reason == 'late': + discount = 100 - rule.penalty_late + if rule.apply_on_late == 'first': + days = 1 + elif rule.apply_on_late == 'days': + days = rule.days_late + elif self.cancelled_reason == 'noshow': + discount = 100 - rule.penalty_noshow + if rule.apply_on_noshow == 'first': + days = 1 + elif rule.apply_on_noshow == 'days': + days = rule.days_late - 1 + elif self.cancelled_reason == 'intime': + discount = 100 + + checkin = self.real_checkin or self.checkin + dates = [] + for i in range(0, days): + dates.append((fields.Date.from_string(checkin) + timedelta(days=i)).strftime( + DEFAULT_SERVER_DATE_FORMAT)) + self.reservation_line_ids.filtered(lambda r: r.date in dates).update({ + 'cancel_discount': discount + }) + self.reservation_line_ids.filtered(lambda r: r.date not in dates).update({ + 'cancel_discount': 100 + }) + else: + self.reservation_line_ids.update({ + 'cancel_discount': 0 + }) + else: + self.reservation_line_ids.update({ + 'cancel_discount': 0 + }) + @api.model def prepare_reservation_lines(self, dfrom, days, pricelist_id, vals=False, update_old_prices=False): total_price = 0.0 @@ -1164,7 +1247,6 @@ class HotelReservation(models.Model): 'price_total': tprice[1], 'parent_reservation': parent_res.id, 'room_type_id': parent_res.room_type_id.id, - 'discount': parent_res.discount, 'state': parent_res.state, 'reservation_line_ids': reservation_lines[1], 'preconfirm': False, diff --git a/hotel/models/hotel_reservation_line.py b/hotel/models/hotel_reservation_line.py index 11432fe80..afce0fe0f 100644 --- a/hotel/models/hotel_reservation_line.py +++ b/hotel/models/hotel_reservation_line.py @@ -22,9 +22,13 @@ class HotelReservationLine(models.Model): ondelete='cascade', required=True, copy=False) date = fields.Date('Date') + state = fields.Selection(related='reservation_id.state') price = fields.Float( string='Price', digits=dp.get_precision('Product Price')) + cancel_discount = fields.Float( + string='Cancel Discount (%)', + digits=dp.get_precision('Discount'), default=0.0) discount = fields.Float( string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) diff --git a/hotel/models/inherited_product_pricelist.py b/hotel/models/inherited_product_pricelist.py index cae65df63..2296f9855 100644 --- a/hotel/models/inherited_product_pricelist.py +++ b/hotel/models/inherited_product_pricelist.py @@ -11,6 +11,9 @@ class ProductPricelist(models.Model): pricelist_type = fields.Selection([ ('daily', 'Daily Plan'), ], string='Pricelist Type', default='daily') + cancelation_rule_id = fields.Many2one( + 'hotel.cancelation.rule', + string="Cancelation Policy") @api.multi @api.depends('name') diff --git a/hotel/views/hotel_cancelation_rule_views.xml b/hotel/views/hotel_cancelation_rule_views.xml new file mode 100644 index 000000000..d765373bc --- /dev/null +++ b/hotel/views/hotel_cancelation_rule_views.xml @@ -0,0 +1,63 @@ + + + + + + + hotel.cancelation.rule.form + hotel.cancelation.rule + +
+ +

+ + +

+ + + + + + + + + + + + +
+
+
+
+ + + + hotel.cancelation.rule.tree + hotel.cancelation.rule + + + + + + + + + + + + + + + Cancelation Rules + hotel.cancelation.rule + form + tree,form + + + + +
diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index 9dbbd7d5c..61086d516 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -34,7 +34,7 @@ + statusbar_visible="draft,sent,sale,done" /> diff --git a/hotel/views/hotel_reservation_views.xml b/hotel/views/hotel_reservation_views.xml index d50458d53..871e6f252 100644 --- a/hotel/views/hotel_reservation_views.xml +++ b/hotel/views/hotel_reservation_views.xml @@ -16,7 +16,7 @@ hotel.reservation 20 -
+
@@ -61,21 +61,21 @@ attrs="{'invisible':[('splitted', '=', False)]}" />
- - - + + + +