[ADD] 14.0 pms real avail compute (#60)

* [ADD]pms: avail real

* [ADD] Wizards adaptation multiproperty & new real avail model

* [ADD] Tests Wizard avail
This commit is contained in:
Darío Lodeiros
2021-03-01 12:04:19 +01:00
committed by GitHub
parent 71d5aa8446
commit 965a03881c
19 changed files with 608 additions and 170 deletions

View File

@@ -201,7 +201,7 @@
<field name="name">Open Talk Away Room</field>
<field name="room_type_id" ref="pms_room_type_4" />
<field name="floor_id" ref="pms_floor_0" />
<field name="capacity">1</field>
<field name="capacity">10</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<!-- product.product for pms services -->

View File

@@ -46,3 +46,4 @@ from . import folio_sale_line
from . import account_bank_statement_line
from . import account_bank_statement
from . import account_journal
from . import pms_room_type_availability

View File

@@ -598,6 +598,8 @@ class PmsReservation(models.Model):
@api.depends(
"reservation_line_ids.date",
"reservation_line_ids.room_id",
"reservation_line_ids.occupies_availability",
"overbooking",
"state",
"preferred_room_id",
@@ -618,7 +620,8 @@ class PmsReservation(models.Model):
checkout=reservation.checkout,
room_type_id=False, # Allow chosen any available room
current_lines=reservation.reservation_line_ids.ids,
pricelist=reservation.pricelist_id.id,
pricelist_id=reservation.pricelist_id.id,
pms_property_id=reservation.pms_property_id.id,
)
reservation.allowed_room_ids = rooms_available
@@ -1314,7 +1317,8 @@ class PmsReservation(models.Model):
checkin=self.checkin,
checkout=self.checkout,
current_lines=self.reservation_line_ids.ids,
pricelist=self.pricelist_id.id,
pricelist_id=self.pricelist_id.id,
pms_property_id=self.pms_property_id.id,
)
# REVIEW: check capacity room
return {
@@ -1358,7 +1362,8 @@ class PmsReservation(models.Model):
self.checkin,
self.checkout,
room_type_id=self.room_type_id.id,
pricelist=self.pricelist_id.id,
pricelist_id=self.pricelist_id.id,
pms_property_id=self.pms_property_id.id,
)
if self.preferred_room_id.id in rooms_available.ids:
default["preferred_room_id"] = self.preferred_room_id.id
@@ -1410,7 +1415,8 @@ class PmsReservation(models.Model):
checkin=self.checkin,
checkout=self.checkout,
room_type_id=self.room_type_id.id or False,
pricelist=self.pricelist_id.id,
pricelist_id=self.pricelist_id.id,
pms_property_id=self.pms_property_id.id,
)
if rooms_available:
room_chosen = rooms_available[0]

View File

