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,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -499,10 +499,7 @@
|
||||
<field name="price" />
|
||||
<field name="discount" />
|
||||
<field name="price_day_total" />
|
||||
<field
|
||||
name="cancel_discount"
|
||||
attrs="{'column_invisible': [('parent.state','!=','cancel')]}"
|
||||
/>
|
||||
<field name="cancel_discount" invisible="1" />
|
||||
<field name="sale_channel_id" />
|
||||
<field name="default_invoice_to" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<field name="document_partner_required" />
|
||||
<field name="check_min_partner_data_invoice" />
|
||||
<field name="pms_invoice_downpayment_policy" />
|
||||
<field name="cancel_penalty_product_id" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user