diff --git a/pms_api_rest/models/__init__.py b/pms_api_rest/models/__init__.py
index d57002e00..8a708fe07 100644
--- a/pms_api_rest/models/__init__.py
+++ b/pms_api_rest/models/__init__.py
@@ -7,3 +7,4 @@ from . import account_bank_statement
from . import product_template
from . import ota_property_settings
from . import pms_api_log
+from . import pms_folio
diff --git a/pms_api_rest/models/pms_api_log.py b/pms_api_rest/models/pms_api_log.py
index 2509ee551..9e3dcb2bf 100644
--- a/pms_api_rest/models/pms_api_log.py
+++ b/pms_api_rest/models/pms_api_log.py
@@ -1,4 +1,4 @@
-from odoo import fields, models
+from odoo import _, api, fields, models
class PmsApiLog(models.Model):
@@ -72,3 +72,63 @@ class PmsApiLog(models.Model):
string="Response URL",
help="Response URL",
)
+ model_id = fields.Many2one(
+ string="Model",
+ help="Model",
+ comodel_name="ir.model",
+ )
+
+ def related_action_open_record(self):
+ """Open a form view with the record(s) of the record log.
+
+ For instance, for a job on a ``pms.folio``, it will open a
+ ``pms.product`` form view with the product record(s) concerned by
+ the job. If the job concerns more than one record, it opens them in a
+ list.
+
+ This is the default related action.
+
+ """
+ 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)]
+ )
+ if not records:
+ return None
+ action = {
+ "name": _("Related Record"),
+ "type": "ir.actions.act_window",
+ "view_mode": "form",
+ "res_model": records._name,
+ }
+ if len(records) == 1:
+ action["res_id"] = records.id
+ else:
+ action.update(
+ {
+ "name": _("Related Records"),
+ "view_mode": "tree,form",
+ "domain": [("id", "in", records.ids)],
+ }
+ )
+ return action
+
+ @api.model
+ def create(self, vals):
+ """
+ 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
diff --git a/pms_api_rest/models/pms_folio.py b/pms_api_rest/models/pms_folio.py
new file mode 100644
index 000000000..498b1f3fb
--- /dev/null
+++ b/pms_api_rest/models/pms_folio.py
@@ -0,0 +1,15 @@
+from odoo import fields, models
+
+
+class PmsFolio(models.Model):
+ _name = "pms.folio"
+
+ pms_api_log_id = fields.Many2one(
+ string="PMS API Log",
+ help="PMS API Log",
+ comodel_name="pms.api.log",
+ )
+ origin_json = fields.Text(
+ string="Origin JSON",
+ help="Origin JSON",
+ )
diff --git a/pms_api_rest/services/pms_folio_service.py b/pms_api_rest/services/pms_folio_service.py
index 42880f8b1..cb19d9541 100644
--- a/pms_api_rest/services/pms_folio_service.py
+++ b/pms_api_rest/services/pms_folio_service.py
@@ -1,3 +1,4 @@
+import ast
import base64
import logging
from datetime import datetime, timedelta
@@ -651,6 +652,7 @@ class PmsFolioService(Component):
"reservation_type": pms_folio_info.reservationType or "normal",
"children": reservation.children,
"preconfirm": pms_folio_info.preconfirm,
+ "blocked": True if external_app else False,
}
if reservation.reservationLines:
vals_lines = []
@@ -704,6 +706,7 @@ class PmsFolioService(Component):
.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,
)
.create(vals)
)
@@ -753,7 +756,8 @@ class PmsFolioService(Component):
or reservation_record.board_service_room_id == 0
):
reservation_record.with_context(
- skip_compute_service_ids=False
+ skip_compute_service_ids=False,
+ 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
@@ -789,7 +793,9 @@ class PmsFolioService(Component):
date_to=date_to,
)
if external_app:
- self.env["pms.api.log"].sudo().create(
+ self.env["pms.api.log"].with_context(
+ record_ids=folio.ids
+ ).sudo().create(
{
"pms_property_id": pms_folio_info.pmsPropertyId,
"client_id": self.env.user.id,
@@ -799,6 +805,9 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "POST",
"endpoint": "/folios",
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", "pms.folio")])
+ .id,
}
)
return folio.id
@@ -818,6 +827,9 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "POST",
"endpoint": "/folios",
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", "pms.folio")])
+ .id,
}
)
if not external_app:
@@ -1590,7 +1602,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"].create(
+ self.env["pms.api.log"].with_context(record_ids=folio.ids).sudo().create(
{
"pms_property_id": pms_folio_info.pmsPropertyId,
"client_id": self.env.user.id,
@@ -1600,6 +1612,9 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", "pms.folio")])
+ .id,
}
)
return folio.id
@@ -1619,6 +1634,9 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", "pms.folio")])
+ .id,
}
)
if not external_app:
@@ -1646,7 +1664,7 @@ class PmsFolioService(Component):
if not folio:
raise MissingError(_("Folio not found"))
self.update_folio_values(folio, pms_folio_info)
- self.env["pms.api.log"].create(
+ self.env["pms.api.log"].with_context(record_ids=folio.ids).sudo().create(
{
"pms_property_id": pms_folio_info.pmsPropertyId,
"client_id": self.env.user.id,
@@ -1656,8 +1674,12 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", "pms.folio")])
+ .id,
}
)
+
return folio.id
except Exception as e:
_logger.error(
@@ -1675,6 +1697,9 @@ class PmsFolioService(Component):
"request_date": fields.Datetime.now(),
"method": "PUT",
"endpoint": "/folios",
+ "model_id": self.env["ir.model"]
+ .search([("model", "=", "pms.folio")])
+ .id,
}
)
if not external_app:
@@ -1684,6 +1709,15 @@ 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":
folio.action_cancel()
@@ -1699,9 +1733,15 @@ class PmsFolioService(Component):
# reservation.confirm()
if (
pms_folio_info.internalComment is not None
- and folio.internal_comment != pms_folio_info.internalComment
+ and pms_folio_info.internalComment not in folio.internal_comment
):
- folio_vals.update({"internal_comment": pms_folio_info.internalComment})
+ folio_vals.update(
+ {
+ "internal_comment": folio.internal_comment
+ + " "
+ + pms_folio_info.internalComment
+ }
+ )
if pms_folio_info.partnerId and folio.partner_id.id != pms_folio_info.partnerId:
folio_vals.update({"partner_id": pms_folio_info.partnerId})
elif not pms_folio_info.partnerId:
@@ -1735,12 +1775,18 @@ class PmsFolioService(Component):
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:
+ if val[0] == 1:
+ update_reservation_ids.append(val[1])
folio.reservation_ids.filtered(
- lambda r: r.state != "cancel"
+ lambda r: r.state != "cancel" and r.id not in update_reservation_ids
).with_context(modified=True, force_write_blocked=True).action_cancel()
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)
@@ -1819,34 +1865,83 @@ class PmsFolioService(Component):
"""
external_app = self.env.user.pms_api_client
cmds = []
+ saved_reservations = folio.reservation_ids
for info_reservation in info_reservations:
- vals = {}
- vals.update({"folio_id": folio.id})
- if info_reservation.roomTypeId:
- vals.update({"room_type_id": info_reservation.roomTypeId})
- if info_reservation.checkin:
- vals.update({"checkin": info_reservation.checkin})
- if info_reservation.checkout:
- vals.update({"checkout": info_reservation.checkout})
- if info_reservation.pricelistId:
- vals.update({"pricelist_id": info_reservation.pricelistId})
- if info_reservation.boardServiceId:
- vals.update(
- {
- "board_service_room_id": self.get_board_service_room_type_id(
- info_reservation.boardServiceId,
- info_reservation.roomTypeId,
- folio.pms_property_id.id,
- )
- }
+ # 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
+ )
+ if proposed_reservation:
+ saved_reservations -= proposed_reservation
+ vals = {}
+ new_res = not proposed_reservation
+ if new_res:
+ vals.update({"folio_id": folio.id})
+ if info_reservation.roomTypeId:
+ if (
+ new_res
+ or proposed_reservation.room_type_id.id
+ != info_reservation.roomTypeId
+ ):
+ vals.update({"room_type_id": info_reservation.roomTypeId})
+ if info_reservation.checkin:
+ if new_res or proposed_reservation.checkin != info_reservation.checkin:
+ vals.update({"checkin": info_reservation.checkin})
+ if info_reservation.checkout:
+ if (
+ new_res
+ or proposed_reservation.checkout != info_reservation.checkout
+ ):
+ vals.update({"checkout": info_reservation.checkout})
+ if info_reservation.pricelistId:
+ if (
+ new_res
+ or proposed_reservation.pricelist_id.id
+ != info_reservation.pricelistId
+ ):
+ vals.update({"pricelist_id": info_reservation.pricelistId})
+ if info_reservation.boardServiceId:
+ board_service_id = self.get_board_service_room_type_id(
+ info_reservation.boardServiceId,
+ info_reservation.roomTypeId,
+ folio.pms_property_id.id,
+ )
+ if (
+ new_res
+ or proposed_reservation.board_service_room_id.id != board_service_id
+ ):
+ vals.update({"board_service_room_id": board_service_id})
if info_reservation.preferredRoomId:
- vals.update({"preferred_room_id": info_reservation.preferredRoomId})
+ if (
+ new_res
+ or proposed_reservation.preferredRoomId
+ != info_reservation.preferredRoomId
+ ):
+ vals.update({"preferred_room_id": info_reservation.preferredRoomId})
if info_reservation.adults:
- vals.update({"adults": info_reservation.adults})
+ if new_res or proposed_reservation.adults != info_reservation.adults:
+ vals.update({"adults": info_reservation.adults})
if info_reservation.children:
- vals.update({"children": info_reservation.children})
- if info_reservation.reservationLines:
+ if (
+ new_res
+ or proposed_reservation.children != info_reservation.children
+ ):
+ vals.update({"children": info_reservation.children})
+ if info_reservation.reservationLines and new_res:
# 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"):
@@ -1883,14 +1978,19 @@ class PmsFolioService(Component):
vals.update({"reservation_line_ids": reservation_lines_cmds})
if info_reservation.services:
reservation_services_cmds = self.wrapper_reservation_services(
- info_reservation.services
+ info_services=info_reservation.services,
+ services=proposed_reservation.service_ids
+ if proposed_reservation
+ else False,
)
if reservation_services_cmds:
vals.update({"service_ids": reservation_services_cmds})
if not vals:
continue
- else:
+ elif new_res:
cmds.append((0, False, vals))
+ else:
+ cmds.append((1, proposed_reservation.id, vals))
return cmds
def wrapper_reservation_lines(self, reservation, board_day_price=0):
@@ -1909,17 +2009,27 @@ class PmsFolioService(Component):
)
return cmds
- def wrapper_reservation_services(self, info_reservations):
+ def wrapper_reservation_services(self, info_services, services=False):
cmds = []
- for service in info_reservations:
+ for info_service in info_services:
+ if services:
+ service_id = services.filtered(
+ lambda s: s.product_id.id == info_service.productId
+ )
+ if service_id:
+ service_id = service_id[0]
+ services -= service_id
+ else:
+ service_id = False
+
cmds.append(
(
0,
False,
{
- "product_id": service.productId,
- "product_qty": service.quantity,
- "discount": service.discount or 0,
+ "product_id": info_service.productId,
+ "product_qty": info_service.quantity,
+ "discount": info_service.discount or 0,
},
)
)
diff --git a/pms_api_rest/views/pms_api_log_views.xml b/pms_api_rest/views/pms_api_log_views.xml
index 75d83467d..ec8b9bcbc 100644
--- a/pms_api_rest/views/pms_api_log_views.xml
+++ b/pms_api_rest/views/pms_api_log_views.xml
@@ -21,6 +21,13 @@
pms.api.log