@@ -78,6 +78,14 @@ class PmsReservationLine(models.Model):
store=True,
readonly=False,
)
avail_id = fields.Many2one(
string="Availability Day",
comodel_name="pms.room.type.availability",
ondelete="restrict",
compute="_compute_avail_id",
store=True,
)
discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0)
occupies_availability = fields.Boolean(
string="Occupies",
@@ -108,7 +116,7 @@ class PmsReservationLine(models.Model):
key=lambda r: (r.reservation_id, r.date)
):
reservation = line.reservation_id
if reservation.preferred_room_id or not line.room_id:
if reservation.preferred_room_id != line.room_id or not line.room_id:
# If reservation has a preferred_room_id We can allow
# select room_id regardless room_type_id selected on reservation
free_room_select = True if reservation.preferred_room_id else False
@@ -122,7 +130,8 @@ class PmsReservationLine(models.Model):
if not free_room_select
else False,
current_lines=line.reservation_id.reservation_line_ids.ids,
pricelist=line.reservation_id.pricelist_id.id,
pricelist_id=line.reservation_id.pricelist_id.id,
pms_property_id=line.pms_property_id.id,
)
# if there is availability for the entire stay
if rooms_available:
@@ -154,6 +163,7 @@ class PmsReservationLine(models.Model):
room_type_id=line.reservation_id.room_type_id.id,
current_lines=line._origin.reservation_id.reservation_line_ids.ids,
pricelist=line.reservation_id.pricelist_id,
pms_property_id=line.pms_property_id.id,
):
raise ValidationError(
_("%s: No room type available")
@@ -166,9 +176,11 @@ class PmsReservationLine(models.Model):
# we go through the rooms of the type
for room in self.env["pms.room"].search(
[("room_type_id", "=", reservation.room_type_id.id)]
[
("room_type_id", "=", reservation.room_type_id.id),
("pms_property_id", "=", reservation.pms_property_id.id),
]
):
# we iterate the dates from the date of the line to the checkout
for date_iterator in [
line.date + datetime.timedelta(days=x)
@@ -391,6 +403,35 @@ class PmsReservationLine(models.Model):
# else:
# reservation.reservation_line_ids.update({"cancel_discount": 0})
@api.depends("room_id", "pms_property_id", "date", "occupies_availability")
def _compute_avail_id(self):
for record in self:
if (
record.room_id.room_type_id
and record.date
and record.pms_property_id
and record.occupies_availability
):
avail = self.env["pms.room.type.availability"].search(
[
("date", "=", record.date),
("room_type_id", "=", record.room_id.room_type_id.id),
("pms_property_id", "=", record.pms_property_id.id),
]
)
if avail:
record.avail_id = avail.id
else:
record.avail_id = self.env["pms.room.type.availability"].create(
{
"date": record.date,
"room_type_id": record.room_id.room_type_id.id,
"pms_property_id": record.pms_property_id.id,
}
)
else:
record.avail_id = False
# Constraints and onchanges
@api.constrains("date")
def constrains_duplicated_date(self):

View File

@@ -18,6 +18,25 @@ class PmsRoomType(models.Model):
_inherits = {"product.product": "product_id"}
_order = "sequence, code_type, name"
# Defaults and Gets
def name_get(self):
result = []
for room_type in self:
name = room_type.name
if self._context.get("checkin") and self._context.get("checkin"):
avail = self.env[
"pms.room.type.availability.plan"
].get_count_rooms_available(
checkin=self._context.get("checkin"),
checkout=self._context.get("checkout"),
room_type_id=room_type.id,
pms_property_id=self._context.get("pms_property_id") or False,
pricelist_id=self._context.get("pricelist_id") or False,
)
name += " (%s)" % avail
result.append((room_type.id, name))
return result
# Fields declaration
product_id = fields.Many2one(
"product.product",

View File

@@ -0,0 +1,88 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsRoomTypeAvailability(models.Model):
_name = "pms.room.type.availability"
_description = "Room type availability per day"
room_type_id = fields.Many2one(
comodel_name="pms.room.type",
string="Room Type",
required=True,
ondelete="cascade",
readonly=True,
)
date = fields.Date(
string="Date",
required=True,
readonly=True,
)
pms_property_id = fields.Many2one(
comodel_name="pms.property",
string="Property",
ondelete="restrict",
required=True,
readonly=True,
)
reservation_line_ids = fields.One2many(
string="Reservation Lines",
comodel_name="pms.reservation.line",
inverse_name="avail_id",
readonly=True,
)
real_avail = fields.Integer(
compute="_compute_real_avail",
store=True,
readonly=True,
)
_sql_constraints = [
(
"room_type_registry_unique",
"unique(room_type_id, date, pms_property_id)",
"Only can exists one availability in the same \
day for the same room type!",
)
]
@api.depends("reservation_line_ids.occupies_availability")
def _compute_real_avail(self):
for record in self:
Rooms = self.env["pms.room"]
RoomLines = self.env["pms.reservation.line"]
total_rooms = Rooms.search_count(
[
("room_type_id", "=", record.room_type_id.id),
("pms_property_id", "=", record.pms_property_id.id),
]
)
room_ids = record.room_type_id.mapped("room_ids.id")
rooms_not_avail = RoomLines.search_count(
[
("date", "=", record.date),
("room_id", "in", room_ids),
("pms_property_id", "=", record.pms_property_id.id),
("occupies_availability", "=", True),
# ("id", "not in", current_lines if current_lines else []),
]
)
record.real_avail = total_rooms - rooms_not_avail
@api.constrains(
"room_type_id",
"pms_property_id",
)
def _check_property_integrity(self):
for rec in self:
if rec.pms_property_id and rec.room_type_id:
if (
rec.room_type_id.pms_property_ids.ids
and rec.pms_property_id.id
not in rec.room_type_id.pms_property_ids.ids
):
raise ValidationError(
_("Property not allowed on availability day compute")
)

View File

@@ -2,7 +2,9 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
class PmsRoomTypeAvailability(models.Model):
@@ -69,66 +71,157 @@ class PmsRoomTypeAvailability(models.Model):
checkout,
room_type_id=False,
current_lines=False,
pricelist=False,
pricelist_id=False,
pms_property_id=False,
):
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
rooms_not_avail = (
self.env["pms.reservation.line"]
.search(
[
("date", ">=", checkin),
("date", "<=", checkout - datetime.timedelta(1)),
("occupies_availability", "=", True),
("id", "not in", current_lines if current_lines else []),
]
)
.mapped("room_id.id")
free_rooms = self.get_real_free_rooms(
checkin, checkout, room_type_id, current_lines, pms_property_id
)
domain_rooms = [
("id", "not in", rooms_not_avail if len(rooms_not_avail) > 0 else []),
]
domain_rules = [
("date", ">=", checkin),
("date", "<=", checkout),
(
"date",
"<=",
checkout,
), # TODO: only closed_departure take account checkout date!
]
if pms_property_id:
domain_rooms.append(("pms_property_id", "=", pms_property_id))
domain_rules.append(("pms_property_id", "=", pms_property_id))
if room_type_id:
domain_rooms.append(("room_type_id", "=", room_type_id))
domain_rules.append(("room_type_id", "=", room_type_id))
if pricelist_id:
pricelist = self.env["product.pricelist"].browse(pricelist_id)
if pricelist and pricelist.availability_plan_id:
domain_rules.append(
("availability_plan_id", "=", pricelist.availability_plan_id.id)
)
rule_items = self.env["pms.room.type.availability.rule"].search(
domain_rules
)
free_rooms = self.env["pms.room"].search(domain_rooms)
if len(rule_items) > 0:
room_types_to_remove = []
for item in rule_items:
if self.any_rule_applies(checkin, checkout, item):
room_types_to_remove.append(item.room_type_id.id)
free_rooms = free_rooms.filtered(
lambda x: x.room_type_id.id not in room_types_to_remove
)
elif not pricelist:
raise ValidationError(_("Pricelist not found"))
return free_rooms.sorted(key=lambda r: r.sequence)
if pricelist:
def get_real_free_rooms(
self,
checkin,
checkout,
room_type_id=False,
current_lines=False,
pms_property_id=False,
):
Avail = self.env["pms.room.type.availability"]
if isinstance(checkin, str):
checkin = datetime.datetime.strptime(
checkin, DEFAULT_SERVER_DATE_FORMAT
).date()
if isinstance(checkout, str):
checkout = datetime.datetime.strptime(
checkout, DEFAULT_SERVER_DATE_FORMAT
).date()
domain = [
("date", ">=", checkin),
("date", "<=", checkout - datetime.timedelta(1)),
]
rooms_not_avail = (
Avail.search(domain)
.reservation_line_ids.filtered(
lambda l: l.id not in current_lines if current_lines else []
)
.room_id.ids
)
domain_rooms = [
("id", "not in", rooms_not_avail if rooms_not_avail else []),
]
if pms_property_id:
domain_rooms.append(("pms_property_id", "=", pms_property_id))
if room_type_id:
domain_rooms.append(("room_type_id", "=", room_type_id))
return self.env["pms.room"].search(domain_rooms)
@api.model
def get_count_rooms_available(
self,
checkin,
checkout,
room_type_id,
pms_property_id,
current_lines=False,
pricelist_id=False,
):
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
avail = self.get_count_real_free_rooms(
checkin, checkout, room_type_id, pms_property_id, current_lines
)
domain_rules = [
("date", ">=", checkin),
(
"date",
"<=",
checkout,
), # TODO: only closed_departure take account checkout date!
("room_type_id", "=", room_type_id),
("pms_property_id", "=", pms_property_id),
]
if pricelist_id:
pricelist = self.env["product.pricelist"].browse(pricelist_id)
if pricelist and pricelist.availability_plan_id:
domain_rules.append(
("availability_plan_id.pms_pricelist_ids", "=", pricelist)
("availability_plan_id", "=", pricelist.availability_plan_id.id)
)
rule_items = self.env["pms.room.type.availability.rule"].search(
domain_rules
)
if len(rule_items) > 0:
room_types_to_remove = []
for item in rule_items:
if self.any_rule_applies(checkin, checkout, item):
room_types_to_remove.append(item.room_type_id.id)
free_rooms = free_rooms.filtered(
lambda x: x.room_type_id.id not in room_types_to_remove
)
return 0
avail = min(rule_items.mapped("plan_avail"))
return avail
# if pms_property_id:
# free_rooms = free_rooms.filtered(
# lambda x: x.pms_property_id = pms_property_id
# )
return free_rooms.sorted(key=lambda r: r.sequence)
def get_count_real_free_rooms(
self,
checkin,
checkout,
room_type_id,
pms_property_id,
current_lines=False,
):
Avail = self.env["pms.room.type.availability"]
count_free_rooms = len(self.env["pms.room.type"].browse(room_type_id).room_ids)
if isinstance(checkin, str):
checkin = datetime.datetime.strptime(
checkin, DEFAULT_SERVER_DATE_FORMAT
).date()
if isinstance(checkout, str):
checkout = datetime.datetime.strptime(
checkout, DEFAULT_SERVER_DATE_FORMAT
).date()
for avail in Avail.search(
[
("date", ">=", checkin),
("date", "<=", checkout - datetime.timedelta(1)),
("room_type_id", "=", room_type_id),
("pms_property_id", "=", pms_property_id),
]
):
if avail.real_avail < count_free_rooms:
count_free_rooms = avail.real_avail
return count_free_rooms
@api.model
def splitted_availability(
@@ -138,7 +231,16 @@ class PmsRoomTypeAvailability(models.Model):
room_type_id=False,
current_lines=False,
pricelist=False,
pms_property_id=False,
):
if isinstance(checkin, str):
checkin = datetime.datetime.strptime(
checkin, DEFAULT_SERVER_DATE_FORMAT
).date()
if isinstance(checkout, str):
checkout = datetime.datetime.strptime(
checkout, DEFAULT_SERVER_DATE_FORMAT
).date()
for date_iterator in [
checkin + datetime.timedelta(days=x)
for x in range(0, (checkout - checkin).days)
@@ -148,7 +250,8 @@ class PmsRoomTypeAvailability(models.Model):
checkout=date_iterator + datetime.timedelta(1),
room_type_id=room_type_id,
current_lines=current_lines,
pricelist=pricelist.id,
pricelist_id=pricelist.id,
pms_property_id=pms_property_id,
)
if len(rooms_avail) < 1:
return False

View File

@@ -59,7 +59,6 @@ class PmsRoomTypeAvailabilityRule(models.Model):
compute="_compute_quota",
help="Generic Quota assigned.",
)
max_avail = fields.Integer(
string="Max. Availability",
store=True,
@@ -67,13 +66,12 @@ class PmsRoomTypeAvailabilityRule(models.Model):
compute="_compute_max_avail",
help="Maximum simultaneous availability on own Booking Engine.",
)
pms_property_id = fields.Many2one(
comodel_name="pms.property",
string="Property",
ondelete="restrict",
required=True,
)
allowed_property_ids = fields.Many2many(
"pms.property",
"allowed_availability_move_rel",
@@ -84,6 +82,22 @@ class PmsRoomTypeAvailabilityRule(models.Model):
readonly=True,
compute="_compute_allowed_property_ids",
)
avail_id = fields.Many2one(
string="Avail record",
comodel_name="pms.room.type.availability",
compute="_compute_avail_id",
store=True,
readonly=False,
ondelete="restrict",
)
real_avail = fields.Integer(
related="avail_id.real_avail",
store="True",
)
plan_avail = fields.Integer(
compute="_compute_plan_avail",
store="True",
)
_sql_constraints = [
(
@@ -94,6 +108,44 @@ class PmsRoomTypeAvailabilityRule(models.Model):
)
]
@api.depends("room_type_id", "date", "pms_property_id")
def _compute_avail_id(self):
for record in self:
if record.room_type_id and record.pms_property_id and record.date:
avail = self.env["pms.room.type.availability"].search(
[
("date", "=", record.date),
("room_type_id", "=", record.room_type_id.id),
("pms_property_id", "=", record.pms_property_id.id),
]
)
if avail:
record.avail_id = avail.id
else:
record.avail_id = self.env["pms.room.type.availability"].create(
{
"date": record.date,
"room_type_id": record.room_type_id.id,
"pms_property_id": record.pms_property_id.id,
}
)
else:
record.avail_id = False
@api.depends("quota", "max_avail", "real_avail")
def _compute_plan_avail(self):
for record in self.filtered("real_avail"):
real_avail = record.real_avail
plan_avail = min(
[
record.max_avail if record.max_avail >= 0 else real_avail,
record.quota if record.quota >= 0 else real_avail,
real_avail,
]
)
if not record.plan_avail or record.plan_avail != plan_avail:
record.plan_avail = plan_avail
@api.depends("room_type_id")
def _compute_quota(self):
for record in self:

View File

@@ -12,6 +12,7 @@ user_access_pms_room_type_class,user_access_pms_room_type_class,model_pms_room_t
user_access_pms_room,user_access_pms_room,model_pms_room,pms.group_pms_user,1,0,0,0
user_access_shared_pms_room,user_access_pms_shared_room,model_pms_shared_room,pms.group_pms_user,1,0,0,0
user_access_pms_room_type_availability_rule,user_access_pms_room_type_availability_rule,model_pms_room_type_availability_rule,pms.group_pms_user,1,0,0,0
user_access_pms_room_type_availability,user_access_pms_room_type_availability,model_pms_room_type_availability,pms.group_pms_user,1,1,1,0
user_access_pms_reservation,user_access_pms_reservation,model_pms_reservation,pms.group_pms_user,1,1,1,1
user_access_pms_folio,user_access_pms_folio,model_pms_folio,pms.group_pms_user,1,1,1,1
user_access_pms_room_type,user_access_pms_room_type,model_pms_room_type,pms.group_pms_user,1,0,0,0
@@ -38,6 +39,7 @@ manager_access_pms_room,manager_access_pms_room,model_pms_room,pms.group_pms_man
manager_access_pms_shared_room,manager_access_pms_shared_room,model_pms_shared_room,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_availability_rule,manager_access_pms_room_type_availability_rule,model_pms_room_type_availability_rule,pms.group_pms_manager,1,1,1,1
manager_access_pms_reservation,manager_access_pms_reservation,model_pms_reservation,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_availability,manager_access_pms_room_type_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,0
manager_access_pms_folio,manager_access_pms_folio,model_pms_folio,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type,manager_access_pms_room_type,model_pms_room_type,pms.group_pms_manager,1,1,1,1
manager_access_pms_board_service_room_type,manager_access_pms_board_service_room_type,model_pms_board_service_room_type,pms.group_pms_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
12 user_access_pms_room user_access_pms_room model_pms_room pms.group_pms_user 1 0 0 0
13 user_access_shared_pms_room user_access_pms_shared_room model_pms_shared_room pms.group_pms_user 1 0 0 0
14 user_access_pms_room_type_availability_rule user_access_pms_room_type_availability_rule model_pms_room_type_availability_rule pms.group_pms_user 1 0 0 0
15 user_access_pms_room_type_availability user_access_pms_room_type_availability model_pms_room_type_availability pms.group_pms_user 1 1 1 0
16 user_access_pms_reservation user_access_pms_reservation model_pms_reservation pms.group_pms_user 1 1 1 1
17 user_access_pms_folio user_access_pms_folio model_pms_folio pms.group_pms_user 1 1 1 1
18 user_access_pms_room_type user_access_pms_room_type model_pms_room_type pms.group_pms_user 1 0 0 0
39 manager_access_pms_shared_room manager_access_pms_shared_room model_pms_shared_room pms.group_pms_manager 1 1 1 1
40 manager_access_pms_room_type_availability_rule manager_access_pms_room_type_availability_rule model_pms_room_type_availability_rule pms.group_pms_manager 1 1 1 1
41 manager_access_pms_reservation manager_access_pms_reservation model_pms_reservation pms.group_pms_manager 1 1 1 1
42 manager_access_pms_room_type_availability manager_access_pms_room_type_availability model_pms_room_type_availability pms.group_pms_manager 1 1 1 0
43 manager_access_pms_folio manager_access_pms_folio model_pms_folio pms.group_pms_manager 1 1 1 1
44 manager_access_pms_room_type manager_access_pms_room_type model_pms_room_type pms.group_pms_manager 1 1 1 1
45 manager_access_pms_board_service_room_type manager_access_pms_board_service_room_type model_pms_board_service_room_type pms.group_pms_manager 1 1 1 1

View File

@@ -271,7 +271,6 @@ class TestPmsReservations(TestHotel):
}
)
r_test.flush()
# ASSERT
self.assertEqual(
expected_num_changes,

View File

@@ -262,6 +262,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True, # <- (1/2)
"pms_property_id": self.test_property.id,
}
)
# ACT
@@ -269,7 +270,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
checkin=fields.date.today(),
checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(),
# room_type_id=False, # <- (2/2)
pricelist=self.test_pricelist1.id,
pricelist_id=self.test_pricelist1.id,
)
# ASSERT
self.assertNotIn(
@@ -295,6 +296,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=0)).date(),
"pms_property_id": self.test_property.id,
}
)
@@ -410,7 +412,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
checkin=checkin,
checkout=checkout,
room_type_id=self.test_room_type_double.id,
pricelist=self.test_pricelist1.id,
pricelist_id=self.test_pricelist1.id,
)
# ASSERT
@@ -437,6 +439,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property.id,
}
)
checkin = datetime.datetime.now()
@@ -475,6 +478,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property.id,
}
)
@@ -537,6 +541,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_double.id,
"date": datetime.date.today(),
"quota": 1,
"pms_property_id": self.test_property.id,
}
)
r1 = self.env["pms.reservation"].create(
@@ -587,6 +592,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_double.id,
"date": datetime.date.today(),
"quota": test_quota,
"pms_property_id": self.test_property.id,
}
)
reservation = self.env["pms.reservation"].create(
@@ -687,7 +693,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
fields.datetime.today() + datetime.timedelta(days=2)
).date(),
room_type_id=self.test_room_type_special.id,
pricelist=self.test_pricelist1.id,
pricelist_id=self.test_pricelist1.id,
pms_property_id=p["property"],
)
# ASSERT
@@ -730,6 +736,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_special.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property1.id,
}
)
# Test cases when creating a availability_rule
@@ -794,6 +801,7 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
"room_type_id": self.test_room_type_special.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property1.id,
}
)
@@ -802,6 +810,3 @@ class TestPmsRoomTypeAvailabilityRules(TestHotel):
self.availability_rule1.allowed_property_ids.mapped("id"),
"error",
)
# plan 1 property 01 | rule property: False
# plan 1 property 02 | rule property: False

