[IMP]pms_api_rest: Imrpovement API logs, PUT folio service and action_confirm reservation

This commit is contained in:
Darío Lodeiros
2024-04-17 11:27:24 +02:00
parent 7bbc603b33
commit 9df63f790b
7 changed files with 486 additions and 321 deletions

View File

@@ -25,6 +25,7 @@
"data/sql_reports.xml",
"data/auth_jwt_validator.xml",
"data/pms_app_reset_password_template.xml",
"data/cron_jobs.xml",
"views/pms_property_views.xml",
"views/res_users_views.xml",
"views/pms_room_type_class_views.xml",

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">
<record model="ir.cron" id="clean_log_pms_api_rest">
<field name="name">Clean Log PMS API REST</field>
<field name="interval_number">1</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="state">code</field>
<field name="model_id" ref="model_pms_api_log" />
<field
name="nextcall"
eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 06:00:00')"
/>
<field name="code">model.clean_log_data(offset=60)</field>
</record>
</odoo>

View File

@@ -1,4 +1,6 @@
from odoo import _, api, fields, models
from datetime import timedelta
from odoo import _, fields, models
class PmsApiLog(models.Model):
@@ -72,10 +74,39 @@ class PmsApiLog(models.Model):
string="Response URL",
help="Response URL",
)
model_id = fields.Many2one(
string="Model",
help="Model",
comodel_name="ir.model",
request_type = fields.Selection(
string="Request Type",
help="Request Type",
selection=[
("folios", "Folios"),
("availability", "Availability"),
("restrictions", "Restrictions rules"),
("prices", "Prices"),
],
)
target_date_from = fields.Date(
string="Target Date From",
help="Target Date From",
)
target_date_to = fields.Date(
string="Target Date To",
help="Target Date To",
)
folio_ids = fields.Many2many(
string="Folios",
help="Folios",
comodel_name="pms.folio",
relation="pms_folio_pms_api_log_rel",
column1="pms_api_log_ids",
column2="folio_ids",
)
room_type_ids = fields.Many2many(
string="Room Types",
help="Room Types",
comodel_name="pms.room.type",
relation="pms_room_type_pms_api_log_rel",
column1="pms_api_log_ids",
column2="room_type_ids",
)
def related_action_open_record(self):
@@ -90,10 +121,7 @@ class PmsApiLog(models.Model):
"""
self.ensure_one()
if "pms_api_log_id" in self.env[self.model_id.model]._fields:
records = self.env[self.model_id.model].search(
[("pms_api_log_id", "=", self.id)]
)
records = self.folio_ids
if not records:
return None
action = {
@@ -114,21 +142,15 @@ class PmsApiLog(models.Model):
)
return action
@api.model
def create(self, vals):
def clean_log_data(self, offset=60):
"""Clean log data older than the offset.
:param int offset: The number of days to keep the log data.
"""
set pms_api_log_id and origin_json in related records
if record_ids id present in context
"""
log_record = super().create(vals)
if self.env.context.get("record_ids"):
records = self.env[self.env.context.get("model")].browse(
self.env.context.get("record_ids")
)
records.write(
{
"pms_api_log_id": log_record.id,
"origin_json": log_record.request,
}
)
return log_record
self.sudo().search(
[
("status", "=", "success"),
("create_date", "<", fields.Datetime.now() - timedelta(days=offset)),
]
).unlink()

View File

@@ -4,12 +4,11 @@ from odoo import fields, models
class PmsFolio(models.Model):
_inherit = "pms.folio"
pms_api_log_id = fields.Many2one(
string="PMS API Log",
help="PMS API Log",
pms_api_log_ids = fields.Many2many(
string="API Logs",
help="API Logs",
comodel_name="pms.api.log",
)
origin_json = fields.Text(
string="Origin JSON",
help="Origin JSON",
relation="pms_folio_pms_api_log_rel",
column1="folio_ids",
column2="pms_api_log_ids",
)

View File

@@ -573,6 +573,9 @@ class PmsProperty(models.Model):
clients = client
else:
clients = self.env["res.users"].search([("pms_api_client", "=", True)])
room_type_ids = []
endpoint = ""
response = None
_logger.info("PMS API push batch")
if isinstance(date_from, str):
date_from = datetime.datetime.strptime(date_from, "%Y-%m-%d").date()
@@ -593,73 +596,112 @@ class PmsProperty(models.Model):
]
)
for pms_property in pms_properties:
property_client_conf = self.env["ota.property.settings"].search(
[
("pms_property_id", "=", pms_property.id),
("agency_id", "=", client.partner_id.id),
]
)
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 property_client_conf.excluded_room_type_ids.ids
try:
property_client_conf = (
self.env["ota.property.settings"]
.sudo()
.search(
[
("pms_property_id", "=", pms_property.id),
("agency_id", "=", client.partner_id.id),
]
)
)
.ids
)
payload = {
"pmsPropertyId": pms_property_id,
}
data = []
for room_type_id in room_type_ids:
if call_type == "availability":
endpoint = client.url_endpoint_availability
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,
)
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 property_client_conf.excluded_room_type_ids.ids
)
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}"""
.ids
)
payload = {
"pmsPropertyId": pms_property_id,
}
data = []
for room_type_id in room_type_ids:
if call_type == "availability":
endpoint = client.url_endpoint_availability
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()
self.env["pms.api.log"].sudo().create(
{
"pms_property_id": pms_property_id,
"client_id": client.id,
"request": payload,
"response": str(response),
"status": "success" if response.ok else "error",
"request_date": fields.Datetime.now(),
"method": "PUSH",
"endpoint": endpoint,
"target_date_from": date_from,
"target_date_to": date_to,
"request_type": call_type,
"room_type_ids": room_type_ids,
}
)
except Exception as e:
_logger.error(f"""PMS API push batch error: {e}""")
self.env["pms.api.log"].sudo().create(
{
"pms_property_id": pms_property_id,
"client_id": client.id,
"request": payload,
"response": str(e),
"status": "error",
"request_date": fields.Datetime.now(),
"method": "PUSH",
"endpoint": endpoint,
"target_date_from": date_from,
"target_date_to": date_to,
"request_type": call_type,
"room_type_ids": room_type_ids,
}
)
self.invalidate_cache()

View File

@@ -1,4 +1,3 @@
import ast
import base64
import logging
from datetime import datetime, timedelta
@@ -584,6 +583,12 @@ class PmsFolioService(Component):
def create_folio(self, pms_folio_info):
external_app = self.env.user.pms_api_client
log_payload = pms_folio_info
min_checkin_payload = min(
pms_folio_info.reservations, key=lambda x: x.checkin
).checkin
max_checkout_payload = max(
pms_folio_info.reservations, key=lambda x: x.checkout
).checkout
try:
if pms_folio_info.reservationType == "out":
vals = {
@@ -760,7 +765,7 @@ class PmsFolioService(Component):
force_write_blocked=True if external_app else False,
)._compute_board_service_room_id()
pms_folio_info.transactions = self.normalize_payments_structure(
pms_folio_info
pms_folio_info, folio
)
if pms_folio_info.transactions:
self.compute_transactions(folio, pms_folio_info.transactions)
@@ -793,9 +798,7 @@ class PmsFolioService(Component):
date_to=date_to,
)
if external_app:
self.env["pms.api.log"].with_context(
record_ids=folio.ids
).sudo().create(
self.env["pms.api.log"].sudo().create(
{
"pms_property_id": pms_folio_info.pmsPropertyId,
"client_id": self.env.user.id,
@@ -805,9 +808,10 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "POST",
"endpoint": "/folios",
"model_id": self.env["ir.model"]
.search([("model", "=", "pms.folio")])
.id,
"folio_ids": folio.ids,
"target_date_from": min_checkin_payload,
"target_date_to": max_checkout_payload,
"request_type": "folios",
}
)
return folio.id
@@ -827,9 +831,10 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "POST",
"endpoint": "/folios",
"model_id": self.env["ir.model"]
.search([("model", "=", "pms.folio")])
.id,
"folio_ids": [],
"target_date_from": min_checkin_payload,
"target_date_to": max_checkout_payload,
"request_type": "folios",
}
)
if not external_app:
@@ -844,52 +849,62 @@ class PmsFolioService(Component):
reference += transaction.reference
else:
raise ValidationError(_("The transaction reference is required"))
if not self.env["account.payment"].search(
proposed_transaction = self.env["account.payment"].search(
[
("pms_property_id", "=", folio.pms_property_id.id),
("payment_type", "=", transaction.transactionType),
("folio_ids", "in", folio.id),
("ref", "ilike", transaction.reference),
("ref", "ilike", reference),
("state", "=", "posted"),
]
)
if (
not proposed_transaction
or proposed_transaction.amount != transaction.amount
):
journal = self.env["account.journal"].search(
[("id", "=", transaction.journalId)]
)
if not journal:
ota_conf = self.env["ota.property.settings"].search(
[
("pms_property_id", "=", folio.pms_property_id.id),
("agency_id", "=", self.env.user.partner_id.id),
]
)
if ota_conf:
journal = ota_conf.pms_api_payment_journal_id
if transaction.transactionType == "inbound":
folio.do_payment(
journal,
journal.suspense_account_id,
self.env.user,
transaction.amount,
folio,
reservations=False,
services=False,
partner=False,
date=datetime.strptime(transaction.date, "%Y-%m-%d"),
ref=reference,
)
elif transaction.transactionType == "outbound":
folio.do_refund(
journal,
journal.suspense_account_id,
self.env.user,
transaction.amount,
folio,
reservations=False,
services=False,
partner=False,
date=datetime.strptime(transaction.date, "%Y-%m-%d"),
ref=reference,
if proposed_transaction:
proposed_transaction.action_draft()
proposed_transaction.amount = transaction.amount
proposed_transaction.action_post()
else:
journal = self.env["account.journal"].search(
[("id", "=", transaction.journalId)]
)
if not journal:
ota_conf = self.env["ota.property.settings"].search(
[
("pms_property_id", "=", folio.pms_property_id.id),
("agency_id", "=", self.env.user.partner_id.id),
]
)
if ota_conf:
journal = ota_conf.pms_api_payment_journal_id
if transaction.transactionType == "inbound":
folio.do_payment(
journal,
journal.suspense_account_id,
self.env.user,
transaction.amount,
folio,
reservations=False,
services=False,
partner=False,
date=datetime.strptime(transaction.date, "%Y-%m-%d"),
ref=reference,
)
elif transaction.transactionType == "outbound":
folio.do_refund(
journal,
journal.suspense_account_id,
self.env.user,
transaction.amount,
folio,
reservations=False,
services=False,
partner=False,
date=datetime.strptime(transaction.date, "%Y-%m-%d"),
ref=reference,
)
@restapi.method(
[
@@ -913,7 +928,7 @@ class PmsFolioService(Component):
folio.action_cancel()
if pms_folio_info.confirmReservations:
for reservation in folio.reservation_ids:
reservation.confirm()
reservation.action_confirm()
if pms_folio_info.internalComment is not None:
folio_vals.update({"internal_comment": pms_folio_info.internalComment})
if pms_folio_info.partnerId:
@@ -1593,6 +1608,12 @@ class PmsFolioService(Component):
def update_put_external_folio(self, external_reference, pms_folio_info):
external_app = self.env.user.pms_api_client
log_payload = pms_folio_info
min_checkin_payload = min(
pms_folio_info.reservations, key=lambda x: x.checkin
).checkin
max_checkout_payload = max(
pms_folio_info.reservations, key=lambda x: x.checkout
).checkout
try:
folio = self.env["pms.folio"].search(
[
@@ -1603,7 +1624,7 @@ class PmsFolioService(Component):
if not folio or len(folio) > 1:
raise MissingError(_("Folio not found"))
self.update_folio_values(folio, pms_folio_info)
self.env["pms.api.log"].with_context(record_ids=folio.ids).sudo().create(
self.env["pms.api.log"].sudo().create(
{
"pms_property_id": pms_folio_info.pmsPropertyId,
"client_id": self.env.user.id,
@@ -1613,9 +1634,10 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
"model_id": self.env["ir.model"]
.search([("model", "=", "pms.folio")])
.id,
"folio_ids": folio.ids,
"target_date_from": min_checkin_payload,
"target_date_to": max_checkout_payload,
"request_type": "folios",
}
)
return folio.id
@@ -1635,9 +1657,10 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
"model_id": self.env["ir.model"]
.search([("model", "=", "pms.folio")])
.id,
"folio_ids": [],
"target_date_from": min_checkin_payload,
"target_date_to": max_checkout_payload,
"request_type": "folios",
}
)
if not external_app:
@@ -1660,12 +1683,18 @@ class PmsFolioService(Component):
def update_put_folio(self, folio_id, pms_folio_info):
external_app = self.env.user.pms_api_client
log_payload = pms_folio_info
min_checkin_payload = min(
pms_folio_info.reservations, key=lambda x: x.checkin
).checkin
max_checkout_payload = max(
pms_folio_info.reservations, key=lambda x: x.checkout
).checkout
try:
folio = self.env["pms.folio"].browse(folio_id)
if not folio:
raise MissingError(_("Folio not found"))
self.update_folio_values(folio, pms_folio_info)
self.env["pms.api.log"].with_context(record_ids=folio.ids).sudo().create(
self.env["pms.api.log"].sudo().create(
{
"pms_property_id": pms_folio_info.pmsPropertyId,
"client_id": self.env.user.id,
@@ -1675,9 +1704,10 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
"model_id": self.env["ir.model"]
.search([("model", "=", "pms.folio")])
.id,
"folio_ids": folio.ids,
"target_date_from": min_checkin_payload,
"target_date_to": max_checkout_payload,
"request_type": "folios",
}
)
@@ -1698,9 +1728,10 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
"model_id": self.env["ir.model"]
.search([("model", "=", "pms.folio")])
.id,
"folio_ids": [],
"target_date_from": min_checkin_payload,
"target_date_to": max_checkout_payload,
"request_type": "folios",
}
)
if not external_app:
@@ -1710,17 +1741,11 @@ class PmsFolioService(Component):
def update_folio_values(self, folio, pms_folio_info):
external_app = self.env.user.pms_api_client
origin_values_dict = False
if external_app:
origin_values_dict = ast.literal_eval(folio.origin_json)
if origin_values_dict:
# Compare the values of the origin folio with the new values
# and set the new value to None if it is the same as the origin value
for key, value in origin_values_dict:
if value == pms_folio_info[key]:
pms_folio_info[key] = None
folio_vals = {}
if pms_folio_info.state == "cancel":
if pms_folio_info.state == "cancel" and folio.state != "cancel":
draft_invoices = folio.invoice_ids.filtered(lambda i: i.state == "draft")
if draft_invoices:
draft_invoices.action_cancel()
folio.action_cancel()
return folio.id
# if (
@@ -1731,7 +1756,7 @@ class PmsFolioService(Component):
# )
# ):
# for reservation in folio.reservation_ids:
# reservation.confirm()
# reservation.action_confirm()
if (
pms_folio_info.internalComment is not None
and pms_folio_info.internalComment not in folio.internal_comment
@@ -1765,32 +1790,49 @@ class PmsFolioService(Component):
folio_vals.update({"mobile": pms_folio_info.partnerPhone})
if (
self.get_language(pms_folio_info.language)
and self.get_language(pms_folio_info.language) != pms_folio_info.language
and self.get_language(pms_folio_info.language) != folio.lang
):
folio_vals.update({"lang": self.get_language(pms_folio_info.language)})
reservations_vals = []
if pms_folio_info.reservations:
reservations_vals = self.wrapper_reservations(
folio, pms_folio_info.reservations
)
if reservations_vals:
folio_vals.update({"reservation_ids": reservations_vals})
if folio_vals:
if reservations_vals:
# Cancel the old reservations that have not been included in the update
update_reservation_ids = []
for val in reservations_vals:
# Cancel the old reservations that have not been included in the update
if val[0] == 1:
if val[2].get("state") == "cancel":
self.env["pms.reservation"].with_context(
force_write_blocked=True
).browse(val[1]).action_cancel()
# delete from reservations_vals the reservation that has been canceled
reservations_vals.pop(reservations_vals.index(val))
if val[2].get("state") == "confirm":
self.env["pms.reservation"].with_context(
force_write_blocked=True
).browse(val[1]).action_confirm()
# delete from reservations_vals the field state
val[2].pop("state")
update_reservation_ids.append(val[1])
folio.reservation_ids.filtered(
old_reservations_to_cancel = folio.reservation_ids.filtered(
lambda r: r.state != "cancel" and r.id not in update_reservation_ids
).with_context(modified=True, force_write_blocked=True).action_cancel()
)
old_reservations_to_cancel.with_context(
modified=True, force_write_blocked=True
).action_cancel()
folio_vals.update({"reservation_ids": reservations_vals})
if folio_vals:
folio.with_context(
skip_compute_service_ids=False if external_app else True,
force_overbooking=True if external_app else False,
force_write_blocked=True if external_app else False,
).write(folio_vals)
# Compute OTA transactions
pms_folio_info.transactions = self.normalize_payments_structure(pms_folio_info)
pms_folio_info.transactions = self.normalize_payments_structure(
pms_folio_info, folio
)
if pms_folio_info.transactions:
self.compute_transactions(folio, pms_folio_info.transactions)
# Force update availability
@@ -1804,7 +1846,7 @@ class PmsFolioService(Component):
date_to=date_to,
)
def normalize_payments_structure(self, pms_folio_info):
def normalize_payments_structure(self, pms_folio_info, folio):
"""
This method use the OTA payment structure to normalize the structure
and incorporate them in the transactions datamodel param
@@ -1821,6 +1863,17 @@ class PmsFolioService(Component):
("agency_id", "=", self.env.user.partner_id.id),
]
)
if not ota_conf:
raise ValidationError(
_("No OTA configuration found for this property")
)
if not ota_conf.pms_api_payment_journal_id:
raise ValidationError(
_(
"No payment journal configured for this property for %s"
% ota_conf.name
)
)
transaction.journalId = ota_conf.pms_api_payment_journal_id.id
elif pms_folio_info.agencyId:
ota_conf = self.env["ota.property.settings"].search(
@@ -1849,7 +1902,7 @@ class PmsFolioService(Component):
pmsTransactionInfo(
journalId=journal.id,
transactionType="inbound",
amount=pms_folio_info.totalPrice,
amount=round(folio.amount_total, 2),
date=fields.Date.today().strftime("%Y-%m-%d"),
reference=pms_folio_info.externalReference,
)
@@ -1871,22 +1924,17 @@ class PmsFolioService(Component):
# Search a reservation in saved_reservations whose sum of night amounts is equal
# to the sum of night amounts of info_reservation, and dates equal,
# if we find it we update it
payload_nights = round(
sum(info_reservation.reservationLines.mapped("price")), 2
)
proposed_reservation = saved_reservations.filtered(
lambda r: r.checkin == info_reservation.checkin
and r.checkout == info_reservation.checkout
and r.room_type_id == info_reservation.roomTypeId
).filtered(
lambda r: round(
sum(r.reservation_line_ids.mapped("price"))
+ r.service_ids.filtered(lambda s: s.is_board_service).mapped(""),
2,
)
== payload_nights
lambda r: r.checkin
== datetime.strptime(info_reservation.checkin, "%Y-%m-%d").date()
and r.checkout
== datetime.strptime(info_reservation.checkout, "%Y-%m-%d").date()
and r.room_type_id.id == info_reservation.roomTypeId
and r.adults == info_reservation.adults
and r.children == info_reservation.children
)
if proposed_reservation:
proposed_reservation = proposed_reservation[0]
saved_reservations -= proposed_reservation
vals = {}
new_res = not proposed_reservation
@@ -1900,12 +1948,17 @@ class PmsFolioService(Component):
):
vals.update({"room_type_id": info_reservation.roomTypeId})
if info_reservation.checkin:
if new_res or proposed_reservation.checkin != info_reservation.checkin:
if (
new_res
or proposed_reservation.checkin
!= datetime.strptime(info_reservation.checkin, "%Y-%m-%d").date()
):
vals.update({"checkin": info_reservation.checkin})
if info_reservation.checkout:
if (
new_res
or proposed_reservation.checkout != info_reservation.checkout
or proposed_reservation.checkout
!= datetime.strptime(info_reservation.checkout, "%Y-%m-%d").date()
):
vals.update({"checkout": info_reservation.checkout})
if info_reservation.pricelistId:
@@ -1929,7 +1982,7 @@ class PmsFolioService(Component):
if info_reservation.preferredRoomId:
if (
new_res
or proposed_reservation.preferredRoomId
or proposed_reservation.preferred_room_id.id
!= info_reservation.preferredRoomId
):
vals.update({"preferred_room_id": info_reservation.preferredRoomId})
@@ -1942,7 +1995,9 @@ class PmsFolioService(Component):
or proposed_reservation.children != info_reservation.children
):
vals.update({"children": info_reservation.children})
if info_reservation.reservationLines and new_res:
if new_res or info_reservation.stateCode != proposed_reservation.state:
vals.update({"state": info_reservation.stateCode})
if info_reservation.reservationLines:
# The service price is included in day price when it is a board service (external api)
board_day_price = 0
if external_app and vals.get("board_service_room_id"):
@@ -1974,6 +2029,7 @@ class PmsFolioService(Component):
reservation_lines_cmds = self.wrapper_reservation_lines(
reservation=info_reservation,
board_day_price=board_day_price,
proposed_reservation=proposed_reservation,
)
if reservation_lines_cmds:
vals.update({"reservation_line_ids": reservation_lines_cmds})
@@ -1994,20 +2050,38 @@ class PmsFolioService(Component):
cmds.append((1, proposed_reservation.id, vals))
return cmds
def wrapper_reservation_lines(self, reservation, board_day_price=0):
def wrapper_reservation_lines(
self, reservation, board_day_price=0, proposed_reservation=False
):
cmds = []
for line in reservation.reservationLines:
cmds.append(
(
0,
False,
{
"date": line.date,
"price": line.price - board_day_price,
"discount": line.discount or 0,
},
if proposed_reservation:
# Not is necesay check new dates, becouse a if the dates change, the reservation is new
proposed_line = proposed_reservation.reservation_line_ids.filtered(
lambda l: l.date == datetime.strptime(line.date, "%Y-%m-%d").date()
)
)
if proposed_line:
vals = {}
if round(proposed_line.price, 2) != round(
line.price - board_day_price, 2
):
vals.update({"price": line.price - board_day_price})
if round(proposed_line.discount, 2) != round(line.discount, 2):
vals.update({"discount": line.discount})
if vals:
cmds.append((1, proposed_line.id, vals))
else:
cmds.append(
(
0,
False,
{
"date": line.date,
"price": line.price - board_day_price,
"discount": line.discount or 0,
},
)
)
return cmds
def wrapper_reservation_services(self, info_services, services=False):
@@ -2045,17 +2119,21 @@ class PmsFolioService(Component):
It is used to override potential availability changes on the channel made unilaterally,
for example, upon entering or canceling a reservation.
"""
api_clients = self.env["res.users"].search(
[
("pms_api_client", "=", True),
("pms_property_ids", "in", pms_property_id),
]
api_clients = (
self.env["res.users"]
.sudo()
.search(
[
("pms_api_client", "=", True),
("pms_property_ids", "in", pms_property_id),
]
)
)
if not room_type_ids or not api_clients:
return False
for room_type_id in room_type_ids:
pms_property = self.env["pms.property"].browse(pms_property_id)
self.env["pms.property"].pms_api_push_batch(
self.env["pms.property"].sudo().pms_api_push_batch(
call_type="availability", # 'availability', 'prices', 'restrictions'
date_from=date_from.strftime("%Y-%m-%d"), # 'YYYY-MM-DD'
date_to=date_to.strftime("%Y-%m-%d"), # 'YYYY-MM-DD'

View File

@@ -3,12 +3,12 @@ from datetime import datetime, timedelta
from odoo import _, fields
from odoo.exceptions import MissingError
from odoo.osv import expression
from odoo.tools.safe_eval import safe_eval
from odoo.addons.base_rest import restapi
from odoo.addons.base_rest_datamodel.restapi import Datamodel
from odoo.addons.component.core import Component
from odoo.osv import expression
from odoo.tools.safe_eval import safe_eval
class PmsReservationService(Component):
@@ -284,13 +284,12 @@ class PmsReservationService(Component):
if reservation_data.stateCode == "cancel":
reservation.action_cancel()
if reservation_data.stateCode == "confirm":
reservation.confirm()
reservation.action_confirm()
if reservation_data.toCheckout is not None and reservation_data.toCheckout:
reservation.action_reservation_checkout()
if reservation_data.undoOnboard:
reservation.action_undo_onboard()
def _get_reservation_lines_mapped(self, origin_data, reservation_line=False):
# Return dict witch reservation.lines values (only modified if line exist,
# or all pass values if line not exist)
@@ -587,7 +586,8 @@ class PmsReservationService(Component):
if checkin_partner.document_number
else None,
documentExpeditionDate=datetime.combine(
checkin_partner.document_expedition_date, datetime.min.time()
checkin_partner.document_expedition_date,
datetime.min.time(),
).isoformat()
if checkin_partner.document_expedition_date
else None,
@@ -658,7 +658,7 @@ class PmsReservationService(Component):
checkin_partner.write(
self.mapping_checkin_partner_values(
pms_checkin_partner_info,
checkin_partner.partner_id.id if checkin_partner.partner_id else False
checkin_partner.partner_id.id if checkin_partner.partner_id else False,
)
)
# if not partner_id we need to force compute to create partner
@@ -860,7 +860,9 @@ class PmsReservationService(Component):
checkin_partner.write(
self.mapping_checkin_partner_values(
pms_checkin_partner_info,
checkin_partner.partner_id.id if checkin_partner.partner_id else False
checkin_partner.partner_id.id
if checkin_partner.partner_id
else False,
)
)
# if not partner_id we need to force compute to create partner
@@ -884,7 +886,9 @@ class PmsReservationService(Component):
if checkin_partner:
checkin_partner.unlink()
def mapping_checkin_partner_values(self, pms_checkin_partner_info, partner_id=False):
def mapping_checkin_partner_values(
self, pms_checkin_partner_info, partner_id=False
):
vals = {
"firstname": pms_checkin_partner_info.firstname,
"lastname": pms_checkin_partner_info.lastname,
@@ -1099,17 +1103,17 @@ class PmsReservationService(Component):
)
def wizard_states(self, reservation_id):
reservation = self.env["pms.reservation"].search([("id", "=", reservation_id)])
today = datetime.now().strftime('%Y-%m-%d')
today = datetime.now().strftime("%Y-%m-%d")
wizard_states = [
{
"code": "overbooking_with_availability",
"title": "Overbooking",
"domain": "["
"('state', 'in', ['draft', 'confirm', 'arrival_delayed']), "
"('overbooking', '=', True), "
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"('state', 'in', ['draft', 'confirm', 'arrival_delayed']), "
"('overbooking', '=', True), "
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"filtered": "lambda r: r.count_alternative_free_rooms",
"text": f"Parece que ha entrado una reserva sin haber disponibilidad para {reservation.room_type_id.name}.",
"priority": 100,
@@ -1118,64 +1122,64 @@ class PmsReservationService(Component):
"code": "overbooking_without_availability",
"title": "Overbooking",
"domain": "["
"('state', 'in', ['draft', 'confirm', 'arrival_delayed']), "
"('overbooking', '=', True), "
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"('state', 'in', ['draft', 'confirm', 'arrival_delayed']), "
"('overbooking', '=', True), "
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"filtered": "lambda r: r.count_alternative_free_rooms <= 0",
"text": f"Parece que ha entrado una reserva sin haber disponibilidad para {reservation.room_type_id.name}."
f"Por desgracia no parece que hay ninguna "
f"habitación disponible con la capacidad suficiente para esta reserva",
f"Por desgracia no parece que hay ninguna "
f"habitación disponible con la capacidad suficiente para esta reserva",
"priority": 150,
},
{
"code": "splitted_without_availability",
"title": "Divididas",
"domain": "[('state', 'in', ['draft', 'confirm', 'arrival_delayed']),"
"('splitted', '=', True),"
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"('splitted', '=', True),"
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"filtered": "lambda r: r.count_alternative_free_rooms <= 0",
"text": f"Parece que a {reservation.partner_name} le ha tocado dormir en habitaciones diferentes "
f" pero no hay ninguna habitación disponible para asignarle, puedes probar a mover otras reservas "
f" para poder establecerle una única habitación. ",
f" pero no hay ninguna habitación disponible para asignarle, puedes probar a mover otras reservas "
f" para poder establecerle una única habitación. ",
"priority": 200,
},
{
"code": "splitted_with_availability",
"title": "Divididas",
"domain": "[('state', 'in', ['draft', 'confirm', 'arrival_delayed']),"
"('splitted', '=', True),"
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"('splitted', '=', True),"
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"filtered": "lambda r: r.count_alternative_free_rooms",
"text": f"Parece que a {reservation.partner_name} le ha tocado dormir en habitaciones diferentes"
f" pero tienes la posibilidad de moverlo a {reservation.count_alternative_free_rooms} "
f" {' habitación' if reservation.count_alternative_free_rooms == 1 else ' habitaciones'}.",
f" pero tienes la posibilidad de moverlo a {reservation.count_alternative_free_rooms} "
f" {' habitación' if reservation.count_alternative_free_rooms == 1 else ' habitaciones'}.",
"priority": 220,
},
{
"code": "to_assign",
"title": "Por asignar",
"domain": "[('state', 'in', ['draft', 'confirm', 'arrival_delayed']),"
"('to_assign', '=', True),"
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>=', '{today}'),"
"]",
"('to_assign', '=', True),"
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>=', '{today}'),"
"]",
"text": f"La reserva de {reservation.partner_name} ha sido asignada a la habitación {reservation.preferred_room_id.name},"
" puedes confirmar la habitación o cambiar a otra desde aquí.",
" puedes confirmar la habitación o cambiar a otra desde aquí.",
"priority": 300,
},
{
"code": "to_confirm",
"title": "Por confirmar",
"domain": "[('state', '=', 'draft'),"
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff']),"
"]",
f"('checkin', '>=', '{today}'),"
"('reservation_type', 'in', ['normal', 'staff']),"
"]",
"text": f"La reserva de {reservation.partner_name} está pendiente de confirmar, puedes confirmarla desde aquí.",
"priority": 400,
},
@@ -1183,36 +1187,36 @@ class PmsReservationService(Component):
"code": "checkin_done_precheckin",
"title": "Entrada Hoy",
"domain": "[('state', '=', 'confirm'),"
f"('checkin', '=', '{today}'),"
"('pending_checkin_data', '=', 0),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
f"('checkin', '=', '{today}'),"
"('pending_checkin_data', '=', 0),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"text": "Todos los huéspedes de esta reserva tienen los datos registrados, "
" puedes marcar la entrada directamente desde aquí",
" puedes marcar la entrada directamente desde aquí",
"priority": 500,
},
{
"code": "checkin_partial_precheckin",
"title": "Entrada Hoy",
"domain": "[('state', '=', 'confirm'),"
f"('checkin', '=', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('checkin_partner_ids.state','=', 'precheckin'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
f"('checkin', '=', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('checkin_partner_ids.state','=', 'precheckin'),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"text": f"Faltan {reservation.pending_checkin_data} {' huésped ' if reservation.pending_checkin_data == 1 else ' huéspedes '} "
f"por registrar sus datos.Puedes abrir el asistente de checkin "
f" para completar los datos.",
f"por registrar sus datos.Puedes abrir el asistente de checkin "
f" para completar los datos.",
"priority": 530,
},
{
"code": "checkin_no_precheckin",
"title": "Entrada Hoy",
"domain": "[('state', '=', 'confirm'),"
f"('checkin', '=', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
f"('checkin', '=', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('reservation_type', 'in', ['normal', 'staff'])"
"]",
"filtered": "lambda r: all([c.state in ('draft','dummy') for c in r.checkin_partner_ids]) ",
"text": "Registra los datos de los huéspedes desde el asistente del checkin.",
"priority": 580,
@@ -1221,24 +1225,24 @@ class PmsReservationService(Component):
"code": "confirmed_without_payment_and_precheckin",
"title": "Confirmadas a futuro sin pagar y sin precheckin realizado",
"domain": "[('state', 'in', ['draft', 'confirm', 'arrival_delayed']),"
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"text": "Esta reserva está pendiente de cobro y de que los huéspedes "
" registren sus datos: puedes enviarles un recordatorio desde aquí",
" registren sus datos: puedes enviarles un recordatorio desde aquí",
"priority": 600,
},
{
"code": "confirmed_without_payment",
"title": "Confirmadas a futuro sin pagar",
"domain": "[('state', 'in', ['draft', 'confirm', 'arrival_delayed']),"
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>', '{today}'),"
"('pending_checkin_data', '=', 0),"
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>', '{today}'),"
"('pending_checkin_data', '=', 0),"
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"text": "Esta reserva está pendiente de cobro, puedes enviarle sun recordatorio desde aquí",
"priority": 630,
},
@@ -1246,11 +1250,11 @@ class PmsReservationService(Component):
"code": "confirmed_without_precheckin",
"title": "Confirmadas a futuro sin pagar",
"domain": "[('state', 'in', ['draft', 'confirm', 'arrival_delayed']),"
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('folio_payment_state', 'in', ['paid', 'overpayment','nothing_to_pay'])"
"]",
"('reservation_type', 'in', ['normal', 'staff']),"
f"('checkin', '>', '{today}'),"
"('pending_checkin_data', '>', 0),"
"('folio_payment_state', 'in', ['paid', 'overpayment','nothing_to_pay'])"
"]",
"text": "Esta reserva no tiene los datos de los huéspedes registrados, puedes enviarles un recordatorio desde aquí",
"priority": 660,
},
@@ -1258,20 +1262,20 @@ class PmsReservationService(Component):
"code": "cancelled",
"title": "Cancelada con cargos y sin cobrar",
"domain": "[('state', '=', 'cancel'),"
"('cancelled_reason', 'in',['late','noshow']),"
"('folio_payment_state', 'in', ['not_paid', 'partial']),"
"]",
"('cancelled_reason', 'in',['late','noshow']),"
"('folio_payment_state', 'in', ['not_paid', 'partial']),"
"]",
"filtered": "lambda r: r.service_ids.filtered(lambda s: s.is_cancel_penalty and s.price_total > 0)",
"text": f"La reserva de {reservation.partner_name} ha sido cancelada con una penalización de {reservation.service_ids.filtered(lambda s: s.is_cancel_penalty).price_total}€,"
" puedes eliminar la penalización en caso de que no se vaya a cobrar.",
" puedes eliminar la penalización en caso de que no se vaya a cobrar.",
"priority": 700,
},
{
"code": "onboard_without_payment",
"title": "Por cobrar dentro",
"domain": "[('state', 'in', ['onboard', 'departure_delayed']),"
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"text": f"En esta reserva tenemos un pago pendiente de {reservation.folio_pending_amount}. Puedes registrar el pago desde aquí.",
"priority": 800,
},
@@ -1279,37 +1283,37 @@ class PmsReservationService(Component):
"code": "done_without_payment",
"title": "Por cobrar pasadas",
"domain": "[('state', '=', 'done'),"
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"('folio_payment_state', 'in', ['not_paid', 'partial'])"
"]",
"text": f"Esta reserva ha quedado con un cargo pendiente de {reservation.folio_pending_amount}€."
" Cuando gestiones el cobro puedes registrarlo desde aquí.",
" Cuando gestiones el cobro puedes registrarlo desde aquí.",
"priority": 900,
},
{
"code":"checkout",
"code": "checkout",
"title": "Checkout",
"domain": "[('state', 'in', ['onboard', 'departure_delayed']),"
f"('checkout', '=', '{today}'),"
"]",
f"('checkout', '=', '{today}'),"
"]",
"text": "Reserva lista para el checkout, marca la salida directamente desde aquí.",
"priority": 1000,
},
]
# We order the states by priority and return the first state whose domain meets the reservation;
# if the state also has the key 'filtered,' it must also meet that filter.
# We order the states by priority and return the first
# state whose domain meets the reservation;
# if the state also has the key 'filtered,'
# it must also meet that filter.
sorted_wizard_states = sorted(wizard_states, key=lambda x: x['priority'])
sorted_wizard_states = sorted(wizard_states, key=lambda x: x["priority"])
PmsWizardStateInfo = self.env.datamodels["pms.wizard.state.info"]
for state in sorted_wizard_states:
domain = expression.AND([
[("id", "=", reservation_id)],
safe_eval(state["domain"])
])
domain = expression.AND(
[[("id", "=", reservation_id)], safe_eval(state["domain"])]
)
if self.env["pms.reservation"].search_count(domain):
if (
state.get("filtered")
and not self.env["pms.reservation"].browse(reservation_id).filtered(safe_eval(state["filtered"]))
):
if state.get("filtered") and not self.env["pms.reservation"].browse(
reservation_id
).filtered(safe_eval(state["filtered"])):
continue
return PmsWizardStateInfo(
@@ -1319,8 +1323,7 @@ class PmsReservationService(Component):
)
return PmsWizardStateInfo(
code='',
title='',
text='',
)
code="",
title="",
text="",
)