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