View File

@@ -150,7 +150,6 @@ class TestPmsWizardMassiveChanges(TestHotel):
# Set values for the wizard and the total price is correct
# Also check the discount is correctly applied to get
# the total folio price
# (no pricelist applied)
# ARRANGE
# common scenario
@@ -183,6 +182,8 @@ class TestPmsWizardMassiveChanges(TestHotel):
"start_date": checkin,
"end_date": checkout,
"partner_id": self.partner_id.id,
"pms_property_id": self.test_property.id,
"pricelist_id": self.test_pricelist.id,
}
)
@@ -206,7 +207,6 @@ class TestPmsWizardMassiveChanges(TestHotel):
)
lines_availability_test[0].num_rooms_selected = value
for discount in discounts:
with self.subTest(k=discount):
# ACT
@@ -265,6 +265,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
@@ -395,6 +396,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
@@ -444,6 +446,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
@@ -495,6 +498,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
@@ -552,6 +556,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
@@ -585,6 +590,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"room_type_id": self.test_room_type_double,
"partner_id": self.partner_id.id,
"pricelist_id": folio.pricelist_id.id,
"pms_property_id": self.test_property.id,
}
# ASSERT
@@ -593,7 +599,8 @@ class TestPmsWizardMassiveChanges(TestHotel):
with self.subTest(k=key):
self.assertEqual(
reservation[key].id
if key in ["folio_id", "partner_id", "pricelist_id"]
if key
in ["folio_id", "partner_id", "pricelist_id", "pms_property_id"]
else reservation[key],
vals[key],
"The value of " + key + " is not correctly established",
@@ -620,6 +627,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"discount": discount,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
@@ -655,3 +663,87 @@ class TestPmsWizardMassiveChanges(TestHotel):
discount * 100,
"The discount is not correctly established",
)
def test_check_quota_avail(self):
# TEST CASE
# Check avail on room type with quota
# ARRANGE
# common scenario
self.create_common_scenario()
# checkin & checkout
checkin = fields.date.today()
checkout = fields.date.today() + datetime.timedelta(days=1)
self.env["pms.room.type.availability.rule"].create(
{
"quota": 1,
"room_type_id": self.test_room_type_double.id,
"availability_plan_id": self.test_availability_plan.id,
"date": fields.date.today(),
"pms_property_id": self.test_property.id,
}
)
# create folio wizard with partner id => pricelist & start-end dates
wizard_folio = self.env["pms.folio.wizard"].create(
{
"start_date": checkin,
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
room_type_plan_avail = wizard_folio.availability_results.filtered(
lambda r: r.room_type_id.id == self.test_room_type_double.id
).num_rooms_available
# ASSERT
self.assertEqual(room_type_plan_avail, 1, "Quota not applied in Wizard Folio")
def test_check_min_stay_avail(self):
# TEST CASE
# Check avail on room type with quota
# ARRANGE
# common scenario
self.create_common_scenario()
# checkin & checkout
checkin = fields.date.today()
checkout = fields.date.today() + datetime.timedelta(days=1)
self.env["pms.room.type.availability.rule"].create(
{
"min_stay": 3,
"room_type_id": self.test_room_type_double.id,
"availability_plan_id": self.test_availability_plan.id,
"date": fields.date.today(),
"pms_property_id": self.test_property.id,
}
)
# create folio wizard with partner id => pricelist & start-end dates
wizard_folio = self.env["pms.folio.wizard"].create(
{
"start_date": checkin,
"end_date": checkout,
"partner_id": self.partner_id.id,
"pricelist_id": self.test_pricelist.id,
"pms_property_id": self.test_property.id,
}
)
wizard_folio.flush()
room_type_plan_avail = wizard_folio.availability_results.filtered(
lambda r: r.room_type_id.id == self.test_room_type_double.id
).num_rooms_available
# ASSERT
self.assertEqual(room_type_plan_avail, 0, "Quota not applied in Wizard Folio")

View File

@@ -78,6 +78,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"start_date": fields.date.today(),
"end_date": fields.date.today() + datetime.timedelta(days=days),
"room_type_id": self.test_room_type_double.id,
"pms_property_ids": [self.test_property.id],
}
).apply_massive_changes()
@@ -98,7 +99,13 @@ class TestPmsWizardMassiveChanges(TestHotel):
date_from = fields.date.today()
date_to = fields.date.today() + datetime.timedelta(days=3)
num_room_types = self.env["pms.room.type"].search_count([])
num_room_types = self.env["pms.room.type"].search_count(
[
"|",
("pms_property_ids", "=", False),
("pms_property_ids", "in", self.test_property.id),
]
)
num_exp_rules_to_create = ((date_to - date_from).days + 1) * num_room_types
# ACT
@@ -108,6 +115,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"availability_plan_id": self.test_availability_plan.id,
"start_date": date_from,
"end_date": date_to,
"pms_property_ids": [self.test_property.id],
}
).apply_massive_changes()
@@ -144,6 +152,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"closed": True,
"closed_arrival": True,
"closed_departure": True,
"pms_property_ids": [self.test_property.id],
}
# ACT
@@ -155,6 +164,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
del vals["start_date"]
del vals["end_date"]
del vals["room_type_id"]
del vals["pms_property_ids"]
for key in vals:
with self.subTest(k=key):
self.assertEqual(
@@ -190,6 +200,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"room_type_id": self.test_room_type_double.id,
"start_date": date_from,
"end_date": date_to,
"pms_property_ids": [self.test_property.id],
}
)
@@ -243,6 +254,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"start_date": fields.date.today(),
"end_date": fields.date.today() + datetime.timedelta(days=days),
"room_type_id": self.test_room_type_double.id,
"pms_property_ids": [self.test_property.id],
}
).apply_massive_changes()
# ASSERT
@@ -266,7 +278,13 @@ class TestPmsWizardMassiveChanges(TestHotel):
self.create_common_scenario()
date_from = fields.date.today()
date_to = fields.date.today() + datetime.timedelta(days=3)
num_room_types = self.env["pms.room.type"].search_count([])
num_room_types = self.env["pms.room.type"].search_count(
[
"|",
("pms_property_ids", "=", False),
("pms_property_ids", "in", self.test_property.id),
]
)
num_exp_items_to_create = ((date_to - date_from).days + 1) * num_room_types
# ACT
@@ -276,6 +294,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"pricelist_id": self.test_pricelist.id,
"start_date": date_from,
"end_date": date_to,
"pms_property_ids": [self.test_property.id],
}
).apply_massive_changes()
@@ -305,8 +324,8 @@ class TestPmsWizardMassiveChanges(TestHotel):
"date_start": date_from,
"date_end": date_to,
"compute_price": "fixed",
"applied_on": "1_product",
"product_tmpl_id": self.test_room_type_double.product_id.product_tmpl_id,
"applied_on": "0_product_variant",
"product_id": self.test_room_type_double.product_id,
"fixed_price": price,
"min_quantity": min_quantity,
}
@@ -321,6 +340,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"room_type_id": self.test_room_type_double.id,
"price": price,
"min_quantity": min_quantity,
"pms_property_ids": [self.test_property.id],
}
).apply_massive_changes()
vals["date_start_overnight"] = date_from
@@ -362,6 +382,7 @@ class TestPmsWizardMassiveChanges(TestHotel):
"room_type_id": self.test_room_type_double.id,
"start_date": date_from,
"end_date": date_to,
"pms_property_ids": [self.test_property.id],
}
)
for index, test_case in enumerate(test_case_week_days):

