From 1980224512dabcd77bed389a68ef9a03a5f87ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sun, 25 Feb 2024 11:56:01 +0100 Subject: [PATCH] [IMP]pms_api_rest: Add property push methods to pms api clients --- pms_api_rest/models/pms_property.py | 527 +++++++++++++++++++++++++++- 1 file changed, 526 insertions(+), 1 deletion(-) diff --git a/pms_api_rest/models/pms_property.py b/pms_api_rest/models/pms_property.py index 3ef24dd61..ec4df7f74 100644 --- a/pms_api_rest/models/pms_property.py +++ b/pms_api_rest/models/pms_property.py @@ -1,4 +1,14 @@ -from odoo import fields, models +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): @@ -100,3 +110,518 @@ class PmsProperty(models.Model): 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()