diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index d69a40646..d4abb4871 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -381,6 +381,12 @@ class PmsReservation(models.Model): ], tracking=True, ) + cancel_datetime = fields.Datetime( + string="Cancel Date", + help="Date when the reservation was cancelled", + readonly=True, + copy=False, + ) reservation_type = fields.Selection( string="Reservation Type", help="Type of reservations. It can be 'normal', 'staff' or 'out of service", @@ -411,10 +417,13 @@ class PmsReservation(models.Model): help="Field indicating type of cancellation. " "It can be 'late', 'intime' or 'noshow'", copy=False, - compute="_compute_cancelled_reason", - readonly=False, store=True, - selection=[("late", "Late"), ("intime", "In time"), ("noshow", "No Show")], + selection=[ + ("late", "Late"), + ("intime", "In time"), + ("noshow", "No Show"), + ("modified", "Modified"), + ], tracking=True, ) @@ -723,6 +732,11 @@ class PmsReservation(models.Model): lang = fields.Many2one( string="Language", comodel_name="res.lang", compute="_compute_lang" ) + blocked = fields.Boolean( + string="Blocked", + help="Indicates if the reservation is blocked", + default=False, + ) @api.depends("folio_id", "folio_id.external_reference") def _compute_external_reference(self): @@ -2098,6 +2112,17 @@ class PmsReservation(models.Model): return record def write(self, vals): + if ( + any([record.blocked for record in self]) + and not self.env.context.get("force_write_blocked") + and ( + "checkin" in vals + or "checkout" in vals + or "room_type_id" in vals + or "reservation_line_ids" in vals + ) + ): + raise ValidationError(_("Blocked reservations can't be modified")) folios_to_update_channel = self.env["pms.folio"] lines_to_update_channel = self.env["pms.reservation.line"] services_to_update_channel = self.env["pms.service"] @@ -2248,6 +2273,8 @@ class PmsReservation(models.Model): vals.update({"state": "confirm"}) record.write(vals) record.reservation_line_ids.update({"cancel_discount": 0}) + # Unlink penalty service if exist + record.service_ids.filtered(lambda s: s.is_cancel_penalty).unlink() if record.folio_id.state != "confirm": record.folio_id.action_confirm() return True @@ -2259,22 +2286,25 @@ class PmsReservation(models.Model): raise UserError(_("This reservation cannot be cancelled")) else: record.state = "cancel" + record._check_cancel_penalty() + record.cancel_datetime = fields.Datetime.now() record.folio_id._compute_amount() def action_assign(self): for record in self: record.to_assign = False - @api.depends("state") - def _compute_cancelled_reason(self): + def _check_cancel_penalty(self): for record in self: # self.ensure_one() if record.state == "cancel": pricelist = record.pricelist_id - if record._context.get("no_penalty", False): - record.cancelled_reason = "intime" + if record._context.get("modified", False): + record.cancelled_reason = "modified" _logger.info("Modified Reservation - No Penalty") + continue elif pricelist and pricelist.cancelation_rule_id: + rule = pricelist.cancelation_rule_id tz_property = record.pms_property_id.tz today = fields.Date.context_today( record.with_context(tz=tz_property) @@ -2285,10 +2315,80 @@ class PmsReservation(models.Model): ).days if days_diff < 0: record.cancelled_reason = "noshow" + penalty_percent = rule.penalty_noshow + if rule.apply_on_noshow == "first": + days = 1 + elif rule.apply_on_noshow == "days": + days = rule.days_late - 1 elif days_diff < pricelist.cancelation_rule_id.days_intime: record.cancelled_reason = "late" + penalty_percent = rule.penalty_late + if rule.apply_on_late == "first": + days = 1 + elif rule.apply_on_late == "days": + days = rule.days_late else: record.cancelled_reason = "intime" + penalty_percent = 0 + days = 0 + # Generate a penalty service in the reservation + if penalty_percent: + dates = [] + for i in range(0, days): + dates.append( + fields.Date.from_string( + fields.Date.from_string(record.checkin) + + datetime.timedelta(days=i) + ) + ) + amount_penalty = ( + sum( + record.reservation_line_ids.filtered( + lambda l: fields.Date.from_string(l.date) in dates + ).mapped("price") + ) + * penalty_percent + / 100 + ) + if not amount_penalty: + return + if not record.company_id.cancel_penalty_product_id: + # Create a penalty product + record.company_id.cancel_penalty_product_id = self.env[ + "product.product" + ].create( + { + "name": _("Cancel Penalty"), + "type": "service", + } + ) + penalty_product = record.company_id.cancel_penalty_product_id + default_channel_id = ( + self.env["pms.sale.channel"] + .search([("channel_type", "=", "direct")], limit=1) + .id, + ) + self.env["pms.service"].create( + { + "product_id": penalty_product.id, + "folio_id": record.folio_id.id, + "reservation_id": record.id, + "name": penalty_product.name, + "sale_channel_origin_id": default_channel_id, + "service_line_ids": [ + ( + 0, + 0, + { + "product_id": penalty_product.id, + "day_qty": 1, + "price_unit": amount_penalty, + "date": fields.Date.today(), + }, + ) + ], + } + ) else: record.cancelled_reason = False diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py index 70c00aa4d..a6bfd2d59 100644 --- a/pms/models/pms_reservation_line.py +++ b/pms/models/pms_reservation_line.py @@ -74,7 +74,7 @@ class PmsReservationLine(models.Model): cancel_discount = fields.Float( string="Cancelation Discount (%)", help="", - readonly=False, + readonly=True, default=0.0, store=True, digits=("Discount"), @@ -409,53 +409,10 @@ class PmsReservationLine(models.Model): @api.depends("reservation_id.cancelled_reason") def _compute_cancel_discount(self): for line in self: - line.cancel_discount = 0 - reservation = line.reservation_id - pricelist = reservation.pricelist_id - if reservation.state == "cancel": - if ( - reservation.cancelled_reason - and pricelist - and pricelist.cancelation_rule_id - ): - checkin = fields.Date.from_string(reservation.checkin) - checkout = fields.Date.from_string(reservation.checkout) - days = abs((checkin - checkout).days) - rule = pricelist.cancelation_rule_id - discount = 0 - if reservation.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 reservation.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 reservation.cancelled_reason == "intime": - discount = 100 - - dates = [] - for i in range(0, days): - dates.append( - fields.Date.from_string( - fields.Date.from_string(checkin) - + datetime.timedelta(days=i) - ) - ) - reservation.reservation_line_ids.filtered( - lambda r: r.date in dates - ).update({"cancel_discount": discount}) - reservation.reservation_line_ids.filtered( - lambda r: r.date not in dates - ).update({"cancel_discount": 100}) - else: - reservation.reservation_line_ids.update({"cancel_discount": 0}) + if line.state == "cancel": + line.cancel_discount = 100 else: - reservation.reservation_line_ids.update({"cancel_discount": 0}) + line.cancel_discount = 0 @api.depends("room_id", "pms_property_id", "date", "occupies_availability") def _compute_avail_id(self): @@ -548,6 +505,16 @@ class PmsReservationLine(models.Model): elif not record.default_invoice_to: record.default_invoice_to = False + def write(self, vals): + if ( + any([record.reservation_id.blocked for record in self]) + and not self.env.context.get("force_write_blocked") + and ("date" in vals or "price" in vals) + ): + raise ValidationError(_("Blocked reservations can't be modified")) + res = super().write(vals) + return res + # Constraints and onchanges @api.constrains("date") def constrains_duplicated_date(self): diff --git a/pms/models/pms_service.py b/pms/models/pms_service.py index e91d3791b..98c4e96dc 100644 --- a/pms/models/pms_service.py +++ b/pms/models/pms_service.py @@ -203,6 +203,12 @@ class PmsService(models.Model): comodel_name="res.partner", ondelete="restrict", ) + is_cancel_penalty = fields.Boolean( + string="Is Cancel Penalty", + help="Indicates if the service is a cancel penalty", + readonly=True, + compute="_compute_is_cancel_penalty", + ) # Compute and Search methods @api.depends("product_id") @@ -444,6 +450,13 @@ class PmsService(models.Model): elif not record.default_invoice_to: record.default_invoice_to = False + def _compute_is_cancel_penalty(self): + for record in self: + if record.product_id == record.company_id.cancel_penalty_product_id: + record.is_cancel_penalty = True + else: + record.is_cancel_penalty = False + def name_get(self): result = [] for rec in self: diff --git a/pms/models/pms_service_line.py b/pms/models/pms_service_line.py index f8e15648d..7b34693f8 100644 --- a/pms/models/pms_service_line.py +++ b/pms/models/pms_service_line.py @@ -219,7 +219,7 @@ class PmsServiceLine(models.Model): lambda l: l.date == consumed_date ).cancel_discount ) - else: + elif not line.service_id.is_cancel_penalty: line.cancel_discount = 100 else: line.cancel_discount = 0 diff --git a/pms/models/res_company.py b/pms/models/res_company.py index d5556d80a..ce2ca2be5 100644 --- a/pms/models/res_company.py +++ b/pms/models/res_company.py @@ -14,13 +14,11 @@ class ResCompany(models.Model): inverse_name="company_id", ) - url_advert = fields.Char(string="Url Advert", - help="Url to identify the ad") + url_advert = fields.Char(string="Url Advert", help="Url to identify the ad") privacy_policy = fields.Html( string="Privacy Policy", - help="Authorization by the user for the" - "manage of their personal data", + help="Authorization by the user for the" "manage of their personal data", ) check_min_partner_data_invoice = fields.Boolean( @@ -52,3 +50,10 @@ class ResCompany(models.Model): to create a new contact""", default=False, ) + + cancel_penalty_product_id = fields.Many2one( + string="Cancel penalty product", + help="Product used to calculate the cancel penalty", + comodel_name="product.product", + ondelete="restrict", + ) diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index 5b630d6b0..c0082fb4b 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -499,10 +499,7 @@ - + diff --git a/pms/views/res_company_views.xml b/pms/views/res_company_views.xml index 9a4d072c1..218e9855e 100644 --- a/pms/views/res_company_views.xml +++ b/pms/views/res_company_views.xml @@ -18,6 +18,7 @@ +