View File

@@ -178,6 +178,7 @@
placeholder="Room Type"
on_change="1"
nolabel="1"
context="{'checkin': checkin, 'checkout': checkout, 'pms_property_id':pms_property_id, 'pricelist_id':pricelist_id}"
options="{'no_create': True,'no_open': True}"
attrs="{'readonly':[('state','not in',('draft'))]}"
style="margin-right: 30px;"

View File

@@ -85,61 +85,35 @@ class FolioWizard(models.TransientModel):
cmds = [(5, 0, 0)]
for room_type_iterator in self.env["pms.room.type"].search([]):
num_rooms_available_by_date = []
room_type_total_price_per_room = 0
for date_iterator in [
record.start_date + datetime.timedelta(days=x)
for x in range(0, (record.end_date - record.start_date).days)
]:
rooms_available = self.env[
"pms.room.type.availability.plan"
].rooms_available(
date_iterator,
date_iterator + datetime.timedelta(days=1),
room_type_id=room_type_iterator.id,
pricelist=record.pricelist_id.id,
)
num_rooms_available_by_date.append(len(rooms_available))
product = room_type_iterator
product = product.with_context(
lang=record.partner_id.lang,
partner=record.partner_id.id,
quantity=1,
date=fields.Date.today(),
date_overnight=date_iterator,
pricelist=record.pricelist_id.id,
uom=product.uom_id.id,
property=record.pms_property_id.id,
)
room_type_total_price_per_room += product.price
# check there are rooms of the type
if room_type_iterator.total_rooms_count > 0:
# get min availability between start date & end date
num_rooms_available = min(num_rooms_available_by_date)
cmds.append(
(
0,
0,
{
"folio_wizard_id": record.id,
"checkin": record.start_date,
"checkout": record.end_date,
"room_type_id": room_type_iterator.id,
"num_rooms_available": num_rooms_available,
"price_per_room": room_type_total_price_per_room
if num_rooms_available
> 0 # not showing price if there's no availability
else 0,
},
)
for room_type_iterator in self.env["pms.room.type"].search(
[
"|",
("pms_property_ids", "=", False),
("pms_property_ids", "in", record.pms_property_id.id),
]
):
num_rooms_available = self.env[
"pms.room.type.availability.plan"
].get_count_rooms_available(
checkin=record.start_date,
checkout=record.end_date,
room_type_id=room_type_iterator.id,
pricelist_id=record.pricelist_id.id,
pms_property_id=record.pms_property_id.id,
)
cmds.append(
(
0,
0,
{
"folio_wizard_id": record.id,
"checkin": record.start_date,
"checkout": record.end_date,
"room_type_id": room_type_iterator.id,
"num_rooms_available": num_rooms_available,
},
)
)
# remove old items
old_lines = record.availability_results.mapped("id")
for old_line in old_lines:
@@ -158,6 +132,7 @@ class FolioWizard(models.TransientModel):
{
"pricelist_id": record.pricelist_id.id,
"partner_id": record.partner_id.id,
"pms_property_id": record.pms_property_id.id,
}
)
for line in record.availability_results:
@@ -170,6 +145,7 @@ class FolioWizard(models.TransientModel):
"room_type_id": line.room_type_id.id,
"partner_id": folio.partner_id.id,
"pricelist_id": folio.pricelist_id.id,
"pms_property_id": record.pms_property_id.id,
}
)
res.reservation_line_ids.discount = record.discount * 100

