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 @@
+
+
+
+
+
+
+
+