# Copyright 2019  Pablo Quesada
# Copyright 2019  Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import base64
import datetime
import time

import pytz
from dateutil.relativedelta import relativedelta

from odoo import _, api, fields, models, modules
from odoo.exceptions import ValidationError
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT

from odoo.addons.base.models.res_partner import _tz_get


def get_default_logo():
    with open(
        modules.get_module_resource("pms", "static/img", "property_logo.png"), "rb"
    ) as f:
        return base64.b64encode(f.read())


class PmsProperty(models.Model):
    _name = "pms.property"
    _description = "Property"
    _inherits = {"res.partner": "partner_id"}
    _check_company_auto = True

    partner_id = fields.Many2one(
        string="Property",
        help="Current property",
        comodel_name="res.partner",
        required=True,
        index=True,
        ondelete="restrict",
    )
    parent_id = fields.Many2one(
        comodel_name="pms.property", string="Parent Property", index=True
    )
    child_ids = fields.One2many(
        comodel_name="pms.property", inverse_name="parent_id", string="Child Properties"
    )
    pms_property_code = fields.Char(
        string="Property Code",
        help="Short name property",
    )
    company_id = fields.Many2one(
        string="Company",
        help="The company that owns or operates this property.",
        comodel_name="res.company",
        index=True,
        required=True,
    )
    user_ids = fields.Many2many(
        string="Accepted Users",
        help="Field related to res.users. Allowed users on the property",
        comodel_name="res.users",
        relation="pms_property_users_rel",
        column1="pms_property_id",
        column2="user_id",
    )
    room_ids = fields.One2many(
        string="Rooms",
        help="Rooms that a property has.",
        comodel_name="pms.room",
        inverse_name="pms_property_id",
    )
    default_pricelist_id = fields.Many2one(
        string="Product Pricelist",
        help="The default pricelist used in this property.",
        comodel_name="product.pricelist",
        required=True,
        index=True,
        domain="[('is_pms_available', '=', True)]",
        default=lambda self: self.env.ref("product.list0").id,
    )
    default_arrival_hour = fields.Char(
        string="Arrival Hour", help="HH:mm Format", default="14:00"
    )
    default_departure_hour = fields.Char(
        string="Departure Hour", help="HH:mm Format", default="12:00"
    )
    folio_sequence_id = fields.Many2one(
        string="Folio Sequence",
        help="The sequence that formed the name of the folio.",
        check_company=True,
        copy=False,
        index=True,
        comodel_name="ir.sequence",
    )
    checkin_sequence_id = fields.Many2one(
        string="Checkin Sequence",
        help="Field used to create the name of the checkin partner",
        check_company=True,
        copy=False,
        index=True,
        comodel_name="ir.sequence",
    )

    tz = fields.Selection(
        string="Timezone",
        help="This field is used to determine de timezone of the property.",
        required=True,
        default=lambda self: self.env.user.tz or "UTC",
        selection=_tz_get,
    )

    cardex_warning = fields.Text(
        string="Warning in Cardex",
        default="Time to access rooms: 14: 00h. "
        "Departure time: 12: 00h. If the accommodation "
        "is not left at that time, the establishment will "
        "charge a day's stay according to current rate that day",
        help="Notice under the signature on the traveler's ticket.",
    )
    free_room_ids = fields.One2many(
        string="Rooms available",
        help="allows you to send different parameters in the context "
        "(checkin(required), checkout(required), room_type_id, ubication_id, capacity, "
        "amenity_ids and / or pricelist_id) and return rooms available",
        comodel_name="pms.room",
        compute="_compute_free_room_ids",
    )
    availability = fields.Integer(
        string="Number of rooms available",
        help="allows you to send different parameters in the context "
        "(checkin(required), checkout(required), room_type_id, ubication_id, capacity,"
        "amenity_ids and / or pricelist_id) check the availability for the hotel",
        compute="_compute_availability",
    )

    mail_information = fields.Html(
        string="Mail Information", help="Additional information of the mail"
    )

    privacy_policy = fields.Html(string="Privacy Policy", help="Mail privacy policy ")

    property_confirmed_template = fields.Many2one(
        string="Confirmation Email",
        help="Confirmation email template",
        comodel_name="mail.template",
    )

    property_modified_template = fields.Many2one(
        string="Modification Email",
        help="Modification email template",
        comodel_name="mail.template",
    )

    property_exit_template = fields.Many2one(
        string="Exit Email",
        comodel_name="mail.template",
    )

    property_canceled_template = fields.Many2one(
        string="Cancellation Email",
        help="Cancellation email template",
        comodel_name="mail.template",
    )

    is_confirmed_auto_mail = fields.Boolean(string="Auto Send Confirmation Mail")
    is_modified_auto_mail = fields.Boolean(string="Auto Send Modification Mail")
    is_exit_auto_mail = fields.Boolean(string="Auto Send Exit Mail")
    is_canceled_auto_mail = fields.Boolean(string="Auto Send Cancellation Mail")

    default_invoicing_policy = fields.Selection(
        string="Default Invoicing Policy",
        selection=[
            ("manual", "Manual"),
            ("checkout", "Checkout"),
            ("month_day", "Month Day Invoice"),
        ],
        default="manual",
    )

    margin_days_autoinvoice = fields.Integer(
        string="Margin Days",
        help="Days from Checkout to generate the invoice",
    )

    invoicing_month_day = fields.Integer(
        string="Invoicing Month Day",
        help="The day of the month to invoice",
    )

    journal_simplified_invoice_id = fields.Many2one(
        string="Simplified Invoice Journal",
        comodel_name="account.journal",
        domain=[
            ("type", "=", "sale"),
        ],
        help="Journal used to create the simplified invoice",
        check_company=True,
        check_pms_properties=True,
    )

    journal_normal_invoice_id = fields.Many2one(
        string="Normal Invoice Journal",
        comodel_name="account.journal",
        domain=[
            ("type", "=", "sale"),
            ("is_simplified_invoice", "=", False),
        ],
        help="Journal used to create the normal invoice",
        check_company=True,
        check_pms_properties=True,
    )

    max_amount_simplified_invoice = fields.Float(
        string="Max Amount Simplified Invoice",
        help="Maximum amount to create the simplified invoice",
        default=400.0,
    )
    avoid_simplified_max_amount_downpayment = fields.Boolean(
        string="Downpayment Invoive without limit amount",
        help="Avoid simplified invoice max amount downpayment",
        default=True,
    )
    user_id = fields.Many2one(
        string="Team Leader",
        copy=False,
        comodel_name="res.users",
        ondelete="restrict",
        tracking=True,
    )
    member_ids = fields.One2many(
        string="Team Members",
        comodel_name="pms.team.member",
        inverse_name="pms_property_id",
        copy=False,
    )
    logo = fields.Binary(
        string="Image in checkin",
        default=get_default_logo(),
    )
    block_create_past_reservations = fields.Boolean(
        string="Block Create Past Reservations",
        help="Block the creation of reservations in the past",
        default=False,
    )

    @api.depends_context(
        "checkin",
        "checkout",
        "real_avail",
        "room_type_id",
        "ubication_id",
        "capacity",
        "amenity_ids",
        "pricelist_id",
        "class_id",
        "overnight_rooms",
        "current_lines",
    )
    def _compute_free_room_ids(self):
        checkin = self._context["checkin"]
        checkout = self._context["checkout"]

        if isinstance(checkin, str):
            checkin = datetime.datetime.strptime(
                checkin, DEFAULT_SERVER_DATE_FORMAT
            ).date()
        if isinstance(checkout, str):
            checkout = datetime.datetime.strptime(
                checkout, DEFAULT_SERVER_DATE_FORMAT
            ).date()
        current_lines = self.env.context.get("current_lines", False)
        if current_lines and not isinstance(current_lines, list):
            current_lines = [current_lines]

        pricelist_id = self.env.context.get("pricelist_id", False)
        room_type_id = self.env.context.get("room_type_id", False)
        class_id = self._context.get("class_id", False)
        real_avail = self._context.get("real_avail", False)
        overnight_rooms = self._context.get("overnight_rooms", False)
        capacity = self._context.get("capacity", False)
        for pms_property in self:
            free_rooms = pms_property.get_real_free_rooms(
                checkin, checkout, current_lines
            )
            if pricelist_id and not real_avail:
                # TODO: only closed_departure take account checkout date!
                domain_rules = [
                    ("date", ">=", checkin),
                    ("date", "<=", checkout),
                    ("pms_property_id", "=", pms_property.id),
                ]
                if room_type_id:
                    domain_rules.append(("room_type_id", "=", room_type_id))

                pricelist = self.env["product.pricelist"].browse(pricelist_id)
                if pricelist.availability_plan_id:
                    domain_rules.append(
                        ("availability_plan_id", "=", pricelist.availability_plan_id.id)
                    )
                    rule_items = self.env["pms.availability.plan.rule"].search(
                        domain_rules
                    )

                    if len(rule_items) > 0:
                        room_types_to_remove = []
                        for item in rule_items:
                            if pricelist.availability_plan_id.any_rule_applies(
                                checkin, checkout, item
                            ):
                                room_types_to_remove.append(item.room_type_id.id)
                        free_rooms = free_rooms.filtered(
                            lambda x: x.room_type_id.id not in room_types_to_remove
                        )
            if class_id:
                free_rooms = free_rooms.filtered(
                    lambda x: x.room_type_id.class_id.id == class_id
                )
            if overnight_rooms:
                free_rooms = free_rooms.filtered(
                    lambda x: x.room_type_id.overnight_room
                )
            if capacity:
                free_rooms = free_rooms.filtered(lambda x: x.capacity >= capacity)
            if len(free_rooms) > 0:
                pms_property.free_room_ids = free_rooms.ids
            else:
                pms_property.free_room_ids = False

    def get_real_free_rooms(self, checkin, checkout, current_lines=False):
        self.ensure_one()
        Avail = self.env["pms.availability"]
        target_rooms = (
            self.env["pms.room"]
            .with_context(active_test=True)
            .search([("pms_property_id", "=", self.id)])
        )

        room_type_id = self.env.context.get("room_type_id", False)
        if room_type_id:
            target_rooms = target_rooms.filtered(
                lambda r: r.room_type_id.id == room_type_id
            )
        capacity = self.env.context.get("capacity", False)
        if capacity:
            target_rooms = target_rooms.filtered(lambda r: r.capacity >= capacity)

        ubication_id = self.env.context.get("ubication_id", False)
        if ubication_id:
            target_rooms = target_rooms.filtered(
                lambda r: r.ubication_id.id == ubication_id
            )

        amenity_ids = self.env.context.get("amenity_ids", False)
        if amenity_ids:
            if amenity_ids and not isinstance(amenity_ids, list):
                amenity_ids = [amenity_ids]
            target_rooms = target_rooms.filtered(
                lambda r: len(set(amenity_ids) - set(r.room_amenity_ids.ids)) == 0
            )

        if not current_lines:
            current_lines = []

        rooms_not_avail_ids = Avail.get_rooms_not_avail(
            checkin=checkin,
            checkout=checkout,
            room_ids=target_rooms.ids,
            pms_property_id=self.id,
            current_lines=current_lines,
        )
        domain_rooms = [("id", "in", target_rooms.ids)]
        if rooms_not_avail_ids:
            domain_rooms.append(
                ("id", "not in", rooms_not_avail_ids),
            )
        return self.env["pms.room"].with_context(active_test=True).search(domain_rooms)

    @api.depends_context(
        "checkin",
        "checkout",
        "real_avail",
        "room_type_id",
        "ubication_id",
        "capacity",
        "amenity_ids",
        "pricelist_id",
        "class_id",
        "overnight_rooms",
        "current_lines",
    )
    def _compute_availability(self):
        for record in self:
            checkin = self._context["checkin"]
            checkout = self._context["checkout"]
            if isinstance(checkin, str):
                checkin = datetime.datetime.strptime(
                    checkin, DEFAULT_SERVER_DATE_FORMAT
                ).date()
            if isinstance(checkout, str):
                checkout = datetime.datetime.strptime(
                    checkout, DEFAULT_SERVER_DATE_FORMAT
                ).date()
            room_type_id = self.env.context.get("room_type_id", False)
            pricelist_id = self.env.context.get("pricelist_id", False)
            current_lines = self.env.context.get("current_lines", [])
            class_id = self._context.get("class_id", False)
            real_avail = self._context.get("real_avail", False)
            overnight_rooms = self._context.get("overnight_rooms", False)
            capacity = self._context.get("capacity", False)
            pms_property = record.with_context(
                checkin=checkin,
                checkout=checkout,
                room_type_id=room_type_id,
                current_lines=current_lines,
                pricelist_id=pricelist_id,
                class_id=class_id,
                real_avail=real_avail,
                overnight_rooms=overnight_rooms,
                capacity=capacity,
            )
            count_free_rooms = len(pms_property.free_room_ids)
            if current_lines and not isinstance(current_lines, list):
                current_lines = [current_lines]

            domain_rules = [
                ("date", ">=", checkin),
                ("date", "<=", checkout),
                ("pms_property_id", "=", pms_property.id),
            ]
            if room_type_id:
                domain_rules.append(("room_type_id", "=", room_type_id))

            pricelist = False
            if pricelist_id:
                pricelist = self.env["product.pricelist"].browse(pricelist_id)
            if pricelist and pricelist.availability_plan_id and not real_avail:
                domain_rules.append(
                    ("availability_plan_id", "=", pricelist.availability_plan_id.id)
                )
                rule_groups = self.env["pms.availability.plan.rule"].read_group(
                    domain_rules,
                    ["plan_avail:sum"],
                    ["date:day"],
                    lazy=False,
                )
                if len(rule_groups) > 0:
                    # If in the group per day, some room type has the sale blocked,
                    # we must subtract from that day the availability of that room type
                    for group in rule_groups:
                        items = self.env["pms.availability.plan.rule"].search(
                            group["__domain"]
                        )
                        for item in items:
                            if pricelist.availability_plan_id.any_rule_applies(
                                checkin, checkout, item
                            ):
                                group["plan_avail"] -= item.plan_avail
                    count_free_rooms = min(i["plan_avail"] for i in rule_groups)
            record.availability = count_free_rooms

    @api.model
    def splitted_availability(
        self,
        checkin,
        checkout,
        pms_property_id,
        room_type_id=False,
        current_lines=False,
        pricelist=False,
        real_avail=False,
    ):
        if isinstance(checkin, str):
            checkin = datetime.datetime.strptime(
                checkin, DEFAULT_SERVER_DATE_FORMAT
            ).date()
        if isinstance(checkout, str):
            checkout = datetime.datetime.strptime(
                checkout, DEFAULT_SERVER_DATE_FORMAT
            ).date()
        for date_iterator in [
            checkin + datetime.timedelta(days=x)
            for x in range(0, (checkout - checkin).days)
        ]:
            pms_property = self.env["pms.property"].browse(pms_property_id)
            pms_property = pms_property.with_context(
                checkin=date_iterator,
                checkout=date_iterator + datetime.timedelta(1),
                room_type_id=room_type_id,
                current_lines=current_lines,
                pricelist_id=pricelist.id,
                real_avail=real_avail,
            )

            if len(pms_property.free_room_ids) < 1:
                return False
        return True

    @api.constrains("ref")
    def _check_unique_property_ref(self):
        for record in self:
            if record.ref:
                duplicated = self.env["pms.property"].search(
                    [("ref", "=", record.ref), ("id", "!=", record.id)]
                )
                if duplicated:
                    raise ValidationError(
                        _(
                            "Alreay exist other property with this ref: %s (%s)",
                            duplicated.name,
                            duplicated.ref,
                        )
                    )

    @api.constrains("pms_property_code")
    def _check_unique_property_code(self):
        for record in self:
            if record.pms_property_code:
                duplicated = self.env["pms.property"].search(
                    [
                        ("pms_property_code", "=", record.pms_property_code),
                        ("id", "!=", record.id),
                    ]
                )
                if duplicated:
                    raise ValidationError(
                        _(
                            "Alreay exist other property with this code: %s (%s)",
                            duplicated.name,
                            duplicated.pms_property_code,
                        )
                    )

    @api.constrains("default_arrival_hour")
    def _check_arrival_hour(self):
        for record in self:
            try:
                time.strptime(record.default_arrival_hour, "%H:%M")
                return True
            except ValueError:
                raise ValidationError(
                    _(
                        "Format Arrival Hour (HH:MM) Error: %s",
                        record.default_arrival_hour,
                    )
                )

    @api.constrains("default_departure_hour")
    def _check_departure_hour(self):
        for record in self:
            try:
                time.strptime(record.default_departure_hour, "%H:%M")
                return True
            except ValueError:
                raise ValidationError(
                    _(
                        "Format Departure Hour (HH:MM) Error: %s",
                        record.default_departure_hour,
                    )
                )

    def date_property_timezone(self, dt):
        self.ensure_one()
        if self.env.user:
            tz_property = self.tz
            dt = pytz.timezone(tz_property).localize(dt)
            dt = dt.replace(tzinfo=None)
            dt = pytz.timezone(self.env.user.tz or "UTC").localize(dt)
            dt = dt.astimezone(pytz.utc)
            dt = dt.replace(tzinfo=None)
        return dt

    def _get_payment_methods(self, automatic_included=False):
        # We use automatic_included to True to see absolutely
        # all the journals with associated payments, if it is
        # false, we will only see those journals that can be used
        # to pay manually
        self.ensure_one()
        payment_methods = self.env["account.journal"].search(
            [
                ("type", "in", ["cash", "bank"]),
                "|",
                ("pms_property_ids", "in", self.id),
                "|",
                "&",
                ("pms_property_ids", "=", False),
                ("company_id", "=", self.company_id.id),
                "&",
                ("pms_property_ids", "=", False),
                ("company_id", "=", False),
            ]
        )
        if not automatic_included:
            payment_methods = payment_methods.filtered(lambda p: p.allowed_pms_payments)
        return payment_methods

    @api.model
    def create(self, vals):
        name = vals.get("name")
        if "folio_sequence_id" not in vals or not vals.get("folio_sequence_id"):
            folio_sequence = self.env["ir.sequence"].create(
                {
                    "name": "PMS Folio " + name,
                    "code": "pms.folio",
                    "prefix": "F/%(y)s",
                    "suffix": "%(sec)s",
                    "padding": 4,
                    "company_id": vals.get("company_id"),
                }
            )
            vals.update({"folio_sequence_id": folio_sequence.id})
        if "checkin_sequence_id" not in vals or not vals.get("checkin_sequence_id"):
            checkin_sequence = self.env["ir.sequence"].create(
                {
                    "name": "PMS Checkin " + name,
                    "code": "pms.checkin.partner",
                    "prefix": "C/%(y)s",
                    "suffix": "%(sec)s",
                    "padding": 4,
                    "company_id": vals.get("company_id"),
                }
            )
            vals.update({"checkin_sequence_id": checkin_sequence.id})
        record = super(PmsProperty, self).create(vals)
        return record

    @api.model
    def daily_closing(
        self, pms_property_ids, room_type_ids=False, availability_plan_ids=False
    ):
        """
        This method is used to close the daily availability of rooms
        """
        pms_properties = self.browse(pms_property_ids)
        for pms_property in pms_properties:
            if not room_type_ids:
                room_type_ids = (
                    self.env["pms.room.type"]
                    .search(
                        [
                            "|",
                            ("pms_property_ids", "in", pms_property.id),
                            ("pms_property_ids", "=", False),
                        ]
                    )
                    .ids
                )
            if not availability_plan_ids:
                availability_plan_ids = (
                    self.env["pms.availability.plan"]
                    .search(
                        [
                            "|",
                            ("pms_property_ids", "in", pms_property.id),
                            ("pms_property_ids", "=", False),
                        ]
                    )
                    .ids
                )
            for room_type in self.env["pms.room.type"].browse(room_type_ids):
                for availability_plan in self.env["pms.availability.plan"].browse(
                    availability_plan_ids
                ):
                    rule = self.env["pms.availability.plan.rule"].search(
                        [
                            ("pms_property_id", "=", pms_property.id),
                            ("room_type_id", "=", room_type.id),
                            ("availability_plan_id", "=", availability_plan.id),
                            ("date", "=", fields.date.today()),
                        ]
                    )
                    if not rule:
                        rule = self.env["pms.availability.plan.rule"].create(
                            {
                                "pms_property_id": pms_property.id,
                                "room_type_id": room_type.id,
                                "availability_plan_id": availability_plan.id,
                                "date": fields.date.today(),
                                "closed": True,
                            }
                        )
                    elif not rule.closed:
                        rule.write(
                            {
                                "closed": True,
                            }
                        )
        return True

    @api.model
    def autoinvoicing(self, offset=0, with_delay=False, autocommit=False):
        """
        This method is used to invoicing automatically the folios
        and validate the draft invoices created by the folios
        """
        date_reference = fields.Date.today() - relativedelta(days=offset)
        # REVIEW: We clean the autoinvoice_date of the past draft invoices
        # to avoid blocking the autoinvoicing
        self.clean_date_on_past_draft_invoices(date_reference)
        # 1- Invoicing the folios
        folios = self.env["pms.folio"].search(
            [
                ("sale_line_ids.autoinvoice_date", "=", date_reference),
                ("invoice_status", "=", "to_invoice"),
                ("amount_total", ">", 0),
            ]
        )
        paid_folios = folios.filtered(lambda f: f.pending_amount <= 0)
        unpaid_folios = folios.filtered(lambda f: f.pending_amount > 0)
        folios_to_invoice = paid_folios
        # If the folio is unpaid we will auto invoice only the
        # not cancelled lines
        for folio in unpaid_folios:
            if any([res.state != "cancel" for res in folio.reservation_ids]):
                folios_to_invoice += folio
            else:
                folio.sudo().message_post(
                    body=_(
                        "Not invoiced due to pending amounts and cancelled reservations"
                    )
                )
        for folio in folios_to_invoice:
            if with_delay:
                self.with_delay().autoinvoice_folio(folio)
            else:
                self.autoinvoice_folio(folio)
        # 2- Validate the draft invoices created by the folios
        draft_invoices_to_post = self.env["account.move"].search(
            [
                ("state", "=", "draft"),
                ("invoice_date_due", "=", date_reference),
                ("folio_ids", "!=", False),
            ]
        )
        for invoice in draft_invoices_to_post:
            if with_delay:
                self.with_delay().autovalidate_folio_invoice(invoice)
            else:
                self.autovalidate_folio_invoice(invoice)

        # 3- Reverse the downpayment invoices that not was included in final invoice
        downpayments_invoices_to_reverse = self.env["account.move.line"].search(
            [
                ("move_id.state", "=", "posted"),
                ("folio_line_ids.is_downpayment", "=", True),
                ("folio_line_ids.qty_invoiced", ">", 0),
                ("folio_ids", "in", folios.ids),
            ]
        )
        downpayment_invoices = downpayments_invoices_to_reverse.mapped("move_id")
        if downpayment_invoices:
            for downpayment_invoice in downpayment_invoices:
                default_values_list = [
                    {
                        "ref": _(f'Reversal of: {move.name + " - " + move.ref}'),
                    }
                    for move in downpayment_invoice
                ]
                downpayment_invoice.with_context(
                    {"sii_refund_type": "I"}
                )._reverse_moves(default_values_list, cancel=True)
                downpayment_invoice.message_post(
                    body=_(
                        """
                           The downpayment invoice has been reversed
                           because it was not included in the final invoice
                        """
                    )
                )

        return True

    @api.model
    def clean_date_on_past_draft_invoices(self, date_reference):
        """
        This method is used to clean the date on past draft invoices
        """
        journal_ids = (
            self.env["account.journal"]
            .search(
                [
                    ("type", "=", "sale"),
                    ("pms_property_ids", "!=", False),
                ]
            )
            .ids
        )
        draft_invoices = self.env["account.move"].search(
            [
                ("state", "=", "draft"),
                ("invoice_date", "<", date_reference),
                ("journal_id", "in", journal_ids),
            ]
        )
        if draft_invoices:
            draft_invoices.write({"invoice_date": date_reference})
        return True

    def autovalidate_folio_invoice(self, invoice):
        try:
            with self.env.cr.savepoint():
                invoice.action_post()
        except Exception as e:
            invoice.message_post(body=_("Error in autovalidate invoice: " + str(e)))

    def autoinvoice_folio(self, folio):
        try:
            with self.env.cr.savepoint():
                # REVIEW: folio sale line "_compute_auotinvoice_date" sometimes
                # dont work in services (probably cache issue¿?), we ensure that the date is
                # set or recompute this
                for line in folio.sale_line_ids.filtered(
                    lambda l: not l.autoinvoice_date
                ):
                    line._compute_autoinvoice_date()
                invoices = folio.with_context(autoinvoice=True)._create_invoices(
                    grouped=True,
                    final=False,
                )
                downpayments = folio.sale_line_ids.filtered(
                    lambda l: l.is_downpayment and l.qty_invoiced > 0
                )
                for invoice in invoices:
                    if (
                        invoice.amount_total
                        > invoice.pms_property_id.max_amount_simplified_invoice
                        and invoice.journal_id.is_simplified_invoice
                    ):
                        hosts_to_invoice = (
                            invoice.folio_ids.partner_invoice_ids.filtered(
                                lambda p: p._check_enought_invoice_data()
                            ).mapped("id")
                        )
                        if hosts_to_invoice:
                            invoice.partner_id = hosts_to_invoice[0]
                            invoice.journal_id = (
                                invoice.pms_property_id.journal_normal_invoice_id
                            )
                        else:
                            mens = _(
                                "The total amount of the simplified invoice is higher than the "
                                "maximum amount allowed for simplified invoices, and dont have "
                                "enought data in hosts to create a normal invoice."
                            )
                            folio.sudo().message_post(body=mens)
                            raise ValidationError(mens)
                    for downpayment in downpayments.filtered(
                        lambda d: d.default_invoice_to == invoice.partner_id
                    ):
                        # If the downpayment invoice partner is the same that the
                        # folio partner, we include the downpayment in the normal invoice
                        invoice_down_payment_vals = downpayment._prepare_invoice_line(
                            sequence=max(invoice.invoice_line_ids.mapped("sequence"))
                            + 1,
                        )
                        invoice.write(
                            {"invoice_line_ids": [(0, 0, invoice_down_payment_vals)]}
                        )
                    invoice.action_post()
                # The downpayment invoices that not was included in final invoice, are reversed
                downpayment_invoices = (
                    downpayments.filtered(
                        lambda d: d.qty_invoiced > 0
                    ).invoice_lines.mapped("move_id")
                ).filtered(lambda i: i.is_simplified_invoice)
                if downpayment_invoices:
                    default_values_list = [
                        {
                            "ref": _(f'Reversal of: {move.name + " - " + move.ref}'),
                        }
                        for move in downpayment_invoices
                    ]
                    downpayment_invoices.with_context(
                        {"sii_refund_type": "I"}
                    )._reverse_moves(default_values_list, cancel=True)
        except Exception as e:
            folio.sudo().message_post(body=_("Error in autoinvoicing folio: " + str(e)))

    @api.constrains("journal_normal_invoice_id")
    def _check_journal_normal_invoice(self):
        for pms_property in self.filtered("journal_normal_invoice_id"):
            if pms_property.journal_normal_invoice_id.is_simplified_invoice:
                raise ValidationError(
                    _("Journal %s is not allowed to be used for normal invoices")
                    % pms_property.journal_normal_invoice_id.name
                )

    @api.constrains("journal_simplified_invoice_id")
    def _check_journal_simplified_invoice(self):
        for pms_property in self.filtered("journal_simplified_invoice_id"):
            if not pms_property.journal_simplified_invoice_id.is_simplified_invoice:
                pms_property.journal_simplified_invoice_id.is_simplified_invoice = True

    @api.model
    def _get_folio_default_journal(self, partner_invoice_id):
        self.ensure_one()
        partner = self.env["res.partner"].browse(partner_invoice_id)
        if (
            not partner
            or partner.id == self.env.ref("pms.various_pms_partner").id
            or (
                not partner._check_enought_invoice_data()
                and self._context.get("autoinvoice")
            )
        ):
            return self.journal_simplified_invoice_id
        return self.journal_normal_invoice_id

    def _get_adr(self, start_date, end_date, domain=False):
        """
        Calculate monthly ADR for a property
        :param start_date: start date
        :param pms_property_id: pms property id
        :param domain: domain to filter reservations (channel, agencies, etc...)
        """
        self.ensure_one()
        domain = [] if not domain else domain
        domain.extend(
            [
                ("pms_property_id", "=", self.id),
                ("occupies_availability", "=", True),
                ("reservation_id.reservation_type", "=", "normal"),
                ("date", ">=", start_date),
                ("date", "<=", end_date),
            ]
        )
        group_adr = self.env["pms.reservation.line"].read_group(
            domain,
            ["price:avg"],
            ["date:day"],
        )
        if not len(group_adr):
            return 0
        adr = 0
        for day_adr in group_adr:
            adr += day_adr["price"]

        return round(adr / len(group_adr), 2)

    def _get_revpar(self, start_date, end_date, domain=False):
        """
        Calculate monthly revpar for a property only in INE rooms
        :param start_date: start date
        :param pms_property_id: pms property id
        :param domain: domain to filter reservations (channel, agencies, etc...)
        """
        self.ensure_one()
        domain = [] if not domain else domain
        domain.extend(
            [
                ("pms_property_id", "=", self.id),
                ("occupies_availability", "=", True),
                ("room_id.in_ine", "=", True),
                ("date", ">=", start_date),
                ("date", "<=", end_date),
            ]
        )
        price_domain = expression.AND(
            [domain, [("reservation_id.reservation_type", "=", "normal")]]
        )
        sum_group_price = self.env["pms.reservation.line"].read_group(
            price_domain,
            ["price"],
            [],
        )
        not_allowed_rooms_domain = expression.AND(
            [
                domain,
                [("reservation_id.reservation_type", "!=", "normal")],
            ]
        )
        count_room_days_not_allowed = len(
            self.env["pms.reservation.line"].search(not_allowed_rooms_domain)
        )
        date_range_days = (end_date - start_date).days + 1
        count_total_room_days = len(self.room_ids) * date_range_days
        count_available_room_days = count_total_room_days - count_room_days_not_allowed
        if not sum_group_price[0]["price"]:
            return 0
        revpar = round(sum_group_price[0]["price"] / count_available_room_days, 2)
        return revpar

    @api.model
    def _name_search(
        self, name, args=None, operator="ilike", limit=100, name_get_uid=None
    ):
        args = args or []
        domain = []
        if name:
            domain = [
                "|",
                "|",
                ("ref", "=ilike", name.split(" ")[0] + "%"),
                ("pms_property_code", "=ilike", name.split(" ")[0] + "%"),
                ("name", operator, name),
            ]
            if operator in expression.NEGATIVE_TERM_OPERATORS:
                domain = ["&", "!"] + domain[1:]
        return self._search(
            expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid
        )

    def name_get(self):
        result = []
        for record in self:
            if self.env.context.get("only_code", False) and record.pms_property_code:
                result.append((record.id, record.pms_property_code))
            elif (
                self.env.context.get("only_name", False) or not record.pms_property_code
            ):
                result.append((record.id, record.name))
            else:
                result.append(
                    (record.id, "[" + record.pms_property_code + "] " + record.name)
                )
        return result