View File

@@ -23,11 +23,13 @@
<div class="col-5">
<group>
<field name="partner_id" string="Partner" required="1" />
<field
<field name="pms_property_id" required="1" />
<field
default_focus="1"
name="pricelist_id"
string="Pricelist"
required="1"
domain="['|', ('pms_property_ids', '=', False), ('pms_property_ids', 'in', pms_property_id)]"
/>
</group>
</div>

View File

@@ -33,7 +33,8 @@ class AvailabilityWizard(models.TransientModel):
num_rooms_available = fields.Integer(
string="Available rooms",
default=0,
compute="_compute_num_rooms_available",
store="true",
)
price_per_room = fields.Float(
string="Price per room",
@@ -53,6 +54,10 @@ class AvailabilityWizard(models.TransientModel):
price_total = fields.Float(
string="Total price", default=0, compute="_compute_price_total"
)
pms_property_id = fields.Many2one(
related="folio_wizard_id.pms_property_id",
string="Property",
)
@api.depends("num_rooms_selected", "checkin", "checkout")
def _compute_price_total(self):
@@ -62,22 +67,13 @@ class AvailabilityWizard(models.TransientModel):
# this field refresh is just to update it and take into account @ xml
record.value_num_rooms_selected = record.num_rooms_selected.value
num_rooms_available_by_date = []
room_type_total_price_per_room = 0
for date_iterator in [
record.checkin + datetime.timedelta(days=x)
for x in range(0, (record.checkout - record.checkin).days)
]:
rooms_available = self.env[
"pms.room.type.availability.plan"
].rooms_available(
date_iterator,
date_iterator + datetime.timedelta(days=1),
room_type_id=record.room_type_id.id,
pricelist=record.folio_wizard_id.pricelist_id.id,
)
num_rooms_available_by_date.append(len(rooms_available))
partner = record.folio_wizard_id.partner_id
product = record.room_type_id.product_id
product = product.with_context(
@@ -92,10 +88,6 @@ class AvailabilityWizard(models.TransientModel):
)
room_type_total_price_per_room += product.price
# get the availability for the entire stay (min of all dates)
if num_rooms_available_by_date:
record.num_rooms_available = min(num_rooms_available_by_date)
# udpate the price per room
record.price_per_room = room_type_total_price_per_room
@@ -117,6 +109,19 @@ class AvailabilityWizard(models.TransientModel):
record.price_total = record.price_per_room * record.num_rooms_selected.value
@api.depends("room_type_id", "checkin", "checkout")
def _compute_num_rooms_available(self):
for record in self:
record.num_rooms_available = self.env[
"pms.room.type.availability.plan"
].get_count_rooms_available(
record.checkin,
record.checkout,
room_type_id=record.room_type_id.id,
pricelist_id=record.folio_wizard_id.pricelist_id.id,
pms_property_id=record.folio_wizard_id.pms_property_id.id,
)
def _compute_dynamic_selection(self):
for record in self:
for elem_to_insert in range(0, record.num_rooms_available + 1):

View File

@@ -15,6 +15,13 @@ class AvailabilityWizard(models.TransientModel):
return True if self._context.get("pricelist_id") else False
# Fields declaration
pms_property_ids = fields.Many2many(
comodel_name="pms.property",
string="Property",
default=lambda self: self.env["pms.property"].browse(
self.env.user.get_active_property_ids()[0]
),
)
massive_changes_on = fields.Selection(
[("pricelist", "Pricelist"), ("availability_plan", "Availability Plan")],
string="Massive changes on",
@@ -236,6 +243,9 @@ class AvailabilityWizard(models.TransientModel):
if record.pricelist_id:
domain = [
("pricelist_id", "=", record.pricelist_id.id),
"|",
("pms_property_ids", "=", False),
("pms_property_ids", "in", record.pms_property_ids.ids),
]
if record.start_date:
@@ -327,41 +337,50 @@ class AvailabilityWizard(models.TransientModel):
continue
if not record.room_type_id:
rooms = self.env["pms.room.type"].search([])
room_types = self.env["pms.room.type"].search(
[
"|",
("pms_property_ids", "=", False),
("pms_property_ids", "in", record.pms_property_ids.ids),
]
)
else:
rooms = [record.room_type_id]
for room in rooms:
if record.massive_changes_on == "pricelist":
self.env["product.pricelist.item"].create(
{
"pricelist_id": record.pricelist_id.id,
"date_start_overnight": date,
"date_end_overnight": date,
"compute_price": "fixed",
"applied_on": "1_product",
"product_tmpl_id": room.product_id.product_tmpl_id.id,
"fixed_price": record.price,
"min_quantity": record.min_quantity,
}
)
else:
self.env["pms.room.type.availability.rule"].create(
{
"availability_plan_id": record.availability_plan_id.id,
"date": date,
"room_type_id": room.id,
"quota": record.quota,
"max_avail": record.max_avail,
"min_stay": record.min_stay,
"min_stay_arrival": record.min_stay_arrival,
"max_stay": record.max_stay,
"max_stay_arrival": record.max_stay_arrival,
"closed": record.closed,
"closed_arrival": record.closed_arrival,
"closed_departure": record.closed_departure,
}
)
room_types = [record.room_type_id]
for room_type in room_types:
for pms_property in record.pms_property_ids:
if record.massive_changes_on == "pricelist":
self.env["product.pricelist.item"].create(
{
"pricelist_id": record.pricelist_id.id,
"date_start_overnight": date,
"date_end_overnight": date,
"compute_price": "fixed",
"applied_on": "0_product_variant",
"product_id": room_type.product_id.id,
"fixed_price": record.price,
"min_quantity": record.min_quantity,
"pms_property_ids": [pms_property.id],
}
)
else:
avail_plan_id = record.availability_plan_id.id
self.env["pms.room.type.availability.rule"].create(
{
"availability_plan_id": avail_plan_id,
"date": date,
"room_type_id": room_type.id,
"quota": record.quota,
"max_avail": record.max_avail,
"min_stay": record.min_stay,
"min_stay_arrival": record.min_stay_arrival,
"max_stay": record.max_stay,
"max_stay_arrival": record.max_stay_arrival,
"closed": record.closed,
"closed_arrival": record.closed_arrival,
"closed_departure": record.closed_departure,
"pms_property_id": pms_property.id,
}
)
if (
record.massive_changes_on == "pricelist"
and not record.pricelist_readonly

View File

@@ -26,6 +26,7 @@
</div>
<div class="col-5">
<group class="">
<field name="pms_property_ids" widget="many2many_tags" />
<field
name="massive_changes_on"
attrs="{'invisible':['|', ('avail_readonly','=',True), ('pricelist_readonly', '=', True)]}"
@@ -37,6 +38,7 @@
attrs="{'invisible':['|','|',('massive_changes_on','=','pricelist'), ('avail_readonly','=',True),
('pricelist_readonly', '=', True)],
'required': [('massive_changes_on','=','availability_plan')]}"
domain="['|', ('pms_property_ids', '=', False), ('pms_property_ids', 'in', pms_property_ids)]"
/>
<field
@@ -45,9 +47,13 @@
attrs="{'invisible':['|','|',('massive_changes_on','=','availability_plan'),
('pricelist_readonly','=',True), ('pricelist_readonly', '=', True)],
'required': [('massive_changes_on','=','pricelist')]}"
domain="[('id', 'in', allowed_pricelist_ids)]"
domain="[('id', 'in', allowed_pricelist_ids), '|', ('pms_property_ids', '=', False), ('pms_property_ids', 'in', pms_property_ids)]"
/>
<field
name="room_type_id"
default_focus="1"
domain="['|', ('pms_property_ids', '=', False), ('pms_property_ids', 'in', pms_property_ids)]"
/>
<field name="room_type_id" default_focus="1" />
</group>
</div>