mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[ADD]pms: feature touristic taxes
This commit is contained in:
@@ -791,6 +791,12 @@ class PmsCheckinPartner(models.Model):
|
|||||||
_("Is not possible to create the proposed check-in in this reservation")
|
_("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):
|
def unlink(self):
|
||||||
reservations = self.mapped("reservation_id")
|
reservations = self.mapped("reservation_id")
|
||||||
res = super().unlink()
|
res = super().unlink()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import time
|
|||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from odoo.tools.safe_eval import safe_eval
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -924,9 +925,11 @@ class PmsReservation(models.Model):
|
|||||||
room_type_id=False, # Allows to choose any available room
|
room_type_id=False, # Allows to choose any available room
|
||||||
current_lines=reservation.reservation_line_ids.ids,
|
current_lines=reservation.reservation_line_ids.ids,
|
||||||
pricelist_id=reservation.pricelist_id.id,
|
pricelist_id=reservation.pricelist_id.id,
|
||||||
class_id=reservation.room_type_id.class_id.id
|
class_id=(
|
||||||
if reservation.room_type_id
|
reservation.room_type_id.class_id.id
|
||||||
else False,
|
if reservation.room_type_id
|
||||||
|
else False
|
||||||
|
),
|
||||||
real_avail=True,
|
real_avail=True,
|
||||||
)
|
)
|
||||||
reservation.allowed_room_ids = pms_property.free_room_ids
|
reservation.allowed_room_ids = pms_property.free_room_ids
|
||||||
@@ -2152,6 +2155,7 @@ class PmsReservation(models.Model):
|
|||||||
record.action_cancel()
|
record.action_cancel()
|
||||||
|
|
||||||
record._check_services(vals)
|
record._check_services(vals)
|
||||||
|
record._add_tourist_tax_service()
|
||||||
return record
|
return record
|
||||||
|
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
@@ -2221,6 +2225,8 @@ class PmsReservation(models.Model):
|
|||||||
# that not take access to possible extra beds service in vals
|
# that not take access to possible extra beds service in vals
|
||||||
if "adults" in vals:
|
if "adults" in vals:
|
||||||
self._check_capacity()
|
self._check_capacity()
|
||||||
|
if "checkin" in vals or "checkout" in vals or "reservation_line_ids" in vals:
|
||||||
|
self._update_tourist_tax_service()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_folio_vals(self, reservation_vals):
|
def _get_folio_vals(self, reservation_vals):
|
||||||
@@ -2575,3 +2581,116 @@ class PmsReservation(models.Model):
|
|||||||
"target": "self",
|
"target": "self",
|
||||||
"url": self.get_portal_url(),
|
"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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -64,6 +64,31 @@ class ProductTemplate(models.Model):
|
|||||||
help="Indicates if that product is available in PMS",
|
help="Indicates if that product is available in PMS",
|
||||||
default=True,
|
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")
|
@api.depends_context("allowed_pms_property_ids")
|
||||||
def _compute_daily_limit(self):
|
def _compute_daily_limit(self):
|
||||||
|
|||||||
143
pms/tests/test_tourist_taxes.py
Normal file
143
pms/tests/test_tourist_taxes.py
Normal file
@@ -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",
|
||||||
|
)
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="per_day" />
|
<field name="per_day" />
|
||||||
<field name="per_person" />
|
<field name="per_person" />
|
||||||
|
<field name="is_tourist_tax" />
|
||||||
<field
|
<field
|
||||||
name="consumed_on"
|
name="consumed_on"
|
||||||
widget="radio"
|
widget="radio"
|
||||||
@@ -36,6 +37,24 @@
|
|||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
|
<page
|
||||||
|
string="Tourist tax configuration"
|
||||||
|
attrs="{'invisible': [('is_tourist_tax', '=', False)]}"
|
||||||
|
>
|
||||||
|
<group>
|
||||||
|
<field name="touristic_calculation" />
|
||||||
|
<field
|
||||||
|
name="occupancy_domain"
|
||||||
|
widget="domain"
|
||||||
|
options="{'model': 'pms.checkin.partner'}"
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="nights_domain"
|
||||||
|
widget="domain"
|
||||||
|
options="{'model': 'pms.reservation'}"
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
Reference in New Issue
Block a user