Files
pms/pms_api_rest/models/pms_property.py

628 lines
25 KiB
Python

import datetime
import json
import logging
import requests
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools.translate import _
_logger = logging.getLogger(__name__)
class PmsProperty(models.Model):
_inherit = "pms.property"
color_option_config = fields.Selection(
string="Color Option Configuration",
help="Configuration of the color code for the planning.",
selection=[("simple", "Simple"), ("advanced", "Advanced")],
default="simple",
)
simple_out_color = fields.Char(
string="Reservations Outside",
help="Color for done reservations in the planning.",
default="rgba(94,208,236)",
)
simple_in_color = fields.Char(
string="Reservations Inside",
help="Color for onboard and departure_delayed reservations in the planning.",
default="rgba(0,146,183)",
)
simple_future_color = fields.Char(
string="Future Reservations",
help="Color for confirm, arrival_delayed and draft reservations in the planning.",
default="rgba(1,182,227)",
)
pre_reservation_color = fields.Char(
string="Pre-Reservation",
help="Color for draft reservations in the planning.",
default="rgba(162,70,128)",
)
confirmed_reservation_color = fields.Char(
string="Confirmed Reservation",
default="rgba(1,182,227)",
help="Color for confirm reservations in the planning.",
)
paid_reservation_color = fields.Char(
string="Paid Reservation",
help="Color for done paid reservations in the planning.",
default="rgba(126,126,126)",
)
on_board_reservation_color = fields.Char(
string="Checkin",
help="Color for onboard not paid reservations in the planning.",
default="rgba(255,64,64)",
)
paid_checkin_reservation_color = fields.Char(
string="Paid Checkin",
help="Color for onboard paid reservations in the planning.",
default="rgba(130,191,7)",
)
out_reservation_color = fields.Char(
string="Checkout",
help="Color for done not paid reservations in the planning.",
default="rgba(88,77,118)",
)
staff_reservation_color = fields.Char(
string="Staff",
help="Color for staff reservations in the planning.",
default="rgba(192,134,134)",
)
to_assign_reservation_color = fields.Char(
string="OTA Reservation To Assign",
help="Color for to_assign reservations in the planning.",
default="rgba(237,114,46)",
)
pending_payment_reservation_color = fields.Char(
string="Payment Pending",
help="Color for pending payment reservations in the planning.",
default="rgba(162,70,137)",
)
overpayment_reservation_color = fields.Char(
string="Overpayment",
help="Color for pending payment reservations in the planning.",
default="rgba(4, 95, 118)",
)
hotel_image_pms_api_rest = fields.Image(
string="Hotel image",
store=True,
)
ota_property_settings_ids = fields.One2many(
string="OTA Property Settings",
help="OTA Property Settings",
comodel_name="ota.property.settings",
inverse_name="pms_property_id",
)
# PUSH API NOTIFICATIONS
def get_payload_avail(self, avails, client):
self.ensure_one()
endpoint = client.url_endpoint_avail
pms_property_id = self.id
avails_dict = {"pmsPropertyId": pms_property_id, "avails": []}
room_type_ids = avails.mapped("room_type_id.id")
plan_avail = client.main_avail_plan_id
for room_type_id in room_type_ids:
room_type_avails = sorted(
avails.filtered(lambda r: r.room_type_id.id == room_type_id),
key=lambda r: r.date,
)
avail_room_type_index = {}
for record_avail in room_type_avails:
avail_rule = record_avail.avail_rule_ids.filtered(
lambda r: r.availability_plan_id == plan_avail
)
if avail_rule:
avail = avail_rule.plan_avail
else:
room_type = avail_rule.room_type_id
avail = min(
[
record_avail.real_avail,
room_type.default_max_avail
if room_type.default_max_avail >= 0
else record_avail.real_avail,
room_type.default_quota
if room_type.default_quota >= 0
else record_avail.real_avail,
]
)
previus_date = record_avail.date - datetime.timedelta(days=1)
avail_index = avail_room_type_index.get(previus_date)
if avail_index and avail_index["avail"] == avail:
avail_room_type_index[record_avail.date] = {
"date_from": avail_index["date_from"],
"date_to": datetime.datetime.strftime(
record_avail.date, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"avail": avail,
}
avail_room_type_index.pop(previus_date)
else:
avail_room_type_index[record_avail.date] = {
"date_from": datetime.datetime.strftime(
record_avail.date, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(
record_avail.date, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"avail": avail,
}
avails_dict["avails"].extend(avail_room_type_index.values())
return avails_dict, endpoint
def get_payload_prices(self, prices, client):
self.ensure_one()
endpoint = client.url_endpoint_prices
pms_property_id = self.id
prices_dict = {"pmsPropertyId": pms_property_id, "prices": []}
product_ids = prices.mapped("product_id.id")
for product_id in product_ids:
room_type_id = (
self.env["pms.room.type"].search([("product_id", "=", product_id)]).id
)
product_prices = sorted(
prices.filtered(lambda r: r.product_id.id == product_id),
key=lambda r: r.date_end_consumption,
)
price_product_index = {}
for price in product_prices:
previus_date = price.date_end_consumption - datetime.timedelta(days=1)
price_index = price_product_index.get(previus_date)
if price_index and round(price_index["price"], 2) == round(
price.fixed_price, 2
):
price_product_index[price.date_end_consumption] = {
"date_from": price_index["date_from"],
"date_to": datetime.datetime.strftime(
price.date_end_consumption, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"price": price.fixed_price,
}
price_product_index.pop(previus_date)
else:
price_product_index[price.date_end_consumption] = {
"date_from": datetime.datetime.strftime(
price.date_end_consumption, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(
price.date_end_consumption, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"price": price.fixed_price,
}
prices_dict["prices"].extend(price_product_index.values())
return prices_dict, endpoint
def get_payload_rules(self, rules, client):
self.ensure_one()
endpoint = client.url_endpoint_rules
pms_property_id = self.id
rules_dict = {"pmsPropertyId": pms_property_id, "rules": []}
room_type_ids = rules.mapped("room_type_id.id")
for room_type_id in room_type_ids:
room_type_rules = sorted(
rules.filtered(lambda r: r.room_type_id.id == room_type_id),
key=lambda r: r.date,
)
rules_room_type_index = {}
for rule in room_type_rules:
previus_date = rule.date - datetime.timedelta(days=1)
avail_index = rules_room_type_index.get(previus_date)
if (
avail_index
and avail_index["min_stay"] == rule.min_stay
and avail_index["max_stay"] == rule.max_stay
and avail_index["closed"] == rule.closed
and avail_index["closed_arrival"] == rule.closed_arrival
and avail_index["closed_departure"] == rule.closed_departure
):
rules_room_type_index[rule.date] = {
"date_from": avail_index["date_from"],
"date_to": datetime.datetime.strftime(rule.date, "%Y-%m-%d"),
"roomTypeId": room_type_id,
"min_stay": rule.min_stay,
"max_stay": rule.max_stay,
"closed": rule.closed,
"closed_arrival": rule.closed_arrival,
"closed_departure": rule.closed_departure,
}
rules_room_type_index.pop(previus_date)
else:
rules_room_type_index[rule.date] = {
"date_from": datetime.datetime.strftime(rule.date, "%Y-%m-%d"),
"date_to": datetime.datetime.strftime(rule.date, "%Y-%m-%d"),
"roomTypeId": room_type_id,
"min_stay": rule.min_stay,
"max_stay": rule.max_stay,
"closed": rule.closed,
"closed_arrival": rule.closed_arrival,
"closed_departure": rule.closed_departure,
}
rules_dict["rules"].extend(rules_room_type_index.values())
return rules_dict, endpoint
def pms_api_push_payload(self, payload, endpoint, client):
token = client.external_public_token
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"accept": "text/json",
}
response = requests.post(endpoint, headers=headers, data=json.dumps(payload))
return response
def generate_availability_json(
self, date_from, date_to, pms_property_id, room_type_id, client
):
avail_records = self.env["pms.availability"].search(
[
("date", ">=", date_from),
("date", "<=", date_to),
("pms_property_id", "=", pms_property_id),
("room_type_id", "=", room_type_id),
],
order="date",
)
avail_data = []
current_avail = None
current_date_from = None
current_date_to = None
all_dates = [
date_from + datetime.timedelta(days=x)
for x in range((date_to - date_from).days + 1)
]
plan_avail = client.main_avail_plan_id
for date in all_dates:
avail_record = avail_records.filtered(lambda r: r.date == date)
if avail_record:
avail_rule = avail_record.avail_rule_ids.filtered(
lambda r: r.availability_plan_id == plan_avail
)
if avail_rule:
avail = avail_rule.plan_avail
else:
room_type = avail_rule.room_type_id
avail = min(
[
avail_record.real_avail,
room_type.default_max_avail
if room_type.default_max_avail >= 0
else avail_record.real_avail,
room_type.default_quota
if room_type.default_quota >= 0
else avail_record.real_avail,
]
)
else:
room_type = self.env["pms.room.type"].browse(room_type_id)
avail = min(
[
len(
room_type.room_ids.filtered(
lambda r: r.active
and r.pms_property_id.id == pms_property_id
)
),
room_type.default_max_avail
if room_type.default_max_avail >= 0
else avail_record.real_avail,
room_type.default_quota
if room_type.default_quota >= 0
else avail_record.real_avail,
]
)
if current_avail is None:
current_avail = avail
current_date_from = date
current_date_to = date
elif current_avail == avail:
current_date_to = date
else:
avail_data.append(
{
"date_from": datetime.datetime.strftime(
current_date_from, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(
current_date_to, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"avail": current_avail,
}
)
current_avail = avail
current_date_from = date
current_date_to = date
if current_avail is not None:
avail_data.append(
{
"date_from": datetime.datetime.strftime(
current_date_from, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(current_date_to, "%Y-%m-%d"),
"roomTypeId": room_type_id,
"avail": current_avail,
}
)
return avail_data
def generate_restrictions_json(
self, date_from, date_to, pms_property_id, room_type_id, client
):
"""
Group by range of dates with the same restrictions
Output format:
rules_data: [
{
'date_from': '2023-08-01',
'date_to': '2023-08-30',
'roomTypeId': 2,
'min_stay': 2,
'max_stay': 6,
'closed': false,
'closed_arrival': false,
'closed_departure': false
}
]
"""
rules_records = self.env["pms.availability.plan.rule"].search(
[
("date", ">=", date_from),
("date", "<=", date_to),
("pms_property_id", "=", pms_property_id),
("room_type_id", "=", room_type_id),
("availability_plan_id", "=", self.main_avail_plan_id.id),
],
order="date",
)
rules_data = []
current_rule = None
current_date_from = None
current_date_to = None
all_dates = [
date_from + datetime.timedelta(days=x)
for x in range((date_to - date_from).days + 1)
]
for date in all_dates:
rules_record = rules_records.filtered(lambda r: r.date == date)
if rules_record:
rule = rules_record[0]
else:
rule = None
if current_rule is None:
current_rule = rule
current_date_from = date
current_date_to = date
elif (
current_rule.min_stay == rule.min_stay
and current_rule.max_stay == rule.max_stay
and current_rule.closed == rule.closed
and current_rule.closed_arrival == rule.closed_arrival
and current_rule.closed_departure == rule.closed_departure
):
current_date_to = date
else:
if current_rule:
rules_data.append(
{
"date_from": datetime.datetime.strftime(
current_date_from, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(
current_date_to, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"min_stay": current_rule.min_stay,
"max_stay": current_rule.max_stay,
"closed": current_rule.closed,
"closed_arrival": current_rule.closed_arrival,
"closed_departure": current_rule.closed_departure,
}
)
current_rule = rule
current_date_from = date
current_date_to = date
return rules_data
def generate_prices_json(
self, date_from, date_to, pms_property_id, room_type_id, client
):
"""
prices: [
{
'date_from': '2023-07-02',
'date_to': '2023-07-05',
'roomTypeId': 2,
'price': 50
}
]
"""
all_dates = [
date_from + datetime.timedelta(days=x)
for x in range((date_to - date_from).days + 1)
]
product = self.env["pms.room.type"].browse(room_type_id).product_id
pms_property = self.env["pms.property"].browse(pms_property_id)
pricelist = client.main_pricelist_id
product_context = dict(
self.env.context,
date=datetime.datetime.today().date(),
pricelist=3, # self.get_default_pricelist(),
uom=product.uom_id.id,
fiscal_position=False,
property=pms_property_id,
)
prices_data = []
current_price = None
current_date_from = None
current_date_to = None
for index, date in enumerate(all_dates):
product_context["consumption_date"] = date
product = product.with_context(product_context)
price = round(
self.env["account.tax"]._fix_tax_included_price_company(
self.env["product.product"]._pms_get_display_price(
pricelist_id=pricelist.id,
product=product,
company_id=pms_property.company_id.id,
product_qty=1,
partner_id=False,
),
product.taxes_id,
product.taxes_id,
pms_property.company_id,
),
2,
)
if current_price is None:
current_price = price
current_date_from = date
current_date_to = date
elif current_price == price and index < len(all_dates) - 1:
current_date_to = date
else:
prices_data.append(
{
"date_from": datetime.datetime.strftime(
current_date_from, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(
current_date_to, "%Y-%m-%d"
),
"roomTypeId": room_type_id,
"price": current_price,
}
)
current_price = price
current_date_from = date
current_date_to = date
if current_price is not None:
prices_data.append(
{
"date_from": datetime.datetime.strftime(
current_date_from, "%Y-%m-%d"
),
"date_to": datetime.datetime.strftime(current_date_to, "%Y-%m-%d"),
"roomTypeId": room_type_id,
"price": current_price,
}
)
return prices_data
@api.model
def pms_api_push_batch(
self,
call_type,
date_from=lambda: datetime.datetime.today().date(),
date_to=lambda: datetime.datetime.today().date() + datetime.timedelta(days=365),
filter_room_type_id=False,
pms_property_codes=False,
client=False,
):
if client:
clients = client
else:
clients = self.env["res.users"].search([("pms_api_client", "=", True)])
_logger.info("PMS API push batch")
if isinstance(date_from, str):
date_from = datetime.datetime.strptime(date_from, "%Y-%m-%d").date()
if date_from < datetime.datetime.today().date():
raise ValidationError(_("Invalid date from"))
if isinstance(date_to, str):
date_to = datetime.datetime.strptime(date_to, "%Y-%m-%d").date()
if date_to <= date_from:
raise ValidationError(_("Invalid date to"))
for client in clients:
if not pms_property_codes:
pms_properties = client.pms_property_ids
else:
pms_properties = self.env["pms.property"].search(
[
("pms_property_code", "in", pms_property_codes),
("id", "in", client.pms_property_ids.ids),
]
)
for pms_property in pms_properties:
pms_property_id = pms_property.id
room_type_ids = (
[filter_room_type_id]
if filter_room_type_id
else self.env["pms.room"]
.search([("pms_property_id", "=", pms_property_id)])
.mapped("room_type_id")
.filtered(lambda r: r.id not in client.excluded_room_type_ids.ids)
.ids
)
payload = {
"pmsPropertyId": pms_property_id,
}
data = []
for room_type_id in room_type_ids:
if call_type == "availability":
endpoint = client.url_endpoint_avail
data.extend(
pms_property.generate_availability_json(
date_from=date_from,
date_to=date_to,
pms_property_id=pms_property_id,
room_type_id=room_type_id,
client=client,
)
)
key_data = "avails"
elif call_type == "restrictions":
endpoint = client.url_endpoint_rules
data.extend(
pms_property.generate_restrictions_json(
date_from=date_from,
date_to=date_to,
pms_property_id=pms_property_id,
room_type_id=room_type_id,
client=client,
)
)
key_data = "rules"
elif call_type == "prices":
endpoint = client.url_endpoint_prices
data.extend(
pms_property.generate_prices_json(
date_from=date_from,
date_to=date_to,
pms_property_id=pms_property_id,
room_type_id=room_type_id,
client=client,
)
)
key_data = "prices"
else:
raise ValidationError(_("Invalid call type"))
if data:
payload[key_data] = data
response = self.pms_api_push_payload(payload, endpoint, client)
_logger.info(
f"""PMS API push batch response to
{endpoint}: {response.status_code} - {response.text}"""
)
self.invalidate_cache()