diff --git a/pms/models/account_move_line.py b/pms/models/account_move_line.py
index b3a803cc4..0cc5d19a6 100644
--- a/pms/models/account_move_line.py
+++ b/pms/models/account_move_line.py
@@ -1,13 +1,14 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
-from odoo import fields, models
+from odoo import api, fields, models
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
# Fields declaration
+ # TODO: REVIEW why not a Many2one?
folio_line_ids = fields.Many2many(
"folio.sale.line",
"folio_sale_line_invoice_rel",
@@ -23,6 +24,50 @@ class AccountMoveLine(models.Model):
"folio_id",
string="Folios",
)
+ name_changed_by_user = fields.Boolean(
+ default=False,
+ readonly=False,
+ store=True,
+ string="Custom label",
+ compute="_compute_name_changed_by_user",
+ )
+
+ @api.depends("name")
+ def _compute_name_changed_by_user(self):
+ for record in self:
+ # if not record._context.get("auto_name"):
+ if not self._context.get("auto_name"):
+ record.name_changed_by_user = True
+ else:
+ record.name_changed_by_user = False
+
+ name = fields.Char(
+ compute="_compute_name",
+ store=True,
+ readonly=False,
+ )
+
+ @api.depends("quantity")
+ def _compute_name(self):
+ for record in self:
+ record.name = self.env["folio.sale.line"].generate_folio_sale_name(
+ record.folio_line_ids.reservation_id,
+ record.product_id,
+ record.folio_line_ids.service_id,
+ record.folio_line_ids.reservation_line_ids,
+ record.folio_line_ids.service_line_ids,
+ qty=record.quantity,
+ )
+ # TODO: check why this code doesn't work
+ # if not record.name_changed_by_user:
+ # record.with_context(auto_name=True).name = self
+ # .env["folio.sale.line"].generate_folio_sale_name(
+ # record.folio_line_ids.service_id,
+ # record.folio_line_ids.reservation_line_ids,
+ # record.product_id,
+ # qty=record.quantity)
+ # record.with_context(auto_name=True)
+ # ._compute_name_changed_by_user()
def _copy_data_extend_business_fields(self, values):
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
diff --git a/pms/models/folio_sale_line.py b/pms/models/folio_sale_line.py
index f3a2571eb..d21141587 100644
--- a/pms/models/folio_sale_line.py
+++ b/pms/models/folio_sale_line.py
@@ -1,6 +1,8 @@
# Copyright 2020 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+from math import ceil
+
from odoo import _, api, fields, models
from odoo.osv import expression
from odoo.tools import float_compare, float_is_zero
@@ -9,7 +11,8 @@ from odoo.tools import float_compare, float_is_zero
class FolioSaleLine(models.Model):
_name = "folio.sale.line"
_description = "Folio Sale Line"
- _order = "folio_id, sequence, id"
+ _order = "folio_id, sequence, reservation_order desc, service_order, date_order"
+
_check_company_auto = True
@api.depends("state", "product_uom_qty", "qty_to_invoice", "qty_invoiced")
@@ -41,26 +44,34 @@ class FolioSaleLine(models.Model):
else:
line.invoice_status = "no"
- @api.depends("reservation_line_ids", "service_id")
+ @api.depends("reservation_line_ids", "service_line_ids", "service_id")
def _compute_name(self):
for record in self:
- if not record.name_updated:
- record.name = record._get_compute_name()
+ record.name = self.generate_folio_sale_name(
+ record.reservation_id,
+ record.product_id,
+ record.service_id,
+ record.reservation_line_ids,
+ record.service_line_ids,
+ )
- @api.depends("name")
- def _compute_name_updated(self):
- self.name_updated = False
- for record in self.filtered("name"):
- if record.name != record._get_compute_name():
- record.name_updated = True
-
- def _get_compute_name(self):
- self.ensure_one()
- if self.reservation_line_ids:
+ @api.model
+ def generate_folio_sale_name(
+ self,
+ reservation_id,
+ product_id,
+ service_id,
+ reservation_line_ids,
+ service_line_ids,
+ qty=False,
+ ):
+ if reservation_line_ids:
month = False
name = False
- lines = self.reservation_line_ids.sorted("date")
- for date in lines.mapped("date"):
+ lines = reservation_line_ids.sorted(key="date")
+ for index, date in enumerate(lines.mapped("date")):
+ if qty and index > (qty - 1):
+ break
if date.month != month:
name = name + "\n" if name else ""
name += date.strftime("%B-%Y") + ": "
@@ -68,11 +79,28 @@ class FolioSaleLine(models.Model):
month = date.month
else:
name += ", " + date.strftime("%d")
- return name
- elif self.service_id:
- return self.service_id.name
+
+ return "{} ({}).".format(product_id.name, name)
+ elif service_line_ids:
+ month = False
+ name = False
+ lines = service_line_ids.filtered(
+ lambda x: x.service_id == service_id
+ ).sorted(key="date")
+
+ for index, date in enumerate(lines.mapped("date")):
+ if qty and index > (ceil(qty / reservation_id.adults) - 1):
+ break
+ if date.month != month:
+ name = name + "\n" if name else ""
+ name += date.strftime("%B-%Y") + ": "
+ name += date.strftime("%d")
+ month = date.month
+ else:
+ name += ", " + date.strftime("%d")
+ return "{} ({}).".format(service_id.name, name)
else:
- return False
+ return service_id.name
@api.depends("product_uom_qty", "discount", "price_unit", "tax_ids")
def _compute_amount(self):
@@ -133,7 +161,7 @@ class FolioSaleLine(models.Model):
@api.depends("reservation_id.room_type_id", "service_id.product_id")
def _compute_product_id(self):
for record in self:
- if record.reservation_id:
+ if record.reservation_id and not record.service_id:
record.product_id = record.reservation_id.room_type_id.product_id
elif record.service_id:
record.product_id = record.service_id.product_id
@@ -367,7 +395,6 @@ class FolioSaleLine(models.Model):
name = fields.Text(
string="Description", compute="_compute_name", store=True, readonly=False
)
- name_updated = fields.Boolean(compute="_compute_name_updated", store=True)
reservation_line_ids = fields.Many2many(
"pms.reservation.line",
string="Nights",
@@ -562,6 +589,68 @@ class FolioSaleLine(models.Model):
help="Technical field for UX purpose.",
)
+ service_order = fields.Integer(
+ string="Service id",
+ compute="_compute_service_order",
+ help="Field to order by service id",
+ store=True,
+ readonly=True,
+ )
+
+ reservation_order = fields.Integer(
+ string="Reservation id",
+ compute="_compute_reservation_order",
+ help="Field to order by reservation id",
+ store=True,
+ readonly=True,
+ )
+
+ date_order = fields.Date(
+ string="Date",
+ compute="_compute_date_order",
+ help="Field to order by service date",
+ store=True,
+ readonly=True,
+ )
+
+ @api.depends("qty_to_invoice")
+ def _compute_service_order(self):
+ for record in self:
+ record.service_order = (
+ record.service_id
+ if record.service_id
+ else -1
+ if record.display_type
+ else 0
+ )
+
+ @api.depends("service_order")
+ def _compute_date_order(self):
+ for record in self:
+ if record.display_type:
+ record.date_order = 0
+ elif record.reservation_id and not record.service_id:
+ record.date_order = (
+ min(record.reservation_line_ids.mapped("date"))
+ if record.reservation_line_ids
+ else 0
+ )
+ elif record.reservation_id and record.service_id:
+ record.date_order = (
+ min(record.service_line_ids.mapped("date"))
+ if record.service_line_ids
+ else 0
+ )
+ else:
+ record.date_order = 0
+
+ @api.depends("date_order")
+ def _compute_reservation_order(self):
+ for record in self:
+ record.reservation_order = (
+ record.reservation_id if record.reservation_id else 0
+ )
+
@api.depends("reservation_line_ids", "service_line_ids", "service_line_ids.day_qty")
def _compute_product_uom_qty(self):
for line in self:
diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py
index 066eb463a..405750e7a 100644
--- a/pms/models/pms_folio.py
+++ b/pms/models/pms_folio.py
@@ -123,8 +123,6 @@ class PmsFolio(models.Model):
readonly=True,
required=True,
ondelete="restrict",
- # comodel_name="res.currency",
- # compute="_compute_currency_id"
)
pricelist_id = fields.Many2one(
"product.pricelist",
@@ -259,7 +257,6 @@ class PmsFolio(models.Model):
client_order_ref = fields.Char(string="Customer Reference", copy=False)
reservation_type = fields.Selection(
[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")],
- required=True,
string="Type",
default=lambda *a: "normal",
)
@@ -397,87 +394,139 @@ class PmsFolio(models.Model):
"service_ids.service_line_ids.price_day_total",
"service_ids.service_line_ids.discount",
"service_ids.service_line_ids.cancel_discount",
+ "service_ids.service_line_ids.day_qty",
+ "service_ids.service_line_ids.tax_ids",
"reservation_ids.reservation_line_ids",
"reservation_ids.reservation_line_ids.price",
"reservation_ids.reservation_line_ids.discount",
"reservation_ids.reservation_line_ids.cancel_discount",
+ "reservation_ids.tax_ids",
)
def _compute_sale_line_ids(self):
for folio in self:
- sale_lines = [(5, 0, 0)]
- reservations = folio.reservation_ids
- services_without_room = folio.service_ids.filtered(
- lambda s: not s.reservation_id
- )
- # TODO: Not delete old sale line ids
- for reservation in reservations:
- sale_lines.append(
- (
- 0,
- False,
- {
- "display_type": "line_section",
- "name": reservation.name,
- },
- )
+ for reservation in folio.reservation_ids:
+ # RESERVATION LINES
+ # res = self.env['pms.reservation'].browse(reservation.id)
+ self.generate_reservation_lines_sale_lines(folio, reservation)
+
+ # RESERVATION SERVICES
+ self.generate_reservation_services_sale_lines(folio, reservation)
+
+ # FOLIO SERVICES
+ self.generate_folio_services_sale_lines(folio)
+
+ @api.model
+ def generate_reservation_lines_sale_lines(self, folio, reservation):
+ if not reservation.sale_line_ids.filtered(lambda x: x.name == reservation.name):
+ reservation.sale_line_ids = [
+ (
+ 0,
+ 0,
+ {
+ "name": reservation.name,
+ "display_type": "line_section",
+ "folio_id": folio.id,
+ },
)
- group_reservation_lines = {}
- for line in reservation.reservation_line_ids:
- # On resevations the price, and discounts fields are used
- # by group, we need pass this in the create line
- group_key = (
- reservation.id,
- line.price,
- line.discount,
- line.cancel_discount,
- )
- if line.cancel_discount == 100:
- continue
- discount_factor = 1.0
- for discount in [line.discount, line.cancel_discount]:
- discount_factor = discount_factor * ((100.0 - discount) / 100.0)
- final_discount = 100.0 - (discount_factor * 100.0)
- if group_key not in group_reservation_lines:
- group_reservation_lines[group_key] = {
- "reservation_id": reservation.id,
- "discount": final_discount,
- "price_unit": line.price,
- "reservation_line_ids": [(4, line.id)],
- }
- else:
- group_reservation_lines[group_key][
- ("reservation_line_ids")
- ].append((4, line.id))
- for item in group_reservation_lines.items():
- sale_lines.append((0, False, item[1]))
- for service in reservation.service_ids:
- # Service days with different prices,
- # go to differente sale lines
- group_service_lines = {}
- for service_line in service.service_line_ids:
- service_group_key = (
- service_line.price_unit,
- service_line.discount,
- service_line.cancel_discount,
- )
- if service_group_key not in group_service_lines:
- # On service the price, and discounts fields are
- # compute in the sale.order.line
- group_service_lines[service_group_key] = {
- "name": service.name,
- "service_id": service.id,
- "discount": service_line.discount,
- "price_unit": service_line.price_unit,
- "service_line_ids": [(4, service_line.id)],
- }
- else:
- group_service_lines[service_group_key][
- ("service_line_ids")
- ].append((4, service_line.id))
- for item in group_service_lines.items():
- sale_lines.append((0, False, item[1]))
- if services_without_room:
- sale_lines.append(
+ ]
+ expected_reservation_lines = self.env["pms.reservation.line"].read_group(
+ [
+ ("reservation_id", "=", reservation.id),
+ ("cancel_discount", "<", 100),
+ ],
+ ["price", "discount", "cancel_discount"],
+ ["price", "discount", "cancel_discount"],
+ lazy=False,
+ )
+ current_sale_line_ids = reservation.sale_line_ids.filtered(
+ lambda x: x.reservation_id.id == reservation.id
+ and not x.display_type
+ and not x.service_id
+ )
+
+ for index, item in enumerate(expected_reservation_lines):
+ lines_to = self.env["pms.reservation.line"].search(item["__domain"])
+ final_discount = self.concat_discounts(
+ item["discount"], item["cancel_discount"]
+ )
+
+ if current_sale_line_ids and index <= (len(current_sale_line_ids) - 1):
+ current_sale_line_ids[index].price_unit = item["price"]
+ current_sale_line_ids[index].discount = final_discount
+ current_sale_line_ids[index].reservation_line_ids = lines_to.ids
+ else:
+ new = {
+ "reservation_id": reservation.id,
+ "price_unit": item["price"],
+ "discount": final_discount,
+ "folio_id": folio.id,
+ "reservation_line_ids": [(6, 0, lines_to.ids)],
+ }
+ reservation.sale_line_ids = [(0, 0, new)]
+ if len(expected_reservation_lines) < len(current_sale_line_ids):
+ folio_sale_lines_to_remove = [
+ value.id
+ for index, value in enumerate(current_sale_line_ids)
+ if index > (len(expected_reservation_lines) - 1)
+ ]
+ for fsl in folio_sale_lines_to_remove:
+ self.env["folio.sale.line"].browse(fsl).unlink()
+
+ @api.model
+ def generate_reservation_services_sale_lines(self, folio, reservation):
+ for service in reservation.service_ids:
+ expected_reservation_services = self.env["pms.service.line"].read_group(
+ [
+ ("reservation_id", "=", reservation.id),
+ ("service_id", "=", service.id),
+ ("cancel_discount", "<", 100),
+ ],
+ ["price_unit", "discount", "cancel_discount"],
+ ["price_unit", "discount", "cancel_discount"],
+ lazy=False,
+ )
+ current_sale_service_ids = reservation.sale_line_ids.filtered(
+ lambda x: x.reservation_id.id == reservation.id
+ and not x.display_type
+ and x.service_id.id == service.id
+ )
+
+ for index, item in enumerate(expected_reservation_services):
+ lines_to = self.env["pms.service.line"].search(item["__domain"])
+ final_discount = self.concat_discounts(
+ item["discount"], item["cancel_discount"]
+ )
+
+ if current_sale_service_ids and index <= (
+ len(current_sale_service_ids) - 1
+ ):
+ current_sale_service_ids[index].price_unit = item["price_unit"]
+ current_sale_service_ids[index].discount = final_discount
+ current_sale_service_ids[index].service_line_ids = lines_to.ids
+ else:
+ new = {
+ "service_id": service.id,
+ "price_unit": item["price_unit"],
+ "discount": final_discount,
+ "folio_id": folio.id,
+ "service_line_ids": [(6, 0, lines_to.ids)],
+ }
+ reservation.sale_line_ids = [(0, 0, new)]
+ if len(expected_reservation_services) < len(current_sale_service_ids):
+ folio_sale_lines_to_remove = [
+ value.id
+ for index, value in enumerate(current_sale_service_ids)
+ if index > (len(expected_reservation_services) - 1)
+ ]
+ for fsl in folio_sale_lines_to_remove:
+ self.env["folio.sale.line"].browse(fsl).unlink()
+
+ @api.model
+ def generate_folio_services_sale_lines(self, folio):
+ folio_services = folio.service_ids.filtered(lambda x: not x.reservation_id)
+ if folio_services:
+ if not folio.sale_line_ids.filtered(lambda x: x.name == _("Others")):
+ folio.sale_line_ids = [
(
0,
False,
@@ -486,19 +535,65 @@ class PmsFolio(models.Model):
"name": _("Others"),
},
)
+ ]
+ for folio_service in folio_services:
+ expected_folio_services = self.env["pms.service.line"].read_group(
+ [
+ ("service_id.folio_id", "=", folio.id),
+ ("service_id", "=", folio_service.id),
+ ("reservation_id", "=", False),
+ ("cancel_discount", "<", 100),
+ ],
+ ["price_unit", "discount", "cancel_discount"],
+ ["price_unit", "discount", "cancel_discount"],
+ lazy=False,
)
- for service in services_without_room:
- sale_lines.append(
- (
- 0,
- False,
- {
- "name": service.name,
- "service_id": service.id,
- },
- )
+ current_folio_service_ids = folio.sale_line_ids.filtered(
+ lambda x: x.service_id.folio_id.id == folio.id
+ and not x.display_type
+ and not x.reservation_id
+ and x.service_id.id == folio_service.id
+ )
+
+ for index, item in enumerate(expected_folio_services):
+ lines_to = self.env["pms.service.line"].search(item["__domain"])
+ final_discount = self.concat_discounts(
+ item["discount"], item["cancel_discount"]
)
- folio.sale_line_ids = sale_lines
+ if current_folio_service_ids and index <= (
+ len(current_folio_service_ids) - 1
+ ):
+ current_folio_service_ids[index].price_unit = item["price_unit"]
+ current_folio_service_ids[index].discount = final_discount
+ current_folio_service_ids[index].service_line_ids = lines_to.ids
+ else:
+ new = {
+ "service_id": folio_service.id,
+ "price_unit": item["price_unit"],
+ "discount": final_discount,
+ "folio_id": folio.id,
+ "service_line_ids": [(6, 0, lines_to.ids)],
+ }
+ folio.sale_line_ids = [(0, 0, new)]
+ if len(expected_folio_services) < len(current_folio_service_ids):
+ folio_sale_lines_to_remove = [
+ value.id
+ for index, value in enumerate(current_folio_service_ids)
+ if index > (len(expected_folio_services) - 1)
+ ]
+ for fsl in folio_sale_lines_to_remove:
+ self.env["folio.sale.line"].browse(fsl).unlink()
+ else:
+ to_unlink = folio.sale_line_ids.filtered(lambda x: x.name == _("Others"))
+ to_unlink.unlink()
+
+ @api.model
+ def concat_discounts(self, discount, cancel_discount):
+ discount_factor = 1.0
+ for discount in [discount, cancel_discount]:
+ discount_factor = discount_factor * ((100.0 - discount) / 100.0)
+ final_discount = 100.0 - (discount_factor * 100.0)
+ return final_discount
@api.depends("partner_id", "agency_id")
def _compute_pricelist_id(self):
@@ -1168,7 +1263,7 @@ class PmsFolio(models.Model):
moves = (
self.env["account.move"]
.sudo()
- .with_context(default_move_type="out_invoice")
+ .with_context(default_move_type="out_invoice", auto_name=True)
.create(invoice_vals_list)
)
diff --git a/pms/models/pms_service_line.py b/pms/models/pms_service_line.py
index 80ac13808..61cacc7e2 100644
--- a/pms/models/pms_service_line.py
+++ b/pms/models/pms_service_line.py
@@ -87,8 +87,8 @@ class PmsServiceLine(models.Model):
store=True,
related="service_id.currency_id",
)
- room_id = fields.Many2one(
- string="Room",
+ reservation_id = fields.Many2one(
+ string="Reservation",
help="Room to which the services will be applied",
readonly=True,
store=True,
@@ -104,7 +104,11 @@ class PmsServiceLine(models.Model):
compute="_compute_discount",
)
cancel_discount = fields.Float(
- string="Cancelation Discount", help="", compute="_compute_cancel_discount"
+ string="Cancelation Discount",
+ help="",
+ compute="_compute_cancel_discount",
+ readonly=True,
+ store=True,
)
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
diff --git a/pms/tests/__init__.py b/pms/tests/__init__.py
index dbe4b7344..8247dc4eb 100644
--- a/pms/tests/__init__.py
+++ b/pms/tests/__init__.py
@@ -38,3 +38,4 @@ from . import test_pms_board_service_line
from . import test_pms_board_service_room_type
from . import test_pms_board_service_room_type_line
from . import test_pms_folio_invoice
+from . import test_pms_folio_sale_line
diff --git a/pms/tests/test_pms_folio_sale_line.py b/pms/tests/test_pms_folio_sale_line.py
new file mode 100644
index 000000000..1dfba6cca
--- /dev/null
+++ b/pms/tests/test_pms_folio_sale_line.py
@@ -0,0 +1,1336 @@
+import datetime
+
+from .common import TestPms
+
+
+class TestPmsFolioSaleLine(TestPms):
+ def create_common_scenario(self):
+ # create a room type availability
+ self.room_type_availability = self.env["pms.availability.plan"].create(
+ {"name": "Availability plan for TEST"}
+ )
+
+ # create a property
+ self.property = self.env["pms.property"].create(
+ {
+ "name": "MY PMS TEST",
+ "company_id": self.env.ref("base.main_company").id,
+ "default_pricelist_id": self.env.ref("product.list0").id,
+ }
+ )
+
+ # create room type class
+ self.room_type_class = self.env["pms.room.type.class"].create(
+ {"name": "Room", "code_class": "ROOM"}
+ )
+
+ # create room type
+ self.room_type_double = self.env["pms.room.type"].create(
+ {
+ "pms_property_ids": [self.property.id],
+ "name": "Double Test",
+ "default_code": "DBL_Test",
+ "class_id": self.room_type_class.id,
+ "price": 25,
+ }
+ )
+ # create room
+ self.room1 = self.env["pms.room"].create(
+ {
+ "pms_property_id": self.property.id,
+ "name": "Double 101",
+ "room_type_id": self.room_type_double.id,
+ "capacity": 2,
+ }
+ )
+
+ # RESERVATION LINES
+ def test_comp_fsl_rooms_all_same_group(self):
+ # TEST CASE
+ # 2-night reservation and same price, discount & cancel_discount for
+ # all nights
+ # should generate just 1 reservation sale line
+
+ # ARRANGE
+ expected_sale_lines = 1
+ self.create_common_scenario()
+
+ # ACT
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ASSERT
+ self.assertEqual(
+ expected_sale_lines,
+ len(r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)),
+ "Folio should contain {} sale lines".format(expected_sale_lines),
+ )
+
+ def test_comp_fsl_rooms_different_prices(self):
+ # TEST CASE
+ # 2-night reservation and different price per night
+ # should generate 2 reservation sale lines
+
+ # ARRANGE
+ expected_sale_lines = 2
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ACT
+ r_test.reservation_line_ids[0].price = 50.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_sale_lines,
+ len(r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)),
+ "Folio should contain {} reservation sale lines".format(
+ expected_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_rooms_different_discount(self):
+ # TEST CASE
+ # 2-night reservationwith different discount per night
+ # should generate 2 reservation sale lines
+
+ # ARRANGE
+ expected_sale_lines = 2
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ACT
+ r_test.reservation_line_ids[0].discount = 50.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_sale_lines,
+ len(r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)),
+ "Folio should contain {} reservation sale lines".format(
+ expected_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_rooms_different_cancel_discount(self):
+ # TEST CASE
+ # 2-night-reservation with different discount per night
+ # should generate 2 reservation sale lines
+
+ # ARRANGE
+ expected_sale_lines = 2
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ACT
+ r_test.reservation_line_ids[0].cancel_discount = 50.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_sale_lines,
+ len(r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)),
+ "Folio should contain {} reservation sale lines".format(
+ expected_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_rooms_one_full_cancel_discount(self):
+ # TEST CASE
+ # 2-night reservation with 100% cancelled discount for 1 night
+ # should generate just 1 reservation sale line because the
+ # full cancel discount shouldn't be present @ invoice lines
+
+ # ARRANGE
+ expected_sale_lines = 1
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+
+ # ACT
+ r_test.reservation_line_ids[0].cancel_discount = 100.0
+ r_test.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_sale_lines,
+ len(r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)),
+ "Folio should contain {} reservation sale lines".format(
+ expected_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_rooms_increase_stay(self):
+ # TEST CASE
+ # 2-night reservation increases 1 night with the same price,
+ # discount and cancel discount for all the reservation nights
+ # Should keep the same reservation sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r_test.flush()
+ previous_folio_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type
+ )[0]
+
+ # ACT
+ r_test.checkout = datetime.datetime.now() + datetime.timedelta(days=4)
+ r_test.flush()
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)[0],
+ "Previous records of reservation sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ def test_comp_fsl_rooms_decrease_stay(self):
+ # TEST CASE
+ # 2-night reservation decreases 1 night with the same price,
+ # discount & cancel_discount for all the reservation nights
+ # Should keep the same reservation sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r_test.flush()
+ previous_folio_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type
+ )[0]
+
+ # ACT
+ r_test.checkout = datetime.datetime.now() + datetime.timedelta(days=2)
+ r_test.flush()
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)[0],
+ "Previous records of reservation sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ def test_comp_fsl_rooms_same_stay(self):
+ # TEST CASE
+ # Price is changed for all nights of a 2-night reservation. But
+ # price, discount & cancel discount after the change is the same
+ # for all nights.
+ # Should keep the same reservation sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ r_test.flush()
+ previous_folio_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type
+ )[0]
+
+ # ACT
+ r_test.reservation_line_ids.price = 50
+ r_test.flush()
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(lambda x: not x.display_type)[0],
+ "Previous records of reservation sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ # BOARD SERVICES
+ def test_comp_fsl_board_services_all_same_group(self):
+ # TEST CASE
+ # 2-night reservation and same price, discount & cancel_discount for
+ # all reservation board services
+ # should generate just 1 board service sale line
+
+ # ARRANGE
+ expected_board_service_sale_lines = 1
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+
+ # ACT
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+
+ # ASSERT
+ self.assertEqual(
+ expected_board_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.reservation_id and x.service_id and x.is_board_service
+ )
+ ),
+ "Folio should contain {} board service sale lines".format(
+ expected_board_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_board_services_different_prices(self):
+ # TEST CASE
+ # 2-night reservation and different price per day on board services
+ # should generate just 1 board service sale line
+
+ # ARRANGE
+ expected_board_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+ r_test.service_ids[0].service_line_ids[0].price_unit = 1.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_board_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )
+ ),
+ "Folio should contain {} board service sale lines".format(
+ expected_board_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_board_services_different_discount(self):
+ # TEST CASE
+ # 2-night reservation and different discount per day on board services
+ # should generate 2 board service sale lines
+
+ # ARRANGE
+ expected_board_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+
+ # ACT
+ r_test.service_ids[0].service_line_ids[0].discount = 1.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_board_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )
+ ),
+ "Folio should contain {} board service sale lines".format(
+ expected_board_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_board_services_different_cancel_discount(self):
+ # TEST CASE
+ # 2-night reservation and different cancel discount per day on
+ # board services
+ # should generate 2 board service sale lines
+
+ # ARRANGE
+ expected_board_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+
+ # ACT
+ r_test.service_ids[0].service_line_ids[0].cancel_discount = 1.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_board_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )
+ ),
+ "Folio should contain {} board service sale lines".format(
+ expected_board_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_board_services_one_full_cancel_discount(self):
+ # TEST CASE
+ # 2-night reservation with 100% cancelled discount for 1 board service
+ # should generate just 1 board service sale line because the
+ # full cancel discount shouldn't be present @ invoice lines
+
+ # ARRANGE
+ expected_board_service_sale_lines = 1
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+
+ # ACT
+ r_test.service_ids[0].service_line_ids[0].cancel_discount = 100.0
+
+ # ASSERT
+ self.assertEqual(
+ expected_board_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )
+ ),
+ "Folio should contain {} board service sale lines".format(
+ expected_board_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_board_services_increase_stay(self):
+ # TEST CASE
+ # 2-night reservation increases 1 night with the same price,
+ # discount & cancel_discount for all the board services
+ # Should keep the same board service sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+ previous_folio_board_service_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )[0]
+
+ # ACT
+ r_test.checkout = datetime.datetime.now() + datetime.timedelta(days=4)
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_board_service_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )[0],
+ "Previous records of board service sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ def test_comp_fsl_board_services_decrease_stay(self):
+ # TEST CASE
+ # 2-night reservation decreases 1 night with the same price,
+ # discount & cancel_discount for all the board services
+ # Should keep the same board service sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+
+ previous_folio_board_service_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )[0]
+
+ # ACT
+ r_test.checkout = datetime.datetime.now() + datetime.timedelta(days=2)
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_board_service_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )[0],
+ "Previous records of board service sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ def test_comp_fsl_board_services_same_stay(self):
+ # TEST CASE
+ # Price is changed for all board services of a 2-night reservation.
+ # But price, discount & cancel discount after the change is the same
+ # for all nights.
+ # Should keep the same board service sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ }
+ )
+
+ board_service_test = self.board_service = self.env["pms.board.service"].create(
+ {
+ "name": "Test Board Service",
+ }
+ )
+
+ self.env["pms.board.service.line"].create(
+ {
+ "pms_board_service_id": board_service_test.id,
+ "product_id": product_test1.id,
+ "amount": 8,
+ }
+ )
+
+ board_service_room_type = self.env["pms.board.service.room.type"].create(
+ {
+ "pms_room_type_id": self.room_type_double.id,
+ "pms_board_service_id": board_service_test.id,
+ }
+ )
+
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ "board_service_room_id": board_service_room_type.id,
+ }
+ )
+
+ previous_folio_board_service_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )[0]
+
+ # ACT
+ r_test.service_ids.filtered(
+ lambda x: x.is_board_service
+ ).service_line_ids.price_unit = 50
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_board_service_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.display_type and x.is_board_service
+ )[0],
+ "Previous records of board service sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ # RESERVATION EXTRA DAILY SERVICES
+ def test_comp_fsl_res_extra_services_all_same_group(self):
+ # TEST CASE
+ # 2-night reservation and same price, discount & cancel_discount for
+ # all reservation services
+ # should generate just 1 reservation service sale line
+
+ # ARRANGE
+ expected_extra_service_sale_lines = 1
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+
+ # ACT
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_extra_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )
+ ),
+ "Folio should contain {} reservation service sale lines".format(
+ expected_extra_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_res_extra_services_different_prices(self):
+ # TEST CASE
+ # 2-night reservation and different price per day on services
+ # should generate just 1 reservation service sale line
+
+ # ARRANGE
+ expected_extra_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+
+ # ACT
+ r_test.service_ids.service_line_ids[0].price_unit = 44.5
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_extra_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )
+ ),
+ "Folio should contain {} reservation service sale lines".format(
+ expected_extra_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_res_extra_services_different_discount(self):
+ # TEST CASE
+ # 2-night reservation and different discount per day on reservation services
+ # should generate 2 reservation service sale lines
+
+ # ARRANGE
+ expected_extra_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+
+ # ACT
+ r_test.service_ids.service_line_ids[0].discount = 44.5
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_extra_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )
+ ),
+ "Folio should contain {} reservation service sale lines".format(
+ expected_extra_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_res_extra_services_different_cancel_discount(self):
+ # TEST CASE
+ # 2-night reservation and different cancel discount per day on
+ # reservation services
+ # should generate 2 reservation service sale lines
+
+ # ARRANGE
+ expected_extra_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+
+ # ACT
+ r_test.service_ids.service_line_ids[0].cancel_discount = 44.5
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_extra_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )
+ ),
+ "Folio should contain {} reservation service sale lines".format(
+ expected_extra_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_res_extra_services_one_full_cancel_discount(self):
+ # TEST CASE
+ # 2-night reservation with 100% cancelled discount for 1 reservation
+ # service
+ # should generate just 1 reservation service sale line because the
+ # full cancel discount shouldn't be present @ invoice lines
+
+ # ARRANGE
+ expected_extra_service_sale_lines = 1
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+
+ # ACT
+ r_test.service_ids.service_line_ids[0].cancel_discount = 100
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_extra_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )
+ ),
+ "Folio should contain {} reservation service sale lines".format(
+ expected_extra_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_res_extra_services_increase_stay(self):
+ # TEST CASE
+ # 2-night reservation increases 1 night with the same price,
+ # discount & cancel_discount for all the reservation services
+ # Should keep the same reservation service sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+ previous_folio_extra_service_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )[0]
+
+ # ACT
+ r_test.checkout = datetime.datetime.now() + datetime.timedelta(days=4)
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_extra_service_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ ),
+ "Previous records of reservation service sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ def test_comp_fsl_res_extra_services_decrease_stay(self):
+ # TEST CASE
+ # 2-night reservation decreases 1 night with the same price,
+ # discount & cancel_discount for all the reservation services
+ # Should keep the same reservation service sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+ previous_folio_extra_service_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )[0]
+
+ # ACT
+ r_test.checkout = datetime.datetime.now() + datetime.timedelta(days=2)
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_extra_service_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ ),
+ "Previous records of reservation service sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ def test_comp_fsl_res_extra_services_same_stay(self):
+ # TEST CASE
+ # Price is changed for all reservation services of a 2-night reservation.
+ # But price, discount & cancel discount after the change is the same
+ # for all nights.
+ # Should keep the same reservation service sales line record.
+
+ # ARRANGE
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ "per_day": True,
+ "consumed_on": "after",
+ # REVIEW 'before' -> create pms.service.line with price 0.0
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+ r_test.service_ids = [(4, extra_service.id)]
+ r_test.service_ids.service_line_ids.flush()
+ previous_folio_extra_service_sale_line = r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )[0]
+
+ # ACT
+ r_test.service_ids.filtered(
+ lambda x: x.id == extra_service
+ ).service_line_ids.price_unit = 50
+ r_test.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ previous_folio_extra_service_sale_line,
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ ),
+ "Previous records of reservation service sales lines should not be "
+ "deleted if it is not necessary",
+ )
+
+ # FOLIO EXTRA SERVICES
+ def test_comp_fsl_fol_extra_services_one(self):
+ # TEST CASE
+ # Folio with extra services
+ # should generate 1 folio service sale line
+
+ # ARRANGE
+ expected_folio_service_sale_lines = 1
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ }
+ )
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+
+ # ACT
+ r_test.folio_id.service_ids = [(4, extra_service.id)]
+ r_test.folio_id.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_folio_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: x.service_id == extra_service
+ )
+ ),
+ "Folio should contain {} folio service sale lines".format(
+ expected_folio_service_sale_lines
+ ),
+ )
+
+ def test_comp_fsl_fol_extra_services_two(self):
+ # TEST CASE
+ # Folio with 2 extra services (but the same product)
+ # Should generate 2 folio service sale line
+
+ # ARRANGE
+ expected_folio_service_sale_lines = 2
+ self.create_common_scenario()
+ product_test1 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ }
+ )
+ product_test2 = self.env["product.product"].create(
+ {
+ "name": "Test Product 1",
+ }
+ )
+
+ r_test = self.env["pms.reservation"].create(
+ {
+ "pms_property_id": self.property.id,
+ "checkin": datetime.datetime.now(),
+ "checkout": datetime.datetime.now() + datetime.timedelta(days=3),
+ "adults": 2,
+ "room_type_id": self.room_type_double.id,
+ "partner_id": self.env.ref("base.res_partner_12").id,
+ }
+ )
+ extra_service1 = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test1.id,
+ }
+ )
+
+ extra_service2 = self.env["pms.service"].create(
+ {
+ "is_board_service": False,
+ "product_id": product_test2.id,
+ }
+ )
+
+ # ACT
+ r_test.folio_id.service_ids = [(4, extra_service1.id)]
+ r_test.folio_id.service_ids = [(4, extra_service2.id)]
+ r_test.folio_id.service_ids.service_line_ids.flush()
+
+ # ASSERT
+ self.assertEqual(
+ expected_folio_service_sale_lines,
+ len(
+ r_test.folio_id.sale_line_ids.filtered(
+ lambda x: not x.reservation_id and not x.display_type
+ )
+ ),
+ "Folio should contain {} folio service sale lines".format(
+ expected_folio_service_sale_lines
+ ),
+ )
diff --git a/pms/views/account_move_views.xml b/pms/views/account_move_views.xml
index 7566699d1..d6fa4eba8 100644
--- a/pms/views/account_move_views.xml
+++ b/pms/views/account_move_views.xml
@@ -8,6 +8,9 @@
+
+
+
diff --git a/pms/views/pms_folio_views.xml b/pms/views/pms_folio_views.xml
index 26166d710..ca945c881 100644
--- a/pms/views/pms_folio_views.xml
+++ b/pms/views/pms_folio_views.xml
@@ -265,6 +265,7 @@
@@ -283,7 +284,10 @@
context="{'default_display_type': 'line_note'}"
/>
-
+
+
+
+
@@ -291,11 +295,22 @@
+
+
-
+
-
+