From 8921d7e10efa09b3f574e9140d5e1086ff414091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sun, 15 Dec 2024 17:01:03 +0100 Subject: [PATCH] [ADD]pms: feature touristic taxes --- pms/models/pms_checkin_partner.py | 6 ++ pms/models/pms_reservation.py | 125 ++++++++++++++++++++++- pms/models/product_template.py | 25 +++++ pms/tests/test_tourist_taxes.py | 143 +++++++++++++++++++++++++++ pms/views/product_template_views.xml | 19 ++++ 5 files changed, 315 insertions(+), 3 deletions(-) create mode 100644 pms/tests/test_tourist_taxes.py diff --git a/pms/models/pms_checkin_partner.py b/pms/models/pms_checkin_partner.py index 325d4fe17b..d29dea4510 100644 --- a/pms/models/pms_checkin_partner.py +++ b/pms/models/pms_checkin_partner.py @@ -791,6 +791,12 @@ def create(self, vals): _("Is not possible to create the proposed check-in in this reservation") ) + def write(self, vals): + res = super().write(vals) + for record in self: + record.reservation_id._update_tourist_tax_service() + return res + def unlink(self): reservations = self.mapped("reservation_id") res = super().unlink() diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 76bb28d10e..002aed7991 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -7,6 +7,7 @@ from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError +from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -924,9 +925,11 @@ def _compute_allowed_room_ids(self): room_type_id=False, # Allows to choose any available room current_lines=reservation.reservation_line_ids.ids, pricelist_id=reservation.pricelist_id.id, - class_id=reservation.room_type_id.class_id.id - if reservation.room_type_id - else False, + class_id=( + reservation.room_type_id.class_id.id + if reservation.room_type_id + else False + ), real_avail=True, ) reservation.allowed_room_ids = pms_property.free_room_ids @@ -2152,6 +2155,7 @@ def create(self, vals): record.action_cancel() record._check_services(vals) + record._add_tourist_tax_service() return record def write(self, vals): @@ -2221,6 +2225,8 @@ def write(self, vals): # that not take access to possible extra beds service in vals if "adults" in vals: self._check_capacity() + if "checkin" in vals or "checkout" in vals or "reservation_line_ids" in vals: + self._update_tourist_tax_service() return res def _get_folio_vals(self, reservation_vals): @@ -2575,3 +2581,116 @@ def preview_reservation(self): "target": "self", "url": self.get_portal_url(), } + + def _add_tourist_tax_service(self): + for record in self: + tourist_tax_products = self.env["product.product"].search( + [("is_tourist_tax", "=", True)] + ) + for product in tourist_tax_products: + if product.touristic_calculation == "occupancy": + checkins = record.checkin_partner_ids.filtered_domain( + safe_eval(product.occupancy_domain) + ) + quantity = len(checkins) + elif product.touristic_calculation == "nights": + if not record.filtered_domain(safe_eval(product.nights_domain)): + continue + quantity = (record.checkout - record.checkin).days + elif product.touristic_calculation == "occupancyandnights": + checkins = record.checkin_partner_ids.filtered_domain( + safe_eval(product.occupancy_domain) + ) + if not record.filtered_domain(safe_eval(product.nights_domain)): + continue + quantity = len(checkins) * (record.checkout - record.checkin).days + else: + quantity = 1 + + if quantity == 0: + continue + + product = product.with_context( + lang=record.partner_id.lang, + partner=record.partner_id.id, + quantity=quantity, + date=record.date_order, + consumption_date=record.checkin, + pricelist=record.pricelist_id.id, + uom=product.uom_id.id, + property=record.pms_property_id.id, + ) + price = self.env["account.tax"]._fix_tax_included_price_company( + product.price, + product.taxes_id, + record.tax_ids, + record.pms_property_id.company_id, + ) + + self.env["pms.service"].create( + { + "reservation_id": record.id, + "product_id": product.id, + "quantity": quantity, + "price_unit": price, + } + ) + + def _update_tourist_tax_service(self): + for record in self: + services = self.env["pms.service"].search( + [ + ("reservation_id", "=", record.id), + ("product_id.is_tourist_tax", "=", True), + ] + ) + for service in services: + product = service.product_id + if product.touristic_calculation == "occupancy": + checkins = record.checkin_partner_ids.filtered_domain( + safe_eval(product.occupancy_domain) + ) + quantity = len(checkins) + elif product.touristic_calculation == "nights": + if not record.filtered_domain(safe_eval(product.nights_domain)): + service.unlink() + continue + quantity = (record.checkout - record.checkin).days + elif product.touristic_calculation == "occupancyandnights": + checkins = record.checkin_partner_ids.filtered_domain( + safe_eval(product.occupancy_domain) + ) + if not record.filtered_domain(safe_eval(product.nights_domain)): + service.unlink() + continue + quantity = len(checkins) * (record.checkout - record.checkin).days + else: + quantity = 1 + + if quantity == 0: + service.unlink() + continue + + product = product.with_context( + lang=record.partner_id.lang, + partner=record.partner_id.id, + quantity=quantity, + date=record.date_order, + consumption_date=record.checkin, + pricelist=record.pricelist_id.id, + uom=product.uom_id.id, + property=record.pms_property_id.id, + ) + price = self.env["account.tax"]._fix_tax_included_price_company( + product.price, + product.taxes_id, + record.tax_ids, + record.pms_property_id.company_id, + ) + + service.write( + { + "quantity": quantity, + "price_unit": price, + } + ) diff --git a/pms/models/product_template.py b/pms/models/product_template.py index 3b1b697330..ea535e8e99 100644 --- a/pms/models/product_template.py +++ b/pms/models/product_template.py @@ -64,6 +64,31 @@ class ProductTemplate(models.Model): help="Indicates if that product is available in PMS", default=True, ) + is_tourist_tax = fields.Boolean( + string="Is tourist tax", + help="Indicates if that product is a tourist tax", + default=False, + ) + touristic_calculation = fields.Selection( + string="Touristic calculation", + help="Indicates how the tourist tax is calculated", + selection=[ + ("occupany", "Occupancy"), + ("nights", "Nights"), + ("occupancyandnights", "Occupancy and Nights"), + ], + default="occupancyandnights", + ) + occupancy_domain = fields.Char( + string="Occupancy domain", + help="Domain to filter checkins", + default="", + ) + nights_domain = fields.Char( + string="Nights domain", + help="Domain to filter reservations", + default="[('state', '!=', 'cancel')]", + ) @api.depends_context("allowed_pms_property_ids") def _compute_daily_limit(self): diff --git a/pms/tests/test_tourist_taxes.py b/pms/tests/test_tourist_taxes.py new file mode 100644 index 0000000000..3abd2361c1 --- /dev/null +++ b/pms/tests/test_tourist_taxes.py @@ -0,0 +1,143 @@ +import datetime + +from odoo import fields +from odoo.tests.common import TransactionCase + + +class TestTouristTaxes(TransactionCase): + def setUp(self): + super(TestTouristTaxes, self).setUp() + self.product_tourist_tax = self.env["product.product"].create( + { + "name": "Tourist Tax", + "is_tourist_tax": True, + "touristic_calculation": "occupancy", + "occupancy_domain": "[('state', '!=', 'cancel')]", + "nights_domain": "[('state', '!=', 'cancel')]", + } + ) + self.partner = self.env["res.partner"].create( + { + "name": "Test Partner", + } + ) + self.room_type = self.env["pms.room.type"].create( + { + "name": "Test Room Type", + "product_id": self.env["product.product"] + .create( + { + "name": "Room Product", + "type": "service", + } + ) + .id, + } + ) + self.room = self.env["pms.room"].create( + { + "name": "Test Room", + "room_type_id": self.room_type.id, + } + ) + self.reservation = self.env["pms.reservation"].create( + { + "partner_id": self.partner.id, + "room_type_id": self.room_type.id, + "checkin": fields.Date.today(), + "checkout": fields.Date.today() + datetime.timedelta(days=2), + "adults": 2, + } + ) + + def test_add_tourist_tax_service(self): + """ + Test that a tourist tax service is created when adding a reservation. + Steps: + 1. Add a tourist tax service to the reservation. + 2. Search for the created service. + 3. Assert that the service is created and the quantity is correct. + """ + self.reservation._add_tourist_tax_service() + service = self.env["pms.service"].search( + [ + ("reservation_id", "=", self.reservation.id), + ("product_id", "=", self.product_tourist_tax.id), + ] + ) + self.assertEqual(len(service), 1, "Tourist tax service should be created") + self.assertEqual(service.quantity, 2, "Tourist tax quantity should be 2") + + def test_update_tourist_tax_service(self): + """ + Test that a tourist tax service is updated when modifying the reservation. + Steps: + 1. Add a tourist tax service to the reservation. + 2. Update the number of adults in the reservation. + 3. Update the tourist tax service. + 4. Search for the updated service. + 5. Assert that the service is updated and the quantity is correct. + """ + self.reservation._add_tourist_tax_service() + self.reservation.adults = 3 + self.reservation._update_tourist_tax_service() + service = self.env["pms.service"].search( + [ + ("reservation_id", "=", self.reservation.id), + ("product_id", "=", self.product_tourist_tax.id), + ] + ) + self.assertEqual(len(service), 1, "Tourist tax service should be updated") + self.assertEqual( + service.quantity, 3, "Tourist tax quantity should be updated to 3" + ) + + def test_no_tourist_tax_service_when_quantity_zero(self): + """ + Test that no tourist tax service is created when the quantity is zero. + Steps: + 1. Set the tourist tax calculation to 'occupancyandnights'. + 2. Add a tourist tax service to the reservation. + 3. Search for the created service. + 4. Assert that no service is created. + """ + self.product_tourist_tax.touristic_calculation = "occupancyandnights" + self.reservation._add_tourist_tax_service() + service = self.env["pms.service"].search( + [ + ("reservation_id", "=", self.reservation.id), + ("product_id", "=", self.product_tourist_tax.id), + ] + ) + self.assertEqual( + len(service), + 0, + "Tourist tax service should not be created when quantity is zero", + ) + + def test_remove_tourist_tax_service_when_quantity_zero(self): + """ + Test that a tourist tax service is removed when the quantity becomes zero. + Steps: + 1. Set the tourist tax calculation to 'occupancy'. + 2. Add a tourist tax service to the reservation. + 3. Update the number of adults in the reservation to zero. + 4. Update the tourist tax service. + 5. Search for the updated service. + 6. Assert that the service is removed. + """ + self.product_tourist_tax.touristic_calculation = "occupancy" + self.reservation._add_tourist_tax_service() + self.reservation.adults = 0 + self.reservation._update_tourist_tax_service() + service = self.env["pms.service"].search( + [ + ("reservation_id", "=", self.reservation.id), + ("product_id", "=", self.product_tourist_tax.id), + ] + ) + self.assertEqual( + len(service), + 0, + "Tourist tax service should be removed when quantity is zero", + ) diff --git a/pms/views/product_template_views.xml b/pms/views/product_template_views.xml index 0b21713bc9..d266964b18 100644 --- a/pms/views/product_template_views.xml +++ b/pms/views/product_template_views.xml @@ -28,6 +28,7 @@ + + + + + + + +