diff --git a/pms_api_rest/datamodels/pms_avail.py b/pms_api_rest/datamodels/pms_avail.py index b4b4d25da6..5dfb3b2987 100644 --- a/pms_api_rest/datamodels/pms_avail.py +++ b/pms_api_rest/datamodels/pms_avail.py @@ -1,8 +1,6 @@ from marshmallow import fields from odoo.addons.datamodel.core import Datamodel -from odoo.addons.datamodel.fields import NestedModel - class PmsAvailSearchParam(Datamodel): @@ -26,4 +24,3 @@ class PmsAvailInfoRoomType(Datamodel): _name = "pms.avail.info.room.type" roomTypeId = fields.Integer(required=True, allow_none=False) count = fields.Integer(required=True, allow_none=False) - diff --git a/pms_api_rest/datamodels/pms_folio.py b/pms_api_rest/datamodels/pms_folio.py index 62d2c35e56..64fdb6280d 100644 --- a/pms_api_rest/datamodels/pms_folio.py +++ b/pms_api_rest/datamodels/pms_folio.py @@ -40,6 +40,10 @@ class PmsFolioInfo(Datamodel): reservations = fields.List( NestedModel("pms.reservation.info"), required=False, allow_none=True ) + + services = fields.List( + NestedModel("pms.service.info"), required=False, allow_none=True + ) pricelistId = fields.Integer(required=False, allow_none=True) saleChannelId = fields.Integer(required=False, allow_none=True) agencyId = fields.Integer(required=False, allow_none=True) diff --git a/pms_api_rest/datamodels/pms_reservation.py b/pms_api_rest/datamodels/pms_reservation.py index 2f7ce76889..0032decb8c 100644 --- a/pms_api_rest/datamodels/pms_reservation.py +++ b/pms_api_rest/datamodels/pms_reservation.py @@ -56,7 +56,6 @@ class PmsReservationInfo(Datamodel): name = fields.String(required=False, allow_none=True) folioId = fields.Integer(required=False, allow_none=True) folioSequence = fields.Integer(required=False, allow_none=True) - partnerId = fields.Integer(required=False, allow_none=True) partnerName = fields.String(required=False, allow_none=True) boardServiceId = fields.Integer(required=False, allow_none=True) boardServices = fields.List( diff --git a/pms_api_rest/datamodels/pms_service_line.py b/pms_api_rest/datamodels/pms_service_line.py index b1d2425e10..c3d2cd0fb9 100644 --- a/pms_api_rest/datamodels/pms_service_line.py +++ b/pms_api_rest/datamodels/pms_service_line.py @@ -10,3 +10,4 @@ class PmsServiceLineInfo(Datamodel): priceUnit = fields.Float(required=False, allow_none=True) discount = fields.Float(required=False, allow_none=True) quantity = fields.Integer(required=False, allow_none=True) + isBoardService = fields.Boolean(required=False, allow_none=True) diff --git a/pms_api_rest/models/pms_folio.py b/pms_api_rest/models/pms_folio.py index 6e87063e13..b0bafd0bbb 100644 --- a/pms_api_rest/models/pms_folio.py +++ b/pms_api_rest/models/pms_folio.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from werkzeug.exceptions import BadRequest + +from odoo import _, fields, models class PmsFolio(models.Model): @@ -13,3 +15,321 @@ class PmsFolio(models.Model): column1="folio_ids", column2="pms_api_log_ids", ) + + def _compare_simple_field(self, record, field, new_value, transform=str): + if not record: + return True + return transform(getattr(record, field, None)) != transform(new_value) + + def _compare_field_ids(self, record_field, new_id): + current_id = record_field.id if record_field else None + return new_id != current_id + + def _build_core_fields_vals(self, reservation, reservation_record, pms_folio_info): + vals = {} + if reservation.checkin is not None and self._compare_simple_field( + reservation_record, "checkin", reservation.checkin + ): + vals["checkin"] = reservation.checkin + if reservation.checkout is not None and self._compare_simple_field( + reservation_record, "checkout", reservation.checkout + ): + vals["checkout"] = reservation.checkout + reservation_type = reservation.reservationType or pms_folio_info.reservationType + if reservation_type is not None and self._compare_simple_field( + reservation_record, "reservation_type", reservation_type + ): + vals["reservation_type"] = reservation_type + if pms_folio_info.preconfirm is not None and ( + not reservation_record + or pms_folio_info.preconfirm != reservation_record.preconfirm + ): + vals["preconfirm"] = pms_folio_info.preconfirm + return vals + + def _build_person_vals(self, reservation, reservation_record, pms_folio_info): + vals = {} + partner_id = reservation.partnerId or pms_folio_info.partnerId + if partner_id is not None: + if not reservation_record or self._compare_field_ids( + reservation_record.partner_id, partner_id + ): + vals["partner_id"] = partner_id + if reservation.adults is not None and ( + not reservation_record or reservation.adults != reservation_record.adults + ): + vals["adults"] = reservation.adults + if reservation.children is not None and ( + not reservation_record + or reservation.children != reservation_record.children + ): + vals["children"] = reservation.children + return vals + + def _build_product_vals(self, reservation, reservation_record, pms_folio_info): + vals = {} + if reservation.roomTypeId is not None: + if not reservation_record or self._compare_field_ids( + reservation_record.room_type_id, reservation.roomTypeId + ): + vals["room_type_id"] = reservation.roomTypeId + pricelist_id = reservation.pricelistId or pms_folio_info.pricelistId + if pricelist_id is not None: + if not reservation_record or self._compare_field_ids( + reservation_record.pricelist_id, pricelist_id + ): + vals["pricelist_id"] = pricelist_id + if reservation.boardServiceId is not None: + if not reservation_record or self._compare_field_ids( + reservation_record.board_service_room_id, reservation.boardServiceId + ): + vals["board_service_room_id"] = ( + reservation.boardServiceId + if reservation.boardServiceId != 0 + else False + ) + return vals + + def _build_subrecords_vals(self, reservation, reservation_record): + vals = {} + if reservation.reservationLines: + cmds_lines = self.env["pms.reservation"].build_reservation_lines_cmds( + reservation_record, reservation.reservationLines + ) + if cmds_lines: + vals["reservation_line_ids"] = cmds_lines + cmds_service_ids = self.env["pms.reservation"].build_reservation_services_cmds( + reservation_record, + reservation.services or [], + reservation.boardServiceId or False, + ) + if cmds_service_ids: + vals["service_ids"] = cmds_service_ids + return vals + + def _build_reservation_vals(self, reservation, reservation_record, pms_folio_info): + vals = {} + vals.update( + self._build_core_fields_vals( + reservation, reservation_record, pms_folio_info + ) + ) + vals.update( + self._build_person_vals(reservation, reservation_record, pms_folio_info) + ) + vals.update( + self._build_product_vals(reservation, reservation_record, pms_folio_info) + ) + vals.update(self._build_subrecords_vals(reservation, reservation_record)) + return vals + + def build_reservations_cmds(self, folio_record, pms_folio_info): + cmds = [] + existing_reservation_ids = [] + + for reservation in pms_folio_info.reservations: + reservation_record = self.env["pms.reservation"].search( + [("id", "=", reservation.id)] + ) + if reservation_record: + existing_reservation_ids.append(reservation_record.id) + + vals = self._build_reservation_vals( + reservation, reservation_record, pms_folio_info + ) + if vals: + cmds.append( + (1, reservation_record.id, vals) + if reservation_record + else (0, 0, vals) + ) + + if folio_record and folio_record.reservation_ids.filtered( + lambda x: x.id not in existing_reservation_ids and x.state != "cancel" + ): + raise BadRequest(_("Removing reservations is not allowed")) + + return cmds + + def build_creation_update_services_cmds(self, services): + cmds = [] + existing_service_ids = [] + for service in services: + # search for existing service + service_record = self.env["pms.service"].search([("id", "=", service.id)]) + # if service exists add to existing_service_ids + if service_record: + existing_service_ids.append(service_record.id) + + # initialize vals + service_vals = {} + + # product_id + if service.productId is not None: + if ( + not service_record + or service.productId != service_record.product_id.id + ): + service_vals.update({"product_id": service.productId}) + # name + if service.name is not None: + if not service_record or service.name != service_record.name: + service_vals.update({"name": service.name}) + + # isBoardService + if service.isBoardService is not None: + if ( + not service_record + or service.isBoardService != service_record.is_board_service + ): + service_vals.update({"is_board_service": service.isBoardService}) + + # serviceLines + if service.serviceLines is not None: + cmds_service_lines = self.build_service_lines_cmds( + service_record, service.serviceLines + ) + if cmds_service_lines: + service_vals.update({"service_line_ids": cmds_service_lines}) + service_vals.update({"no_auto_add_lines": True}) + + # add reservation to modify/create cmds + if service_vals: + if service_record: + cmds.append((1, service_record.id, service_vals)) + else: + cmds.append((0, 0, service_vals)) + + return cmds, existing_service_ids + + def build_services_cmds(self, folio_record, services): + cmds, existing_service_ids = self.build_creation_update_services_cmds(services) + + # iterate existing services to remove the ones not in the request + for service_to_remove in folio_record.service_ids.filtered( + lambda x: x.id not in existing_service_ids and not x.reservation_id + ): + cmds.append((2, service_to_remove.id)) + return cmds + + def build_service_lines_cmds(self, service_record, service_lines): + cmds = [] + existing_service_line_ids = [] + for service_line in service_lines: + service_line_record = False + if service_record: + # search for existing service line + service_line_record = self.env["pms.service.line"].search( + [ + ("date", "=", service_line.date), + ("service_id", "=", service_record.id), + ] + ) + # if service line exists add to existing services lines + if service_line_record: + existing_service_line_ids.append(service_line_record.id) + + # initialize vals + service_line_vals = {} + + # date + if service_line.date is not None: + if not service_line_record or service_line.date != str( + service_line_record.date + ): + service_line_vals.update({"date": service_line.date}) + + # priceUnit + if service_line.priceUnit is not None: + if not service_line_record or round(service_line.priceUnit, 2) != round( + service_line_record.price_unit, 2 + ): + service_line_vals.update({"price_unit": service_line.priceUnit}) + + # discount + if service_line.discount is not None: + if not service_line_record or round(service_line.discount, 2) != round( + service_line_record.discount, 2 + ): + service_line_vals.update({"discount": service_line.discount}) + + # quantity + if service_line.quantity is not None: + if ( + not service_line_record + or service_line.quantity != service_line_record.day_qty + ): + service_line_vals.update({"day_qty": service_line.quantity}) + + # add service line to modify/create cmds + if service_line_vals: + if not service_line_record: + cmds.append((0, 0, service_line_vals)) + else: + cmds.append((1, service_line_record.id, service_line_vals)) + + # iterate existing service lines to remove the ones not in the request + if service_record: + for service_line_to_remove in service_record.service_line_ids.filtered( + lambda x: x.id not in existing_service_line_ids + ): + cmds.append((2, service_line_to_remove.id)) + return cmds + + def create_folio_vals(self, folio_record, pms_folio_info): + folio_vals = {} + + def update(field_name, record_attr, key=None, transform=lambda x: x): + key = key or record_attr + incoming_value = getattr(pms_folio_info, field_name) + if incoming_value is not None: + existing_value = ( + getattr(folio_record, record_attr, None) if folio_record else None + ) + if transform(incoming_value) != transform(existing_value): + folio_vals[key] = incoming_value + + update("pmsPropertyId", "pms_property_id") + update("pricelistId", "pricelist_id") + update("reservationType", "reservation_type") + update("partnerId", "partner_id") + update("partnerName", "partner_name") + update("partnerEmail", "email") + update("partnerPhone", "mobile") + update("saleChannelId", "channel_type_id", "sale_channel_origin_id") + update("agencyId", "agency_id") + update("externalReference", "external_reference") + update("internalComment", "internal_comment") + update("closureReasonId", "closure_reason_id") + update( + "outOfServiceDescription", + "out_of_service_description", + "out_service_description", + ) + + # language (special case) + if pms_folio_info.language: + lang_obj = self.env["res.lang"].search( + [("iso_code", "=", pms_folio_info.language)], limit=1 + ) + lang = lang_obj.code if lang_obj else pms_folio_info.language + if not folio_record or lang != folio_record.lang: + folio_vals["lang"] = lang + + # reservation_ids + if pms_folio_info.reservations: + cmds_reservations = self.env["pms.folio"].build_reservations_cmds( + folio_record, pms_folio_info + ) + if cmds_reservations: + folio_vals["reservation_ids"] = cmds_reservations + + # service_ids + if pms_folio_info.services: + cmds_services_folio = self.env["pms.folio"].build_services_cmds( + folio_record, pms_folio_info.services + ) + if cmds_services_folio: + folio_vals["service_ids"] = cmds_services_folio + + return folio_vals diff --git a/pms_api_rest/models/pms_reservation.py b/pms_api_rest/models/pms_reservation.py index 80b7b75502..405c18b254 100644 --- a/pms_api_rest/models/pms_reservation.py +++ b/pms_api_rest/models/pms_reservation.py @@ -10,3 +10,92 @@ def create(self, vals_list): for record in records: record._portal_ensure_token() return records + + def build_reservation_lines_cmds(self, reservation, reservation_lines): + cmds = [] + existing_reservation_line_ids = [] + for reservation_line in reservation_lines: + reservation_line_vals = {} + # search reservation line record + reservation_line_record = self.env["pms.reservation.line"].search( + [ + ("date", "=", reservation_line.date), + ("reservation_id", "=", reservation.id), + ] + ) + # add reservation line record id to existing_reservation_line_ids + if reservation_line_record: + existing_reservation_line_ids.append(reservation_line_record.id) + + # date + if reservation_line.date is not None: + if not reservation_line_record or reservation_line.date != str( + reservation_line_record.date + ): + reservation_line_vals.update({"date": reservation_line.date}) + # price + if reservation_line.price is not None: + if not reservation_line_record or round( + reservation_line.price, 2 + ) != round(reservation_line_record.price, 2): + reservation_line_vals.update({"price": reservation_line.price}) + # discount + if reservation_line.discount is not None: + if not reservation_line_record or round( + reservation_line.discount, 2 + ) != round(reservation_line_record.discount, 2): + reservation_line_vals.update( + {"discount": reservation_line.discount} + ) + # roomId + if reservation_line.roomId is not None: + if ( + not reservation_line_record + or reservation_line.roomId != reservation_line_record.room_id.id + ): + reservation_line_vals.update({"room_id": reservation_line.roomId}) + # isReselling + if reservation_line.isReselling is not None: + if ( + not reservation_line_record + or reservation_line.isReselling + != reservation_line_record.is_reselling + ): + reservation_line_vals.update( + {"is_reselling": reservation_line.isReselling} + ) + + # add reservation lines to modify/create cmds + if reservation_line_vals: + if not reservation_line_record: + cmds.append((0, 0, reservation_line_vals)) + else: + cmds.append((1, reservation_line_record.id, reservation_line_vals)) + + # remove old reservation lines + for reservation_line_to_remove in reservation.reservation_line_ids.filtered( + lambda x: x.id not in existing_reservation_line_ids + ): + cmds.append((2, reservation_line_to_remove.id)) + return cmds + + def build_reservation_services_cmds( + self, reservation_record, services, board_service_id + ): + cmds, existing_service_ids = self.env[ + "pms.folio" + ].build_creation_update_services_cmds(services) + + # remove board services if board_service_id is 0 + if board_service_id == 0: + for board_service_to_remove in reservation_record.service_ids.filtered( + lambda x: x.is_board_service + ): + cmds.append((2, board_service_to_remove.id)) + + # iterate existing services to remove the ones not in the request + for service_to_remove in reservation_record.service_ids.filtered( + lambda x: x.id not in existing_service_ids + ): + cmds.append((2, service_to_remove.id)) + return cmds diff --git a/pms_api_rest/services/pms_agency_service.py b/pms_api_rest/services/pms_agency_service.py index f65b36002c..af62b8195d 100644 --- a/pms_api_rest/services/pms_agency_service.py +++ b/pms_api_rest/services/pms_agency_service.py @@ -45,7 +45,7 @@ def get_agencies(self, agencies_search_param): imageUrl=url_image_pms_api_rest( "res.partner", agency.id, "image_128" ), - saleChannelId=agency.sale_channel_id.id + saleChannelId=agency.sale_channel_id.id, ) ) return result_agencies @@ -80,7 +80,7 @@ def get_agency(self, agency_id): id=agency.id, name=agency.name if agency.name else None, imageUrl=url_image_pms_api_rest("res.partner", agency.id, "image_128"), - saleChannelId=agency.sale_channel_id.id + saleChannelId=agency.sale_channel_id.id, ) else: raise MissingError(_("Agency not found")) diff --git a/pms_api_rest/services/pms_folio_service.py b/pms_api_rest/services/pms_folio_service.py index e9884401d9..7a439c6f98 100644 --- a/pms_api_rest/services/pms_folio_service.py +++ b/pms_api_rest/services/pms_folio_service.py @@ -1117,71 +1117,16 @@ def compute_transactions(self, folio, transactions): ) # flake8:noqa=C901 def update_folio(self, folio_id, pms_folio_info): - folio = self.env["pms.folio"].sudo().browse(folio_id) - pms_api_check_access(user=self.env.user, records=folio) - folio_vals = {} - if not folio: + folio_record = self.env["pms.folio"].search([("id", "=", folio_id)]) + if not folio_record: raise MissingError(_("Folio not found")) - if pms_folio_info.cancelReservations: - folio.action_cancel() - if pms_folio_info.confirmReservations: - for reservation in folio.reservation_ids: - 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: - folio_vals.update({"partner_id": pms_folio_info.partnerId}) - else: - if folio.partner_id: - folio.partner_id = False - if pms_folio_info.partnerName is not None: - folio_vals.update({"partner_name": pms_folio_info.partnerName}) - if pms_folio_info.partnerEmail is not None: - folio_vals.update({"email": pms_folio_info.partnerEmail}) - if pms_folio_info.partnerPhone is not None: - folio_vals.update({"mobile": pms_folio_info.partnerPhone}) - if pms_folio_info.language: - folio_vals.update({"lang": pms_folio_info.language}) - if pms_folio_info.reservations: - for reservation in pms_folio_info.reservations: - vals = { - "folio_id": folio.id, - "room_type_id": reservation.roomTypeId, - "checkin": reservation.checkin, - "checkout": reservation.checkout, - "pms_property_id": pms_folio_info.pmsPropertyId, - "pricelist_id": pms_folio_info.pricelistId, - "external_reference": pms_folio_info.externalReference, - "board_service_room_id": reservation.boardServiceId, - "preferred_room_id": reservation.preferredRoomId, - "adults": reservation.adults, - "reservation_type": pms_folio_info.reservationType, - "children": reservation.children, - } - reservation_record = self.env["pms.reservation"].sudo().create(vals) - if reservation.services: - for service in reservation.services: - vals = { - "product_id": service.productId, - "reservation_id": reservation_record.id, - "is_board_service": False, - "service_line_ids": [ - ( - 0, - False, - { - "date": line.date, - "price_unit": line.priceUnit, - "discount": line.discount or 0, - "day_qty": line.quantity, - }, - ) - for line in service.serviceLines - ], - } - self.env["pms.service"].sudo().create(vals) + pms_api_check_access(user=self.env.user, records=folio_record) + pms_folio_info = self.adjust_board_services_input(pms_folio_info) + folio_vals = self.env["pms.folio"].create_folio_vals( + folio_record=folio_record, pms_folio_info=pms_folio_info + ) if folio_vals: - folio.write(folio_vals) + folio_record.with_context(skip_compute_service_ids=True).write(folio_vals) # ------------------------------------------------------------------------------------ # FOLIO SERVICES---------------------------------------------------------------- @@ -2807,3 +2752,58 @@ def get_folio_payment_link(self, folio_id, folio_payment_link_search_param): return PmsFolioPaymentLinkInfo( paymentLink=payment_link, ) + + def adjust_board_services_input(self, pms_folio_info): + external_app = self.env.user.pms_api_client + # service datamodel + PmsServiceInfo = self.env.datamodels["pms.service.info"] + + # add service to reservations which have boardServiceId and not services with field isBoardService = True + if pms_folio_info.reservations: + for reservation in pms_folio_info.reservations: + res_has_bs = False + if ( + reservation.boardServiceId is not None + and reservation.boardServiceId != 0 + and reservation.services is not None + ): + if external_app: + reservation.boardServiceId = ( + self.get_board_service_room_type_id( + reservation.boardServiceId, + reservation.roomTypeId, + pms_folio_info.pmsPropertyId, + ) + ) + for service in reservation.services: + if service.isBoardService: + res_has_bs = True + break + if ( + reservation.boardServiceId is not None + and reservation.boardServiceId != 0 + and not res_has_bs + ): + board_service_record = self.env[ + "pms.board.service.room.type" + ].search( + [ + ( + "id", + "=", + reservation.boardServiceId, + ) + ] + ) + if not board_service_record: + raise MissingError(_("Board Service not found")) + if reservation.services is None: + reservation.services = [] + for service in board_service_record.board_service_line_ids: + reservation.services.append( + PmsServiceInfo( + productId=service.product_id.id, + isBoardService=True, + ) + ) + return pms_folio_info