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