mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[IMP]pms: Manager block reservations and refact cancel penalties
This commit is contained in:
@@ -381,6 +381,12 @@ class PmsReservation(models.Model):
|
|||||||
],
|
],
|
||||||
tracking=True,
|
tracking=True,
|
||||||
)
|
)
|
||||||
|
cancel_datetime = fields.Datetime(
|
||||||
|
string="Cancel Date",
|
||||||
|
help="Date when the reservation was cancelled",
|
||||||
|
readonly=True,
|
||||||
|
copy=False,
|
||||||
|
)
|
||||||
reservation_type = fields.Selection(
|
reservation_type = fields.Selection(
|
||||||
string="Reservation Type",
|
string="Reservation Type",
|
||||||
help="Type of reservations. It can be 'normal', 'staff' or 'out of service",
|
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. "
|
help="Field indicating type of cancellation. "
|
||||||
"It can be 'late', 'intime' or 'noshow'",
|
"It can be 'late', 'intime' or 'noshow'",
|
||||||
copy=False,
|
copy=False,
|
||||||
compute="_compute_cancelled_reason",
|
|
||||||
readonly=False,
|
|
||||||
store=True,
|
store=True,
|
||||||
selection=[("late", "Late"), ("intime", "In time"), ("noshow", "No Show")],
|
selection=[
|
||||||
|
("late", "Late"),
|
||||||
|
("intime", "In time"),
|
||||||
|
("noshow", "No Show"),
|
||||||
|
("modified", "Modified"),
|
||||||
|
],
|
||||||
tracking=True,
|
tracking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -723,6 +732,11 @@ class PmsReservation(models.Model):
|
|||||||
lang = fields.Many2one(
|
lang = fields.Many2one(
|
||||||
string="Language", comodel_name="res.lang", compute="_compute_lang"
|
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")
|
@api.depends("folio_id", "folio_id.external_reference")
|
||||||
def _compute_external_reference(self):
|
def _compute_external_reference(self):
|
||||||
@@ -2098,6 +2112,17 @@ class PmsReservation(models.Model):
|
|||||||
return record
|
return record
|
||||||
|
|
||||||
def write(self, vals):
|
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"]
|
folios_to_update_channel = self.env["pms.folio"]
|
||||||
lines_to_update_channel = self.env["pms.reservation.line"]
|
lines_to_update_channel = self.env["pms.reservation.line"]
|
||||||
services_to_update_channel = self.env["pms.service"]
|
services_to_update_channel = self.env["pms.service"]
|
||||||
@@ -2248,6 +2273,8 @@ class PmsReservation(models.Model):
|
|||||||
vals.update({"state": "confirm"})
|
vals.update({"state": "confirm"})
|
||||||
record.write(vals)
|
record.write(vals)
|
||||||
record.reservation_line_ids.update({"cancel_discount": 0})
|
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":
|
if record.folio_id.state != "confirm":
|
||||||
record.folio_id.action_confirm()
|
record.folio_id.action_confirm()
|
||||||
return True
|
return True
|
||||||
@@ -2259,22 +2286,25 @@ class PmsReservation(models.Model):
|
|||||||
raise UserError(_("This reservation cannot be cancelled"))
|
raise UserError(_("This reservation cannot be cancelled"))
|
||||||
else:
|
else:
|
||||||
record.state = "cancel"
|
record.state = "cancel"
|
||||||
|
record._check_cancel_penalty()
|
||||||
|
record.cancel_datetime = fields.Datetime.now()
|
||||||
record.folio_id._compute_amount()
|
record.folio_id._compute_amount()
|
||||||
|
|
||||||
def action_assign(self):
|
def action_assign(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
record.to_assign = False
|
record.to_assign = False
|
||||||
|
|
||||||
@api.depends("state")
|
def _check_cancel_penalty(self):
|
||||||
def _compute_cancelled_reason(self):
|
|
||||||
for record in self:
|
for record in self:
|
||||||
# self.ensure_one()
|
# self.ensure_one()
|
||||||
if record.state == "cancel":
|
if record.state == "cancel":
|
||||||
pricelist = record.pricelist_id
|
pricelist = record.pricelist_id
|
||||||
if record._context.get("no_penalty", False):
|
if record._context.get("modified", False):
|
||||||
record.cancelled_reason = "intime"
|
record.cancelled_reason = "modified"
|
||||||
_logger.info("Modified Reservation - No Penalty")
|
_logger.info("Modified Reservation - No Penalty")
|
||||||
|
continue
|
||||||
elif pricelist and pricelist.cancelation_rule_id:
|
elif pricelist and pricelist.cancelation_rule_id:
|
||||||
|
rule = pricelist.cancelation_rule_id
|
||||||
tz_property = record.pms_property_id.tz
|
tz_property = record.pms_property_id.tz
|
||||||
today = fields.Date.context_today(
|
today = fields.Date.context_today(
|
||||||
record.with_context(tz=tz_property)
|
record.with_context(tz=tz_property)
|
||||||
@@ -2285,10 +2315,80 @@ class PmsReservation(models.Model):
|
|||||||
).days
|
).days
|
||||||
if days_diff < 0:
|
if days_diff < 0:
|
||||||
record.cancelled_reason = "noshow"
|
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:
|
elif days_diff < pricelist.cancelation_rule_id.days_intime:
|
||||||
record.cancelled_reason = "late"
|
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:
|
else:
|
||||||
record.cancelled_reason = "intime"
|
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:
|
else:
|
||||||
record.cancelled_reason = False
|
record.cancelled_reason = False
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class PmsReservationLine(models.Model):
|
|||||||
cancel_discount = fields.Float(
|
cancel_discount = fields.Float(
|
||||||
string="Cancelation Discount (%)",
|
string="Cancelation Discount (%)",
|
||||||
help="",
|
help="",
|
||||||
readonly=False,
|
readonly=True,
|
||||||
default=0.0,
|
default=0.0,
|
||||||
store=True,
|
store=True,
|
||||||
digits=("Discount"),
|
digits=("Discount"),
|
||||||
@@ -409,53 +409,10 @@ class PmsReservationLine(models.Model):
|
|||||||
@api.depends("reservation_id.cancelled_reason")
|
@api.depends("reservation_id.cancelled_reason")
|
||||||
def _compute_cancel_discount(self):
|
def _compute_cancel_discount(self):
|
||||||
for line in self:
|
for line in self:
|
||||||
line.cancel_discount = 0
|
if line.state == "cancel":
|
||||||
reservation = line.reservation_id
|
line.cancel_discount = 100
|
||||||
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})
|
|
||||||
else:
|
else:
|
||||||
reservation.reservation_line_ids.update({"cancel_discount": 0})
|
line.cancel_discount = 0
|
||||||
|
|
||||||
@api.depends("room_id", "pms_property_id", "date", "occupies_availability")
|
@api.depends("room_id", "pms_property_id", "date", "occupies_availability")
|
||||||
def _compute_avail_id(self):
|
def _compute_avail_id(self):
|
||||||
@@ -548,6 +505,16 @@ class PmsReservationLine(models.Model):
|
|||||||
elif not record.default_invoice_to:
|
elif not record.default_invoice_to:
|
||||||
record.default_invoice_to = False
|
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
|
# Constraints and onchanges
|
||||||
@api.constrains("date")
|
@api.constrains("date")
|
||||||
def constrains_duplicated_date(self):
|
def constrains_duplicated_date(self):
|
||||||
|
|||||||
@@ -203,6 +203,12 @@ class PmsService(models.Model):
|
|||||||
comodel_name="res.partner",
|
comodel_name="res.partner",
|
||||||
ondelete="restrict",
|
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
|
# Compute and Search methods
|
||||||
@api.depends("product_id")
|
@api.depends("product_id")
|
||||||
@@ -444,6 +450,13 @@ class PmsService(models.Model):
|
|||||||
elif not record.default_invoice_to:
|
elif not record.default_invoice_to:
|
||||||
record.default_invoice_to = False
|
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):
|
def name_get(self):
|
||||||
result = []
|
result = []
|
||||||
for rec in self:
|
for rec in self:
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ class PmsServiceLine(models.Model):
|
|||||||
lambda l: l.date == consumed_date
|
lambda l: l.date == consumed_date
|
||||||
).cancel_discount
|
).cancel_discount
|
||||||
)
|
)
|
||||||
else:
|
elif not line.service_id.is_cancel_penalty:
|
||||||
line.cancel_discount = 100
|
line.cancel_discount = 100
|
||||||
else:
|
else:
|
||||||
line.cancel_discount = 0
|
line.cancel_discount = 0
|
||||||
|
|||||||
@@ -14,13 +14,11 @@ class ResCompany(models.Model):
|
|||||||
inverse_name="company_id",
|
inverse_name="company_id",
|
||||||
)
|
)
|
||||||
|
|
||||||
url_advert = fields.Char(string="Url Advert",
|
url_advert = fields.Char(string="Url Advert", help="Url to identify the ad")
|
||||||
help="Url to identify the ad")
|
|
||||||
|
|
||||||
privacy_policy = fields.Html(
|
privacy_policy = fields.Html(
|
||||||
string="Privacy Policy",
|
string="Privacy Policy",
|
||||||
help="Authorization by the user for the"
|
help="Authorization by the user for the" "manage of their personal data",
|
||||||
"manage of their personal data",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
check_min_partner_data_invoice = fields.Boolean(
|
check_min_partner_data_invoice = fields.Boolean(
|
||||||
@@ -52,3 +50,10 @@ class ResCompany(models.Model):
|
|||||||
to create a new contact""",
|
to create a new contact""",
|
||||||
default=False,
|
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",
|
||||||
|
)
|
||||||
|
|||||||
@@ -499,10 +499,7 @@
|
|||||||
<field name="price" />
|
<field name="price" />
|
||||||
<field name="discount" />
|
<field name="discount" />
|
||||||
<field name="price_day_total" />
|
<field name="price_day_total" />
|
||||||
<field
|
<field name="cancel_discount" invisible="1" />
|
||||||
name="cancel_discount"
|
|
||||||
attrs="{'column_invisible': [('parent.state','!=','cancel')]}"
|
|
||||||
/>
|
|
||||||
<field name="sale_channel_id" />
|
<field name="sale_channel_id" />
|
||||||
<field name="default_invoice_to" />
|
<field name="default_invoice_to" />
|
||||||
<field name="pms_property_id" invisible="1" />
|
<field name="pms_property_id" invisible="1" />
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<field name="document_partner_required" />
|
<field name="document_partner_required" />
|
||||||
<field name="check_min_partner_data_invoice" />
|
<field name="check_min_partner_data_invoice" />
|
||||||
<field name="pms_invoice_downpayment_policy" />
|
<field name="pms_invoice_downpayment_policy" />
|
||||||
|
<field name="cancel_penalty_product_id" />
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
Reference in New Issue
Block a user